Skip to content

Add stack overflow and nesting depth protection to parser - #167

Closed
beltoforion wants to merge 3 commits into
masterfrom
claude/security-audit-Iczmo
Closed

beltoforion wants to merge 3 commits into
masterfrom
claude/security-audit-Iczmo

Conversation

@beltoforion

Copy link
Copy Markdown
Owner

Summary

This PR adds runtime safety checks to prevent stack overflow and excessive nesting depth in the muParser expression evaluator. These changes protect against both malicious and accidental inputs that could cause buffer overflows or stack exhaustion.

Key Changes

  • Stack overflow protection: Added iStackMaxSize variable to track the maximum allowed stack size and inserted bounds checks before all stack push operations (cmVAR, cmVAL, cmVARPOW2, cmVARPOW3, cmVARPOW4, cmVARMUL, and function calls with 0 arguments)

  • Nesting depth limits:

    • Added MaxNestingDepth constant (1000) to muParserDef.h
    • Added validation in cmIF bytecode generation to prevent excessive if/else nesting
    • Added validation in token reader to prevent excessive bracket nesting
  • Error handling improvements:

    • Replaced MUP_ASSERT with runtime Error() call in cmIF case to handle negative stack index
    • Added check in GetMaxStackSize() to detect invalid stack size values
    • Fixed string variable buffer bounds check in token reader
  • Code quality:

    • Added #include <limits> to support std::numeric_limits
    • Fixed character encoding issue in comment (control character representation)
    • Removed trailing whitespace

Implementation Details

The stack protection works by calculating the maximum stack size per thread at the beginning of expression evaluation and checking before each push operation. If a push would exceed the limit, an ecINTERNAL_ERROR is raised.

The nesting depth protection prevents deeply nested expressions from consuming excessive memory or causing stack exhaustion during parsing and bytecode generation.

https://claude.ai/code/session_01AdA3xUA3X15KMYv6VC7tXQ

claude added 3 commits April 9, 2026 11:38
1. Out-of-bounds read in IsStrVarTok (muParserTokenReader.cpp):
   The string-variable buffer index check only tested for a non-empty
   buffer, not that item->second was a valid index. Replace with a
   proper bounds check: `item->second >= m_vStringVarBuf.size()`.

2. No runtime bounds check on eval-loop stack index `sidx`
   (muParserBase.cpp):
   - The only guard was a MUP_ASSERT (disabled in release builds).
   - Replace the MUP_ASSERT in the cmIF case with a real Error() call.
   - Compute iStackMaxSize once and guard every ++sidx push operation
     (cmVAR, cmVAL, cmVARPOW*, cmVARMUL, 0-arg cmFUNC/cmFUNC_BULK)
     against exceeding the allocated buffer.

3. Unbounded nesting depth (muParserDef.h, muParserTokenReader.cpp,
   muParserBase.cpp):
   - Add MaxNestingDepth = 1000 constant.
   - Enforce it on the bracket stack in IsOprt() so deeply-nested
     bracket expressions are rejected instead of consuming unbounded
     heap memory.
   - Enforce it on the ifElseCounter in CreateRPN() for nested
     ternary expressions.

4. Integer overflow in GetMaxStackSize() (muParserBytecode.cpp):
   `m_iMaxStackSize + 1` wraps to 0 when m_iMaxStackSize == SIZE_MAX,
   causing the stack buffer to be under-allocated. Add an overflow
   guard that throws ecINTERNAL_ERROR in that (theoretical) case.

https://claude.ai/code/session_01AdA3xUA3X15KMYv6VC7tXQ
The previous MUP_ASSERT(sidx >= 0) was already a runtime check (MUP_ASSERT
always throws ParserError, it is not a debug-only macro). However, it was
placed inside the 'if (stack[sidx--] == 0)' body, so a negative sidx was
only detected when the condition evaluated to zero. When the condition was
non-zero the decrement still happened but the bounds check was skipped.

Restructure the check so sidx is validated unconditionally after the
post-decrement, regardless of which branch the ternary takes.

https://claude.ai/code/session_01AdA3xUA3X15KMYv6VC7tXQ
Instead of restructuring the conditional with an intermediate bool,
simply place MUP_ASSERT(sidx >= 0) after the if block so it runs
unconditionally on both branches.

https://claude.ai/code/session_01AdA3xUA3X15KMYv6VC7tXQ
beltoforion added a commit that referenced this pull request Apr 10, 2026
added internal checks to avoid potential access violations; I had no test cases or indications they posed a problem or could be triggered but better be safe then sorry.
@beltoforion

Copy link
Copy Markdown
Owner Author

closed; ai generated code will not be merged; i added the modifications manually after a review.

@beltoforion
beltoforion deleted the claude/security-audit-Iczmo branch April 10, 2026 21:07
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.

2 participants