Heap spray works beautifully — until the object
you want to corrupt lives in its own kmem_cache. A UAF in kmalloc-256 can't
reclaim a struct cred (own cache: cred_jar) or a struct file (own cache:
filp) by spraying, because SLUB serves each cache from separate slabs. Cross-cache
attacks demolish that isolation by operating one level down — at the page.
- problem
- the victim UAF is in cache A; the object you want is in cache B
- isolation
- SLUB never mixes two caches' objects on one slab
- lever
- but slabs are just pages, and the buddy allocator recycles pages across caches
SLUB sits on top of the page allocator#
A kmem_cache doesn't own memory forever. It requests slabs — runs of pages —
from the buddy (page) allocator, carves them into objects, and when a slab becomes
entirely free, SLUB can hand that page back to the buddy allocator. The buddy
allocator has no notion of caches: the next code to ask for a page of that order
gets it, whatever cache it's for. That return-and-reuse is the whole attack.
|
the choreography#
The hard part is step 2 — emptying the victim's slab so the whole page frees, not just one object. You groom: allocate enough victims to fill a fresh slab, arrange the dangling reference to one of them, then free the entire slab's worth so SLUB releases the page:
|
Now a UAF that could only ever corrupt a kmalloc-256 object reaches a
struct cred — and overwriting the right bytes of a cred is
root, without ever leaking an address or corrupting
a pointer the mitigations watch.
a kmalloc UAF that ends up inside cred_jar
why it matters now, and what fights it#
- It's the answer to dedicated caches. Modern hardening puts dangerous structs in isolated caches precisely to stop reclaim-by-spray; cross-cache steps under that isolation at the page layer, so it's the default technique in current kernel exploits.
- It composes with DirtyCred. Cross-cache
to get a
cred/filepage where you want it, then swap the privileged object in. - The defences are page-level too.
CONFIG_SLAB_VIRTUAL, per-cache page pools, and not returning slabs to the buddy allocator promptly all raise the cost; the attack adapts by grooming harder and racing the page reuse.
The mental shift from userland: in glibc you reshape bins; in the kernel you reshape slabs and pages. SLUB's caches look like strong isolation until you remember they're all renting from the same page allocator — and a page you free in one cache is a page another cache will gladly reuse.