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 n
Python 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 total
fetch_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

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.