Ret2page dedicated-cache UAF¶
Escalate a use-after-free on an object in an isolated dedicated
kmem_cacheto a page-level UAF: free the whole slab so its page returns to the buddy allocator, reclaim that page as a user page table, then corrupt page table entries for arbitrary physical read/write.
Mechanism¶
Note
Modern slab hardening isolates security-critical objects into their own dedicated caches (SLAB_ACCOUNT / kmem_cache_create per type, kmalloc-cg-* separation, RANDOM_KMALLOC_CACHES). The intended invariant is type isolation: a UAF on an object in a dedicated cache can only be reclaimed by another object of the same type, denying the attacker a useful overlapping victim.
Ret2page breaks that isolation by moving up one level — from the slab to the page. A slab is built from pages drawn from the buddy (page) allocator. If every object in the dedicated slab is freed, the slab is discarded and its backing page is handed back to the buddy allocator, where it is type-agnostic. The page can then be re-handed to anything that asks the page allocator for an order-0 page — including the page-table allocator. Page table pages (PTE/PMD/PUD) are allocated straight from the buddy allocator (pte_alloc_one() → alloc_pages(...)), so a freed dedicated-cache page can be reclaimed as a process page table. The original UAF reference, which used to point into a typed object, now points into a page table. Writing through it edits PTEs — and a controlled PTE means a controlled physical mapping: arbitrary physical-memory read/write, the most powerful kernel primitive there is, bypassing virtual-address-based protections entirely.
Walkthrough¶
-
Drain the dedicated slab. Allocate enough objects so the target slab's page becomes a candidate for discard, then free all objects in that slab (respecting
objs_per_slabandcpu_partialso the slab is actually released rather than parked on the per-CPU partial list). The page returns to the buddy allocator. The dangling UAF reference still points into this now-free page. -
Reclaim the page as a page table. Force the kernel to allocate a new page table page that lands on the just-freed page. A user fault on an unpopulated address triggers
pte_alloc_one(), which pulls an order-0 page from the buddy allocator:
/* page-table page comes straight from the buddy allocator */
/* pte_alloc_one() -> pagetable_alloc(...) -> alloc_pages(gfp | __GFP_COMP, 0) */
char *p = mmap(NULL, SIZE, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
for (long off = 0; off < SIZE; off += 0x200000)
p[off] = 0; /* fault each PMD region -> allocate PT pages */
- Corrupt a PTE through the UAF. The dangling reference now overlaps the freshly-allocated page table. Use the UAF write to overwrite a page-table entry, pointing a user-visible virtual address at an arbitrary physical frame with the present+writable bits set:
/* a PTE: bits[51:12] = physical frame, low bits = permissions */
#define PTE(phys, flags) (((phys) & PHYS_MASK) | (flags))
*uaf_ptr = PTE(target_phys, 0x67 /* PRESENT|RW|USER|ACCESSED|DIRTY */);
- Read/write arbitrary physical memory. Accessing the virtual address mapped by the corrupted PTE now reads/writes
target_physdirectly. Point it at kernel.text, thecredof the current task, ormodprobe_pathto escalate. Because the attacker operates on physical mappings, page-level virtual-address protections (SMEP/SMAP, per-mapping permissions) do not stand in the way.
Why dedicated caches don't help here
Same-cache hardening (random caches, cg separation) only constrains which slab object can reclaim a freed slot. Ret2page never reclaims a slab object — it reclaims the whole page after the slab is gone, as a page table. The page allocator is type-blind, so the dedicated-cache isolation that blocks intra-slab reuse is simply sidestepped. This is the same page-reuse insight that powers cross-cache-attack and dirty-pagetable-page-table-data-only-attack, specialized to page tables as the reclaim victim.
Mitigation¶
Defenses that pin slab page address spaces so freed slab pages cannot be re-typed (the proposed SLAB_VIRTUAL) directly break the slab-page → page-table reclaim step. Page-table integrity hardening and keeping page-table pages out of the general buddy pool would likewise blunt the reclaim. Same-cache mitigations (RANDOM_KMALLOC_CACHES, cache separation) do not stop ret2page, which is precisely why it targets the page level. Related page-table-centric techniques: dirty-pagetable-page-table-data-only-attack, page-table-manipulation-attack, pagejack-page-level-uaf.