Uncontrolled recursion occurs when a program calls itself repeatedly without a proper limit, allowing an attacker to exhaust the call stack and crash the…
Uncontrolled recursion occurs when a program calls itself repeatedly without a proper limit, allowing an attacker to exhaust the call stack and crash the application. Unlike infinite loops, which consume CPU, unbounded recursion consumes memory on the stack itself — a finite resource that, once exhausted, terminates the process. This weakness is particularly dangerous in parsers, tree walkers, and data processors that accept untrusted input.
02How It Happens
Recursive functions are designed to solve problems by breaking them into smaller instances of the same problem. However, if the recursion depth is not bounded or validated against input size, an attacker can craft input that forces the function to recurse far deeper than intended. For example, a parser that recursively descends through nested structures (JSON, XML, file systems) without checking depth will crash if given deeply nested input. Similarly, functions that recurse based on user-controlled parameters (array size, string length, object depth) can be exploited if those parameters are not validated before recursion begins.
03Real-World Impact
An attacker exploiting uncontrolled recursion can trigger a denial-of-service (DoS) condition by crashing the application with a single malformed request. This affects availability — legitimate users cannot access the service. In multi-threaded environments, a single recursive request may consume one thread's stack, but repeated requests can exhaust all available threads. Recovery typically requires restarting the application. In some cases, stack exhaustion can also corrupt memory or trigger undefined behavior.
04Vulnerable & Fixed Patterns
Vulnerable pattern
import json
def parse_nested_structure(data):
"""Recursively parse nested JSON without depth limit."""
if isinstance(data, dict):
return {k: parse_nested_structure(v) for k, v in data.items()}
elif isinstance(data, list):
return [parse_nested_structure(item) for item in data]
else:
return data
# Attacker sends deeply nested JSON
user_input = json.loads(request_body)
result = parse_nested_structure(user_input)
Why it's vulnerable: The function recurses for every level of nesting in the input without checking depth. An attacker can send JSON with thousands of nested levels, exhausting the call stack.
Fixed pattern
import json
def parse_nested_structure(data, max_depth=100, current_depth=0):
"""Recursively parse nested JSON with depth limit."""
if current_depth > max_depth:
raise ValueError(f"Nesting depth exceeds maximum of {max_depth}")
if isinstance(data, dict):
return {k: parse_nested_structure(v, max_depth, current_depth + 1)
for k, v in data.items()}
elif isinstance(data, list):
return [parse_nested_structure(item, max_depth, current_depth + 1)
for item in data]
else:
return data
user_input = json.loads(request_body)
result = parse_nested_structure(user_input, max_depth=100)
Vulnerable pattern
<?php
function process_tree($node) {
// Recursively process tree nodes without depth limit
echo $node['value'];
foreach ($node['children'] as $child) {
process_tree($child); // No depth check
}
}
$tree = json_decode($_POST['tree_data'], true);
process_tree($tree);
?>
Why it's vulnerable: The function recurses through all children without validating tree depth. An attacker can send a tree with thousands of levels, causing stack overflow.
Fixed pattern
<?php
function process_tree($node, $max_depth = 100, $current_depth = 0) {
if ($current_depth > $max_depth) {
throw new Exception("Tree depth exceeds maximum of {$max_depth}");
}
echo htmlspecialchars($node['value']);
if (isset($node['children']) && is_array($node['children'])) {
foreach ($node['children'] as $child) {
process_tree($child, $max_depth, $current_depth + 1);
}
}
}
$tree = json_decode($_POST['tree_data'], true);
process_tree($tree, 100);
?>
05Prevention Checklist
Set a maximum recursion depth and pass it as a parameter to every recursive function; reject input that would exceed it before recursion begins.
Validate input size and structure before processing — check array lengths, string lengths, and nesting depth against reasonable limits for your use case.
Use iterative algorithms where practical — convert recursive tree/graph traversal to stack-based or queue-based iteration to eliminate stack pressure entirely.
Monitor stack usage in production — set OS-level stack size limits and alert on near-exhaustion; use profiling tools to identify unexpectedly deep recursion during testing.
Document recursion limits in code comments and API documentation so developers understand the constraints and don't accidentally remove them.
Test with adversarial input — include test cases with maximum nesting depth, large arrays, and deeply nested structures to verify your limits hold.
06Signs You May Already Be Affected
Look for application crashes or restarts with stack overflow errors in logs, particularly after processing user-supplied JSON, XML, or file uploads. If your parser or tree-walking code lacks depth checks and accepts untrusted input, you are at risk. Monitor for repeated crashes from the same endpoint or file type, which may indicate an attacker probing your recursion limits.