CVE-2023-33107: Qualcomm Adreno GPU KGSL_IOCTL_GPUOBJ_IMPORT integer overflow (in-the-wild)¶
Adreno KGSL SVM address allocator'ındaki bir unsigned integer overflow, user-supplied bir GPU mapping'in address space'i wrap etmesine izin verir; overlap check'lerini yener ve GPU'ya free edilmiş kernel page'lerine read/write erişimi veren dangling IOMMU page-table entry'leri bırakır.
Mechanism¶
Invariant: gpuaddr + size asla wrap etmemeli
KGSL driver'ı, user buffer'ları GPU'nun IOMMU address space'ine, GPU virtual address'in buffer'ın CPU virtual address'ine eşit olduğu bir "shared virtual memory" (SVM) modeli altında map'ler. Free region'lar bir red-black tree'de izlenir ve allocator'ın doğruluğu tek bir aritmetik invariant'a dayanır:
for a requested mapping at [gpuaddr, gpuaddr + size):
gpuaddr + size > gpuaddr (the end must be strictly above the start)
kgsl_iommu_set_svm_region() ve onun overlap helper'ı, aday bir range'in
mevcut tree entry'leriyle çakışıp çakışmadığını gpuaddr + size <= start
biçimindeki check'lerle test eder. Hem gpuaddr hem de size 64-bit,
user-influenced büyüklükler. gpuaddr + size overflow ederse, hesaplanan
end address start'tan daha küçük olur. Monoton bir [start, end) aralığı
varsayan her overlap karşılaştırması artık yanlış: gerçek span'i tüm address
space olan bir "BOGUS" range, tree mantığına boş veya önemsiz derecede küçük
bir aralık gibi görünür, dolayısıyla seve seve insert edilir.
Wrap'lenmiş bir BOGUS entry bir kez tree'ye oturunca, attacker meşru bir
UAF mapping'iyle gerçekten çakışan bir OVERLAP range'i insert
edebilir, çünkü bozulmuş tree artık çakışmayı reddetmiyor. Asıl mesele
teardown'da: IOMMU map adımı UAF range'inin yalnızca ilk page-table
entry'sini (PTE) siler, sonra __arm_lpae_unmap() range'i dolaşır, zaten
sıfırlanmış ilk PTE'ye çarpar ve erken durur. Kalan PTE'ler artık free
edilmiş physical page'lere işaret eder bırakılır. GPU hâlâ onlara geçerli
IOMMU translation'ları tutarken kernel o page'leri başka allocation'lar için
yeniden kullanır -- ders kitabı page-level use-after-free, ama unprivileged
bir app'ten GPU command submission üzerinden erişilebilir. Bu, kavramsal
olarak page-uaf ve
page-table-manipulation-attack'e
komşu; tek farkı dangling reference'ın CPU'nun MMU'sunda değil GPU'nun
IOMMU'sunda yaşaması.
Walkthrough¶
Bug'a, aynı SVM allocator'a yönlendiren iki IOCTL üzerinden ulaşılabilir:
type = KGSL_USER_MEM_TYPE_ADDRileKGSL_IOCTL_GPUOBJ_IMPORTmemtype = KGSL_MEM_ENTRY_USERileKGSL_IOCTL_MAP_USER_MEM
Kavramsal olarak zafiyetli check şöyle görünüyordu (overlap accept path):
/* msm/kgsl_iommu.c (pre-patch, simplified) */
static bool iommu_addr_in_svm_ranges(struct kgsl_iommu_pt *pt,
u64 gpuaddr, u64 size)
{
u64 end = gpuaddr + size; /* <-- can wrap: end < gpuaddr */
if ((gpuaddr >= pt->compat_va_start && gpuaddr < pt->compat_va_end) &&
(end > pt->compat_va_start && end <= pt->compat_va_end))
return true;
/* ... */
return false; /* wrapped 'end' slips past the range tests */
}
Exploit şekli (in-the-wild RCA'yı yansıtan, üç crafted range):
/* 1. BOGUS: size chosen so gpuaddr + size overflows 64 bits.
* Inserts as a giant/empty interval and corrupts the RB-tree invariant. */
import_addr(fd, /*gpuaddr=*/BOGUS_BASE, /*size=*/-BOGUS_BASE /* wraps */);
/* 2. UAF: a normal, legitimately mapped object we intend to free later. */
int uaf_id = import_addr(fd, UAF_BASE, UAF_SIZE);
/* 3. OVERLAP: range that overlaps UAF; accepted only because the tree is corrupt. */
import_addr(fd, OVERLAP_BASE, OVERLAP_SIZE);
/* 4. Free the UAF object. The IOMMU unmap deletes only the first PTE; the
* remaining PTEs stay valid and point at pages the kernel will recycle. */
free_gpuobj(fd, uaf_id);
Free sonrası GPU hâlâ UAF GPU address'lerini translate eder. Geri dönüştürülmüş kernel memory'ye ulaşmak için o address'leri okuyan/yazan GPU command paket'leri submit et:
/* Spray task_struct (or similar) into the recycled pages, then use GPU
* read packets to scan for it and GPU write packets to corrupt a target
* field (the RCA notes addr_limit / KERNEL_DS-style overwrites on affected
* kernels) -> arbitrary kernel read/write -> root. */
gpu_read_region(uaf_gpuaddr, scratch, len); /* locate victim object */
gpu_write_region(uaf_gpuaddr + off, &val, 8); /* corrupt security field */
Zafiyetli bir cihazda beklenen davranış: import çağrıları overlap'e rağmen
başarılı olur (-EINVAL yok) ve free edilmiş address'leri hedefleyen GPU
command'leri fault vermek yerine reallocate edilmiş kernel page'lerinden canlı
data döner. Patch'lenmiş bir cihazda overlapping/wrapping import reddedilir ve
GPU free edilmiş range üzerinde fault verir.
Detection¶
gpuaddr + size'ıgpuaddr'dan küçük olan bir KGSL import/map her zaman kötü niyetlidir; böyle istekleri logla veya-EINVALver.- Az önce unmap edilmiş address'ler üzerinde
KGSL_IOCTL_GPUOBJ_FREE'den kısa süre sonra gelen IOMMU translation fault'ları dangling PTE'leri gösterir. - Project Zero / Google TAG, sınırlı, hedefli in-the-wild exploitation'a atfetti; CVE-2023-33107, Ekim 2023 aktif-exploit edilen kümede CVE-2023-33106 ve CVE-2023-26083 ile birlikte paketlendi.
Mitigation¶
- Qualcomm'un 2 Ekim 2023 patch'ini uygula. Fix, SVM range check'ine bir
wraparound guard (
if (end <= gpuaddr) return false;) ekler vearm_lpae_init_pte()'nin dönüşünü doğrular, böylece PTE sayımı desenkronize olamaz. - Android security patch level'ını 2023-10-05 veya sonrasında tut.