dangling, double-freed, used-after-free
Pointer Mismanagement
Use-after-free, double-free, and dangling references — roughly 70% of serious CVEs. The bugs the DoD wrote a memo about.
A pointer outlives what it points to, and the bytes start to lie.
01in the wild
In the wild
Use-After-Free & Double-Free
Freed memory is still pointed at, then read, written, or freed a second time.
example.c
char *p = malloc(16);
free(p);
strcpy(p, "oops"); /* use-after-free: undefined behavior */
free(p); /* double-free: heap corruption */
/* RIGHT: null after free, single owner, check allocations */
free(p);
p = NULL; /* now a stray use is a clean NULL deref */Roughly 70% of serious CVEs are memory-safety bugs like these. Nulling after free turns silent corruption into a loud crash.
// observed
raw: use-after-free -> exploitable corruption p=NULL: later use -> immediate, debuggable crash
example.rs
// Ownership makes use-after-free a compile error.
let s = String::from("hello");
let moved = s; // ownership moves out of `s`
// println!("{s}"); // error: borrow of moved value `s`
// Borrowing rules also separate owned String from borrowed &str
fn len(text: &str) -> usize { text.len() } // borrows, never freesThe compiler tracks who owns each value and frees it exactly once, at the end of scope. Use-after-free cannot be written.
// observed
use-after-move: rejected at compile time borrow &str: no ownership taken, no double-free
Dangling Returns (String vs &str)
Returning a reference to a local hands back a pointer to memory that just died.
example.rs
// Returning a reference to a local would dangle.
// Rust rejects it at compile time.
fn greeting() -> &str { // error: missing lifetime; borrows a local
let s = String::from("hi");
&s // `s` is dropped at end of scope
}
// RIGHT: hand back ownership, not a borrow
fn greeting() -> String { String::from("hi") }An owned String moves out to the caller; a borrowed &str into a local cannot outlive it, and the borrow checker says so.
// observed
borrow of local: rejected (would dangle) owned String: caller owns it, no dangling
example.c
/* SMELL: returning the address of a stack local */
char *greeting(void) {
char buf[3] = "hi";
return buf; /* buf dies when the function returns */
}
/* RIGHT: heap-allocate (caller frees) or pass a buffer in */
char *greeting(void) { return strdup("hi"); }C will happily return &buf; the pointer dangles the instant the frame is popped. strdup gives the caller something that actually lives.
// observed
stack return: dangling pointer, garbage on read strdup: valid until the caller free()s it
02cross-pollination
Where this compounds
Nondeterminism
- Use-After-Free / Dangling Read × Aliasing & Mutable Defaults
Data Corruption
- Buffer Overflow (Out-of-Bounds Write) × Index Out of Bounds & Missing Keys
- Type-Confusion Memory Corruption × Type Errors in Dynamic Languages
03weakness catalog
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.