Skip to content

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_ADDR ile KGSL_IOCTL_GPUOBJ_IMPORT
  • memtype = KGSL_MEM_ENTRY_USER ile KGSL_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 + sizegpuaddr'dan küçük olan bir KGSL import/map her zaman kötü niyetlidir; böyle istekleri logla veya -EINVAL ver.
  • 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 ve arm_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.

References