CVE-2021-1905: Qualcomm Adreno GPU memory mapping use-after-free (exploited in the wild)¶
Qualcomm
kgsldriver'ındaki bir state-tracking hatası, backing page'leri free edildikten sonra bile birstruct kgsl_mem_entry'yi erişilebilir bırakır; bu da in-the-wild bir Android exploit chain'inde kernel LPE primitive'i olarak kullanılan bir use-after-free doğurur.
Mechanism¶
Neden çalışır: 'mapped' flag'i çok erken temizlenen paylaşımlı bir mapping objesi
Qualcomm kgsl GPU driver'ı, userspace'in GPU buffer'larını process
address space'ine map'lemesine izin verir. Her buffer bir
struct kgsl_mem_entry ile takip edilir ve memdesc.useraddr alanı,
buffer'ın o an map'lendiği user virtual address'i kaydeder. Driver, sıfırdan
farklı bir useraddr'ı "bu entry zaten map'li, tekrar map'leme" anlamına
gelen bir flag olarak kullanır.
Bug'ın tetiklenmesi için iki koşulun üst üste gelmesi gerekir:
-
State error. Tek bir GPU buffer, process içinde birden fazla VMA tarafından backing edilebilir — örneğin daha büyük bir mapping parçalara bölündüğünde ya da KGSL file descriptor'ı
fork()üzerinden inherit edildiğinde, böylece birkaç address space aynıkgsl_mem_entry'yi paylaşır. Bu VMA'lardan biri tear down edildiğinde kernelkgsl_gpumem_vm_close()çağırır ve bu da paylaşımlı entry'ninmemdesc.useraddralanını temizler. O alan, entry'yi gösteren tüm VMA'lar tarafından paylaşıldığı için, tek bir VMA için temizlemek diğerleri hakkında yalan söyler: entry hâlâ başka yerde map'li, ama driver artık onun unmap'li olduğuna inanır. -
Race.
useraddryanlışlıkla temizlenmişken, ikinci bir mapping denemesi "zaten map'li mi?" kontrolünden sızabilir. Birden fazla process (örneğin aynı GPU address space'ini paylaşan bir parent ve fork edilmiş bir child) aynıkgsl_mem_entryüzerinde eşzamanlı çalışırkenkgsl_get_unmapped_area()ve onu saranget_mmap_entry()path'i düzgün serialize edilmez. İki thread de (un)mapping'den kendisinin sorumlu olduğuna inanabilir.
Birleşik etki, kgsl_mem_entry için reference counting / mapping
bookkeeping'inin gerçeklikten desync olmasıdır. Bir VMA — ve dolayısıyla
userspace — alttaki page'leri hâlâ map'lerken, ya da başka bir code path
entry'ye hâlâ dangling pointer tutarken, entry release edilip belleği free
edilebilir. Bu klasik bir use-after-free'dir: free edilen object (ya da onun
backing page'leri) başka bir kernel amacı için reallocate edilir ve stale
reference, attacker'ın yeni sakini okumasına veya yazmasına izin verir.
Android'de bu güçlü bir local privilege escalation primitive'idir; çünkü
kgsl device node'u (/dev/kgsl-3d0) untrusted app sandbox'ından özel izin
gerektirmeden erişilebilir.
Güvenlik açığı, Google'ın Threat Analysis Group ve Project Zero ekipleri tarafından in-the-wild exploit edilen bir dizi zero-day'den biri olarak rapor edildi ve Android 2021-05-01 security patch level'ında düzeltildi.
Walkthrough¶
Attack surface, unprivileged GPU device node'udur. Aşağıdaki kavramsal adımlar Project Zero root-cause analysis'ini takip eder; somut offset'ler ve spray hedefleri device'a ve kernel'e özgüdür.
Driver'a app sandbox'ından ulaşma
/* The KGSL device is world-usable on stock Android: */
int fd = open("/dev/kgsl-3d0", O_RDWR);
/* Allocate a GPU buffer via the KGSL ioctl interface, then mmap it so it
gets a struct kgsl_mem_entry with a non-zero memdesc.useraddr. */
void *p = mmap(NULL, len, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, gpu_offset);
Koşul 1'in (state error) tetiklenmesi: tek bir mapping'i, aynı entry'yi paylaşan iki VMA'ya bölüp ardından bunlardan birini kapatarak:
/* Split the single mapping into two adjacent VMAs that both reference the
same struct kgsl_mem_entry (e.g. via an mprotect/partial-munmap that splits
the VMA, or by mapping the same fd offset twice). */
munmap((char *)p + (len / 2), len / 2); /* closes one VMA -> kgsl_gpumem_vm_close() */
/* kgsl_gpumem_vm_close() now clears entry->memdesc.useraddr for the SHARED
entry, even though the first half is still mapped. The entry's "mapped"
flag no longer reflects reality. */
Koşul 2'nin (race) tetiklenmesi: böylece desync olan state bir dangling reference'a dönüşür:
/* Share the GPU address space across a fork so both processes hold the same
kgsl_mem_entry; then race two map/unmap operations through
kgsl_get_unmapped_area()/get_mmap_entry() with no lock protecting the
now-inconsistent useraddr state. The winning interleaving releases the
entry / frees its pages while a VMA still maps them. */
pid_t child = fork(); /* child inherits fd and GPU address space */
if (child == 0) { /* race the mapping path */ ... }
else { /* race the unmap/close path */ ... }
Vulnerable bir build'de beklenen gözlemlenebilir sonuç: free edilen
kgsl_mem_entry (ya da onun backing page'leri) reallocate edilir ve stale
mapping, yeni object'e bir read/write verir. Exploit'ler bunu sonra arbitrary
kernel read/write'a çevirir — örneğin free edilen slab object'ini kontrol
edilebilir bir structure ile reclaim edip page-table veya credential overwrite'a
doğru pivot ederek
(bkz. dirty-pagetable-page-table-data-only-attack.md).
Patch'li bir kernel'da kgsl_gpumem_vm_close(), bir entry'yi paylaşan
birden fazla VMA'yı dikkate alır (paylaşımlı useraddr'ı körlemesine
temizlemez) ve mapping path'i memdesc state'inin etrafına locking ekler;
böylece iki koşula da ulaşılamaz: mmap/munmap tutarlı davranır ve hiçbir
dangling reference üretilmez.
Detection¶
Göstergeler
/dev/kgsl-3d0'ı açıp GPU buffer'ları üzerinde hızlı, iç içe geçmişmmap/munmap/mprotectdöngüleri yapan unprivileged process'ler; özellikle aynı descriptor'ı paylaşan fork edilmiş child'lardan.- KGSL ile ilgili
BUG/KASAN use-after-freeraporları ya dadmesgiçindekgsl_gpumem_vm_close/kgsl_get_unmapped_areaiçindeki oops'lar. - Filolarda device'ların 2021-05-01 (ya da daha yeni) patch level'ı taşıdığından emin olun; daha eski patch level'lar vulnerable'dır.
Mitigation¶
Düzeltme ve hardening
- Android 2021-05-01 security patch level'ını ya da daha yenisini
uygulayın. Düzeltme,
kgsl_gpumem_vm_close()içindeki multi-VMA handling'ini düzeltir vememdescstate'ine locking ekler. - Mümkün olan yerlerde, GPU device node'larına erişimi SELinux üzerinden
kısıtlayın; böylece
kgslioctl/mmap surface'ine yalnızca graphics stack ulaşabilsin — rastgele app'ler değil. - Defense-in-depth allocator/UAF mitigation'ları (testte KASAN, slab hardening, freelist protection), free edilen entry'yi reclaim etmenin maliyetini yükseltir.