Skip to content

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 etpivot → 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.

References