Sunday, November 8, 2026 Independent journalism
MediaChannel

technology

What is a buffer overflow and how does it crash software?

A buffer overflow happens when a program writes more data into a memory region than it was designed to hold. It's behind some of the most serious software vulnerabilities in computing history.

Close-up of HTML code lines highlighting web development concepts and techniques.

Photo by Pixabay on Pexels

A buffer overflow is one of the oldest and most consequential bugs in software engineering. It happens when a program writes more data into a fixed-size block of memory than that block can hold, spilling data into adjacent memory regions. The result ranges from an immediate crash to a full security compromise, depending on what that adjacent memory contains. Developers have known about buffer overflows since the late 1960s, yet they kept appearing in critical software for decades, and they still turn up today.

What a buffer actually is

A buffer is simply a contiguous section of memory reserved to hold temporary data. Think of it like a glass: it holds a fixed amount. When a program receives input, it typically stores that input in a buffer before processing it. A login form might allocate a 64-byte buffer for a username. If the program reads input without checking its length first, a 100-byte username overflows that buffer by 36 bytes.

Those 36 extra bytes don't vanish. They land in whatever memory sits immediately after the buffer. That adjacent space might hold another variable, a function return address, or program control data. Corrupting it changes how the software behaves, sometimes in ways the original programmer never imagined.

Stack overflows vs heap overflows

Buffer overflows split into two broad families depending on where the overflow occurs in memory.

A stack-based overflow targets the call stack, the region of memory where a program stores local variables and return addresses for functions. When a function finishes, the program reads its return address from the stack and jumps back to the calling code. If an attacker overwrites that return address with a value pointing to their own code, the processor follows that pointer instead. This is the classic attack pattern described in Aleph One's 1996 paper "Smashing the Stack for Fun and Profit," which turned buffer overflows from a curiosity into a well-understood exploit technique.

A heap-based overflow targets dynamically allocated memory, the region managed by functions like malloc() in C. Heap overflows are harder to exploit predictably, but they're just as dangerous. Corrupting heap metadata can redirect control flow or leak sensitive data stored in nearby allocations.

Why C and C++ are especially vulnerable

Most buffer overflow vulnerabilities appear in programs written in C or C++. Neither language checks array bounds automatically. When you write str[i] = 'A';, the language trusts that i is a valid index. It doesn't verify. The programmer carries that responsibility entirely.

Functions like strcpy(), gets(), and sprintf() are notorious for copying input without checking how long it is. The C standard library shipped gets() for decades before it was formally deprecated in C11 and removed in C17. It's essentially a buffer overflow in a function. Modern alternatives like strncpy() and snprintf() require the programmer to pass a maximum length explicitly, making accidental overflows harder but not impossible.

Languages like Python, Java, and Rust handle bounds checking automatically at the language level. Rust's ownership model, in particular, makes the entire class of memory safety bugs it covers a compile-time error rather than a runtime surprise. That's a meaningful shift.

How attackers exploit a buffer overflow

A basic stack smashing attack works in three steps. First, the attacker sends input large enough to overflow the target buffer and reach the return address on the stack. Second, that input includes machine code (called shellcode) embedded within the overflow data. Third, the overwritten return address points back into the buffer where the shellcode now lives. When the function returns, the processor jumps to the shellcode and executes it with the same permissions as the original program.

If the program runs as root or as a system service, the attacker gains those same privileges. The 2003 Slammer worm exploited a buffer overflow in Microsoft SQL Server. It spread to 75,000 hosts in the first 10 minutes after release, doubling its infection count every 8.5 seconds. No human could respond that quickly. The worm self-propagated entirely through one unpatched overflow.

This is closely related to the broader concept of how injection attacks exploit untrusted input: in both cases, the attacker is feeding data the program wasn't designed to handle safely.

Defences that modern systems use

Operating systems and compilers now ship several layers of protection against buffer overflows, though none of them are perfect in isolation.

Stack canaries: The compiler inserts a random value (called a canary, after the coal-mine bird) between local variables and the return address. Before a function returns, it checks whether that value has been modified. If it has, the program exits immediately rather than jumping to corrupted code. Stack canaries are on by default in GCC and Clang.

Address Space Layout Randomisation (ASLR): The operating system randomises where the stack, heap, and libraries are loaded in memory each time a program runs. This makes it much harder for an attacker to predict where their shellcode lands. Linux, macOS, and Windows all enable ASLR by default.

Non-executable memory (NX / DEP): Modern processors support marking memory regions as either executable or writable but not both. Data regions, including the stack and heap, are marked non-executable. Even if an attacker injects shellcode there, the CPU refuses to run it. Windows calls this feature Data Execution Prevention (DEP). It doesn't stop all attacks, but it blocks the simplest shellcode injection.

Attackers have adapted. Return-oriented programming (ROP) chains together small snippets of existing executable code rather than injecting new code. It's more complex but it sidesteps NX/DEP entirely. This is why layered defences matter rather than relying on any single mechanism.

Understanding these layers connects directly to how security tools like firewalls protect networks at the perimeter while memory protections defend at the process level. Each layer addresses a different attack surface.

Finding and fixing buffer overflows

Static analysis tools like AddressSanitizer instrument a compiled binary to detect out-of-bounds memory accesses at runtime during testing. When the program writes past a buffer boundary, AddressSanitizer catches it immediately and reports exactly where in the code it happened. It's standard practice in security-focused development pipelines.

Fuzz testing (fuzzing) sends randomly malformed or oversized inputs at a program automatically, looking for crashes. Google's OSS-Fuzz project has found thousands of real vulnerabilities this way in open-source projects, including buffer overflows in codecs, compression libraries, and network parsers. The bugs were already there. Fuzzing found them before attackers did.

At the code level, the fix is almost always the same: validate the length of input before copying it, use length-bounded functions, or switch to a language that handles bounds checking for you. Simple in principle. Harder in a codebase written over twenty years by dozens of contributors with varying security awareness.

Why buffer overflows still matter in 2026

Buffer overflows haven't disappeared. The US National Vulnerability Database still catalogues hundreds of buffer overflow vulnerabilities each year across embedded systems, network devices, and legacy enterprise software. Firmware running on routers, industrial controllers, and medical devices is often written in C with minimal testing, deployed for a decade, and rarely patched.

The lesson hasn't changed in 50 years. Trusting input without checking its length is a defect, not a shortcut. Every program that reads external data, from a keyboard, a network socket, or a file, needs to treat that data as potentially malicious. That attitude is the foundation of sound cybersecurity practice at every level of software development.