The slab UAF and every kernel use-after-free that follows share one requirement: after you free the victim, you need to reclaim its slot with bytes you control. That's a spray, and the kernel hands you several syscalls that allocate objects of an attacker-chosen size and content. Knowing this small zoo — which object, what size, how long it lives — is most of practical kernel heap exploitation.
- allocator
- SLUB (kmalloc-8 .. kmalloc-8k, plus dedicated caches)
- need
- allocate an object of a chosen size, filled with chosen bytes, on command
- the catch
- lifetime — some sprays are persistent, some vanish unless you pin them
what makes an object useful#
You want elastic objects: the size is controllable (so you can hit any
kmalloc-N cache the victim lives in) and the content is controllable (so you
can forge whatever the victim's bytes mean — a function pointer, a length, a
reference count, a type tag). Four cover almost everything:
|
msg_msg — the workhorse#
System V message queues allocate a struct msg_msg header plus your data in one
kmalloc of a size you pick by message length. The data is fully attacker-
controlled, it stays allocated until you receive it, and — because msg_msg
carries a m_ts length and a next pointer for segmented messages — a partial
overwrite of those fields turns into an OOB read or a second UAF:
|
add_key — persistent, simple, reliable#
add_key("user", ...) allocates a user_key_payload with your size and bytes and
keeps it alive in the keyring. No header math, no queue bookkeeping — the go-to when
you just need "N bytes of my data, parked, until I revoke it."
setxattr — total control, but it evaporates#
setxattr kmallocs a buffer of your exact size, copies your exact bytes in, does
its work, and frees it immediately. Perfect content control, useless lifetime —
unless you stop time. Register the source buffer with
userfaultfd or FUSE so the copy_from_user
blocks mid-copy: the allocation now sits there, filled with your data, frozen
for as long as you hold the fault. That pairing — setxattr for content,
userfaultfd for lifetime — is a staple.
the spray pattern#
The choreography around a UAF is always the same shape:
|
the freed slot, now full of my bytes
what stands in the way#
- Dedicated caches. Many juicy structs (
cred,file,task_struct) live in their ownkmem_cache, so akmalloc-Nspray can't reach them. That's what cross-cache attacks are for. - Cache randomization & hardening.
CONFIG_SLAB_FREELIST_RANDOMandRANDOM_KMALLOC_CACHESperturb which slot you get; you spray more and rely on statistics, or groom deterministically. CONFIG_SLAB_VIRTUAL/ autoslab and freelist hardening raise the bar on what a freed pointer can become.
These syscalls are the kernel-pwn equivalent of malloc(size) with attacker bytes.
Once you can put your data, at your size, into the freed slot of a victim object,
every UAF becomes a question of what the victim's bytes mean — and the next two
parts are about reaching the victims that don't live in a kmalloc-N cache at all.