PageJack page-level UAF¶
Bir out-of-bounds/UAF/double-free bug'ını, bir slab object'in
struct page *field'ını corrupt etmeye pivot et, dangling bir reference tutarken o 4 KB'lık physical page'i free et, kritik object'leri page üzerine yeniden spray'le, sonra onları doğrudan oku/yaz — KASLR leak'i gerektirmeyen ve object-level cross-cache mitigation'larını atlatan data-only bir primitive.
Mechanism¶
Note
Bir struct page (yeni kernel'lerde 0x40 = 64 byte) tam olarak bir adet 4 KB'lık physical
page'i tanımlar. Birçok slab object bir struct page * "page-mapping" field'ı tutar
(pipe_buffer->page, bio_vec->bv_page, configfs_buffer->page, …). PageJack'in
invariant'i şu: bu pointer'lardan birini corrupt edip iki object'in aynı physical
page'i reference etmesini sağlarsan, sonra birini free edersen — page buddy allocator'a
döner, ama hayatta kalan object hâlâ onu okur/yazar. Dedike bir cache'i (örn. cred,
file) yeniden spray'lemek, buddy allocator'ın aynı page'i victim slab'a vermesini
sağlar. Şimdi dangling page-mapping object, victim object'leri byte byte okur/yazar.
Bu, alışılmış exploit chain'ini — pivot → KASLR bypass → cross-cache → hedefi corrupt et
— pivot → hedefi corrupt et'e indirir. KASLR leak'i gerekmez çünkü primitive physical-page
uzayında çalışır ve SLAB_VIRTUAL'ı atlatır çünkü o mitigation slab'ın virtual range'lerini
izole eder, alttaki physical page'i değil.
"Page UAF" primitive'i bug-class'tan bağımsızdır: bir page*'ı corrupt ederek invalid-write'tan
(OOB, UAF write) ya da page'i sahiplenen object'i free ederek invalid-free'den (double free)
ulaşılabilir. Page grooming için page-level-heap-feng-shui'ye,
reclaim mekaniği için page-uaf / page-spray'e bak.
Walkthrough¶
PageJack'in beş generic adımı (Black Hat USA 2024, Qian et al.):
Step 1 Layout: groom the vulnerable object adjacent to objects that hold a struct page*
(e.g. two pipe_buffer objects, each pointing at its own 4 KB page).
Step 2 Corrupt: trigger the OOB/UAF write to tamper a neighbouring page* pointer so it
aliases another struct page. Because sizeof(struct page)==0x40, zeroing the last
byte of the pointer is often enough (succeeds if it was 0x40/0x80/0xC0; if it was
already 0x00 the change is a no-op, no harm done) — no KASLR needed.
Step 3 Page UAF: free the 4 KB physical page (e.g. close one pipe) — buddy allocator
reclaims it, but the aliasing dangling pointer still reads/writes it.
Step 4 Spray: allocate many critical objects (file, cred, ...) so the freed page is
reclaimed as a slab page of the dedicated victim cache.
Step 5 R/W: read/write the whole 4 KB page through the dangling page pointer using the
kernel's struct-page-based interfaces (copy_page_from_iter / copy_page_to_iter).
Somut örnek — CVE-2022-0995 (watch_queue watch_queue_set_filter OOB
__set_bit):
/* OOB set_bit: filter.type is bounds-checked against 0x400 at populate/set_bit
* time but only against 0x80 elsewhere, giving a controlled out-of-bounds bit set. */
__set_bit(q->type, wfilter->type_filter); /* writes past type_filter */
Exploit, komşu bir pipe_buffer->page'in 6. bit'ini flip'leyerek iki pipe_buffer'ın
aynı struct page'i (0xff..40) göstermesini sağlar. Bir pipe'ı kapatmak o physical
page'i free eder (page UAF). /etc/passwd ya da bir SUID binary için struct file spray'lemek
page'i reclaim eder; hayatta kalan pipe_buffer üzerinden yazmak, hedef file->f_mode'unu
writable'a flip'ler → dosyayı düzenle → root.
Neden KASLR bypass gerekmiyor
Step 2'deki corruption'ın yapması gereken tek şey, mevcut, halihazırda geçerli bir kernel
struct page *'ının low byte'(lar)ını truncate ederek onu komşu bir struct page'e
düşürmek — high bit'ler (randomize edilmiş kernel base) zaten orada olan pointer'dan miras
alınır. Attacker'ın absolute address'i bilmesi asla gerekmez, yalnızca bilinen-iyi bir
pointer'ı sizeof(struct page)'in katı kadar dürtmesi yeter.
Mitigation¶
CONFIG_SLAB_FREELIST_RANDOM bunu adreslemez; SLAB_VIRTUAL (bir slab'ın virtual range'ini
başka bir cache için asla yeniden kullanmayarak object-level cross-cache'i engelleyen),
PageJack struct page üzerinden physical page'lerde çalıştığı için açıkça atlatılır. CFI de
durduramaz, çünkü bu data-only bir attack'tir (bir code pointer'ı değil, f_mode'u corrupt
eder). Reclaim'de page ownership'i yeniden doğrulayan ya da freed page'leri buddy reuse'undan
önce scrub/poison eden hardening çıtayı yükseltir.