Skip to content

Mali GPU IOMMU race (CVE-2022-38181)

Arm'ın Mali kbase driver'ında artık kalan bir JIT-eviction use-after-free, freed bir GPU memory region'ının backing page'lerinin GPU page-table directory'leri olarak yeniden kullanılmasına izin verir ve page-level bir UAF'yi Pixel 6'da arbitrary physical-memory access ile kernel code execution'a çevirir — klasik bir control-flow hijack olmadan.

Mechanism

Reclaim'den sağ çıkan bir dangling reference, sonra page table olarak yeniden kullanılan bir page

Arm'ın Mali "kbase" driver'ı JIT (Just-In-Time) memory sunar: driver'ın memory pressure altında büyütebildiği, küçültebildiği ve evict edebildiği GPU allocation'ları. Her JIT region bir kbase_va_region'dır ve dağıtıldığında driver, per-context array kctx->jit_alloc[info->id]'de bir back-reference tutar. JIT region'ları üç list'te yaşar: jit_active_head (kullanımda), jit_pool_head (yeniden kullanılabilir) ve jit_destroy_head (silinmeyi bekleyen).

Kırılan invariant basittir: bir nesneye olan her pointer, o nesne free edilmeden önce temizlenmelidir. Kernel-lifecycle için işaretlenmiş JIT region'ları (KBASE_REG_NO_USER_FREE) normalde yalnızca kontrollü kbase_jit_free path'i üzerinden free edilmelidir. Bir app, bir JIT region'ı KBASE_IOCTL_MEM_FLAGS_CHANGE ile (KBASE_REG_DONT_NEED ayarlayarak) evictable işaretleyebilir, bu da onu — kontrollü cleanup'ı çalıştırmadan — eviction list'ine koyar. Bu, daha önce eksik yamalanmış bir sorunun kalıntısıdır (the "leftover patch"), dolayısıyla Google'ın amiral gemisinde "Google'a ait olmayan bir bug". Direct reclaim altında shrinker kbase_mem_evictable_reclaim_scan_objectskbase_jit_backing_lost'u çağırır, ki bu region'ın backing page'lerini free eder. Ama JIT memory allocate edildiğindeki jit_alloc'taki reference kalıcıdır — dangling bir pointer "clean-up logic'ine karşı hâlâ dayanır." Bir sonraki kbase_jit_free sonra backing'i çoktan reclaim edilmiş bir region üzerinde işlem yapar.

Leak edilen memory GPU memory'si olduğu için, exploitation asla bir kernel nesnesi sahtelemeye ya da control flow hijack etmeye ihtiyaç duymaz. Freed backing page'leri UAF target'ıdır. Eğer o physical page'ler Mali memory pool'una geri reclaim edilir ve cihazın IOMMU/MMU'sunun yürüdüğü GPU'nun kendi page table'ları olan Page Global Directory'ler (PGD'ler) olarak dağıtılırsa, stale mapping'e hâlâ referans veren herhangi bir GPU job page table'ların kendisine yazabilir. Bu, page-level bir UAF'yi GPU address space'ine arbitrary physical-page mapping'e, yani arbitrary kernel read/write'a çevirir ve klasik anlamda corrupt bir return address ya da function pointer olmadığı için CFI tarzı mitigation'ları yener.

Walkthrough

Bug, bir /dev/mali0 fd'si tutan unprivileged bir Android app'inden tamamen ulaşılabilir. Zor kısım, dangling jit_alloc reference'ı geride bırakılacak şekilde reclaim'i zamanlamak, sonra freed page'leri yakalamak.

/* 1. Allocate a JIT region; the driver records it in kctx->jit_alloc[id]. */
ioctl(fd, KBASE_IOCTL_JIT_INIT, &jit_init);
/* JIT allocations are requested via a GPU "soft job" (KBASE_JD_REQ_SOFT_JIT_ALLOC). */

/* 2. Mark the region evictable so reclaim can free its backing pages. */
struct kbase_ioctl_mem_flags_change chg = {
    .gpu_va = region_va,
    .flags  = KBASE_REG_DONT_NEED,   /* -> placed on the evict list */
    .mask   = KBASE_REG_DONT_NEED,
};
ioctl(fd, KBASE_IOCTL_MEM_FLAGS_CHANGE, &chg);

/* 3. Apply memory pressure (large mmap churn) to drive direct reclaim, which
 *    calls kbase_mem_evictable_reclaim_scan_objects -> kbase_jit_backing_lost
 *    and frees the backing pages -- while jit_alloc[id] still points at it. */
trigger_direct_reclaim_via_mmap();

Exploit, timing'i tahmin etmek yerine her adımı bir oracle ile doğrular:

Race'i sleep ile değil, polling ile kazan

Man Yue Mo, her allocation'dan sonra "JIT region'ın kbase_mem_evictable_reclaim_scan_objects tarafından free edilip edilmediğini kontrol etmek için" KBASE_IOCTL_MEM_QUERY kullandı — dangling state'e ulaşıldığına dair deterministic bir sinyal, saf bir timing race'in güvenilmezliğini ortadan kaldırır.

Freed page'leri yakalamak fake-object forgery'den tamamen kaçınır:

Meşru bir region'ı yeniden kullan, sonra backing page'leri mapped tutmak için alias'la
/* 4. Replace the freed kbase_va_region with a *real* one from a normal
 *    allocation -- no forged kernel struct needed. */
ioctl(fd, KBASE_IOCTL_MEM_ALLOC, &replacement);

/* 5. Alias the replacement so the (now-freed) backing pages stay GPU-mapped
 *    even after kbase_jit_free tears down the primary mapping. */
ioctl(fd, KBASE_IOCTL_MEM_ALIAS, &alias);

/* 6. Free the JIT region (kbase_jit_free) via the dangling reference.
 *    The physical backing pages are now free *and* still GPU-reachable. */

/* 7. Spray Mali memory-pool allocations so a freed page is handed back out
 *    as a GPU Page Global Directory (PGD). The stale GPU mapping now points
 *    *into the GPU page tables*. */

/* 8. Submit a GPU job that writes through the stale mapping to edit the PGD,
 *    mapping arbitrary physical pages into GPU space -> arbitrary kernel R/W. */

Arbitrary physical access ile son aşama, kernel code / credential'ları overwrite eder ve root'a inmek için SELinux'u devre dışı bırakır — "hiçbir control-flow hijacking dahil değil," dolayısıyla kernel CFI yardımcı olmaz.

Patch'lenmiş bir driver'da (≥ r40p0, Pixel 6 Ağustos 2022 update'i) beklenen sonuç: jit_alloc reference'ı eviction'da düzgün temizlenir, dolayısıyla kbase_jit_free geçerli bir region üzerinde işlem yapar ve UAF penceresi hiç açılmaz.

Detection

  • Patch level. Mali kbase ≥ r40p0 (fix, JIT/no-user-free region'larda flag değişikliklerini reddederek KBASE_REG_NO_USER_FREE'yi korur); Pixel 6 / 6 Pro'da Ağustos 2022 Android security update'i. Eski firmware'de takılı cihazlar (birçok Fire/FireTV ve bütçe Android SoC'leri) exploit edilebilir kaldı, bu yüzden public PoC'ler geniş çapta port edildi.
  • KEV maruziyeti. CVE-2022-38181 CISA Known Exploited Vulnerabilities kataloğundadır; fix'leyen Android Security Patch Level altında olup bilinmeyen app çalıştıran cihazlar temel maruziyettir.
  • Davranışsal. /dev/mali0 açan ve KBASE_IOCTL_JIT_INIT + JIT soft-job'ları
  • KBASE_IOCTL_MEM_FLAGS_CHANGE (KBASE_REG_DONT_NEED)'i ağır mmap güdümlü memory pressure ve tekrarlanan KBASE_IOCTL_MEM_QUERY polling'i ile birleştiren unprivileged bir app, imza reclaim-race pattern'idir.

Mitigation

  • Bir JIT region evict/free edildiğinde jit_alloc back-reference'ını temizleyen Arm fix'ini uygula, dangling-pointer penceresini kapat.
  • Defense in depth: platform izin verdiği yerde güvenilmeyen app domain'lerinden GPU device-node erişimini kısıtla; GPU MMU/IOMMU page-table page'lerinin reclaimable user GPU memory ile paylaşılan bir pool'dan çekilmediğinden emin ol.

References