The commit_creds path hijacks control flow and pays the SMEP/SMAP/KASLR tax. DirtyCred (Zhenpeng Lin, 2022) asks a sharper question: why corrupt anything? The kernel already has privileged objects and unprivileged ones, allocated from the same caches. Free one and let the other land in its slot, and you've swapped privilege without writing a single pointer the mitigations are watching.
- technique
- object-swapping on struct cred / struct file (data-only)
- needs
- a UAF or double-free on a cred or file (often via cross-cache)
- bypasses
- KASLR, SMEP/SMAP/KPTI, CFI — none of them see an object swap
the insight: privilege is a whole object, not a bit#
A struct cred is your privilege; a struct file's f_mode is your
read/write permission. Classic exploits corrupt a field inside these — and modern
kernels harden exactly those fields and watch for the pointer games that reach
them. DirtyCred never touches a field. It frees the entire object and relies on the
allocator to hand the freed slot to a legitimately allocated privileged object of
the same type.
the file variant (the elegant one)#
The cleanest version targets struct file. A file opened read-only and one opened
writable differ only by which struct file your fd points at. Swap them:
|
You just edited /etc/passwd (or a SUID binary, or sudoers) with no permission —
the same end state as Dirty Pipe, reached through a
UAF instead of an uninitialised flag.
the cred variant#
The same move on struct cred gives root directly: free your task's unprivileged
cred (keeping the task referencing the slot), then make a privileged process
allocate a cred — fork/exec a root daemon, or trip any path that calls
prepare_kernel_cred/prepare_creds — so its root cred lands in your freed slot.
Your task now runs on root credentials it never earned:
Because cred and file live in dedicated caches (cred_jar, filp), the free
and the reclaim usually go through a cross-cache step to
line the slabs up.
root, and CFI never fired
why it bypasses everything#
- No pointer is corrupted, so kCFI / CFI / pointer hardening have nothing to flag — every pointer in play is one the kernel set itself.
- No address is leaked, so KASLR is irrelevant — you never name a kernel address.
- No code is executed out of place, so SMEP/SMAP/KPTI don't apply — there's no
ROP, no
ret2usr, no trampoline. - It's bug-generic — any UAF or double-free on a
credorfileplugs in, so one technique weaponises a huge class of bugs.
the defences it forced#
DirtyCred was influential enough to change the kernel: proposals to isolate
privileged credentials onto separate, non-reclaimable memory (so a freed cred
slot can't be reused by an attacker-influenced allocation), virtual-address-based
slab isolation, and tighter checks on cred reuse. The arms race moved from
"protect the field" to "isolate the object."
DirtyCred is the data-only mindset at its purest: the kernel's mitigations are a
fortress built around corruption, and an object swap simply isn't corruption.
When a bug gives you a free on a cred or a file, don't reach for a ROP chain —
reach for a legitimate privileged object and let the allocator do the exploit.