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:
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 |
|---|---|---|
| z | Global, lines 1–8 | Whole program runtime |
| x, y | Local, lines 3–7 | Execution of g |
| w | Local, line 7 | The block at line 7 only |
| buf | Local, lines 3–7 | Execution of g |
| *buf | Heap, lines 4–7 | Only lines 5–6 (malloc → free) |
| "%s" | Constant literal, line 5 | Execution 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 ↗
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
}
{{ quizQ1.question }}
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.
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.
Heap organisation & unlinking
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:
{{ 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:
Exploiting use-after-free
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.
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
{{ 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.
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 }}
References
Links last checked July 2026.