Skip to content

CVE-2021-1905: Qualcomm Adreno GPU memory mapping use-after-free (exploited in the wild)

Qualcomm kgsl driver'ındaki bir state-tracking hatası, backing page'leri free edildikten sonra bile bir struct 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:

  1. 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 kernel kgsl_gpumem_vm_close() çağırır ve bu da paylaşımlı entry'nin memdesc.useraddr alanı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.

  2. Race. useraddr yanlış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ışırken kgsl_get_unmapped_area() ve onu saran get_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);
Buffer, sandbox'tan erişilebilir olduğu için, tüm trigger sıradan, unprivileged bir process olarak çalışır.

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/mprotect döngüleri yapan unprivileged process'ler; özellikle aynı descriptor'ı paylaşan fork edilmiş child'lardan.
  • KGSL ile ilgili BUG/KASAN use-after-free raporları ya da dmesg içinde kgsl_gpumem_vm_close / kgsl_get_unmapped_area iç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 ve memdesc state'ine locking ekler.
  • Mümkün olan yerlerde, GPU device node'larına erişimi SELinux üzerinden kısıtlayın; böylece kgsl ioctl/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.

References