Skip to content

Stack Overflow Prevention

Overview

Implementations MUST prevent stack overflow from deeply nested or recursive input.

The Problem

Recursive descent parsers use the call stack for nesting:

beancount
; Each level of nesting = one stack frame
2024-01-01 * "Transaction"
  Assets:A  1 USD {1 USD, 2024-01-01, "label"} @ 1 USD
    metadata: "deeply"
      nested: "value"

Malicious input with excessive nesting can exhaust the stack.

Attack Vectors

1. Deep Include Chains

file1.beancount includes file2.beancount
file2.beancount includes file3.beancount
...
file1000.beancount includes file1001.beancount

2. Nested Expressions

If arithmetic expressions are supported:

((((((((((((((((((((1+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)

3. Deep Metadata

beancount
2024-01-01 open Assets:Test
  meta1: "value"
  ; Parser stores metadata stack

Requirements

Depth Limits

StructureMinimum LimitRecommended
Include depth10100
Expression nesting50200
Parser call depth1001000

Implementation

Implementations MUST either:

  1. Use iterative parsing with explicit stack
  2. Track recursion depth and reject at limit
  3. Increase stack size with hard upper bound

Iterative Parsing

Convert recursive algorithms to iterative:

python
# Recursive (vulnerable)
def parse_expr(tokens):
    left = parse_term(tokens)
    if tokens.peek() == '+':
        tokens.consume()
        right = parse_expr(tokens)  # Recursive call
        return Add(left, right)
    return left

# Iterative (safe)
def parse_expr(tokens):
    stack = []
    while True:
        term = parse_term(tokens)
        if not stack or tokens.peek() != '+':
            break
        tokens.consume()
        stack.append(term)
    result = term
    while stack:
        result = Add(stack.pop(), result)
    return result

Depth Tracking

rust
fn parse_expr(&mut self, depth: usize) -> Result<Expr, Error> {
    if depth > MAX_DEPTH {
        return Err(Error::MaxDepthExceeded);
    }
    // ... parsing logic
    self.parse_expr(depth + 1)?  // Pass depth
}

Stack Size Considerations

PlatformDefault StackConfigurable
Linux8 MBulimit -s
macOS8 MBulimit -s
Windows1 MBLinker flag
WASM1 MBCompile option

Error Messages

error: Maximum nesting depth exceeded
  --> malicious.beancount:1000:1
  |
  = depth: 101
  = limit: 100
  = hint: simplify expression or increase --max-depth

Testing

Create test files with:

  1. Include depth at exactly limit → accept
  2. Include depth at limit + 1 → reject
  3. Expression nesting at limit → accept
  4. Expression nesting at limit + 1 → reject

Verify no crash, only clean error.