CWE-674 is code that does not properly control how much recursion takes place, so it consumes excessive resources such as allocated memory or the program stack. Parsers, serializers, tree walkers, template engines and anything that handles nested input are the usual victims. An attacker needs nothing more than a deeply nested or self-referencing input.
How it happens
A recursive function calls itself until a stopping condition is met. If an outside party controls the input and the stop depends on that input, they can arrange it so the stop never comes, or comes far too late. Each call uses stack space, and the stack is small. Eventually the program crashes or the runtime kills it.
# Vulnerable: depth follows the input, with no limit
def flatten(node):
out = []
for child in node:
if isinstance(child, list):
out.extend(flatten(child)) # a very deep list exhausts the stack
else:
out.append(child)
return out
A few thousand levels of nesting in the input, easy to produce in a small request body, is enough to raise a recursion error in Python or overflow the stack in languages without a built-in guard.
The record lists four real cases:
- CVE-2007-1285: deeply nested arrays trigger stack exhaustion.
- CVE-2007-3409: self-referencing pointers create an infinite loop and resultant stack exhaustion.
- CVE-2016-10707: a JavaScript application accidentally changes its input in a way that prevents a recursive call from detecting an exit condition.
- CVE-2016-3627: while recovering a corrupted XML file, an infinite recursion protection counter was not always incremented, missing the exit condition.
The last one is instructive. The code had a guard. A path around the counter defeated it.
What an attacker gains
MITRE lists two consequences.
- Availability. CPU, memory and stack can be rapidly consumed or exhausted, leading to an exit or crash. This is a denial of service that costs the attacker very little.
- Confidentiality. In some cases an interpreter kills a process or thread that appears to consume too much, such as PHP's memory_limit setting. The error it reports may contain detailed information about the application, which helps further attacks.
If the recursion lives in a third party library, your code may inherit the weakness without any recursion of your own. The free dependency scan checks up to 100 dependencies in your browser, with no login and with the file staying on your machine, and reachability analysis can show whether your code calls the affected function.
How to find it
The record lists automated static analysis (SAST), which can find some instances by analyzing source or compiled code without executing it. Combine it with the following checks.
- List every recursive function, including indirect recursion where A calls B and B calls A.
- For each one, identify what drives the depth, and whether outside input can control it.
- Look for structures that can contain themselves, such as object graphs and linked pointers.
- Fuzz parsers with deeply nested and self-referencing inputs, and watch for crashes and timeouts.
- Check error handling for guards and counters, and confirm every path updates them.
- Review error messages sent to clients to see whether crashes leak details.
How to fix it
MITRE's mitigations:
- Ensure an end condition will be reached under all logic conditions. The end condition may include checking the depth of recursion and exiting with an error if it goes too deep. MITRE rates this as moderately effective, and notes the effectiveness depends on how well the end condition is built.
- Increase the stack size. MITRE rates this as limited. It may only be a temporary measure because the stack is still not very large and an attacker can still cause an out-of-stack fault.
The first one is the real fix. Pass a depth counter and fail cleanly:
MAX_DEPTH = 100
def flatten(node, depth=0):
if depth > MAX_DEPTH:
raise ValueError("input nested too deeply")
out = []
for child in node:
if isinstance(child, list):
out.extend(flatten(child, depth + 1))
else:
out.append(child)
return out
Beyond that, consider an iterative version with an explicit stack and a size limit, set maximum request body sizes, and return a generic error to the client instead of a stack trace.
Try it yourself
Click through it at your own pace, or open it full screen.
Quick check
Is a bigger stack a fix?
MITRE rates it as limited effectiveness. It raises the bar slightly, but attackers can still exceed it. A depth limit is the real control.
How can an error message become a second problem?
When a process is killed for using too much, the error may report detailed information about the application. MITRE lists this as a confidentiality consequence.
Why did the XML recovery example fail even with protection?
The counter that guarded against infinite recursion was not always incremented, so one path never reached the exit condition.