Skip to content

Fix memory leak in setvar when the current value is not numeric - #3635

Open
tomsommer wants to merge 1 commit into
owasp-modsecurity:v3/masterfrom
tomsommer:fix/setvar-nonnumeric-variablevalue-leak
Open

tomsommer wants to merge 1 commit into
owasp-modsecurity:v3/masterfrom
tomsommer:fix/setvar-nonnumeric-variablevalue-leak

Conversation

@tomsommer

@tomsommer tomsommer commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

what

  • In the += / -= branch of SetVar::evaluate(), copy the current value into a local string and delete the resolved VariableValue objects before calling stoi().
  • Regression case added to collection-tx.json: tx.something set to not_a_number, then setvar:tx.something=+10, expected value 10.

why

  • m_variable->evaluate() fills a std::vector<const VariableValue *> owned by the caller. The elements were deleted only after stoi(l[0]->getValue()). stoi throws on a non-numeric or out-of-range value, the surrounding catch (...) swallows it and resets value to 0, and the delete loop is skipped, leaking every element.
  • Any rule set that increments a variable currently holding a string (for example a tx. score first assigned from an unresolved macro) leaks per request.
  • Behaviour is unchanged: stoi still runs inside the same try, and value is still 0 when it throws.

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

==2== 152 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2==    at 0x4844F93: operator new(unsigned long) (vg_replace_malloc.c:487)
==2==    by 0x49EA951: modsecurity::collection::backend::InMemoryPerProcess::resolveMultiMatches(...) (in_memory-per_process.cc:183)
==2==    by 0x493B6BE: modsecurity::variables::Tx_DynamicElement::evaluate(...) (tx.h:93)
==2==    by 0x49D192C: modsecurity::actions::SetVar::evaluate(...) (set_var.cc:105)

With the fix: All heap blocks were freed -- no leaks are possible; collection-tx.json 7/7, whole regression directory 721 passed / 11 skipped.

references

Summary by CodeRabbit

  • Bug Fixes

    • Fixed numeric variable updates to reliably handle values after evaluation.
    • Invalid numeric values now correctly fall back when applying an addition, producing the expected result.
  • Tests

    • Added regression coverage for updating a non-numeric transaction value with a numeric increment.

@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: ddeb7cb8-b153-474e-b13b-35da1c62a32f

📥 Commits

Reviewing files that changed from the base of the PR and between 7c391e2 and 7ae9b07.

📒 Files selected for processing (1)
  • src/actions/set_var.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: bd1fddad-3c52-4daf-a1af-c7ed6b8cc614

📥 Commits

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

📒 Files selected for processing (2)
  • src/actions/set_var.cc
  • test/test-cases/regression/collection-tx.json

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


📝 Walkthrough

Walkthrough

Changes

Numeric TX update

Layer / File(s) Summary
Safe numeric update and regression coverage
src/actions/set_var.cc, test/test-cases/regression/collection-tx.json
SetVar::evaluate copies the current value before deleting evaluation results. A regression case verifies that adding +10 to TX:something after not_a_number produces 10 with HTTP status 200.

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 1 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 identifies the main change: fixing a memory leak in SetVar when the current value is non-numeric. This matches the implementation and regression test.
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 1 functions across 1 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.

The += and -= branch of SetVar::evaluate() resolves the current value of
the target variable with m_variable->evaluate(), which fills a
std::vector<const VariableValue *> whose elements are owned by the
caller. The elements were deleted only after stoi() had converted the
first one:

    value = stoi(l[0]->getValue());
    for (auto &i : l) {
        delete i;
    }

stoi() throws std::invalid_argument when the current value does not
start with a number and std::out_of_range when it does not fit in an
int. Both exceptions are swallowed by the surrounding catch (...), which
only resets value to 0, so the delete loop is skipped and every
VariableValue in the vector is leaked.

This happens on any request that increments a variable holding a non
numeric value, for example a rule set that does
setvar:tx.score=+1 on a tx.score that was previously assigned a string,
so the leak is per request and unbounded in a long running process.

The value is now copied into a local std::string before the vector is
emptied, and stoi() is called afterwards. The behaviour is unchanged:
the conversion still runs inside the same try block and value stays 0
when it throws.

A regression test is added to collection-tx.json: tx.something is first
set to "not_a_number" and then incremented by 10, which must yield 10.
Running collection-tx.json under valgrind reports

  152 bytes in 1 blocks are definitely lost
     at operator new(unsigned long)
     by modsecurity::collection::backend::InMemoryPerProcess::resolveMultiMatches(...)
     by modsecurity::variables::Tx_DynamicElement::evaluate(...)
     by modsecurity::actions::SetVar::evaluate(...) (set_var.cc:105)

before the change and "All heap blocks were freed -- no leaks are
possible" after it.
@tomsommer
tomsommer force-pushed the fix/setvar-nonnumeric-variablevalue-leak branch from 7c391e2 to 7ae9b07 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