CWE-121: Stack-Based Buffer Overflow — When Input Overwrites the Call Stack

Stack-based buffer overflows are among the most iconic and historically important software vulnerabilities. They have powered everything from early worms to modern exploit chains, and despite decades of mitigations, they remain relevant anywhere unsafe languages and unchecked memory operations persist.

CWE-121 occurs when software writes data past the bounds of a stack-allocated buffer.

In practical terms:

The application overwrites memory on the call stack, potentially corrupting execution state.

This article breaks down how stack-based buffer overflows occur, why developers still introduce them, modern exploitation techniques, framework-specific mitigations, and secure coding patterns.

What Is a Stack-Based Buffer Overflow?

A stack-based buffer overflow happens when a fixed-size buffer allocated on the stack receives more data than it can hold.

Unsafe example:

void process(char *input) {
    char buf[32];
    strcpy(buf, input);
}

If input exceeds 32 bytes, the overflow extends past buf into adjacent stack memory.

That may overwrite:

  • Local variables
  • Saved frame pointer
  • Return address
  • Exception handlers
  • Stack canaries

How Stack-Based Buffer Overflow Actually Works

The root issue is unchecked writes into memory allocated on the stack frame.

Attack Flow

  1. Function allocates stack buffer
  2. Oversized input is copied into buffer
  3. Write exceeds buffer boundary
  4. Adjacent stack data overwritten
  5. Program crashes or execution is hijacked

Visual: Stack Overflow Memory Layout

Return Address Saved Frame Ptr Buffer Overflow Payload Extends Stack Grows Downward

Why Developers Still Get Stack Overflows Wrong

Unsafe Copy Functions

Classic dangerous APIs remain common:

  • strcpy
  • strcat
  • sprintf
  • gets
  • Unbounded scanf

Miscalculated Bounds

Developers validate lengths incorrectly:

  • Off-by-one mistakes
  • Null terminator miscalculations
  • Integer truncation/overflow

Legacy Native Code

Large C/C++ codebases continue to expose stack memory directly.

Assumption That Mitigations “Solve It”

Compiler protections reduce exploitability but do not eliminate the bug.

Modern Exploitation Techniques

Return Address Overwrite

Classic redirection of instruction pointer after function return.

ROP (Return-Oriented Programming)

Chain existing instruction sequences to bypass NX/DEP.

Stack Pivoting

Move stack pointer to attacker-controlled memory.

Partial Return Address Overwrite

Modify only low bytes for precision bypasses.

Data-Only Corruption

Corrupt stack-resident flags/variables without full control-flow hijack.

Visual: Stack Overflow Exploitation Chain

Stack Buffer Overflow Return Address Corruption ROP Chain Logic Corruption RCE

How CWE-121 Differs from CWE-120

Developers often confuse these related CWEs.

CWE-120

Broadly covers:

Buffer copy without checking input size.

CWE-121

Specifically covers:

Stack-allocated buffer overflow.

CWE-121 is a more specific subtype focused on stack memory.

Secure Coding Examples

Unsafe

strcpy(buf, input);

Safer

snprintf(buf, sizeof(buf), "%s", input);

Better

std::string safe = input;

Prefer abstractions that manage memory automatically.

Framework / Language Mitigations

Compiler Protections

Use:

  • Stack canaries (-fstack-protector)
  • ASLR
  • NX/DEP
  • Shadow stacks / CET
  • CFI

Sanitizers

Run during testing:

  • AddressSanitizer
  • UBSan

Memory-Safe Languages

Prefer when practical:

  • Rust
  • Go
  • Java
  • C#

Defense in Depth

Ban Unsafe Stack Copy Patterns

Disallow dangerous functions in coding standards.

Validate Lengths Before Copying

Always prove data fits destination.

Fuzz Boundary Conditions

Boundary bugs frequently surface quickly under fuzzing.

Harden High-Risk Native Components

Parsers and protocol handlers deserve special scrutiny.

Final Thoughts

Stack-based buffer overflows remain foundational to exploit development because they target one of the most security-sensitive memory regions in a process: the call stack.

They persist because:

  • Native code remains widespread
  • Unsafe APIs survive in legacy systems
  • Boundary logic is easy to get wrong
  • Mitigations reduce but do not remove risk

The core lesson is simple:

If untrusted input can overwrite the stack, attackers may control what your program does when the function returns.

Modern mitigations make exploitation harder, but secure software still begins with correct bounds handling.