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_objects →
kbase_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/mali0açan veKBASE_IOCTL_JIT_INIT+ JIT soft-job'ları KBASE_IOCTL_MEM_FLAGS_CHANGE(KBASE_REG_DONT_NEED)'i ağırmmapgüdümlü memory pressure ve tekrarlananKBASE_IOCTL_MEM_QUERYpolling'i ile birleştiren unprivileged bir app, imza reclaim-race pattern'idir.
Mitigation¶
- Bir JIT region evict/free edildiğinde
jit_allocback-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¶
- GHSL-2022-054: Use-after-free (UAF) in the Arm Mali Kernel driver — CVE-2022-38181
- GitHub Security Lab — Pwning the all Google phone with a non-Google bug (Man Yue Mo)
- github/securitylab — SecurityExploits/Android/Mali/CVE_2022_38181 (PoC)
- Pwning Pixel 6 with a leftover patch — GitHub Security Lab blog
- Arm Vulnerability Leads to Code Execution, Root on Pixel 6 — SecurityWeek