recursion with no floor
Stack Overflow Bugs
Unbounded recursion exhausts the stack; runaway allocation exhausts the heap and the OOM killer steps in. Either way the program is reaped.
A call stack that never unwinds, or a heap that never stops growing, runs out of room.
01in the wild
In the wild
Unbounded Recursion
No base case is reached, so the stack grows until it is gone.
example.py
# SMELL: no base case reached (e.g. a cyclic list)
def depth(node):
return 1 + depth(node.next) # recurses forever
# RIGHT: iterate -- constant stack, any length
def depth(node):
n = 0
while node is not None:
n += 1
node = node.next
return nPython caps recursion at ~1000 frames and raises RecursionError. An explicit loop uses O(1) stack and handles any input.
// observed
recursive: RecursionError at ~1000 frames iterative: returns, constant stack
example.c
/* SMELL: deep recursion smashes the fixed-size stack */
long sum(long n) { return n == 0 ? 0 : n + sum(n - 1); }
sum(10000000); /* segfault: stack overflow, no exception */
/* RIGHT: a loop uses constant stack */
long sum(long n) {
long s = 0;
for (; n > 0; n--) s += n;
return s;
}C does not check stack depth. Overflow is undefined behavior — usually a segfault, sometimes silent corruption.
// observed
recursive: segmentation fault (stack overflow) iterative: returns, constant stack
example.java
// SMELL: recursion deeper than the thread stack
int sum(int n) { return n == 0 ? 0 : n + sum(n - 1); }
sum(1_000_000); // throws StackOverflowError
// RIGHT: iterate
int sum(int n) {
int s = 0;
for (; n > 0; n--) s += n;
return s;
}The JVM turns overflow into a catchable StackOverflowError rather than a crash — but it is still a bug, not a recovery strategy.
// observed
recursive: StackOverflowError iterative: returns, constant stack
Runaway Allocation & the OOM Killer
Holding the whole dataset in memory invites the kernel to reap the process.
example.py
# SMELL: materialize everything in RAM -> the OOM killer wins
rows = [transform(r) for r in db.fetch_all()] # 50M rows at once
return summarize(rows)
# RIGHT: stream and process in bounded chunks
total = Summary()
for batch in db.iter_batches(size=10_000):
total.update(transform(r) for r in batch)
return totalfetch_all() loads the entire result set; memory scales with the data and the kernel kills the process. Streaming keeps memory bounded.
// observed
all-in-RAM: Killed (reaped by the OOM killer) streamed: steady memory, completes
02cross-pollination
Where this compounds
Data Corruption
- Stack Smash via Unbounded Recursion × Unconstrained Inputs
03weakness catalog
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.