What is a Use-After-Free?
A Use-After-Free (UAF) vulnerability occurs when a program continues to use a pointer after the memory it points to has been freed. At the C level, it looks deceptively simple:
obj_t *p = malloc(sizeof(obj_t));
free(p);
p->method(); // UAF — dangling pointerThe danger: after free(p), the allocator reclaims that chunk. If an attacker can get a subsequent malloc to return the same chunk — and write controlled data into it before the UAF access — they control what p->method points to. On a virtual dispatch, that's arbitrary code execution.
How ptmalloc2 Manages Heap Chunks
glibc uses ptmalloc2. Every allocation is a chunk with a header containing size flags and free-list pointers. When you call free(p), ptmalloc2 places the chunk into a bin based on its size. Small chunks go into fastbins — a singly-linked LIFO list. A subsequent malloc of the same size pops from that fastbin, returning the exact same pointer.
The Exploitation Chain
Classic UAF exploitation has three phases:
- Trigger the free — cause the target object to be freed while a dangling reference remains
- Spray the heap — allocate a controlled object of the same size to reclaim the freed chunk
- Trigger the UAF — invoke the dangling pointer, now pointing to your controlled data
The attacker-controlled object typically contains a fake vtable pointer. When the program calls a virtual method through the dangling pointer, it dispatches to the attacker's address — code execution achieved.
Heap Feng Shui
Reliable exploitation requires controlling heap layout so your spray allocation lands in exactly the right chunk. Techniques include grooming the heap to defragment and align bins, using size classes to predict which bin a chunk lands in, and forcing allocation patterns to create predictable holes.
Modern mitigations like ASLR, Safe Unlinking, and tcache key checks raise the bar but don't eliminate UAF exploitation — they just require more heap grooming.
CTF Example
A classic UAF CTF challenge involves a note-taking application where freeing a note does not NULL the pointer. By creating a new note of the same size with a controlled payload containing a fake function pointer, we redirect execution to a one_gadget for a shell.
Mitigations
- AddressSanitizer — catches UAF at runtime in debug builds
- Smart pointers —
std::shared_ptrandstd::unique_ptrin C++ prevent most UAF - Memory safe languages — Rust's ownership model eliminates UAF at compile time
- Heap hardening — tcache key checks, Safe Unlinking, delayed free
Takeaway
UAF is one of the most impactful vulnerability classes — it's at the root of a significant portion of browser exploits and kernel CVEs. Understanding the allocator internals is what makes the difference between knowing UAF exists and actually exploiting it reliably.