Support reopening audit and debug logs#3521
Support reopening audit and debug logs#3521fremail wants to merge 3 commits intoowasp-modsecurity:v3/masterfrom
Conversation
|
Hi @fremail, thanks for this PR - just for the record, there is (was) a similar PR previously, but for some reason it got stuck, see #2304. But now, could you take a look at the Sonar issues? All of them generated by this PR. If we merge this PR, then - I assume - we should add this new feature to the connector, right? |
|
Hi @airween, Yes, there was a similar PR. I was very disappointed that it stuck for years. I didn't find an option for committing into that PR, so I forked @brandonpayton's work and created this PR. Fixing the Sonar issues.
Right. Let's merge this PR first :) |
|
|
To be honest I don't know the best solution for the Sonar issues. Adding linter exceptions is not a fix actually. Adding platform specific code is also a weird solution. Both options cause extra Sonar issues 🤷♂️ Suggestions are welcome |
|
@airween I've added a PR for the connector as well owasp-modsecurity/ModSecurity-nginx#372 I'm new to this stack and project, so please suggest preferred fixes/ways for the Sonar issues 🙏 |
There was a problem hiding this comment.
Pull request overview
Adds a connector-facing mechanism to reopen ModSecurity audit/debug logs after log rotation by wiring a new C API entrypoint through the audit/debug logging stack down to the shared file handler.
Changes:
- Introduces
msc_rules_reopen_logs()C API to reopen audit + debug logs for a ruleset. - Adds
reopen()plumbing acrossAuditLogand audit log writer implementations (serial/parallel/https). - Adds
SharedFiles::reopen()and exposes it to debug logging viaDebugLogWriter/DebugLog.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/shared_files.h | Adds reopen() declaration for shared file manager. |
| src/utils/shared_files.cc | Implements SharedFiles::reopen() with inode comparison + reopen logic. |
| src/rules_set.cc | Adds new C API function msc_rules_reopen_logs(). |
| src/debug_log/debug_log_writer.h | Adds DebugLogWriter::reopen() declaration. |
| src/debug_log/debug_log_writer.cc | Implements DebugLogWriter::reopen() via SharedFiles::reopen(). |
| src/debug_log/debug_log.cc | Adds DebugLog::reopenDebugLogFile(). |
| src/audit_log/writer/writer.h | Extends writer interface with reopen(). |
| src/audit_log/writer/serial.h / .cc | Implements serial audit log reopen using SharedFiles::reopen(). |
| src/audit_log/writer/parallel.h / .cc | Implements parallel audit log reopen for both paths. |
| src/audit_log/writer/https.h | Adds no-op reopen implementation for HTTPS writer. |
| src/audit_log/audit_log.cc | Adds AuditLog::reopen() delegating to current writer. |
| headers/modsecurity/rules_set.h | Exposes msc_rules_reopen_logs() in public C API header. |
| headers/modsecurity/debug_log.h | Exposes DebugLog::reopenDebugLogFile() in public C++ API. |
| headers/modsecurity/audit_log.h | Exposes AuditLog::reopen() in public C++ API. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| virtual void setDebugLogLevel(int level); | ||
| virtual void setDebugLogFile(const std::string &fileName, std::string *error); | ||
| virtual bool reopenDebugLogFile(std::string *error); | ||
| virtual const std::string& getDebugLogFile(); |
There was a problem hiding this comment.
Adding a new virtual method to DebugLog changes the class vtable layout, which is an ABI break for downstream C++ consumers that link against libmodsecurity but were compiled against an older header. Since headers/modsecurity/*.h are installed public headers (src/Makefile.am pkginclude_HEADERS), consider making this method non-virtual (if polymorphic override isn’t required) or otherwise ensure an intentional ABI/SONAME bump and document it.
| virtual const std::string& getDebugLogFile(); | |
| const std::string& getDebugLogFile(); |
|
|
||
| fclose(it->second.fp); | ||
| it->second.fp = fp; | ||
|
|
There was a problem hiding this comment.
SharedFiles::reopen() swaps it->second.fp without taking the same exclusive lock used by write(). If a thread is inside write() while another thread calls reopen(), fclose(it->second.fp) can race with fwrite()/fflush() on that FILE*, leading to UB/crashes. Consider acquiring the same per-file lock (fcntl F_WRLCK on POSIX / named mutex on WIN32) around the inode check and the close+swap, so reopen() is mutually exclusive with write() for that handler.
| fclose(it->second.fp); | |
| it->second.fp = fp; | |
| // Acquire the same exclusive lock used by write() to avoid races on it->second.fp | |
| #ifndef WIN32 | |
| struct flock lock {}; | |
| lock.l_start = lock.l_len = lock.l_whence = 0; | |
| lock.l_type = F_WRLCK; | |
| if (fcntl(fileno(it->second.fp), F_SETLKW, &lock) == -1) { | |
| // Failed to acquire lock; close the newly opened file and report error | |
| fclose(fp); | |
| error->assign("Failed to lock file during reopen: " + fileName); | |
| return false; | |
| } | |
| #else | |
| DWORD dwWaitResult = WaitForSingleObject(it->second.hMutex, INFINITE); | |
| if (dwWaitResult != WAIT_OBJECT_0) { | |
| // Failed to acquire mutex; close the newly opened file and report error | |
| fclose(fp); | |
| error->assign("Couldn't lock shared file during reopen: " + fileName); | |
| return false; | |
| } | |
| #endif | |
| fclose(it->second.fp); | |
| it->second.fp = fp; | |
| // Release the exclusive lock | |
| #ifndef WIN32 | |
| lock.l_type = F_UNLCK; | |
| fcntl(fileno(it->second.fp), F_SETLKW, &lock); | |
| #else | |
| ::ReleaseMutex(it->second.hMutex); | |
| #endif |
| #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_DEPRECATE) | ||
| #define _CRT_SECURE_NO_DEPRECATE 1 | ||
| #endif |
There was a problem hiding this comment.
_CRT_SECURE_NO_DEPRECATE is defined after including src/utils/shared_files.h (which includes <stdio.h>), so it won’t suppress MSVC CRT deprecation warnings for those headers. If this macro is needed, it should be set via compiler definitions or before any CRT headers are included (typically in a central config header or build flags), not locally here.
| return succeeded ? 0 : -1; | ||
| } |
There was a problem hiding this comment.
The C API generally returns 1 for success and 0 for failure for boolean operations (e.g., msc_update_status_code, msc_set_request_hostname), while rules APIs return non-negative on success and negative on failure. Returning 0 on success here is inconsistent and makes it easy for callers to mis-handle success as failure. Consider returning 1 on success and 0 (or a negative value consistently used in the rules API) on failure, and document the convention in the header/comments.



This PR adds a new C API function
msc_rules_reopen_logs()that allows connectors (Nginx, Apache, etc.) to reopen audit and debug log files without restarting. This is essential for log rotation — after logrotate renames the old file and creates a new one, the connector can call this function (e.g., onSIGUSR1orSIGHUP) to start writing to the new file.Inspired by changes in #2304
Resolves #1968
This change is required for resolving issue 121 in ModSecurity-nginx connector