Mali GPU MTE bypass kernel exec (CVE-2023-6241)¶
Arm'ın Mali CSF driver'ındaki bir JIT-grow race condition'ı, freed GPU backing page'lerini hâlâ map'li bırakır; GPU fiziksel belleğe doğrudan IOMMU üzerinden eriştiği için ortaya çıkan page UAF, MTE'yi tamamen bypass eder ve MTE-enabled bir Pixel 8 üzerinde arbitrary kernel code execution'a ulaşır.
Mechanism¶
MTE pointer dereference'leri kontrol eder — GPU ise tagged pointer'lara değil, fiziksel belleğe dokunur
Arm'ın Memory Tagging Extension (MTE) mekanizması belleği 16-byte'lık granule'lere boyar ve her pointer'ın üst bitlerinde 4-bit'lik bir tag saklar; dereference sırasında CPU, pointer'ın tag'ini granule'ün tag'iyle karşılaştırıp use-after-free ve overflow durumlarını erişim anında yakalar. Bu koruma temelde CPU pointer dereference'leri ile ilgilidir.
CVE-2023-6241, MALI_USE_CSF (Command Stream Frontend) konfigürasyonunda
JIT memory'deki bir use-after-free'dir. Bug, daha önce freed edilmiş bir JIT
region yeniden kullanılıp physical backing'ini grow etmesi gerektiğinde
kbase_jit_allocate()'tan ulaşılan kbase_jit_grow() içinde yaşar. Page
allocate etmek için kbase_jit_grow, memory pool'u doldururken region
lock'ını geçici olarak bırakır (kbase_gpu_vm_lock / reg lock). Bu pencere
sırasında eşzamanlı bir GPU page fault,
kbase_mmu_page_fault_worker() → page_fault_try_alloc()'u çalıştırır; bu da
aynı region'ı bağımsız olarak grow edip reg->gpu_alloc->nents (committed page
sayısı) değerini değiştirir.
Fonksiyon old_size/delta değerlerini lock'ı bırakmadan önce yakalamıştı,
dolayısıyla devam ettiğinde stale değerler kullanır:
kbase_alloc_phy_pages_helper_locked() ve kbase_mem_grow_gpu_mapping()
tutarsız bir nents üzerinde çalışır. Net etki şudur: bazı physical backing
page'leri freed edilirken GPU address space'ine map'li kalmaya devam eder —
GPU tarafından erişilebilen, page seviyesinde bir UAF.
Bu desync, freed page'lerin GPU virtual mapping'lerinden ayrışmasıyla oluşur: MMU teardown gap'i unmap etmeyi atlar ve free edilmiş fiziksel page'leri hâlâ GPU-accessible bırakır (bkz. use-after-free; MTE as implemented).
MTE'nin neden alakasız olduğu şu: exploit yazarı "by using the GPU to access physical addresses directly, I'm able to completely bypass the protection that MTE offers" ve "while MTE protects against dereferences of pointers against inconsistent memory blocks, the exploit does not rely on any of such dereferencing at all" diyor. GPU/IOMMU, hiçbir MTE tag kontrolü olmadan page table'ları walk eder ve fiziksel belleği okur. Dolayısıyla freed page'ler Mali pool'una geri reclaim edilip Page Global Directory (PGD) olarak dağıtıldığında, GPU job'ları GPU page table'larını yeniden yazar ve arbitrary physical-memory mapping elde eder — kernel R/W ve code execution — MTE'nin yakalayabileceği tagged-CPU dereference'i olmadan.
Walkthrough¶
Exploit, hem kernel hem userspace'te MTE enabled olan, o sırada en güncel ama
hâlâ vulnerable olan November 2023 patch level'ini (Android Security Bulletin
Kasım 2023; build UD1A.231105.004) çalıştıran bir Pixel 8 üzerinde
demonstrate edildi — fix ancak Mali r47p0 (2023-12-14) ve March 2024 ASB ile
geldi (bkz. Detection).
/* 1. Reuse a freed JIT region so kbase_jit_allocate() takes the grow path
* and enters kbase_jit_grow() (which will drop the lock to get pages). */
submit_jit_alloc_softjob(fd, /* id reusing a previously freed region */);
Race, GPU'nun kendi fault handler'ına karşı
İkinci racer başka bir syscall thread'i değil, GPU page-fault worker'ıdır.
kbase_jit_grow pool'u doldurmak için lock'ı bıraktığı sırada, kasıtlı olarak
tetiklenen bir GPU fault, kbase_mmu_page_fault_worker →
page_fault_try_alloc'u sürer ve reg->gpu_alloc->nents'i mutate eder.
Race'i kazanmak, grow mantığının stale old_size/delta ile devam etmesi
demektir.
/* 2. From a second context/thread, fault the SAME region's GPU mapping so the
* fault worker grows it concurrently, desynchronising nents. */
touch_unbacked_gpu_address(region_va); /* -> GPU page fault */
Stale-nents page UAF'tan kernel code execution'a (MTE devrede değil)
kbase_jit_grow(): capture old_size, delta
drop kbase_gpu_vm_lock --------------------+
| race window
GPU fault -> kbase_mmu_page_fault_worker |
-> page_fault_try_alloc: grows region, nents++ |
reacquire lock <-------------------------- +
kbase_alloc_phy_pages_helper_locked() / kbase_mem_grow_gpu_mapping()
use STALE old_size/delta -> pages freed but still GPU-mapped
|
v
Reclaim freed pages into Mali pool -> handed out as a PGD (GPU page table)
|
v
GPU job writes the PGD -> map arbitrary physical pages into GPU space
|
v
Arbitrary kernel R/W -> overwrite kernel code / creds, disable SELinux -> root
Her write GPU/IOMMU üzerinden fiziksel adreslere karşı gerçekleştiği için, MTE (bir CPU pointer-tag mekanizması) fault üretme şansını hiç bulamaz.
Patched bir driver'da (Mali r47p0, public 2023-12-14; Android Security
Bulletin March 2024) beklenen davranış: kbase_jit_grow, lock'ı yeniden
edindikten sonra size input'larını tekrar okur (veya grow boyunca lock'ı tutar),
böylece stale-nents penceresi kapanır ve hiçbir page freed-yet-mapped halde
bırakılmaz.
Detection¶
- Patch level. Mali kbase ≥ r47p0; Pixel 8'de March 2024 Android
security update. Affected konfigürasyon CSF (
MALI_USE_CSF) Valhall / 5.-nesil GPU'lardır. - Behavioral. Aynı region üzerinde JIT alloc/grow soft-job'larını kasıtlı
olarak tetiklenen GPU page fault'larıyla iç içe geçiren — GPU fault worker'ına
karşı race yapan bir userland thread'i — unprivileged
/dev/mali0kullanımı temel sinyaldir; özellikle de bunu, freed page'leri PGD olarak yakalamayı amaçlayan Mali-pool spray'leri takip ettiğinde.
Mitigation¶
kbase_jit_grow'a, stale-nentsrace'ini ortadan kaldıran Arm fix'ini uygula (lock drop boyunca tutarlı page accounting).- Mimari defense in depth: GPU MMU page-table page'lerini (PGD'ler) reclaimable user GPU memory ile paylaşılan bir pool'dan çekme; böylece bir page UAF, page-table-control primitive'ine promote edilemez.
- MTE'nin kapsamını kabul et: DMA/IOMMU yetenekli peripheral'ları kapsamaz. GPU driver'ları için threat model'ler, cihazın MTE-blind fiziksel erişimini varsaymalıdır.