Use-After-Free, Double-Free, Dangling Pointers
What This Concept Is
A pointer whose target no longer exists is dangling. Two common ways to get one:
- the target is freed (
free(p)), butpis still used later: use-after-free - the target is a local variable whose function has returned
reallocmoves a block, invalidating every pointer into the old location
Calling free twice on the same block is double-free. The allocator's bookkeeping assumes each allocation is freed at most once, and violating that corrupts the free list, which surfaces as a mysterious crash on a later, unrelated malloc.
Fundamentally these are lifetime bugs: the code assumes an object lives longer than it does.
Why It Matters Here
Use-after-free is the second-most-common memory vulnerability class after buffer overflows. It is often exploitable: freed chunks get reused for attacker-controlled data, so a late read of an old pointer can leak secrets, and a late write can corrupt a newly placed structure (often including function pointers). Even in non-security contexts, these bugs are very hard to debug because they manifest far from the offense.
Concrete Example
Use-after-free:
#include <stdlib.h>
#include <stdio.h>
int main(void) {
char *p = malloc(16);
if (!p) return 1;
for (int i = 0; i < 15; i++) p[i] = 'a' + i;
p[15] = '\0';
free(p);
printf("%s\n", p); /* UAF: p is dangling */
return 0;
}
Pointer state over time:
Before free: p ---> [ a b c d e f g h i j k l m n o \0 ] (16 bytes, owned)
After free: p ---> (unowned bytes; allocator may reuse)
Later printf: reads through p anyway -> UAF
Double-free:
char *q = malloc(8);
free(q);
free(q); /* double-free: allocator's free list is now corrupt */
Dangling local:
int *bad(void) {
int local = 42;
return &local; /* frame dies on return; pointer dangles */
}
Common Confusion / Misconception
"The data is still there right after free, so reading is fine." Sometimes true for a moment, never guaranteed. The allocator may reuse the block, zero it, or have the OS unmap the underlying page.
"free(NULL) is a bug." free(NULL) is explicitly defined to be a no-op. This is why the common idiom is free(p); p = NULL;.
"realloc is harmless." realloc(p, new_size) may return a different pointer. Every other pointer into the old block is now dangling. Always assign back: p = realloc(p, n); and handle NULL separately to avoid leaking the old block.
How To Use It
For every pointer in your program:
- Write a one-line ownership note next to the declaration: who frees it and when.
- Set freed pointers to
NULLif they outlive thefree. - On
realloc, use a temporary:tmp = realloc(p, n); if (!tmp) handle; p = tmp;. - Never return the address of a local unless it is
static.
Check Yourself
- Why is
free(p); p = NULL;a safer idiom thanfree(p);alone? - How can a double-free become a write-what-where primitive for an attacker?
- What exactly is dangling after
char *p = malloc(16); p = realloc(p, 64);ifreallocmoved the block?
Mini Drill or Application
Create a tiny linked-list node, free it, then try to use it:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node { int v; struct Node *next; } Node;
int main(void) {
Node *a = malloc(sizeof *a);
a->v = 1; a->next = NULL;
Node *b = malloc(sizeof *b);
b->v = 2; b->next = a;
free(a); /* a is freed, but b->next still points to it */
printf("%d\n", b->next->v); /* UAF */
free(b);
return 0;
}
Build with ASan: gcc -Wall -Wextra -O1 -fsanitize=address -g -o uaf uaf.c. Run. ASan reports "heap-use-after-free" with both allocation and free stack traces. Fix by clearing b->next = NULL; before free(a);. Rebuild and verify ASan is silent.