Skip to content

Fix memory leak of the macro expanded regex in @rx and @rxGlobal - #3634

Open
tomsommer wants to merge 1 commit into
owasp-modsecurity:v3/masterfrom
tomsommer:fix/rx-macro-regex-leak
Open

tomsommer wants to merge 1 commit into
owasp-modsecurity:v3/masterfrom
tomsommer:fix/rx-macro-regex-leak

Conversation

@tomsommer

@tomsommer tomsommer commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

what

  • Rx::evaluate() and RxGlobal::evaluate() hold the macro-expanded, per-evaluation Utils::Regex in a std::unique_ptr for the whole scope instead of a raw new/delete pair placed before the final returns.
  • Two regression cases added to operator-rx.json: a macro expanding to a non-compiling pattern, and a macro pattern that exceeds SecPcreMatchLimit.

why

  • When an @rx / @rxGlobal parameter contains a macro, the pattern is expanded and compiled on every evaluation. The compiled Regex was only deleted right before the final return, which the early return false paths never reach: re->hasError() (expanded pattern does not compile) and regex_result != Ok (PCRE error such as MATCH_LIMIT; modsecurity.conf-recommended sets SecPcreMatchLimit 1000, so this is reachable with ordinary custom rules).
  • Each hit leaks the Regex with its compiled pcre2 code and JIT block, once per target variable per request, so a long-running worker grows without bound.
  • No test is added for the RxGlobal half: with PCRE2 Regex::searchGlobal() always returns Ok, so that early return is only reachable in --with-pcre builds; the change fixes it there too.

Evidence, unfixed tree, valgrind --leak-check=full ./.libs/regression_tests test-cases/regression/operator-rx.json:

==2== 346 (96 direct, 250 indirect) bytes in 2 blocks are definitely lost in loss record 3 of 3
==2==    at 0x4844F93: operator new(unsigned long) (vg_replace_malloc.c:487)
==2==    by 0x4A06269: modsecurity::operators::Rx::evaluate(...) (rx.cc:49)
==2==    by 0x49F8A79: modsecurity::operators::Operator::evaluateInternal(...) (operator.cc:75)

With the fix: All heap blocks were freed -- no leaks are possible; operator-rx.json 8/8, whole regression directory 722 passed / 11 skipped.

Side note for a separate change: RxGlobal::evaluate() has no hasError() check, so a macro that expands to an invalid pattern reaches Regex::searchGlobal() with a null pcre2_code. Not touched here.

references

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability of regex-based operators when processing macro-supplied patterns.
    • Preserved clear error reporting for invalid regular expressions and match-limit conditions.
  • Tests

    • Added regression coverage for invalid macro-generated patterns and regular expressions exceeding configured match limits.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Warning

Review limit reached

Next included review available in 29 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 8 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: ae6eef37-90b4-4803-aa8f-8e2a314524b7

📥 Commits

Reviewing files that changed from the base of the PR and between fccee89 and 5e1425f.

📒 Files selected for processing (2)
  • src/operators/rx.cc
  • src/operators/rx_global.cc

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: ee7e2429-5638-45fb-b92e-68ec51d093cf

📥 Commits

Reviewing files that changed from the base of the PR and between 7ea9fef and fccee89.

📒 Files selected for processing (3)
  • src/operators/rx.cc
  • src/operators/rx_global.cc
  • test/test-cases/regression/operator-rx.json

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


📝 Walkthrough

Walkthrough

Changes

Regex lifetime and validation

Layer / File(s) Summary
Automatic regex ownership
src/operators/rx.cc, src/operators/rx_global.cc
Macro-generated Regex instances now use std::unique_ptr. Manual deletion was removed.
Macro regex regression cases
test/test-cases/regression/operator-rx.json
Adds tests for invalid macro-expanded patterns and PCRE match-limit errors. Tests verify debug logs, HTTP 200, and TX:MSC_PCRE_LIMITS_EXCEEDED.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 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: fixing the memory leak caused by macro-expanded regex patterns in @rx and @rxGlobal.
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 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. (1 skipped: 1 unsupported.)

✨ 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.

When the parameter of @rx or @rxglobal contains a macro, the pattern has
to be expanded and compiled on every evaluation, so Rx::evaluate() and
RxGlobal::evaluate() allocate a temporary Utils::Regex with `new`. That
object was only released by an explicit `delete` placed just before the
final return statements, which the early `return false` paths never
reach:

 * Rx::evaluate() returns early when the expanded pattern does not
   compile (re->hasError()) and when the match fails with a PCRE error
   such as MATCH_LIMIT.
 * RxGlobal::evaluate() returns early when the match fails with a PCRE
   error.

Every request that hits one of those paths therefore leaks the Regex
object together with the compiled pcre2 code and, when available, its
JIT compiled counterpart. The operators are evaluated at least once per
request and per target variable, so the leak grows without bound in a
long running process.

The raw pointer is replaced by a std::unique_ptr<Regex> that owns the
macro expanded regex for the whole scope of evaluate(), and the manual
delete is removed. The non macro case still uses the pre-compiled m_re
owned by the operator, which is not freed by the unique_ptr.

Two regression tests are added to operator-rx.json, one per early return
in Rx::evaluate(): a macro expanding to a non-compiling pattern and a
macro pattern that exceeds SecPcreMatchLimit. Running that file under
valgrind reports

  346 (96 direct, 250 indirect) bytes in 2 blocks are definitely lost
     at operator new(unsigned long)
     by modsecurity::operators::Rx::evaluate(...) (rx.cc:49)

before the change and "All heap blocks were freed -- no leaks are
possible" after it.

No regression test is added for RxGlobal::evaluate(). With PCRE2, which
is the default, Regex::searchGlobal() always returns RegexResult::Ok, so
that early return is only reachable in --with-pcre (PCRE1) builds; the
leak is fixed there all the same.
@tomsommer
tomsommer force-pushed the fix/rx-macro-regex-leak branch from fccee89 to 5e1425f Compare September 19, 2026 14:49
@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