Buffer Overflows, Out-of-Bounds Access, and Stack Smashing
What This Concept Is
A buffer overflow is any write past the end (or read before the start) of an allocated region. On the stack, the nearby bytes you corrupt can include saved registers, the saved frame pointer, and the return address. Overwriting the return address redirects control flow when the function returns; this is the classic stack smashing attack.
Three related errors often appear together:
- off-by-one (
<=instead of<onsize_tlength): one byte past the end - unbounded copy (
strcpy,gets,sprintf): writes until a\0that may never come - integer truncation / wraparound on a size computation: a small signed number becomes a huge unsigned one that passes the bounds check
Why It Matters Here
Most memory-safety CVEs fall into this family. Beyond security, even benign overflows corrupt unrelated data: a one-byte overflow can flip a boolean flag in the next variable, change a loop count, or shift a pointer by a few bytes. The bug "works on my machine" because the corrupted byte happens to be unused.
Concrete Example
#include <string.h>
#include <stdio.h>
void greet(const char *name) {
char buf[8];
strcpy(buf, name); /* no length check: overflow if name >= 8 */
printf("hi %s\n", buf);
}
int main(void) {
greet("aaaaaaaaaaaaaaaaaa"); /* 18 a's */
return 0;
}
Stack view inside greet (addresses grow downward; diagram high-to-low):
+-------------------+
| ret addr of greet | <- overwritten if we write 24 bytes into buf
+-------------------+
| saved rbp | <- overwritten if we write 16+ bytes
+-------------------+
| buf[0..7] | <- 8-byte buffer
+-------------------+
strcpy copies until the terminating \0. With 18 as plus \0, bytes 8-17 spill into saved rbp and the return address. When greet returns, it jumps to whatever we wrote, usually a crash, sometimes an exploit.
Common Confusion / Misconception
"strncpy is safe." Safer, but it does not guarantee null-termination if the source is longer than n. Prefer snprintf, strlcpy, or an explicit copy with a final '\0'.
"If the program does not crash, there was no overflow." Writes into padding or into unused saved-register slots may not produce visible effects. The bug can still be there, waiting for an input that lands on something important.
"Stack canaries eliminate the problem." Canaries (from -fstack-protector) detect contiguous overwrites that cross the canary slot. They do nothing against non-contiguous writes like buf[-1] = ... or about carefully chosen offsets.
How To Use It
For every buffer you write to:
- Know the capacity in bytes, including the null terminator for strings.
- Use the length-bounded API:
snprintf,strlcpy,memcpywith an explicit size. - Validate lengths before copying; reject rather than truncate silently.
- Compile with
-Wall -Wextra -Wformat=2 -Wshadow -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2 -fsanitize=address.
Check Yourself
- Why is
char buf[10]; gets(buf);unconditionally unsafe and why did C11 removegets? - How does a saved return address come to sit right after a stack array?
- What is a "stack canary" and what does it detect?
Mini Drill or Application
A small program that triggers an out-of-bounds read:
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[16] = "abcdefghijklmno"; /* 15 chars + NUL */
for (size_t i = 0; i <= 20; i++) { /* deliberately past end */
printf("%zu: 0x%02x\n", i, (unsigned char)buf[i]);
}
return 0;
}
Build with ASan: gcc -Wall -Wextra -O1 -fsanitize=address -g -o oob oob.c. Run ./oob. ASan reports "stack-buffer-overflow" with the exact access. Fix the loop bound; rebuild; verify ASan is silent. Then try -fsanitize=undefined as well.