Skip to content

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 cred page'ini cross-cache grooming yapıp uid'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

  1. Birçok child fork'layarak struct cred'i spray'le (fork/cloneprepare_creds() cred_jar'ı doldurur).
  2. PACKET_TX_RING / alloc_pages aracılığıyla order-0 sayfaları grooming yap; buddy allocator onları birleştiremesin diye her ikincisini free et.
  3. Taze bir cred slab page'inin açık veren object page'ine komşu carve edilmesini zorla.
  4. 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
  1. İ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.

References