A buffer overflow happens when a program tries to stuff more data into a block of memory than that block was designed to hold. The excess data spills over into adjacent memory, overwriting whatever was stored there. This can crash the program, corrupt data, or, in the worst case, give an attacker a way to run their own code on your machine. It ranked #11 on MITRE’s 2025 list of the most dangerous software weaknesses, appearing in the Top 25 for the first time.
How Memory Buffers Work
When a program runs, it reserves small, fixed-size blocks of memory called buffers to temporarily hold data: a username someone typed in, a chunk of a file being read, a network packet arriving from the internet. Each buffer has a set capacity. A buffer meant to hold 64 characters, for instance, has room for exactly that and nothing more.
The problem arises when a program accepts input without checking whether it fits. If someone (or something) sends 200 characters into that 64-character buffer, the extra 136 characters don’t just vanish. They write over whatever sits next to the buffer in memory. That neighboring memory might contain other variables the program relies on, instructions the processor is about to execute, or pointers that tell the program where to go next. Overwriting any of these can change the program’s behavior in unpredictable and dangerous ways.
Stack Overflows vs. Heap Overflows
Programs use two main regions of memory, and overflows in each work a bit differently.
The stack is where a program keeps track of function calls. Every time your program calls a function, it pushes a small frame onto the stack containing the function’s local variables and, critically, a return address that tells the program where to pick up after the function finishes. A stack-based overflow overwrites that return address. If an attacker crafts the overflow carefully, they can replace the return address with one that points to malicious code they’ve injected. When the function finishes and tries to “return,” it jumps to the attacker’s code instead.
The heap is a more free-form pool of memory that programs use for data allocated on the fly. Heap overflows are harder to exploit because the layout of heap memory is less predictable. An attacker needs to manipulate the pattern of memory allocations so that something valuable, like a pointer to another function, ends up right next to the buffer they’re overflowing. Heap exploits are highly specific to the program and the system’s memory manager, but they’re still a real threat.
Why Overflows Let Attackers Run Code
The leap from “data spilled past a buffer” to “an attacker controls your computer” comes down to control flow. Every program follows a sequence of instructions. Certain values in memory act as signposts, telling the processor which instruction to execute next. The return address on the stack is the most classic example.
If an attacker can overwrite one of these signposts, they can redirect the program to execute whatever they want. In practice, an attacker sends a carefully constructed string of data that contains both the overflow padding and a payload of malicious instructions. The overflow overwrites the return address so it points to those instructions. The program, following its normal logic, finishes the current function, reads the corrupted return address, and jumps straight into the attacker’s code. This is the core mechanism behind remote code execution vulnerabilities, where someone on the internet can run commands on a server they’ve never physically touched.
Languages like C and C++ make this possible because they give programmers direct access to memory and don’t automatically check whether data fits in a buffer. The program simply writes the data and trusts that the programmer got the size right. When that trust is misplaced, the door opens.
Which Languages Are Vulnerable
Buffer overflows are overwhelmingly a problem in languages that let programmers manage memory manually, primarily C and C++. These languages were designed for speed and low-level hardware access, and they don’t include built-in checks on array boundaries. If you write past the end of an array in C, the language won’t stop you or even warn you at runtime.
Languages with built-in memory safety protections are effectively immune to classic buffer overflows. A joint advisory from NSA and CISA lists Ada, C#, Go, Java, Python, Ruby, Rust, and Swift as memory-safe alternatives. In these languages, trying to write past the end of a buffer triggers an error or exception rather than silently corrupting memory. The tradeoff is a small performance cost for the safety check, which is why performance-critical software like operating system kernels, device drivers, and game engines has historically been written in C or C++.
Rust deserves a special mention. It provides memory safety without the performance overhead of a garbage collector, making it a practical replacement for C in systems programming. DARPA is currently funding a program called TRACTOR that aims to automatically translate existing C codebases into Rust, a sign of how seriously the security community takes this class of vulnerability.
Common Coding Mistakes That Cause Overflows
Several functions in the C standard library are notorious for causing buffer overflows because they copy data without checking how much space is available.
- gets() reads a line of input with no size limit at all. It’s so dangerous that it was removed from the C language standard entirely. The safe replacement, fgets(), requires you to specify a maximum number of characters.
- strcpy() copies one string into another without checking the destination’s size. The safer alternative, strncpy(), takes a length parameter.
- sprintf() formats data into a string buffer with no size constraint. Its replacement, snprintf(), caps the output at a specified length.
The pattern is the same in every case: the original function blindly copies data, while the replacement forces the programmer to declare how much room is available. Modern compilers will often flag the unsafe versions with warnings, but legacy codebases are full of them.
How Overflows Are Detected
Developers and security teams use two broad approaches to find buffer overflows before attackers do.
Static analysis tools scan source code without running it, looking for patterns that suggest a possible overflow. Tools like Splint, Flawfinder, and commercial products such as PolySpace C Verifier examine how data flows through a program and flag places where a buffer might be written past its bounds. A study from MIT Lincoln Laboratory tested five static analysis tools against 14 known exploitable buffer overflows in real-world software (including the Sendmail email server and the BIND DNS server) and found that no single tool caught everything. Each had blind spots, which is why security-conscious teams typically run multiple tools.
Dynamic analysis takes the opposite approach: it runs the program and watches what happens. Techniques like fuzzing throw massive amounts of random or semi-random input at a program to see if any of it triggers a crash. Address sanitizers, built into modern compilers, add runtime checks that detect out-of-bounds memory access the moment it happens during testing. These tools are excellent at catching overflows that static analysis misses, but they can only find bugs in code paths that actually get exercised during the test.
Built-In Operating System Defenses
Modern operating systems include several layers of protection that make buffer overflows harder to exploit, even when they exist in code.
Address Space Layout Randomization (ASLR) shuffles the memory locations of key program components each time a program runs. Since a stack overflow exploit depends on knowing exactly where to redirect execution, randomizing those addresses forces the attacker to guess, and guessing wrong typically crashes the program harmlessly.
Data Execution Prevention (DEP), also called the NX bit, marks certain regions of memory as non-executable. Even if an attacker manages to inject code into a buffer, the processor refuses to run it because the buffer’s memory region is flagged as “data only.” Attackers have developed workarounds (such as reusing existing code fragments already in memory), but DEP still raises the difficulty significantly.
Stack canaries are small, random values placed between a buffer and the return address on the stack. Before a function returns, the program checks whether the canary value has changed. If an overflow overwrote the return address, it almost certainly also overwrote the canary, and the program terminates before the corrupted return address can be used.
None of these defenses are perfect on their own. Skilled attackers have techniques to bypass each one individually. But layered together, they make exploiting a buffer overflow far more complex than it was in the 1990s, when a simple overflow in a network service could give an attacker full control of a machine in seconds.

