Six-byte cross-cache overflow against cred¶
Küçük (6-byte) bir out-of-bounds write'ı, açık veren object'e komşu bir
struct credpage'ini cross-cache grooming yapıpuid'inin düşük byte'larını sıfırlayarak root'a çevir — leak'siz, data-only bir escalation.
Mechanism¶
Modern kernel'ler struct cred'i kendi cred_jar cache'inde izole eder ve hardened bir challenge object'i de kendi dedicated cache'inde yaşar; dolayısıyla bug'dan bir cred üzerine same-cache overflow imkânsızdır. Ancak her iki cache de nihayetinde buddy allocator'dan order-0 sayfalar çeker.
Note
Exploit, page allocator'ı taze bir cred slab page'i açık veren object page'ine fiziksel olarak komşu düşecek şekilde grooming yapar (order-0 sayfalar allocate et, buddy coalescing'i önlemek için bir aralı olanları free et, sonra yeni bir cred slab'ını zorla). 6-byte'lık bir overflow, SLUB freelist metadata'sını forge etmek için fazlasıyla küçüktür (FREELIST_HARDENED, randomization ve mid-object freelist pointer'larıyla yenilir); dolayısıyla tek faydalı komşu hedef, komşu bir cred'in en başındaki privilege field'larıdır. struct cred, bu exploit'in hedeflediği kernel ailesinde 4-byte usage refcount'uyla (eski atomic_t/refcount_t) başlar, sonra uid, gid gelir (aşağıdaki version-scope note'una bkz.). Write ilk 4 byte'ı 1'e (makul bir refcount, böylece cred free edilmez) ve sonraki 2 byte'ı 0'a ayarlar — uid'in düşük yarısını sıfırlar. Gerçek uid'ler ≤ 65535 olduğundan bu uid 0'ı üretir: root.
Version-scope: usage field boyutu
Bu layout, usage'ın 4-byte olduğu (eski atomic_t/refcount_t) kernel'lere özgüdür — 6-byte write'ın ilk 4 byte'ı usage'ı kaplar, kalan 2 byte komşu uid'in düşük yarısına ulaşır (referans verilen 2022 corCTF/willsroot exploit'lerinin hedefi bu kernel ailesidir). Modern kernel'lerde (≥6.3) usage atomic_long_t yani 8-byte'tır (krş. cred-struct-overwrite); orada düz bir 6-byte head write yalnızca usage'ı kirletir ve uid'e ulaşmaz, dolayısıyla bu byte-pattern olduğu gibi taşınmaz.
Bu leak'siz ve data-only'dir — KASLR break yok, arbitrary R/W yok, ROP yok, commit_creds yok. Bir child task basitçe kendini root olarak gözlemler.
Walkthrough¶
- Birçok child fork'layarak
struct cred'i spray'le (fork/clone→prepare_creds()cred_jar'ı doldurur). PACKET_TX_RING/alloc_pagesaracılığıyla order-0 sayfaları grooming yap; buddy allocator onları birleştiremesin diye her ikincisini free et.- Taze bir cred slab
page'inin açık veren objectpage'ine komşu carve edilmesini zorla. - 6-byte'lık overflow'u açık veren object'ten komşu cred'in başına tetikle:
cred: [ usage(4) ][ uid(4) ][ gid(4) ] ...
write: 01 00 00 00 00 00 <-- usage=1, low 2 bytes of uid=0 => uid 0
- İlgili fork'lanmış child artık uid 0'a sahiptir;
setresuid/bir root shell exec et.
Beklenen sonuç: sakin bir sistemde uid 0'lı bir child process, hiçbir info leak gerektirmeden.
Detection¶
cred->uid'i herhangi bir setuid/commit_creds call path'i olmadan 0 olan bir child ya da açıklanamayan cred refcount değerleri anormaldir; cred field'ları yüksek değerli bir integrity-monitoring hedefidir. Unprivileged bir task'tan order-0 page grooming patlamaları artı PACKET_TX_RING allocation'ları kaba bir sinyaldir.
Mitigation¶
Cred allocation'larını pin'le/randomize et ve cred sayfalarını koru (ör. SLAB_VIRTUAL-tarzı page pinning, önerildi ama mainline değil); böylece free edilmiş slab sayfaları cred'e komşu olacak şekilde yeniden tiplendirilemez; cred field'larında usercopy/integrity check'leri; unprivileged task'lara açık olan page-allocator determinizmini azaltmak.