CS 161, Lesson 10, in 50 slides and code mode. It covers dangling pointers and use-after-free, the exploitation primitive in which two objects share one block, double-free corrupting the allocator's free list, and the C++ vtable-pointer overwrite, which hijacks a pointer to a pointer. It adds type confusion as supplemental material and explains why stack canaries do nothing for the heap. The examples are toys running in a sandbox, and it is anchored to textbook section 3.6.
Subject: Computer Security · 86 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
CS 161 · Lesson 10 of 45
dangling pointers · use-after-free · double-free · C++ vtable hijack · type confusion
Objectives
free(p) does not set p to NULL.Warm-up
Discussion prompt
Before we open L10 · Heap Bugs: Use-After-Free, Double-Free, vtable & Type Confusion: without looking back, what was the main idea of L09 · Format Strings, Integer Conversion & Off-by-One Bugs, and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
CS 161 Lesson 9 (50 slides, code mode): three memory-safety vuln classes — format-string bugs (printf's stack walk, %x leak, %n write), integer conversion bugs (signed/unsigned, overflow under-allocation, truncation), and off-by-one bugs (fence-post, single-null sfp overwrite, strncpy non-termination). Multiple full trace tables.
Concept
Every example here is a toy in a sandbox, for authorized security education — exactly how the textbook (§3.6) teaches it. You learn the bug so you can recognize and fix it.
We never write real shellcode bytes; the attacker's payload stays abstract as [attacker code]. The skill being built is reading a heap and spotting the freed-then-used block.
Counterexample
Discussion prompt
Every example here is a toy in a sandbox, for authorized security education — exactly how the textbook (§3.6) teaches it. You learn the bug so you can recognize and fix it.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
We never write real shellcode bytes; the attacker's payload stays abstract as [attacker code]. The skill being built is reading a heap and spotting the freed-then-used block.
Concept
char *p = malloc(32); creates two things: the pointer p (a local, on the stack) and the 32-byte block it points at (on the heap). They are separate.
The heap grows up (toward higher addresses); the stack grows down. malloc/free hand out and reclaim blocks from the heap. Keeping p and its block distinct is the whole key to the bugs below.
| Thing | Lives where | Holds |
|---|---|---|
| p (the pointer) | stack | an address into the heap |
| the 32-byte block | heap | the actual bytes |
| free(p) | — | returns the block, not p |
Comparison
Comparison matrix
From Recall from L4: the pointer is on the stack, the block is…: refill the Lives where column from what you know. The rest of the table is as it appeared.
| Thing | Lives where | Holds |
|---|---|---|
| p (the pointer) | stack | an address into the heap |
| the 32-byte block | heap | the actual bytes |
| free(p) | — | returns the block, not p |
Section
Part 1 · §3.6
Concept
dangling pointer — A pointer that still holds the address of a region that has already been free()d — the address is no longer valid, but the pointer never noticed.
free(p) returns the block to the allocator. It does not touch p: p still holds the old address. Dereferencing it now reads or writes memory the program no longer owns.
Analogy
Discussion prompt
Explain A dangling pointer by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
free(p) returns the block to the allocator. It does not touch p: p still holds the old address. Dereferencing it now reads or writes memory the program no longer owns.
Concept
use-after-free — A bug where an object is freed but a still-living (dangling) pointer is then used to read or write it.
On its own a UAF might just read stale data. It becomes an exploit when the freed block gets handed back out to a second allocation — now two pointers, of two different types, alias the same memory.
Concept
Dangling pointers appear whenever a pointer outlives the thing it points at. The most common source is free(), but aliasing makes it worse: two pointers to one block, you free through one, the other is now dangling and the freeing code may not even know it exists.
| Cause | What dangles |
|---|---|
| free(p); keep using p | p itself |
| q = p; free(p); use q | the alias q (silently) |
| return &local; | pointer to a popped stack frame |
Trade off
Comparison matrix
From Where dangling pointers come from: every row here is a choice with a cost. Fill the What dangles column, then say which row you would actually pick and what you give up for it.
| Cause | What dangles |
|---|---|
| free(p); keep using p | p itself |
| q = p; free(p); use q | the alias q (silently) |
| return &local; | pointer to a popped stack frame |
Intuition
Picture the heap as an apartment building. malloc is the leasing office handing you a unit; free is you moving out. But you kept a copy of the key (the dangling pointer).
The office, seeing the unit vacant, leases it to a new tenant (the next malloc of the same size). Now you and the new tenant both have keys to the same rooms — and you can rearrange their furniture while they sleep.
Explain it
Discussion prompt
Explain Two tenants, one apartment to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Picture the heap as an apartment building. malloc is the leasing office handing you a unit; free is you moving out. But you kept a copy of the key (the dangling pointer).
Concept
§3.6, precisely: the attacker triggers creation of two separate objects that — because of the use-after-free on the first — actually share the same memory.
The attacker then uses the second object to manipulate the interpretation of the first. The first object thinks its fields are intact; the second object is writing over them through the shared block.
Ranking
Put in order
Put the moves of Tracing free → realloc → shared block into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. p holds the address of a fresh 32-byte block B on the heap.
Worked example
Watch one 32-byte block get freed and then immediately reused by a second allocation of the same size.
char *p = malloc(32); // block B handed to p
free(p); // B returned to allocator; p still = &B (dangling)
char *q = malloc(32); // allocator likely re-hands the SAME B to q
q[0] = 'X'; // writes into B...
// ...which p still points at: *p now reads 'X'malloc(32) gives p block B
Why: p holds the address of a fresh 32-byte block B on the heap.
free(p) returns B but leaves p alone
Why: B goes back on the allocator's free-list. p still equals &B — it is now a dangling pointer.
malloc(32) for q reuses B
Why: Allocators prefer to recycle a just-freed block of the right size, so q very likely gets the same address: q == p.
Writing through q changes what p sees
Why: q and p alias one block. q[0]='X' is visible as *p — two 'separate' variables share one piece of memory.
| Step | Block B state | p | q |
|---|---|---|---|
| p = malloc(32) | allocated → p | &B (valid) | — |
| free(p) | on free-list | &B (dangling) | — |
| q = malloc(32) | re-allocated → q | &B (dangling) | &B (valid) |
| q[0] = 'X' | byte 0 = 'X' | *p sees 'X' | wrote 'X' |
Discrimination
Sort into buckets
Sort these by p, from memory, without looking back at Tracing free → realloc → shared block. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Concept
If the first object holds a function pointer or a length, and the second object lets the attacker write arbitrary bytes into the shared block, the attacker rewrites the first object's function pointer or length without ever touching it directly.
The program keeps using the first object, trusting its fields — which the attacker now controls. This is the core of the C++ object exploitation we build to in Part 3.
Concept
Severity depends on what fills the reused block and what the dangling pointer's object trusts. If the first object holds a length, an attacker-controlled length is an out-of-bounds read/write. If it holds a function pointer, it is direct control-flow hijack.
| Field in object 1 | Attacker writes via object 2 | Result |
|---|---|---|
| a length / size | a huge value | out-of-bounds access |
| a function pointer | an address | control-flow hijack |
| a vtable pointer | a fake-vtable address | method hijack (Part 3) |
Anomaly
Predict first
A student writes this, and it looks reasonable:
After free(p), what does p hold?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Feels safe — but free only takes the value of p (the address).
After free(p), what does p hold?
Why: Feels safe — but free only takes the value of p (the address). It frees the block and returns; it never writes back to the variable p. p is unchanged.
Trap
After free(p), what does p hold?
Assume free(p) sets p = NULL
Why: Feels safe — but free only takes the value of p (the address). It frees the block and returns; it never writes back to the variable p. p is unchanged.
After free(p), what does p hold?
p still holds the OLD address — it is now dangling
Why: §3.6: the block is reclaimed but p keeps pointing at it. That's exactly the dangling pointer. The defensive habit is to set p = NULL; yourself right after free(p).
Anomaly
Predict first
A student writes this, and it looks reasonable:
Reading *p just after free(p).
It is wrong. Say what breaks — and say it before you turn the page.
Correct: free() almost never clears bytes — clearing is slow and not required.
Reading *p just after free(p).
Why: free() almost never clears bytes — clearing is slow and not required. The old contents linger, and the allocator may have written free-list pointers into the first bytes.
Trap
Reading *p just after free(p).
Assume free() zeroes the block, so a stale read returns 0
Why: free() almost never clears bytes — clearing is slow and not required. The old contents linger, and the allocator may have written free-list pointers into the first bytes.
Reading *p just after free(p).
The bytes are undefined — and may already belong to someone else
Why: §3.6: the block can be re-handed by the next malloc. Your stale read may see another object's data, or allocator metadata. It is undefined behavior, not a safe zero.
Section
Part 2 · §3.6
Concept
double-free — Calling free() twice on the same pointer, so the allocator processes one block as if it were freed two independent times.
The allocator tracks free blocks in an internal free-list — metadata stored, on many allocators, inside the freed blocks themselves. Freeing a block twice corrupts that list.
Explain it
Discussion prompt
Explain Freeing the same pointer twice to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
The allocator tracks free blocks in an internal free-list — metadata stored, on many allocators, inside the freed blocks themselves. Freeing a block twice corrupts that list.
Intuition
The free-list is the library's record of which books are back on the shelf. Returning a book you already returned puts the same book on the 'available' list twice.
Now two different patrons can both be told 'it's available' and check it out. Both think they hold a private copy — they hold the same one, and each can scribble in the other's margins.
Analogy
Discussion prompt
Explain Returning a library book twice by analogy to something with no Computer Security in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
The free-list is the library's record of which books are back on the shelf. Returning a book you already returned puts the same book on the 'available' list twice.
Concept
Many allocators store free-list bookkeeping in-band — the forward/back links of the free-list are written into the first bytes of the freed block itself, since the block is idle anyway.
That design is fast, but it means a pointer that aliases a freed block aliases allocator metadata. Corrupt those bytes and you corrupt the allocator's own map of memory — the lever a double-free pulls.
Counterexample
Discussion prompt
Many allocators store free-list bookkeeping in-band — the forward/back links of the free-list are written into the first bytes of the freed block itself, since the block is idle anyway.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Concept
§3.6: a double-free corrupts the allocator's free-list metadata, and that corruption can be turned into an arbitrary-write primitive — the attacker gets to write a value of their choice to an address of their choice.
Mechanically: the same block sits on the free-list twice, so two later mallocs return it. The attacker uses one alias to forge the free-list pointers the allocator will follow on a future allocation, steering the next malloc to return a chosen address.
Step zero
Discussion prompt
Tracing a double-free aliasing two allocations — before any calculation: what is the plan? Name the moves in order, in plain English, without doing the arithmetic.
Hint: It starts with: First free(p) lists block B once
Answer:
Worked example
One block, freed twice, handed out to two pointers that now alias.
char *p = malloc(32); // block B
free(p); // B on free-list (once)
free(p); // B on free-list AGAIN — corruption
char *a = malloc(32); // gets B
char *b = malloc(32); // also gets B (it was listed twice)
// a and b alias B; writing free-list bytes via a steers a later mallocFirst free(p) lists block B once
Why: Normal: B is now available for reuse.
Second free(p) lists B a second time
Why: The allocator does not notice B is already free; the free-list now contains B twice — its internal links are inconsistent.
Two mallocs both return B
Why: Because B appears twice, a and b are handed the same address — two 'distinct' allocations alias one block.
Forge free-list pointers via the alias
Why: The attacker writes chosen bytes into B's free-list-link area through one alias, so the next malloc returns an attacker-chosen address — the arbitrary-write primitive.
| Step | Free-list | a | b |
|---|---|---|---|
| malloc → p | — | — | — |
| free(p) | [B] | — | — |
| free(p) again | [B, B] | — | — |
| a = malloc(32) | [B] | &B | — |
| b = malloc(32) | [ ] | &B | &B |
Error analysis
Annotate
Walk the callouts on Tracing a double-free aliasing two allocations. Each one is a place this is easy to get subtly wrong.
a and b are handed the same address — two 'distinct' allocations alias one block.Trap
Calling free(p) a second time by mistake.
Shrug — at worst it leaks or double-counts memory
Why: Treats it like a tidy-up slip. But the second free mutates allocator metadata, and that metadata controls where future allocations land.
Calling free(p) a second time by mistake.
It corrupts the free-list — an exploitable write primitive
Why: §3.6: double-free corrupts internal allocator metadata, which an attacker turns into an arbitrary write. CWE-415 is a serious memory-corruption class, not a leak. Null the pointer after freeing so a second free is a no-op.
Estimation
Predict first
One line neutralizes both the dangling-pointer use AND the double-free: set the pointer to NULL right after freeing.
Commit before you compute: what does The defensive habit: null after free come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: A second free(p) is now harmless
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. The C standard guarantees free(NULL) does nothing, so the double-free path can't corrupt the free-list.
Worked example
One line neutralizes both the dangling-pointer use AND the double-free: set the pointer to NULL right after freeing.
free(p);
p = NULL; // now p is not dangling, and free(p) again is a safe no-op
// ...
free(p); // free(NULL) does nothing — no double-freeSet p = NULL immediately after free
Why: A later *p crashes loudly (null deref) instead of silently using a recycled block — turning a stealthy UAF into an obvious bug.
A second free(p) is now harmless
Why: The C standard guarantees free(NULL) does nothing, so the double-free path can't corrupt the free-list.
| After free(p) | *p does | free(p) again does |
|---|---|---|
| p left dangling | uses recycled block (UAF) | corrupts free-list (double-free) |
| p = NULL | crashes loudly (null deref) | free(NULL): nothing |
Comparison
Comparison matrix
From The defensive habit: null after free: refill the free(p) again does column from what you know. The rest of the table is as it appeared.
| After free(p) | *p does | free(p) again does |
|---|---|---|
| p left dangling | uses recycled block (UAF) | corrupts free-list (double-free) |
| p = NULL | crashes loudly (null deref) | free(NULL): nothing |
Section
Part 3 · §3.6 (the centerpiece)
Concept
A C++ object with virtual methods stores, at its very start, a vtable pointer — a pointer to an array of function pointers, the object's methods. The object's instance variables are stored directly above the vtable pointer.
vtable — A per-class array of function pointers (the virtual methods). Each polymorphic object holds a pointer to its class's vtable so method calls can be looked up at runtime.
Definition probe
Sort into buckets
Every line below is part of the definition of dangling pointer or of vtable — one or the other, never both. Put each where it belongs.
Intuition
Think of the vtable pointer as a name tag stapled to the front of every polymorphic object: it says 'I am a Cat, look up my methods in the Cat table.' The methods themselves aren't in the object — just the tag pointing to them.
Swap the name tag to point at a forged table you wrote, and the program will dutifully look up 'this object's methods' in your table and run whatever addresses you put there. The object never changed; only its tag did.
Concept
When the program calls a method on object y, it does two hops: follow y's vtable pointer to the vtable, then read the method's address out of that vtable, then call it.
That double indirection — pointer to a table of pointers — is exactly what makes the attack a pointer-to-a-pointer problem. Remember both hops; the trap later turns on them.
Sorting
Sort into buckets
These are the pieces of L10 · Heap Bugs: Use-After-Free, Double-Free, vtable & Type Confusion, out of order. Put each one back under the part of the lesson it belongs to.
Picture it
Figure (svg): Heap layout, low to high: x's vtable pointer, then x's instance variable buffer, then y's vtable pointer, then y's instance variables. An overflow of x's buffer climbs up into y's vtable pointer.
Discussion prompt
Read the picture before the words. What is this showing, and what is the one thing it is built to make obvious? Commit to an answer, then read on.
Hint: Name the parts, then say what changes between them — and if nothing changes, say what is being held still.
Answer:
Suppose object x and object y are both on the heap, with y sitting above x (at higher addresses, since the heap grows up). x has an instance-variable buffer; y begins with its vtable pointer.
Concept
Suppose object x and object y are both on the heap, with y sitting above x (at higher addresses, since the heap grows up). x has an instance-variable buffer; y begins with its vtable pointer.
Figure (svg): Heap layout, low to high: x's vtable pointer, then x's instance variable buffer, then y's vtable pointer, then y's instance variables. An overflow of x's buffer climbs up into y's vtable pointer.
| Heap slot (low→high) | Belongs to | Attacker's interest |
|---|---|---|
| x: vtable ptr | object x | — |
| x: instance buf | object x | the overflow source |
| y: vtable ptr | object y | the overwrite target |
| y: instance vars | object y | — |
Intuition
The book's framing: this is very similar to stack smashing — an unchecked write runs off the end of a buffer into control data next door. On the stack that data was the saved rip; on the heap it's y's vtable pointer.
However, overwriting a C++ vtable requires overwriting a pointer to a pointer. On the stack you overwrote an address the CPU jumps to. Here you overwrite a pointer that points to a table of addresses — one extra hop of indirection.
Ranking
Put in order
Put the moves of Tracing the vtable-pointer overwrite into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. The unchecked strcpy writes past x's instance buffer, climbing up the heap toward y.
Worked example
The attacker overflows x's instance buffer, lands on y's vtable pointer, and aims it at a buffer they control.
// toy/sandbox C++-on-heap sketch
strcpy(x->buf, attacker_input); // no bounds check: overflows x->buf
// input is long enough to climb past x->buf into y's vtable pointer
// y->vptr now = &fake_vtable (an attacker-controlled buffer)
// fake_vtable[0] = &attacker_code
y->method(); // follows y->vptr -> fake_vtable[0] -> attacker_codeOverflow x->buf with no bounds check
Why: The unchecked strcpy writes past x's instance buffer, climbing up the heap toward y.
Overwrite y's vtable POINTER
Why: The bytes that land on y's first word replace y's vtable pointer with the address of an attacker-controlled buffer (a fake vtable).
Put &attacker_code in the fake vtable
Why: In that buffer the attacker writes, at the slot the method call will read, the address of malicious code.
Calling y->method() runs attacker code
Why: The call follows y's (hijacked) vtable pointer to the fake vtable, reads the method address out of it, and jumps there — two hops, attacker-chosen at each.
| Hop | Reads | Now points at |
|---|---|---|
| 1: y->vptr | y's first word | attacker's fake vtable (overwritten) |
| 2: fake_vtable[k] | the method slot | &attacker_code |
| call | that address | executes attacker_code |
Error analysis
Annotate
Walk the callouts on Tracing the vtable-pointer overwrite. Each one is a place this is easy to get subtly wrong.
Estimation
Predict first
Stack smashing overwrote one address the CPU jumps to. A vtable hijack overwrites a pointer that points to a table of addresses. Count the dereferences to see the extra hop.
Commit before you compute: what does Counting the levels of indirection come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: vtable hijack: overwrite a pointer to a table of addresses
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. y->vptr is not the target — it points to the table that holds the target.
Worked example
Stack smashing overwrote one address the CPU jumps to. A vtable hijack overwrites a pointer that points to a table of addresses. Count the dereferences to see the extra hop.
// stack smashing (L8): ONE level
ret; // CPU reads saved rip (an address) and jumps
// vtable hijack (L10): TWO levels
call *(y->vptr)[k]; // hop 1: y->vptr -> table; hop 2: table[k] -> codeStack smashing: overwrite the address itself
Why: The saved rip IS the jump target. One dereference (ret pops it) and the CPU is at the attacker's address.
vtable hijack: overwrite a pointer to a table of addresses
Why: y->vptr is not the target — it points to the table that holds the target. Two dereferences happen before the call. That is the 'pointer to a pointer' the book stresses.
| Attack | Overwritten word | Dereferences before jump |
|---|---|---|
| stack smashing (L8) | saved rip (the address) | 1 |
| vtable hijack (L10) | vtable POINTER (ptr to ptr) | 2 |
| vtable ENTRY (not this attack) | an address in the table | 1 from the table |
Trade off
Comparison matrix
From Counting the levels of indirection: every row here is a choice with a cost. Fill the Dereferences before jump column, then say which row you would actually pick and what you give up for it.
| Attack | Overwritten word | Dereferences before jump |
|---|---|---|
| stack smashing (L8) | saved rip (the address) | 1 |
| vtable hijack (L10) | vtable POINTER (ptr to ptr) | 2 |
| vtable ENTRY (not this attack) | an address in the table | 1 from the table |
Concept
On the stack, neighbors are fixed by the compiler's frame layout. On the heap, the attacker can often influence allocation order (allocate x, then y) so that x's buffer ends up just below the object whose vtable pointer they want — a technique called heap grooming.
That control over layout is what makes 'overflow x to hit y's vtable pointer' realistic rather than a lucky accident.
Intuition
The attack needs x's buffer directly below y's vtable pointer. The attacker doesn't get to pick addresses — but they can pick the order and sizes of allocations, and allocators are predictable: same-size requests tend to come back adjacent.
So the attacker 'grooms' the heap — allocate filler, free a hole, allocate x then y into adjacent slots — until the layout lines up. It's less aiming a rifle and more arranging the dominoes.
Anomaly
Predict first
A student writes this, and it looks reasonable:
What did the heap overflow overwrite?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Conflates the two levels. The vtable itself usually lives in read-only memory and is shared by all objects of the class — you typically can't overflow into a vtable entry, and changing one would affect every object.
What did the heap overflow overwrite?
Why: Conflates the two levels. The vtable itself usually lives in read-only memory and is shared by all objects of the class — you typically can't overflow into a vtable entry, and changing one would affect every object.
Trap
What did the heap overflow overwrite?
Say it overwrote a function pointer INSIDE the vtable
Why: Conflates the two levels. The vtable itself usually lives in read-only memory and is shared by all objects of the class — you typically can't overflow into a vtable entry, and changing one would affect every object.
What did the heap overflow overwrite?
It overwrote y's vtable POINTER — a pointer to a pointer
Why: §3.6: the overflow changes the per-object pointer that names which vtable to use, redirecting it to an attacker-built fake table. That's one level above a vtable entry — the textbook's 'pointer to a pointer' point exactly.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Where do a C++ object's instance variables sit relative to its vtable pointer?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: If that were so, an overflow of one object's data couldn't climb into the next object's vtable pointer — and the attack would be impossible.
Where do a C++ object's instance variables sit relative to its vtable pointer?
Why: If that were so, an overflow of one object's data couldn't climb into the next object's vtable pointer — and the attack would be impossible.
Trap
Where do a C++ object's instance variables sit relative to its vtable pointer?
Place instance vars BELOW the vtable pointer
Why: If that were so, an overflow of one object's data couldn't climb into the next object's vtable pointer — and the attack would be impossible.
Where do a C++ object's instance variables sit relative to its vtable pointer?
The vtable pointer is at offset 0; instance vars sit ABOVE it
Why: §3.6 / Itanium C++ ABI: the vtable pointer is the object's first word; instance variables follow at higher addresses. So overflowing object x's instance buffer climbs up into the next object's vtable pointer.
Section
Part 4 · §3.6 + forward link
Concept
⊕ Supplemental — flagged as beyond §3.6's core depth. Type confusion is treating memory as the wrong type after an unchecked cast (a C union misused, or a C++ downcast to the wrong class).
type confusion — ⊕ Accessing the same bytes as the wrong type, so a field that was data is reinterpreted as a pointer (or vice versa) without any overflow. (CWE-843.)
Matching
Match the pairs
Match each term to the definition this lesson gave it — not the one you would guess from the word.
Why: These are the working definitions of dangling pointer, use-after-free, double-free, vtable, type confusion as L10 · Heap Bugs: Use-After-Free, Double-Free, vtable & Type Confusion uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.
Intuition
⊕ Bits in memory carry no inherent meaning — 0x41414141 is just 32 bits. The type is the pair of glasses you read them through: one lens says 'integer 1094795585', another says 'address to call'.
⊕ Type confusion is handing the program the wrong glasses. No byte moved, nothing overflowed — the program simply trusts a cast that was never checked and reads value-bytes as a pointer it will follow.
Ranking
Put in order
Put the moves of ⊕ A union reinterpreting data as a pointer into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. u.data is just an integer field; the attacker sets its bits to a value of their choosing.
Worked example
⊕ The bits never change — only how we read them. Write an integer through one union member, read it as a pointer through another.
union U { long data; void (*fn)(void); };
union U u;
u.data = 0x41414141; // attacker-controlled integer
u.fn(); // SAME bytes, now CALLED as a function pointerStore attacker data through u.data
Why: u.data is just an integer field; the attacker sets its bits to a value of their choosing.
Read the SAME bytes through u.fn
Why: A union overlays both members on one storage. Reading u.fn reinterprets those exact bits as a code address — no overflow, no free, just a bad type assumption.
Calling u.fn() jumps to attacker bits
Why: What was 'data' is now executed as a function pointer. A field meant for values became control flow purely through reinterpretation.
| Same 4 bytes | Read as type | Interpreted as |
|---|---|---|
| 0x41414141 | long (u.data) | the integer 1094795585 |
| 0x41414141 | void(*)() (u.fn) | a code address to call |
| (unchanged) | — | data becomes control flow |
Comparison
Comparison matrix
From ⊕ A union reinterpreting data as a pointer: refill the Interpreted as column from what you know. The rest of the table is as it appeared.
| Same 4 bytes | Read as type | Interpreted as |
|---|---|---|
| 0x41414141 | long (u.data) | the integer 1094795585 |
| 0x41414141 | void(*)() (u.fn) | a code address to call |
| (unchanged) | — | data becomes control flow |
Concept
⊕ Type confusion is very common in browser engines: JavaScript engines juggle millions of polymorphic objects, and a single missed type check on a downcast lets attacker-shaped data be used as the wrong object — a field that was a number becomes a pointer the engine trusts.
⊕ It needs no buffer overflow and no free — just an unchecked cast. That is what makes it distinct from the bugs in Parts 1–3, and why it's flagged as supplemental here.
Concept
⊕ It's worth pinning down what makes type confusion different. UAF and double-free are lifetime bugs (using memory at the wrong time); heap overflow is a bounds bug (writing past the end). Type confusion is a type bug — right place, right time, in bounds, wrong interpretation.
| Bug | What's violated | Needs an overflow? |
|---|---|---|
| use-after-free | object lifetime | no |
| double-free | allocator invariant | no |
| heap overflow / vtable | buffer bounds | yes |
| ⊕ type confusion | type identity (a cast) | no |
Discrimination
Sort into buckets
Sort these by Needs an overflow?, from memory, without looking back at ⊕ Type confusion vs the Part 1–3 bugs. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.
Concept
Stack canaries (L14) place a secret guard value on the stack, between the locals and the saved rip, and check it before returning. They detect a stack buffer overflow that ran over the canary.
Every bug in this lesson lives on the heap. A UAF, a double-free, a vtable-pointer overwrite, or a type confusion never touches the stack canary — there is no canary on the heap to trip. This is exactly why heap bugs remain prized (sets up Weeks 4–5).
Anomaly
Predict first
A student writes this, and it looks reasonable:
How does type confusion corrupt memory?
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Imports the stack-smashing mental model.
How does type confusion corrupt memory?
Why: Imports the stack-smashing mental model. But type confusion plants no out-of-bounds bytes at all.
Trap
How does type confusion corrupt memory?
Assume it must overflow a buffer to plant the wrong bytes
Why: Imports the stack-smashing mental model. But type confusion plants no out-of-bounds bytes at all.
How does type confusion corrupt memory?
It's a bad CAST — the bytes are in bounds, just misread
Why: §3.6 / CWE-843: no overflow occurs. The same in-bounds bytes are accessed as an incompatible type, so a data field is reinterpreted as a pointer. The bug is the missing type check, not a length check.
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
char *p = malloc(32); creates two things: the pointer p (a local, on the stack) and the 32-byte block it points at (on the heap). They are separate.; The program keeps using the first object, trusting its fields — which the attacker now controls. This is the core of the C++ object exploitation we build to in Part 3.; The free-list is the library's record of which books are back on the shelf. Returning a book you already returned puts the same book on the 'available' list twice.free(p), what does p hold?; Reading *p just after free(p).Section
Part 5
Constraint
Discussion prompt
Run Pattern: recognize and classify a heap bug with this step confiscated:
Did an unchecked write run off a heap object into the next object? → heap overflow; on a C++ object the target is the vtable pointer (a pointer-to-a-pointer hijack), not a vtable entry.
Is it still possible? If it is, say what takes its place and what it costs you. If it is not, say exactly what that step was providing that nothing else does.
Hint: A step you can drop for free was never load-bearing. If you cannot drop it, name the thing that goes wrong the moment it is gone.
Answer:
malloc so two objects **share one…mallocs alias it → arbitrary-write primitive.p = NULL after free; never free twice; bounds-check every copy into a heap buffer; check every cast — and remember **stack canaries do…Pattern
malloc so two objects share one block.mallocs alias it → arbitrary-write primitive.p = NULL after free; never free twice; bounds-check every copy into a heap buffer; check every cast — and remember stack canaries do nothing for any of these.Edge cases
Discussion prompt
Pattern: recognize and classify a heap bug works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".
Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.
Answer:
malloc so two objects **share one…mallocs alias it → arbitrary-write primitive.p = NULL after free; never free twice; bounds-check every copy into a heap buffer; check every cast — and remember **stack canaries do…Elimination
Eliminate the wrong options
What bug does this sequence demonstrate, and what is the key fact about p?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: free(p) returns the block but leaves p holding the old address (dangling). The next malloc(32) recycles that exact block to q, so p and q alias one piece of memory — writing through q changes what p sees. That is a use-after-free, and the load-bearing fact is that free does NOT null the pointer.
Check
Read the sequence, then classify it before clicking.char *p = malloc(32); free(p); char *q = malloc(32); q[0] = 'X'; — and q ends up at the same address p held.
Check your understanding
What bug does this sequence demonstrate, and what is the key fact about p?
Answer: A
Why: free(p) returns the block but leaves p holding the old address (dangling). The next malloc(32) recycles that exact block to q, so p and q alias one piece of memory — writing through q changes what p sees. That is a use-after-free, and the load-bearing fact is that free does NOT null the pointer.
Concept
Concept
Concept
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — Dangling Pointers & Use-After-Free · Double-Free · Heap Overflow & C++ vtables · Type Confusion ⊕ & Stack Defenses · Consolidate. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
You can define a dangling pointer (free does NOT null p), trace a use-after-free where two objects share one freed block, explain double-free free-list corruption, walk the C++ vtable-pointer overwrite as a pointer-to-a-pointer hijack, distinguish type confusion from an overflow, and say why stack canaries miss the heap.
| Idea | The one fact |
|---|---|
| Dangling pointer | free(p) reclaims the block; p keeps the old address |
| Use-after-free | freed block is re-handed → two objects alias one block |
| Double-free | free-list corruption → arbitrary-write primitive |
| C++ vtable overwrite | overwrite the vtable POINTER (pointer-to-a-pointer), not an entry |
| Type confusion ⊕ | bad cast: in-bounds bytes read as the wrong type |
| Why it matters | heap bugs sit off the stack canary → motivate memory-safe languages |
Want this taught 1-on-1? Alexander tutors Computer Security — $55/session, free consultation.