{{ course.notes }} · Lecture 2 · Temporal Memory Errors
Instructors: {{ course.instructors }}

Temporal Memory Errors

Spatial bugs touch the wrong place; temporal bugs touch memory at the wrong time, after it has stopped being valid. Use-after-free, double-free, and the write-anywhere exploit.

Builds on  Spatial Memory Errors Prereq  C pointers, malloc/free Time  ~30 min
01 · Foundations

Lifetime and scope of variables

Two ideas govern every temporal bug, and they are programming-language abstractions; the instruction set and hardware know nothing about them:

Scope
The region of code where a variable can be named. E.g. global, function-local, heap.
Lifetime {{ cite.educative_lifetime }}
The portion of execution during which storage is guaranteed to exist. E.g. automatic vs. static.
1  int z = 0;
2  int g(int x, int y) {
3    char* buf;
4    buf = malloc(50);
5    scanf("%s", buf);
6    free(buf);
7    { int w = 0; ... }
8  }
Variable Scope Valid lifetime
zGlobal, lines 1–8Whole program runtime
x, yLocal, lines 3–7Execution of g
wLocal, line 7The block at line 7 only
bufLocal, lines 3–7Execution of g
*bufHeap, lines 4–7Only lines 5–6 (malloc → free)
"%s"Constant literal, line 5Execution of line 5

Notice the mismatch on *buf: buf stays in scope through line 7, but the memory it points at is only alive between malloc and free. That gap is where bugs live. More on lifetime in C ↗

02 · Definition

What is a temporal memory error?

Read this carefully before answering the checkpoint. What does foo() return?

int foo() {
  int *p = NULL;
  {
    int x = 5;
    p = &x;      // p points into the inner block
  }
  return *p;    // ...but x is gone by here
}
Checkpoint · {{ quizQ1.tag }}

{{ quizQ1.question }}

{{ quizQ1.explanation }}
Definition

A temporal memory error occurs when a program accesses memory beyond its valid lifetime. In foo, x's lifetime ends with the inner block; the compiler may reclaim its storage. p is still in scope, but the object it points to is accessed out of lifetime: undefined behaviour under the C11 standard, which means the compiler is free to do anything at all: what you observe changes with the compiler, the optimisation level and the machine.

03 · Temporal error

Use-after-free

The heap version of the same mistake: you free a chunk, but a dangling pointer keeps pointing at it, and later you dereference it. The allocator may have already handed that memory to some other object, so the dangling pointer now reads or writes a different object's data. Use-after-free is one of the most exploited bug classes in browsers and kernels, enough that defences exist which do nothing but null out pointers as objects die {{ cite.dangnull }}.

Here is an example of a bug in a web browser that parses a webpage into a common data structure called the DOM {{ cite.mdn_dom }}. A Document keeps a pointer to a Body object. Watch the Body stay live while it is linked, then disappear the moment it is freed, leaving doc->child dangling:

Why so dangerous? The attacker often controls what gets allocated into the freed slot next (heap grooming). If they can place an object they control where the dangling pointer expects a trusted one (e.g. a function-pointer table), the use-after-free becomes control-flow hijacking, just like a stack overflow.

04 · Recap

Heap organisation & unlinking

Introduced as background in Lecture 1B · How the heap works: here it is again, because double-free abuses the unlink directly.

To see why double-free is exploitable, recall how glibc (the standard C library) manages memory:

  • Allocated chunks are tracked in a linked list; unallocated (free) chunks in another list.
  • Each chunk stores a forward and backward list pointer.
  • A previously freed chunk can be re-allocated; when all memory is used, glibc asks the OS for more virtual pages (fixed-size blocks of address space).

Removing a chunk means unlinking it from its list. This is the operation attackers hijack, documented as an arbitrary-write primitive in Phrack in 2001 {{ cite.phrack_heap }}; step through the pointer rewrites:

Unlinking chunk p · step {{ uNum }} of 4
list_head prev p{{ pTag }} next top = next pointers · bottom = prev pointers
/* remove chunk p from the doubly-linked list */
p->next->prev = p->prev;
p->prev->next = p->next;
/* p is now spliced out of the list */
Hover a pointer expression above to trace that link in the diagram.

{{ uCaption }}

Earlier we watched doc->child dangle after the Body was freed. So why is that dangling read dangerous, rather than merely wrong? Because Body is a C++ object, and its first word is a pointer to its vtable, the per-class table of function pointers that virtual calls dispatch through. The call doc->child->getAlign() is virtual: it is dispatched through that pointer.

If the attacker can get the freed chunk reallocated with bytes they control, the vtable pointer becomes theirs, and the innocent-looking method call turns into a call to attacker-chosen code. Step through it:

Back to the DOM example

Exploiting use-after-free

Why it is so powerful

The vulnerable program never contains a single line of attacker code; the hijack rides entirely on a stale pointer and the allocator’s willingness to reuse freed memory. This is exactly the pattern behind a long line of browser exploits, where a freed DOM node is reclaimed by an attacker-shaped object before a dangling reference fires.

05 · Temporal error

Double-free Bug

Freeing the same chunk twice is another lifetime violation, and a spectacularly powerful one. Consider:

char *p, *q;
p = (char*) malloc(SIZE);
if (abrt) {
    free(p);              // p freed here...
}
q = (char*) malloc(2*SIZE);   // q may reuse p's chunk
strncpy(q, ext_input, 2*SIZE);// attacker fills the chunk
...
free(p);                 // double free of a controlled chunk
Double-free → write-anything-anywhere · step {{ dfNum }} of 4
heap memory: low addresses left, high addresses right
q’s chunk · 2×SIZE = 64 B
{{ pBracketLabel }}
{{ c.val }}
{{ c.addr }}
p → 0x1000 q → 0x1000  (same base, q now aliases p)
{{ writeText }}

{{ dfCaption }}

When the allocator unlinks the doubly-freed chunk, it runs p->prev->next = p->next. If the attacker controls the chunk's prev and next fields, that single statement writes an attacker-chosen value (anything) to an attacker-chosen address (anywhere), the classic write-anything-anywhere exploit primitive.

Summary & key takeaways

The whole memory-safety picture

  • Memory vulnerabilities are spatial (wrong place, out of bounds) or temporal (wrong time, out of lifetime).
  • Exploit outcomes range from control-flow hijacking, seizing the instruction pointer, to data-oriented corruption.
  • C, C++, and the hardware do not give you memory safety; the gap between intended and real behaviour is yours to close.

{{ course.notes }} · Instructors: {{ course.instructors }}

04 · Sources

References

[{{ r.n }}] {{ r.cite }} ↗ {{ r.linkLabel }}

Links last checked July 2026.