JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Memory-Corruption Classes: Overflows and Use-After-Free

You already know what a stack frame, a heap block, and a dangling pointer are. Now we turn that knowledge sideways and ask the attacker's question: when a program writes past where it should, or touches memory it already freed, what exactly gets corrupted — and why is that the seed of nearly every exploit?

From bug to weapon: what "corruption" really means

In the earlier rungs you met a whole family of memory mistakes as bugs — a buffer overflow that scribbles past an array, a use-after-free that touches a block after free(), an off-by-one that walks one step too far. You learned them as things that make your own program crash with a segfault or behave strangely. This rung reframes every one of them through an attacker's eyes. The same write-past-the-end that sometimes crashes is, to an attacker, a controlled write into memory the program never meant to expose — and that is the raw material of an exploit. The shift is not new theory; it is the same mechanics, asked "who else benefits when this goes wrong?"

The reason memory corruption is so powerful traces straight back to something you already hold firmly: memory is one flat array of bytes, and the same byte means different things depending on who reads it. A byte can be part of your string, part of a return address the CPU will jump to, part of a pointer some later code will follow, or part of allocator metadata the heap trusts. The hardware does not police which bytes are "data" and which are "control." So when an overflow lets an attacker change bytes that happen to be a return address or a pointer, they are no longer just corrupting data — they are reaching toward control-flow hijacking, steering the program itself.

Stack overflows: writing onto the road home

Recall the layout of a stack frame from the assembly rung. When a function runs, the CPU has pushed a return address onto the stack — the address it will jump back to when the function returns — and the function's local variables sit nearby, including any fixed-size buffer like a `char buf[64]`. On most architectures the buffer grows toward higher addresses while the return address sits just above it. So if code copies attacker-controlled bytes into that buffer without checking the length, the write spills past the end of `buf` and keeps going — straight over the saved return address. This is a classic stack buffer overflow, and the danger is geometric: the attacker is overwriting the exact bytes that decide where the CPU goes next.

higher addresses
   +----------------------+
   |  saved return addr   | <-- CPU jumps HERE on return
   +----------------------+
   |  saved rbp           |
   +----------------------+
   |  char buf[64]        | <-- copy starts here...
   |  ...                 |     ...and a too-long copy
   |  ...                 |     keeps writing UPWARD,
   +----------------------+     over rbp, over the
lower addresses                 return address.
A function's stack frame on a typical x86-64 layout. An unchecked copy into buf grows upward and clobbers the saved return address. Whoever controls that address controls where execution resumes.

The textbook trigger is one of the dangerous string functions — strcpy(), gets(), sprintf() — that copy until a terminating NUL byte with no idea how big the destination is. If the source is longer than `buf`, the copy runs off the end; the function has no length to stop it. The fix is mundane and you already know it: bound every copy by the destination's size, prefer the length-limited calls, and treat any input crossing a trust boundary as hostile. The exploit chapters of this rung build on exactly this primitive — overwriting the return address — but the defense is the discipline you have been practicing since the C rungs.

Heap overflows: corrupting the allocator's bookkeeping

The heap is more subtle. When you call malloc(), the allocator hands back a block, but it also keeps its own bookkeeping — chunk sizes, free-list links, flags — usually stored right next to your block, in allocation metadata that lives in the same byte array your data does. A heap overflow happens when you write past the end of a heap block: instead of trampling a return address, you trample the allocator's own data structures. The next call to malloc() or free() then walks a corrupted free list and can be tricked into returning an attacker-chosen address, turning a buffer overrun into the ability to write anywhere.

Two cousins of the overflow live entirely in how you manage heap blocks. A double free — calling free() twice on the same pointer — is dangerous because the second free re-inserts an already-freed block into the free list, leaving the list in a state where the same chunk can be handed out twice; a double-free is a corruption of allocator state even though no byte of your data overflowed. And an integer mistake feeds these too: if a size calculation wraps around — say `count * size` exceeds what a `size_t` can hold and wraps to a tiny number — malloc() returns a buffer far smaller than the caller believes, and the very next write overflows it. The lesson the earlier rungs drilled returns with teeth: check what malloc() actually gave you, and never trust a size you did not bound.

Use-after-free: a pointer that outlives its block

A use-after-free is the time-shifted sibling of an overflow. You allocate a block, you free() it, but you keep a copy of the pointer and later read or write through it — a dangling pointer still aimed at memory the allocator now considers free. Nothing crashes immediately, because the bytes are still there; the danger arrives when the allocator reuses that same region for a fresh allocation. Now two different parts of the program believe they own the same bytes. The stale pointer reads or writes one object while the new owner sees another — and an attacker who can control what gets allocated into the reused slot controls what the stale pointer touches.

Why is this such a prized exploit primitive? Because objects in real programs often contain pointers — a use-after-free exploit frequently targets an object that holds a function pointer or a virtual-table pointer (in C++). If the attacker can free that object, then heap-spray the freed slot with bytes of their choosing, the stale reference reads their fake pointer as if it were real, and following it leads to control-flow hijacking or an arbitrary read/write of memory. The overflow corrupted space; the use-after-free corrupts time — but both end in the same place: the attacker influencing a value the program will trust as a pointer or an address.

  1. Allocate an object on the heap with malloc() that holds a pointer the program will later call or follow.
  2. free() that object, but leave a dangling copy of the pointer to it still in use.
  3. Trigger a new allocation of the same size so the allocator reuses the freed slot, and fill it with attacker-chosen bytes.
  4. Make the program use the dangling pointer: it now reads the attacker's fake inner pointer and follows it where the attacker wants.

Format strings, and the through-line of the rung

One more class deserves a place beside the overflows, because it surprises people: the format-string bug. When code writes `printf(user_input)` instead of `printf("%s", user_input)`, it hands attacker-controlled text to printf() as the format string. Now the attacker writes the format directives. A `%x` leaks bytes off the stack; a string of them dumps memory, defeating the address secrecy that later mitigations rely on; and the obscure `%n` directive writes the number of bytes printed so far into a pointer argument — turning a logging call into an arbitrary write. It is a pure case of the rung's theme: untrusted data flowing into a position that the program treats as trusted control.

Step back and see the shape shared by all of these. A stack overflow corrupts a return address in space; a heap overflow corrupts allocator metadata; a use-after-free corrupts ownership in time; a format-string bug corrupts the boundary between data and directive. Every one ends with the attacker placing a value the program will trust as a pointer, an address, or a length. That is the whole game, and it is why the next guide can take overwriting a return address as a solved problem and ask the harder question: with the address known, where do you point it? Return-oriented programming is the answer, and it builds directly on this foundation.