Skip to content

Release the libmodsecurity error string on failed rule loading and merging - #398

Open
tomsommer wants to merge 1 commit into
owasp-modsecurity:masterfrom
tomsommer:fix/rules-error-cleanup
Open

tomsommer wants to merge 1 commit into
owasp-modsecurity:masterfrom
tomsommer:fix/rules-error-cleanup

Conversation

@tomsommer

@tomsommer tomsommer commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

what

  • Release the error string libmodsecurity hands back through const char **error when msc_rules_add(), msc_rules_add_file(), msc_rules_add_remote() or msc_rules_merge() fail. New helper ngx_http_modsecurity_rules_error_free() calls msc_rules_error_cleanup() on libmodsecurity 3.0.13 and later (the same version guard the connector already uses for msc_set_request_hostname) and free() on older releases, which is exactly what that API does.
  • In the three modsecurity_rules* handlers the existing strdup(error) copy is taken first and libmodsecurity's string released afterwards. The strdup itself is deliberately left in place: fix: replace strdup(error) with nginx pool allocation in config handlers #382 replaces it with a pool allocation, and this change does not duplicate that.
  • In merge_conf() the message is copied into cf->pool with an explicit NUL terminator (nginx prints it with %s; ngx_pstrdup() would not terminate it), libmodsecurity's string is released, and msc_rules_merge() is wrapped in the same pcre_malloc_init/done pair the handlers use.
  • New test tests/modsecurity-config-error.t runs nginx -t on three failing configurations (inline syntax error, rules-file syntax error, rule id duplicated between server and location) and checks the libmodsecurity message reaches the emerg line complete with nginx's in <conf>:<line> suffix.

why

  • A valgrind audit of the connector showed every failed rule load or merge leaking libmodsecurity's message and, in merge_conf(), the connector's own strdup of it. These are per-configuration-parse leaks: they matter for nginx -t loops and for a master process that receives a bad configuration on SIGHUP (the leak repeats on every failed reload attempt; a -s reload from a separate process does not accumulate because that process exits).
  • With the change, the strdup ← msc_rules_add* and both merge_conf records are gone on both libmodsecurity 3.0.9 and 3.0.14; only the record fix: replace strdup(error) with nginx pool allocation in config handlers #382 fixes remains.

references

  • Leaves the strdup(error) in the three handlers to fix: replace strdup(error) with nginx pool allocation in config handlers #382. Note for that change: ngx_pstrdup() copies len bytes without a terminator, while ngx_conf_handler() formats the returned message with %s; the copy needs an explicit NUL as done here for merge_conf().
  • The test asserts on libmodsecurity's own wording ("Rules error.", "Expecting an action, got:", "Rule id: N is duplicated"), present unchanged from 3.0.9 through current master; a rewording upstream would require adjusting the regexes.

Origin: this fix comes from a memory-leak audit of the connector done with Claude Fable 5.1 (Anthropic), following its performance review. Verified by building against nginx master with libmodsecurity 3.0.14 (PCRE2) and with upstream CI's flags against libmodsecurity 3.0.9 (PCRE1), running the full tests/modsecurity*.t suite in both builds (16 files, 257 tests, all passing), and by valgrind runs of nginx -t on each failing configuration before and after the change.

Summary by CodeRabbit

  • Bug Fixes

    • Configuration errors from ModSecurity rules now display as complete, properly terminated messages.
    • Improved handling of rule-loading and rule-merge failures, including errors from inline rules and referenced rule files.
    • Configuration testing now reliably reports invalid actions and duplicate rule IDs with accurate file and line details.
  • Tests

    • Added coverage for invalid rule syntax, missing actions, and duplicate rule identifiers across configuration scopes.

…rging

msc_rules_add(), msc_rules_add_file(), msc_rules_add_remote() and
msc_rules_merge() hand out a strdup()'ed error message and transfer its
ownership to the caller. The connector never released it, so every failed
config parse leaked the message. When the master process itself parses the
broken config (a direct SIGHUP, as systemd's ExecReload does) the leak is
retained for the lifetime of the master and grows with every failed reload.

Add ngx_http_modsecurity_rules_error_free(), which uses the documented
msc_rules_error_cleanup() from libmodsecurity v3.0.13 and falls back to
free() on older releases, and call it in the three ngx_conf_set_rules*
handlers and in ngx_http_modsecurity_merge_conf().

The connector's own strdup() of the message in the three handlers is left
untouched on purpose: replacing it with a pool allocation is PR owasp-modsecurity#382's
change. This commit only adds the release of the string libmodsecurity
handed out, which owasp-modsecurity#382 does not do, and fixes merge_conf(), which owasp-modsecurity#382
does not touch.

In merge_conf() the message is now copied into cf->pool before it is
released. The copy is explicitly NUL-terminated because ngx_conf_handler()
prints it with "%s". msc_rules_merge() is also wrapped in the
pcre_malloc_init()/pcre_malloc_done() pair, like the three
ngx_conf_set_rules* handlers already do.

tests/modsecurity-config-error.t checks that nginx -t rejects an inline
rules syntax error, a rules file syntax error and a rule id duplicated
between a server and a location, and that the libmodsecurity message
reaches the emerg line complete, with nginx's "in <conf>:<line>" suffix
attached.
@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 0a318220-891a-4f3d-be90-fb721d3418d8

📥 Commits

Reviewing files that changed from the base of the PR and between 9eb44fd and 3899ef6.

📒 Files selected for processing (2)
  • src/ngx_http_modsecurity_module.c
  • tests/modsecurity-config-error.t

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

The connector now frees libmodsecurity rule errors after copying them, protects rule merging with PCRE allocation guards, and returns NUL-terminated nginx-pool copies. New tests validate invalid inline, file-based, and merged-rule configuration errors.

Changes

Configuration error handling

Layer / File(s) Summary
Rule-loading error ownership
src/ngx_http_modsecurity_module.c
Adds version-aware cleanup for libmodsecurity errors. The inline, file, and remote rule handlers copy each error before releasing the original.
Merged-configuration error handling
src/ngx_http_modsecurity_module.c
Runs msc_rules_merge inside the PCRE allocation guards. It handles NULL errors and copies valid messages into Nginx pool memory with a terminating NUL byte.
Configuration error coverage
tests/modsecurity-config-error.t
Adds tests for invalid inline rules, invalid rules files, and duplicate rule IDs across configuration scopes. The tests verify failure status and complete error text.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: releasing libmodsecurity error strings when rule loading or merging fails.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant