Skip to content

[Bug]: Resource exhaustion through approved parser boundary β€” eager Pow/factorial evaluation (CWE-400 family of GHSA-mw6r-2hvm-4rp2)Β #25

Description

QWED-MCP Audit Finding: HIGH-05

Severity: 🟠 HIGH
Component: safe_parser.py (approved boundary) + math_engine.py + tools.py (execution sandbox)
Audit ID: HIGH-05
Re-audit of v0.2.1: Finding is NEW β€” distinct from fixed #10 (execution timeout) and from GHSA-mw6r-2hvm-4rp2 (CWE-94)


Description

GHSA-mw6r-2hvm-4rp2 fixed code execution (CWE-94) in the SymPy parser path. The same sink family remains open for resource exhaustion (CWE-400): SymPy eagerly evaluates integer Pow and factorial, and the denylist does not constrain operand magnitude.

Empirically confirmed on v0.2.1:

safe_parse_expr("10**10**6")
# -> sympy.Integer with bit_length = 3,321,929 (~415 KB) β€” FULLY evaluated in memory

Therefore:

Payload Effect Denylist result
10**10**10 ~4 GB integer computation β†’ host OOM βœ… passes
factorial(10**9) ~8.5 billion digits β†’ host OOM βœ… passes
10**9 ** (10**9) variants unbounded allocation βœ… passes

The 5,000-char length cap does not help β€” the payloads above are under 20 chars.

Compounding factor: the execution subprocess in execute_python_code_tool has no memory limits (no resource.setrlimit, no cgroup constraint). The 30s foreground / 120s background timeouts (fix for #10) do not help because the allocation completes in well under a second β€” the OOM killer (or host swap death) fires before the timeout does.

Additionally, math_engine.verify_math_expression runs simplify(actual - claimed) with no operation-level bound. Pathological inputs (simplify on adversarial expressions is a known SymPy CPU sink) give each request up to 30s of free CPU burn inside the semaphore window.


Why This Violates QWED Philosophy

  • Principle 11 β€” Vulnerability Family Thinking: The GHSA fix patched one vulnerability class at the sink (code execution) and left the adjacent class (resource exhaustion) open at the same sink. Per Principle 11, the fix is incomplete while equivalent-impact paths remain.
  • Principle 2 β€” Fail Closed: An unverifiable resource cost is currently accepted by default. Inputs whose evaluation cost cannot be bounded must be rejected.

Expected Behavior

  • Evaluation cost must be bounded before evaluation. Deterministic options:
    • Cap exponent operands in Pow with integer base/exponent above a threshold (e.g., reject a**b when b > 10_000 and a > 1 β€” the result would exceed ~33k bits).
    • Cap factorial(n) at a fixed n ceiling (e.g., n <= 10_000).
    • Reject nested power towers beyond depth 2 with integer operands.
  • The execution subprocess must have a memory ceiling (resource.setrlimit(RLIMIT_AS, ...) on POSIX; Job Objects on Windows) so a blowout kills the child, not the host.
  • Math engine operations (diff, integrate, simplify, solve) should run under an operation-level deadline so a single request cannot consume the entire tool-call window in CPU.

Suggested Fix Direction

# In safe_parser.py β€” AST-level pre-flight after parse, before return
import sympy

_MAX_POW_EXPONENT = 10_000
_MAX_FACTORIAL_ARG = 10_000

def _enforce_cost_bounds(expr):
    for node in sympy.preorder_traversal(expr):
        if isinstance(node, sympy.Pow) and node.exp.is_Integer:
            if abs(int(node.exp)) > _MAX_POW_EXPONENT and node.base != 1:
                raise SafeParserError("Exponent exceeds deterministic cost bound")
        if isinstance(node, sympy.factorial) and node.args[0].is_Integer:
            if int(node.args[0]) > _MAX_FACTORIAL_ARG:
                raise SafeParserError("factorial argument exceeds deterministic cost bound")
    return expr

Note: bounds must be enforced on the parsed expression (post-parse_expr), which means parse_expr itself must not eagerly evaluate β€” use evaluate=False where supported, or pre-scan operand magnitudes with a regex/AST pass before calling parse_expr. Whichever route is chosen, the invariant is: no eager evaluation of unbounded-magnitude operands.


Environment

Field Value
QWED-MCP Version 0.2.1
Component Parser Boundary + Math Engine + Execution Sandbox
Files src/qwed_mcp/engines/safe_parser.py, src/qwed_mcp/engines/math_engine.py, src/qwed_mcp/tools.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions