Skip to content

fix: Lua m.setvar() returned -1, handing the script stack garbage - #3732

Open
fzipi wants to merge 2 commits into
owasp-modsecurity:v3/masterfrom
fzipi:fix/lua-setvar-return-3731
Open

fzipi wants to merge 2 commits into
owasp-modsecurity:v3/masterfrom
fzipi:fix/lua-setvar-return-3731

Conversation

@fzipi

@fzipi fzipi commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

what

Fixes three problems in Lua::setvar() (src/engine/lua.cc):

  • Error paths return 0 instead of -1. This covers the wrong number of arguments and a name with no collection. The script now gets nil.
  • luaL_checkstring() is called before the std::string locals are constructed. It raises a Lua error with longjmp on a non-string argument, which would skip their destructors. This makes the NULL check after it unreachable, so that check is removed.
  • The lua_pop(L, 2) is removed. It popped the value argument while var_value still pointed into it. A lua_CFunction's stack is cleared on return anyway.
  • Typo fixed in the debug message: "funtion must has" is now "function must have".

why

A lua_CFunction returns the number of values it pushed. With -1, Lua returned whatever was on the stack. With the two new test scripts on v3/master, m.setvar("novar", "x") returned the __transaction pointer (userdata: 0x…), and m.setvar("tx.a") returned a stray string.

tests

  • Two new cases in collection-lua.json, with the scripts setvar-error-nodot.lua and setvar-error-onearg.lua, check that the script gets nil. Both fail on v3/master and pass with this change.
  • The other Lua regression files pass: action-exec, directive-sec_rule_script, operator-inpectFile and issue-2427.
  • make check (macOS, Lua 5.5.1, PCRE2): 5139 total, 0 fail.

references

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Calls to set a variable now handle missing or extra arguments consistently, and variable names without a collection prefix return no values instead of an error code.
    • Unknown collections are logged and handled without an unexpected failure. Non-string values produce a Lua script error.
    • Setting a valid transaction variable continues to work and returns no values.
  • Tests
    • Added regression coverage for valid and invalid variable-setting calls.

Lua::setvar() returned -1 on its error paths (wrong number of
arguments, no collection in the name). A lua_CFunction returns the
number of values it pushed, so Lua returned whatever was on the stack:
the script got the __transaction pointer or a stray string. Return 0 so
the script gets nil.

Also call luaL_checkstring() before the std::string locals exist,
since it raises a Lua error with longjmp on a non-string argument,
drop the NULL check it makes unreachable, and stop popping the
arguments while var_value still points into them.

Fixes owasp-modsecurity#3731

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiUJuqXNvvpytiGRiDFyTy
@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e02d2fa4-9954-47d3-8ea1-9cf953417b04
Commits

Reviewing files that changed from the base of the PR and between 3e7d59e and 3b81dca.

Files selected for processing: 5
  • src/engine/lua.cc
  • test/test-cases/data/setvar-error-table.lua
  • test/test-cases/data/setvar-error-unknown-collection.lua
  • test/test-cases/data/setvar-tx.lua
  • test/test-cases/regression/collection-lua.json

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Walkthrough

Lua::setvar now returns zero Lua values for incorrect argument counts and names without a collection separator. It validates string arguments before constructing local strings and logs unknown collection names. New regression cases cover these paths, non-string arguments, and setting a TX variable.

Changes: Lua setvar error handling

Layer / File(s) Summary
Argument checks and return handling
src/engine/lua.cc
setvar checks both arguments before constructing local strings. It returns zero values for an incorrect argument count or a name without a collection separator. It logs unknown collection names.
Regression coverage
test/test-cases/data/setvar-*.lua, test/test-cases/regression/collection-lua.json
New Lua scripts and regression cases check missing and invalid arguments, unknown collections, successful TX assignment, and a non-string argument error.

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

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • Issue 3655 lists the v3 Lua setvar negative return values as one scope item. This change addresses those return values; other issue items are not covered.

Suggested reviewers: airween

Merge Risk: Minimal · up to 3b81d

The changed Lua setvar behavior satisfies the stated requirements and no merge-blocking risk remains.

Pre-merge checks: Passed 4 · Failed 1

Failed 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 with cyclomatic complexity of at least 2. Documentat… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed 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 Lua m.setvar() error paths that returned -1 and exposed invalid script-stack values.
Linked Issues check Passed Issue [#3731] requires safe Lua::setvar() error returns, early luaL_checkstring() calls, the corrected debug message, and regression coverage. src/engine/lua.cc returns 0 for wrong argument co…
Out of Scope Changes check Passed The changes stay within [#3731]. The stack handling change supports the Lua::setvar() return-value fix. The unknown-collection log, TX control case, and non-string case test behavior required by the…
Full details: Linked Issues check

Explanation

Issue [#3731] requires safe Lua::setvar() error returns, early luaL_checkstring() calls, the corrected debug message, and regression coverage. src/engine/lua.cc returns 0 for wrong argument counts and missing collection separators, calls both checks before constructing std::string objects, removes the obsolete stack pop and null check, and uses “function must have”. The regression cases verify nil, error logging, unknown collections, successful TX updates, and non-string argument errors.

Full details: Out of Scope Changes check

Explanation

The changes stay within [#3731]. The stack handling change supports the Lua::setvar() return-value fix. The unknown-collection log, TX control case, and non-string case test behavior required by the issue discussion. No unrelated source or test changes are shown.

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 with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 2 functions across 6 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues remain, and the changes include targeted validation and regression coverage for the corrected invalid-call behavior.

No open issues 🎉

What changed in this PR

This pull request fixes Lua::setvar() so invalid calls return no Lua values instead of exposing stale stack data, while improving argument validation and adding regression coverage.

Changes:

  • Return 0 for incorrect argument counts and variable names without collections.
  • Validate Lua string arguments before constructing C++ strings.
  • Remove unsafe stack popping and correct the debug-message typo.
  • Add regression cases verifying invalid calls evaluate to nil.
File Description
test/​test-cases/​regression/​collection-lua.json Adds regression cases for invalid m.setvar() calls.
test/​test-cases/​data/​setvar-error-onearg.lua Tests the one-argument error path.
test/​test-cases/​data/​setvar-error-nodot.lua Tests the missing-collection error path.
src/​engine/​lua.cc Corrects setvar() error handling, argument validation order, stack handling, and debug text.

Review effort: Lite

Comment thread src/engine/lua.cc
t->m_rules->m_secWebAppId.m_value,
var_value);

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An unknown collection, e.g. m.setvar("FOO.bar", "x"), still falls through all the branches below without a message. Could you add a final else after the USER branch (after line 404), with a level 8 message like the one above, and the name escaped as in #3730?

    else {
        ms_dbg_a(t, 8, "m.setvar: Unknown collection: "
            + utils::string::toHexIfNeeded(collection) + ".");
    }

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3b81dca: a final else logs m.setvar: Unknown collection: <name>. at level 8, with the name passed through toHexIfNeeded().

@airween

airween commented Nov 1, 2026

Copy link
Copy Markdown
Member

@fzipi,

I also started to work on your issue #3731, and here are the test cases for that. They follow your two cases, so they can go at the end of collection-lua.json:

test/test-cases/data/setvar-error-unknown-collection.lua:

function main()
    local r = m.setvar("FOO.bar", "x");
    m.log(4, "m.setvar() with an unknown collection returned: " .. tostring(r));
    return nil;
end

test/test-cases/data/setvar-tx.lua, a successful TX set as a control:

function main()
    local r = m.setvar("tx.a", "x");
    m.log(4, "m.setvar() with TX returned: " .. tostring(r));
    m.log(4, "tx.a is now: " .. tostring(m.getvar("tx.a")));
    return nil;
end

test/test-cases/regression/collection-lua.json, after your two cases:

  {
    "enabled": 1,
    "version_min": 300000,
    "title": "Testing LUA :: m.setvar with an unknown collection returns nil",
    "resource": "lua",
    "client": {
      "ip": "200.249.12.31",
      "port": 123
    },
    "server": {
      "ip": "200.249.12.31",
      "port": 80
    },
    "request": {
      "headers": {
        "Host": "localhost",
        "User-Agent": "My sweet little browser",
        "Accept": "*/*",
        "Content-Length": "0"
      },
      "uri": "/whee?res=1",
      "method": "GET",
      "body": [
        ""
      ]
    },
    "response": {
      "headers": {
        "Content-Length": "8"
      },
      "body": [
        "no need."
      ]
    },
    "expected": {
      "debug_log": "m.setvar: Unknown collection: FOO\\..*m.setvar\\(\\) with an unknown collection returned: nil",
      "http_code": 200
    },
    "rules": [
      "SecRuleEngine On",
      "SecRuleScript test-cases/data/setvar-error-unknown-collection.lua \"id:1,pass\""
    ]
  },
  {
    "enabled": 1,
    "version_min": 300000,
    "title": "Testing LUA :: m.setvar sets a TX variable and returns nil",
    "resource": "lua",
    "client": {
      "ip": "200.249.12.31",
      "port": 123
    },
    "server": {
      "ip": "200.249.12.31",
      "port": 80
    },
    "request": {
      "headers": {
        "Host": "localhost",
        "User-Agent": "My sweet little browser",
        "Accept": "*/*",
        "Content-Length": "0"
      },
      "uri": "/whee?res=1",
      "method": "GET",
      "body": [
        ""
      ]
    },
    "response": {
      "headers": {
        "Content-Length": "8"
      },
      "body": [
        "no need."
      ]
    },
    "expected": {
      "debug_log": "m.setvar\\(\\) with TX returned: nil.*tx.a is now: x",
      "http_code": 200
    },
    "rules": [
      "SecRuleEngine On",
      "SecRuleScript test-cases/data/setvar-tx.lua \"id:1,pass\""
    ]
  }

The first one needs the new else branch, so it fails without it. The second one passes with and without this PR, it's only there to show that the success path still works and returns nil.

Feel free to add them to this PR.

m.setvar("FOO.bar", "x") fell through every collection branch and was
ignored without a message. Log it at level 8, like the other m.setvar()
errors, with the name escaped.

Tests: assert the debug message on each error path as well as the nil
return, since m.setvar() also returns nil on success. Add cases for an
unknown collection, a successful TX set (both from @airween), and a
non-string value, which must fail the script with a Lua error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiUJuqXNvvpytiGRiDFyTy
@fzipi

fzipi commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks @airween. I added both of your cases as they are in 3b81dca, with the new else branch. I also:

  • made the two original cases check the error debug message as well as the nil return, because m.setvar() returns nil on success too, so nil alone can't tell the paths apart;
  • added a case for a non-string value, m.setvar("tx.a", {}). It must fail the script with Lua's bad argument #2 to 'setvar', which covers the reordering of luaL_checkstring().

collection-lua.json now has 12 cases and they all pass, and the full make check gives 5142 total, 0 fail (macOS, Lua 5.5.1).

v2's l_setvar() has the same return -1 and lua_pop() problem, so I'm opening a separate v2 PR for it.

@sonarqubecloud

sonarqubecloud Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Quality Gate Passed Quality Gate passed

Issues
0 New issues
2 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@airween

airween commented Nov 2, 2026

Copy link
Copy Markdown
Member

@fzipi,

thanks for your changes. Unfortunately the Windows tests were failed, all of them dropped a segfault, eg this. Please take a look at them.

This branch has not been deployed

No deployments
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.

v3: Lua m.setvar() returns -1 on errors, so the script gets stack garbage

3 participants