A buffer overflow is a bug where a program writes more data into a fixed-size piece of memory than that memory can hold, so the extra data spills past the end and overwrites whatever sits next to it. It is not a clever attack technique. It is a missing bounds check, and it has been around since the early 1970s.
Here is the version I would want if I were meeting this topic for the first time: a buffer is a small allocated region of memory, the program never checks whether the incoming data fits, and the copy routine writes until the data ends rather than until the buffer ends. Everything past that line is collateral damage.
No products to buy, no vendor pitch. Just the mental model, a C example you can compile yourself, and the reason the bug still shows up in 2026.
Table of Contents
- What Is a Buffer Overflow?
- How Does a Buffer Overflow Happen?
- What Happens When Data Overwrites Adjacent Memory?
- A Simple C Example of a Buffer Overflow
- Stack, Heap and Other Buffer Overflows
- Buffer Overflow vs Stack Overflow vs Stack Smashing
- Why Are Buffer Overflows Dangerous?
- How to Prevent Buffer Overflows
- Dangerous C Functions and Their Safer Replacements
- What ASLR, Stack Canaries and NX Actually Do
- Buffer Overflows in Memory-Safe Languages
- Buffer Overflows and Modern Defenses
- A Practical Checklist for Developers
- Frequently Asked Questions
- What type of attack occurs when data goes beyond the memory areas allocated to an application?
- What is a stack buffer overrun?
- Which best describes a buffer overflow attack?
- Is buffer overflow still a problem?
- What is stack smashing via buffer overflow?
- What can be done to mitigate buffer overflow attacks?
What Is a Buffer Overflow?

A buffer is a block of memory set aside to hold data while it moves from one place to another. Reading a network packet, loading an image, copying a command-line argument: all of it lands in a buffer first.
The important part is that the buffer has a size that was decided when the program was written, and nothing in C automatically stops a write from going past it. The language treats memory as a flat address space and trusts the programmer to know where one object stops and the next begins.
Think of a row of post boxes in an apartment hallway. Each box holds a fixed number of letters. If you shove 40 letters into a box built for 10, the extra 30 do not vanish. They go on the floor, and they land on whatever the next person put there.
That is the whole idea behind what is a buffer overflow: writing past the end of an allocated region and corrupting memory you were never supposed to touch. The data usually comes from outside the program, which is why it is interesting to someone who is not the programmer.
Two conditions have to line up for it to happen. There has to be a buffer with a fixed boundary, and there has to be a write that is not checked against that boundary. Miss either one and nothing happens.
How Does a Buffer Overflow Happen?
- A buffer is allocated with a fixed size. A local array is declared, say
char buf[16], and the program gets 16 bytes of memory. - Input arrives and is never measured against that size. It could be a URL path, a form field, a file header, or an argument the user typed.
- An unsafe copy routine moves the data in. Functions like
strcpy,gets, andsprintfcopy until they hit the end of the source string, not the end of the destination. - The write continues past the last byte of the buffer. Those bytes land in the memory physically next to the buffer.
- Whatever was stored there is now overwritten. On the stack that is usually another variable, a saved frame pointer, or the function’s saved return address.
- The program carries on with corrupted state. Sometimes it crashes immediately. Sometimes it runs with wrong values for a while and fails much later.
What Happens When Data Overwrites Adjacent Memory?
The consequence depends entirely on what got clobbered. Overwriting another local variable usually just produces a wrong answer, which is a bug but a survivable one. Overwriting a pointer that gets dereferenced later usually produces a crash.
Overwriting the saved return address is the interesting case. That value is the address the CPU jumps back to when the function finishes. Replace it and you have changed where the program goes next, which is the difference between a program that glitches and a program that does whatever the new instruction pointer says.
Worth separating the two outcomes clearly: memory corruption is the condition, and everything after that is a question of what the corrupted value controls and whether the system’s defences happen to catch it.
Overwriting by exactly one byte is its own category, an off-by-one error. It slips through review because nothing looks obviously wrong, and it can corrupt a length field or a null terminator that everything downstream depends on.
A Simple C Example of a Buffer Overflow
#include <stdio.h>
#include <string.h>
void greet(const char *name) {
char buf[16];
strcpy(buf, name); /* no size check anywhere */
printf("Hello, %sn", buf);
}
int main(int argc, char *argv[]) {
if (argc < 2) return 1;
greet(argv[1]);
return 0;
}
Compile it with gcc -fno-stack-protector -no-pie demo.c -o demo so the later mitigations stay out of the way while you look at the bug, then run it with a short argument. It works. Run it with 40 characters and it does not.
Here is roughly what the stack frame of greet looks like while it runs, higher addresses at the top:
+--------------------------------+
| saved return address | control data
+--------------------------------+
| saved frame pointer | control data
+--------------------------------+
| buf[16] (16 bytes) | your writes start here
+--------------------------------+
| other local variables |
+--------------------------------+
Sixteen bytes of room, and the copy routine is happy to write forty. Bytes 0 through 15 fill the buffer. Bytes 16 and up march straight up the diagram into the saved frame pointer and the return address.
The bounded version changes one line and drops the compiler flag:
void greet(const char *name) {
char buf[16];
snprintf(buf, sizeof buf, "%s", name);
printf("Hello, %sn", buf);
}
sizeof buf is the important part. It hands the function the destination size, and the function refuses to write more than that. This is what bounds checking looks like in practice: the size travels with the buffer instead of living in someone’s memory.
A caveat that trips up beginners: a modern compiler may notice that a write can never exceed the buffer and quietly remove it. If your example refuses to crash, check that your printf format actually varies with the input length. If it does not vary, there was never an overflow to begin with.
Stack, Heap and Other Buffer Overflows
Stack and heap are just two places memory gets handed out, and an overflow can happen in either. The core problem is not tied to a region; the region only changes what sits nearby and how hard the bug is to hit.
| Type | Where it happens | Typical cause | Usual symptom |
|---|---|---|---|
| Stack buffer overflow | Local arrays and function arguments | Fixed-size array plus an unchecked copy, like the example above | Crash, corrupted return address |
| Heap buffer overflow | Memory from malloc and friends | Under-allocation, then writing past the block you asked for | Silent corruption much later |
| Format string overflow | Anywhere a string is passed as the format | User data handed to printf instead of as an argument | Read or write through a formatted string |
| Integer overflow | Arithmetic on size values | A small allocation size wrapping to something huge or negative | Undersized buffer, later overflow |
| Off-by-one | Any fixed-size buffer | Loop bound or terminator off by a single byte | Corrupted length field |
Heap overflows tend to be nastier to diagnose than stack ones. The write lands in memory you do not own, corrupts metadata that a later free will read, and the crash shows up in an entirely different function minutes later.
Buffer Overflow vs Stack Overflow vs Stack Smashing
These three get used interchangeably in blog posts and they do not mean the same thing. A stack overflow is running out of stack space, usually from unbounded recursion, and the program exhausts a fixed region until it faults. A buffer overflow is writing past the end of one specific object. Stack smashing is the name for a stack buffer overflow that reached far enough to hit control data, and it is also what the message stack smashing detected is telling you.
So when your program aborts with stack smashing detected, the bug is a buffer overflow, the damage was on the stack, and the detection came from a canary value placed between the buffer and the return address. That message is good news. It means a mitigation caught the corruption before the program did something worse with it.
Why Are Buffer Overflows Dangerous?
Ranked by how often you actually see them, the outcomes go like this:
- Denial of service. The simplest and most reliable outcome. Corrupt something the program reads on the way out and it dies. On a shared server that is a real outage, and a repeated version of that is a denial of service attack.
- Wrong results, quietly. A corrupted length or index produces incorrect output with no crash at all. This is the one that hurts in regulated work, where a number that comes out wrong is worse than a program that stops.
- Arbitrary code execution. If the overwritten return address points at attacker-supplied bytes the CPU will execute, the program runs those bytes with its own privileges. This is the outcome the attacker is aiming for.
- Privilege escalation and access bypass. The same technique aimed at a privileged process can bypass a check that the code itself was never designed to get around.
The reason this class of bug has mattered for so long is structural. C and C++ ship no bounds checking on memory access for performance reasons, and both languages underpin operating systems, drivers, network stacks, browsers, and firmware. Every one of those is full of code written in a decade when an overflow was a crash rather than a security issue, and that code is still running.
The history helps explain why the fix is slow. The pattern was documented in 1972. In November 1988 the Morris Worm used buffer overflow techniques to spread across the internet and caused real damage. In 1996 the Phrack article Smashing the Stack for Fun and Profit showed the method clearly enough that a beginner could try it, and the number of published exploits rose sharply. The standard mitigations came out of that period and have been default in most toolchains ever since.
How to Prevent Buffer Overflows

The prevention is not subtle. Never copy data without a bound, never size an allocation from unvalidated arithmetic, and keep the compiler’s warnings switched on so the compiler argues with you too.
In practice that comes down to a handful of habits.
- Use bounded functions by default. Reach for the variant that takes a size. If a function has no size parameter, treat it as a warning sign.
- Validate length before you allocate. Check the input, reject or truncate what is too long, and then size the buffer from the validated value.
- Return errors instead of ignoring them. A truncated copy that nobody checks is still a buffer with the wrong contents.
- Turn warnings up and treat them as errors in CI. Most compilers flag an unbounded copy today. Silencing that warning to get a build through is how these bugs ship.
- Fuzz the parsers. Feeding random and malformed input at a parser finds overflows faster than reading the code does, especially in image, audio, and archive libraries.
- Prefer memory-safe languages for new work. In Rust, Go, Java, C#, and Python the language itself stops most of this class of bug before the code runs.
Dangerous C Functions and Their Safer Replacements
These four functions show up in most buffer overflow discussions because each one copies without knowing how much room there is.
| Risky function | The problem | Safer replacement |
|---|---|---|
| gets | Reads until the newline, with no destination limit at all | fgets, with the buffer size |
| strcpy | Copies the whole source string, destination size unknown | strncpy, or snprintf |
| sprintf | Formats into a fixed buffer with no length limit | snprintf, passing sizeof buf |
| scanf with %s | Reads a word of unbounded length into a small array | scanf with a width limit, or fgets |
| strcat | Appends without checking whether it still fits | strncat with a remaining-space limit |
strncpy deserves a warning of its own. It will not overflow, but it does not add a null terminator when the source is as long as the destination, so a careless replacement introduces a different bug. snprintf is usually the easier habit to build.
What ASLR, Stack Canaries and NX Actually Do
These are not substitutes for correct code, but they are the reason a naive overflow no longer hands an attacker a shell. Each one raises the cost of turning corruption into control.
Stack canaries (stack smashing protector) put a secret value between local buffers and the return address. The function checks it on the way out. If it changed, the frame was overwritten and the program aborts.
ASLR, Address Space Layout Randomization, moves the stack, heap, and libraries to a different place each run, so a hardcoded address in a payload points at nothing useful.
NX, No eXecute, marks data regions non-executable, so bytes written into a buffer cannot be jumped to directly. Exploit techniques worked around it, usually by reusing code already present in the program’s libraries.
| Defence | Windows | macOS | Linux |
|---|---|---|---|
| Stack canaries | Stack protection, on by default in modern builds | Stack protector | Stack smashing protector, on by default |
| ASLR | Address randomization, opt-out per binary | Enabled system wide | PIE and randomized mmap |
| Non-executable memory | DEP | DEP | NX bit on most kernels |
| Control-flow checks | SafeSEH, CFG | Signed pointers | RELRO, and CFI where available |
Reading that table explains a lot of beginner frustration. The first payload you write fails, not because your understanding is wrong, but because the canary tripped before your bytes could be used. That is the system working, and it is why the interesting bugs today live in code where these mitigations are missing or disabled.
Buffer Overflows in Memory-Safe Languages
Python, Java, C#, JavaScript and Go do not give you a raw buffer you can walk off the end of in ordinary code. Strings grow as needed, slices are bounds-checked, and out-of-range access raises an exception instead of corrupting the heap. That removes the classic overflow entirely.
It does not remove the whole category of problem. Native extensions and FFI bindings hand raw pointers across the boundary, where a bug in C or C++ is still a bug. Unsafe blocks in otherwise safe languages exist precisely because some work needs raw memory. Parsing untrusted files with a native library moves the risk rather than deleting it, and it is a common finding in audits of applications written in these languages.
So the honest summary is: a memory-safe language removes the easiest 90 percent of memory-safety bugs, and it leaves you responsible for what you hand to unsafe code.
Buffer Overflows and Modern Defenses
Layered defences are the current state of the art, and the layers matter more than any single one. Correct code at the source, warnings and static analysis in the build, canaries and ASLR and NX at runtime, and least privilege so a compromised process cannot do much anyway.
Least privilege is the one teams skip most often, and it is the one that changes the outcome most. A network service that parses untrusted input should not be running as root with write access to its own install directory. The same bug in a constrained process is an incident; the same bug in a privileged one is a breach.
Mitigations also have known gaps. Legacy binaries compiled before these protections existed will not have them at all. Embedded and IoT firmware frequently cannot afford the cost of full ASLR or NX. Information-leak bugs can disclose an address and undo the randomisation. Closed-source components ship without source, so nobody reviewing the application can see the parsing code at all. For all those reasons the industry keeps moving work to memory-safe languages rather than trusting the mitigations to hold.
Attackers notice the same gaps. Scanning the internet for exposed devices that respond without a canary or with NX off is a normal, automated part of the process. That is why the answer to whether buffer overflows still matter is not a comfortable one.
A Practical Checklist for Developers
If you are reviewing C or C++ code this week, this is the order I would work in.
- Find the fixed-size buffers. Search for local array declarations and any copy call. Every one is a candidate.
- Trace each write back to its size. Follow the data to where it enters the program. Is the length checked before the copy, or assumed?
- Replace the unsafe calls. Move to bounded functions with the size passed alongside the pointer, and confirm the return value is checked.
- Check the allocation arithmetic. If a size is computed, confirm the computation cannot wrap or underflow on hostile input.
- Rebuild with warnings as errors and ASan on. AddressSanitizer finds most overflows at the moment they happen, with the exact line.
- Add tests at the boundaries. Input of length n minus one, exactly n, and n plus one. The off-by-one case lives there.
- Fuzz anything that parses a format you did not design. Image headers and archive formats are where these bugs hide in real code.
- Check the mitigations are actually on. Confirm the build enables canaries, ASLR, and NX, and that no linker flag quietly disabled them.
To see the bug rather than just read about it, compile the example above in a virtual machine or container, and work through capture-the-flag platforms built for exactly this. Practice happens against deliberately vulnerable targets you downloaded yourself, never against software you did not set up.
Frequently Asked Questions
What type of attack occurs when data goes beyond the memory areas allocated to an application?
That attack is a buffer overflow. When a program writes more data than its allocated memory region can hold, the excess spills into adjacent memory and overwrites whatever is stored there, including variables, pointers, and control data such as the saved return address. The result can be a crash, corrupted output, or attacker-controlled program behaviour.
What is a stack buffer overrun?
A stack buffer overrun is a buffer overflow that happens in a function’s local memory, usually a fixed-size array. An unchecked copy writes past the end of that array and into the rest of the stack frame, which often holds a saved frame pointer and the return address. Many compilers detect this at runtime and abort with a stack smashing detected message rather than letting the function return.
Which best describes a buffer overflow attack?
A buffer overflow attack happens when input data exceeds the size of a fixed memory buffer, overwriting adjacent memory. The attacker usually targets data that influences program control, such as a return address or function pointer. That overwrite can crash the program for denial of service, or redirect execution to attacker-supplied instructions running with the program’s own privileges.
Is buffer overflow still a problem?
Yes. Memory-safe languages have pushed overflow bugs out of new code, but C and C++ still underpin operating systems, drivers, network stacks, and firmware. Overflows persist in legacy C code, complex C++ parsers, image and media libraries, and embedded devices where full mitigations are too costly. A 2026 codebase review still finds them regularly in native components.
What is stack smashing via buffer overflow?
Stack smashing is a stack buffer overflow that reached far enough to overwrite the control data in a stack frame, usually the saved return address. The phrase also refers to the canary detection that catches it: the compiler places a secret value between local buffers and the return address, and aborts when the value has changed. Getting that message means a mitigation caught the corruption.
What can be done to mitigate buffer overflow attacks?
Use bounded functions that take a destination size, validate input lengths before allocating, and treat copy return values as errors to handle. Add compiler warnings as errors in CI, run tests at buffer boundaries, and fuzz parsers of untrusted formats. Layer runtime protections on top: stack canaries, ASLR, NX or DEP, and least privilege so a compromised process gains as little as possible.
Start with the copy call. If it does not take a size, that is your bug, and the fix is smaller than the investigation you are about to do.


