Buffer Overflow
A buffer overflow writes more data into a fixed-size memory buffer than it holds, overwriting adjacent memory to crash a program or run attacker-chosen code.
Also known as buffer overrun, stack buffer overflow, stack-based buffer overflow, heap overflow, buffer over-read, stack smashing
Buffer overflow is a memory-safety flaw where a program writes more data into a fixed-size buffer than it holds, so the excess overwrites adjacent memory such as a function's saved return address. An attacker who controls the overwritten bytes can crash the process or redirect execution to run code of their choosing.
The bug lives in languages that let code write to memory without a bounds check, chiefly C and C++. MITRE's CWE-787, Out-of-bounds Write, defines it in one line: "The product writes data past the end, or before the beginning, of the intended buffer." It ranked second on MITRE's 2024 CWE Top 25 Most Dangerous Software Weaknesses. Our position is that the buffer overflow is a solved problem at the language level and an unsolved one at the dependency level: rewrite the code in a memory-safe language and the class disappears, but most projects cannot rewrite the compiled C that arrives inside their open source dependencies.
How does a buffer overflow work?
#A buffer overflow works by overrunning a fixed-size buffer so the spilled bytes land on whatever sits next to it in memory. The classic target is the call stack. When a function runs, the processor pushes a new stack frame holding its local variables, the saved frame pointer (the caller's base pointer), and the return address (where execution resumes when the function returns). A local char buf[16] sits just below those saved values.
If the program copies attacker-controlled input into that buffer with a function that does not check length, such as strcpy, the write runs off the end of buf and keeps going into the saved frame pointer and the return address. Overwrite the return address with an address of the attacker's choosing and the ret instruction jumps there instead of back to the caller. That single overwritten pointer is the whole attack.
The diagram shows the stack frame from high to low address. A 16-byte buf sits below the saved frame pointer (saved RBP) and the return address (saved RIP). On x86-64 the stack grows toward lower addresses, but a strcpy into buf writes toward higher addresses, so an overlong copy walks upward out of the buffer, through the saved frame pointer, and into the return address. Writing 24 or more bytes into a 16-byte buffer is enough to reach the saved RIP on a typical frame.
Where the buffer lives decides the variant. A buffer on the stack is a stack-based buffer overflow (CWE-121); one in heap memory from malloc is a heap-based buffer overflow (CWE-122), which corrupts allocator metadata or adjacent objects rather than a return address. Reading past a buffer instead of writing is a buffer over-read (CWE-125). Heartbleed (CVE-2014-0160) was an over-read: OpenSSL trusted an attacker-supplied length field and copied up to 64 KB of adjacent process memory, including private keys, into the response, without a single byte being overwritten.
What stops a buffer overflow from becoming code execution?
#Three mitigations stand between an overflow and arbitrary code execution, and each was defeated in turn, which is why modern systems stack all of them. A stack canary, introduced by StackGuard in 1998 and shipped by GCC as -fstack-protector, places a random value between the local buffers and the saved return address; the function checks it before returning and aborts if an overflow changed it. This is the Stack Smashing Protector (SSP), and its failure is the familiar *** stack smashing detected *** abort.
Data Execution Prevention (DEP), enforced by the CPU's No-eXecute (NX) bit, marks the stack and heap non-executable, so even if an attacker writes shellcode into the buffer and jumps to it, the processor refuses to run data as code. Address Space Layout Randomization (ASLR) then randomizes where the stack, heap, and libraries load, so the attacker cannot predict the address to jump to in the first place.
Return-Oriented Programming (ROP) defeats NX without injecting any code. Hovav Shacham's 2007 paper The Geometry of Innocent Flesh on the Bone showed how to "combine a large number of short instruction sequences to build gadgets that allow arbitrary computation" entirely out of code already present in the program, chaining fragments that each end in a ret. ASLR is the control that actually blunts ROP, by hiding where those gadgets live, which is why an information leak that defeats ASLR is now the valuable half of a modern exploit.
None of these mitigations fixes the bug; each raises the cost of turning it into code execution, and each has a bypass.
| Control | What it does | What it does not cover |
|---|---|---|
| Stack canary (SSP) | Detects a contiguous overwrite of the return address on return | Heap overflows, over-reads, and writes that skip the canary via a pointer |
| DEP / NX bit | Blocks execution of injected shellcode on the stack or heap | Return-oriented programming, which reuses existing executable code |
| ASLR | Randomizes memory layout so targets are unpredictable | Defeated by any memory-disclosure bug that leaks a real address |
| Memory-safe language | Removes the bug class: bounds are checked or enforced by the compiler | Code in C/C++ dependencies, FFI, and unsafe blocks |
The table summarizes the trade-offs. A stack canary (SSP) catches a contiguous stack overwrite on return but does nothing for heap overflows or over-reads. DEP and the NX bit block injected shellcode but not return-oriented programming. ASLR randomizes the layout but falls to any bug that leaks a real address. Only a memory-safe language removes the class outright, and even then it does not cover the C and C++ code reached through foreign-function interfaces or unsafe blocks.
How do you prevent buffer overflows?
#You prevent buffer overflows by removing the ability to write out of bounds, not by auditing every copy for correctness. The durable fix is a memory-safe language: Rust, Go, Java, Python, and C# bounds-check array access or manage memory so an over-long write raises an error instead of corrupting the frame. MITRE attributes part of the 2024 drop in some overflow-related CWE rankings to "improvements in the adoption of memory-safe programming languages like Rust." The 2023 White House OSS security RFI recorded the same industry consensus, with respondents advocating the adoption of memory-safe languages to reduce entire classes of vulnerabilities at scale.
When C or C++ is unavoidable, the standard controls are bounds-checked APIs (strncpy, snprintf, std::string over raw arrays), compiler hardening, and fuzzing to find the overruns before an attacker does. GCC's -D_FORTIFY_SOURCE=2 is worth calling out because it changes the failure mode: it replaces unchecked libc calls with length-aware versions that abort on overflow. The three protection levels produce three distinct outcomes for the same over-long input, which anyone can reproduce by compiling a program that copies an over-long argument into a fixed buffer at each setting.
| Build setting | Result of an over-long copy |
|---|---|
-fno-stack-protector -O0 | Silent corruption, then a segmentation fault when the clobbered return address is used |
| Default (stack protector on) | *** stack smashing detected ***, process aborts before returning |
-O2 -D_FORTIFY_SOURCE=2 | *** buffer overflow detected ***, the fortified copy aborts at the call |
The table shows the three outcomes. Without the stack protector, the overflow corrupts memory silently and the program only crashes later, when it tries to return through the clobbered address. With the default protector, glibc prints *** stack smashing detected *** and aborts before returning. With FORTIFY_SOURCE, the fortified strcpy knows the destination size and aborts at the copy with *** buffer overflow detected ***. All three stop at a crash, which is a denial of service rather than code execution. Turning every exploitable overflow into a reliable crash is the realistic goal of C hardening; eliminating the crash is why teams move to memory-safe languages.
Buffer overflows in the software supply chain
#A buffer overflow is the rare supply-chain risk that does get a CVE. Malware in a package does not, which is why CVE scanning alone misses most supply-chain attacks and reachability, not severity score, should drive triage. Memory-corruption bugs are the opposite case: they are well-tracked CVEs in widely used C libraries, and the danger is not that they go unrecorded but that they sit deep in a dependency tree where a JavaScript or Python team never sees the C.
That C reaches a Node.js or Python project through native code: a native addon that node-gyp compiles from a binding.gyp file, a prebuilt .node binary, or a Python extension module. An overflow in that compiled code runs with the full privileges of the process that loads it, and no amount of auditing the package.json or the JavaScript source will surface it. The useful move is to analyze what is actually in the package, the compiled artifacts included, rather than what its metadata claims. Socket's Native code alert flags packages that ship compiled binaries, shared libraries, or native build files, so a pure-JavaScript dependency that suddenly gains a .node file in a patch release stands out.
The runtime underneath an application is the other exposure. In October 2026 the PHP2Shell vulnerabilities (CVE-2026-109618 and CVE-2026-109619), two memory-safety flaws in the PHP engine rated 9.2 on CVSS v4.0, let an unauthenticated request trigger remote code execution before any application code ran. A carefully written application was exposed through the memory-unsafe interpreter beneath it. The lesson of the overflow at supply-chain scale is that your exposure is set by the least memory-safe dependency you ship, down to the C compiled into your interpreter, and that is code most teams never read.
