Code of the Day
IntermediateMemory management

Buffer overflows

Understand buffer overflows — how they happen, why they are security vulnerabilities, and the defensive practices that prevent them.

CIntermediate10 min read
By the end of this lesson you will be able to:
  • Explain what a buffer overflow is and what memory it corrupts
  • Identify common C patterns that allow buffer overflows
  • Apply defensive programming practices to prevent them
  • Recognise the impact of stack canaries and ASLR as OS-level mitigations

A occurs when a program writes more data to a buffer than the buffer can hold, overwriting adjacent memory. Buffer overflows are among the most exploited vulnerability class in the history of computing — the Morris Worm (1988), SQL Slammer (2003), and countless CVEs since all exploited variations of this bug.

How a stack buffer overflow happens

#include <stdio.h>
#include <string.h>

void greet(const char *name) {
    char buf[10];        /* 10 bytes on the stack */
    strcpy(buf, name);   /* no bounds check */
    printf("Hello, %s!\n", buf);
}

int main(void) {
    greet("Bob");        /* fine -- 3 chars + null = 4 bytes */
    greet("Christopher Longname"); /* 21 chars + null -- OVERFLOW */
    return 0;
}

On the stack, buf is a fixed 10-byte block. Above it (at a higher address, if the stack grows downward) are other things: perhaps name, the saved frame pointer, and the return address. When strcpy writes past the 10-byte boundary, it overwrites those adjacent values.

In a classic stack-smashing exploit:

  1. The attacker supplies input longer than the buffer.
  2. The overflow reaches and overwrites the saved return address.
  3. The attacker's input contains machine code (shellcode) and redirects the return address to that code.
  4. When greet returns, execution jumps to the shellcode instead of main.

Heap buffer overflow

The same bug can occur in heap-allocated buffers:

char *buf = malloc(10);
memcpy(buf, user_input, user_input_length); /* user_input_length might be > 10 */

Heap overflows can corrupt the allocator's metadata (which stores free-list pointers adjacent to allocated blocks in many allocator implementations), leading to arbitrary writes on the next malloc or free.

Common dangerous patterns

DangerousSafe alternative
gets(buf)fgets(buf, size, stdin)
strcpy(dst, src)strncpy(dst, src, sizeof(dst)-1) then null-terminate
sprintf(buf, fmt, ...)snprintf(buf, sizeof(buf), fmt, ...)
scanf("%s", buf)scanf("%9s", buf) (with length)

gets is so dangerous that it was removed from the C11 standard. Never use it.

Defensive practices

1. Always use size-limited functions:

char buf[64];
fgets(buf, sizeof(buf), stdin);        /* stops at 63 chars + null */
snprintf(buf, sizeof(buf), "%d", n);   /* stops at 63 chars + null */
strncpy(buf, src, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';

2. Check user-supplied lengths:

if (user_len > sizeof(buf)) {
    fprintf(stderr, "Input too long\n");
    return -1;
}
memcpy(buf, user_data, user_len);

3. Compute buffer requirements before allocating:

size_t needed = strlen(prefix) + strlen(suffix) + 2;
char *result = malloc(needed);
if (!result) { return NULL; }
sprintf(result, "%s_%s", prefix, suffix); /* safe: we know the size */

OS-level mitigations

Modern operating systems add layers of defense:

Stack canaries: The compiler inserts a random value ("canary") between local variables and the return address. Before returning, it checks if the canary changed — if it did, it aborts the program. Enabled with -fstack-protector-strong in gcc (often the default).

ASLR (Address Space Layout Randomisation): The OS randomises the addresses of the stack, heap, and libraries on each run. An attacker who overwrites the return address cannot reliably predict where their shellcode landed.

NX/W^X: Memory pages are marked as either writable or executable, but not both. Even if an attacker injects code into a buffer, it cannot be executed because the stack is not executable.

These mitigations do not replace safe code. Stack canaries do not prevent heap overflows. ASLR can be defeated through information leaks. W^X does not prevent return-oriented programming. Write defensively regardless of what the OS provides.

Detecting overflows at development time

Compile with AddressSanitizer:

gcc -g -fsanitize=address overflow.c -o overflow
./overflow

ASan will catch out-of-bounds reads and writes immediately, report the exact line, and show a stack trace. Use it during development and in CI, even if you deploy with a non-instrumented binary.

Where to go next

Next: valgrind basics — using valgrind to detect memory errors during development, and reading its output to locate and fix bugs.

Finished reading? Mark it complete to track your progress.

On this page