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 frees
The 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
Data Corruption
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.