Fastbin dup had to dance around a check. The tcache, for two
whole releases, had none — free a chunk twice and it cheerfully linked it into
the tcache twice. glibc 2.29 finally added a guard, and it's a single field sitting
in the freed chunk's own data, which means you can erase it. This is the most
common double-free you'll actually use.
- arch
- amd64-64-little
- libc
- 2.31
- technique
- tcache double-free -> duplicate chunk -> tcache poisoning
- guard
- the 2.29 `key` field (and how to clear it)
2.27–2.28: just free it twice#
On the first two tcache releases there is nothing to bypass:
Now x and y alias. Free one, edit it through the other, and you control a freed
chunk's fd — which is tcache poisoning: the
next-but-one allocation lands wherever you point.
2.29+: the key field, and erasing it#
glibc 2.29 gave freed tcache chunks a key — the second quadword of the chunk's
data is set to the address of the tcache_perthread_struct. On free, if the
chunk you're freeing already carries that key, glibc walks the bin looking for a
double-free and aborts:
|
The bypass writes itself: between the two frees, overwrite the key to anything
that isn't the tcache pointer. The check e->key == tcache then fails, the scan is
skipped, and the second free goes through:
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
the rest is poisoning
With the duplicate, poison the fd to __free_hook, allocate it out, write
system, free "/bin/sh":
segfault{th3_tc4ch3_n3v3r_l34rns}
safe-linking changes the fd write (2.32+)
From glibc 2.32 the next/fd field is mangled as (&fd >> 12) ^ target, so
after the dup you need a heap leak to compute the poisoned fd. And glibc
2.34 hardened the tcache further with a per-bin count check — a sloppy
double-free can now also trip "free(): double free detected" via the count, so
keep the bin's bookkeeping consistent.
The tcache dup is the workhorse: simplest double-free, straight into the friendliest poison. Next we go back to the classics with a twist — the House of Einherjar, where one null byte lets you consolidate a chunk backward into a fake chunk anywhere.