GPUAF - Using A General GPU Exploit Tech To Attack Pixel8
GPUAF - Using A General GPU Exploit Tech To Attack Pixel8
GPUAF - Using A General GPU Exploit Tech To Attack Pixel8
lwis_ioctl_handle_cmd_pkt:
//…
ret = handle_cmd_pkt(lwis_client, &header, user_msg);
if (ret) {
return ret;
}
user_msg = header.next; ← dead loop
Bug 2
● Integer overflow in prepare_response_locked
Bug 2
● Integer overflow in prepare_response_locked
● transaction->resp was allocated by the overflowed resp_size
Bug 2
● lwis_process_transactions_in_queue will be invoked by another kernel thread,
finally call into process_io_entries to trigger oob:
Bug 2 patch
● Integer overflow in prepare_response_locked
Bug 3
● OOB access in lwis_initialize_transaction_fences
● construct_transaction_from_cmd do init by copy_from_user
Bug 3
● OOB access in lwis_initialize_transaction_fences
● info->trigger_condition.num_nodes is totally controlled by user
Bug 3 patch
● Num_nodes is size_t type, no needs to check for negative number
Bug 3 patch
● k_transaction used right after construct_transaction_from_cmd, sanitize once
is totally enough
Bug 4
● Type confusion in lwis_add_completion_fence
Bug 4 patch
● Structure lwis_fence add a new field called struct_id at the first 4 bytes
Bug 4 patch
● Instead of directly taking the private_data used as fence, it check the struct_id
Bug 5
● Integer overflow bug 2 in prepare_response
Bug 5 patch
● Integer overflow 2 in prepare_response
Bug 6
● Type confusion bug 2 in lwis_trigger_fence_add_transaction
Bug 6 patch
● Type confusion bug 2 in lwis_trigger_fence_add_transaction
Bug 7
● uninit bug in construct_transaction_from_cmd
Bug 7
● num_trigger_fences is an integer type and fetched from kmalloc without
initialization, but under init_all_zero mitigation, it can’t be exploited
Bug 7 patch
● Not sure which commit patch it, but after a merge in android 15 branch, it use
kzalloc to replace kmalloc
Bug 8
● Type confusion bug 3 in lwis_trigger_event_add_weak_transaction
Bug 8
● Type confusion bug 3 in lwis_trigger_event_add_weak_transaction
Bug 8 patch
● Fix is the same, replace the direct fetch by safe lwis_fence_get
Agenda
● Introduction
● Bug analysis
● GPUAF exploit
● Conclusion
GPU Mobile Ecosystem
● MediaTek (Mali)
○ Pixel series, Samsung/Xiaomi/… low end series
● Qualcomm (kgsl)
○ Samsung/Xiaomi/Oppo/Vivo/Honor/… high end series
● Apple (close sourced)
○ iPhone series
GPU mechanisms - memory allocations
● Allocate from gpu driver
● Mali: kbase_api_mem_alloc
● Kgsl: kgsl_ioctl_gpumem_alloc
● Apple: IOGPUDeviceUserClient::new_resource
GPU mechanisms - memory allocations
● Import from CPU’s memory
● Mali: kbase_api_mem_import
● Kgsl: KGSL_MEMFLAGS_USE_CPU_MAP
● Apple: IOGPUDeviceUserClient::new_resource (specify iosurface_id)
GPU mechanisms - Shrinkers
● Recycle the GPU memory
● Mali: kbase_mem_shrink
● Kgsl: kgsl_reclaim_shrinker
● Apple: AGXParameterManagement::checkForShrink
GPU exploits - PUAF
● PUAF (Page Use-after-free) is a strong primitive in exploit
● Many mitigations based on virtual memory (KASLR/Heap isolation/…)
● If we can reuse the memory as kernel objects or even pagetables, we can
easily bypass many mitigations and gain KAARW
● GPU memory objects seems can give us such primitive, and we will dive into
Mali for an example:
GPUAF - Mali
● kbase_va_region represents a GPU memory region, and attributes for CPU/GPU mappings
● Allocate
if (!reg)
return -ENOMEM;
● Free
GPUAF - Mali
● kbase_va_region represents a GPU memory region, and attributes for CPU/GPU mappings
● Allocate
● Free
if (kbase_is_region_invalid_or_free(reg)) {
__func__, gpu_addr);
err = -EINVAL;
goto out_unlock;
}
GPUAF - Mali
● kbase_va_region represents a GPU memory region, and attributes for CPU/GPU mappings
struct kbase_va_region {
// …
struct kbase_mem_phy_alloc *cpu_alloc; // ← phys mem mmap to the CPU when mapping it
struct kbase_mem_phy_alloc *gpu_alloc; // ← phys mem mmap to the GPU when mapping it
// …
}
GPUAF - Mali
● kbase_mem_phy_alloc is physical pages tracking object
struct kbase_mem_phy_alloc {
// …
}
GPUAF - Mali
● kbase_mem_phy_alloc is an elastic object in the general slab cache, size is base + 8 * pages
// …
goto out_unlock;
if (atomic_read(®->cpu_alloc->kernel_mappings) > 0)
goto out_unlock;
// …
} else {
}
GPUAF - “One byte to root them all”
● If we first allocate a native page from GPU, then alias this region, it’s
gpu_mapping field should be 2
● For a memory region allocate in GPU not imported by CPU, the
kernel_mappings is always 0
● Then we overwrite the gpu_mappings to 1 and trigger kbase_mem_commit,
GPU will shrink the page and return it back to mem_pool
● After the page was recycled, we still hold the handler by alias region, thus turn
OOB into PUAF
GPUAF - Mali GPU R/W
● OpenCL
○ A framework for writing programs that execute across heterogeneous platforms consisting of
central processing units (CPUs), graphics processing units (GPUs), digital signal processors
(DSPs), field-programmable gate arrays (FPGAs) and other processors or hardware
accelerators.
○ Specifies programming languages (based on C99, C++14 and C++17) for programming
abovementioned devices
● Reverse engineering the GPU instruction sets
○ https://gitlab.freedesktop.org/panfrost
○ The ioctl for running GPU instructions is KBASE_IOCTL_JOB_SUBMIT
○ Each job contains a header and a payload, and the type of the job is specified in the header
○ MALI_JOB_TYPE_WRITE_VALUE type provides a simple way to write to a GPU address
GPUAF - Mali memory management
● GPU Memory allocate
○ Step 1: allocate from the kctx->mem_pools. If insufficient, goto step 2
○ Step 2: allocate from the kbdev->mem_pools. If insufficient, goto step 3
○ Step 3: allocate from the kernel
● GPU Memory Free
○ Step 1: add the pages to kctx->mem_pools. If full, goto step 2
○ Step 2: add the pages to kbdev->mem_pools. If full, goto step 3
○ Step 3: free the remaining pages to the kernel
GPUAF - Mali post exploit
● Option 1 - Reuse as GPU PGD
struct kbase_mmu_table {
u64 *mmu_teardown_pages[MIDGARD_MMU_BOTTOMLEVEL];
phys_addr_t pgd; // ← Physical address of the page allocated for the top level page table of the context
u8 group_id;
};
GPUAF - Mali post exploit
● Option 1 - Reuse as GPU PGD
static int mmu_get_next_pgd(...) {
p = pfn_to_page(PFN_DOWN(*pgd));
page = kmap(p);
target_pgd = kbdev->mmu_mode->pte_to_phy_addr(page[vpfn]);
if (!target_pgd) {
#if !MALI_USE_CSF
case KBASE_IOCTL_JOB_SUBMIT:
KBASE_HANDLE_IOCTL_IN(KBASE_IOCTL_JOB_SUBMIT,
kbase_api_job_submit,
struct kbase_ioctl_job_submit,
kctx);
break;
#endif /* !MALI_USE_CSF */
GPUAF - Combine together on Pixel 8?
● In this case, we need to use OpenCL for GPU memory read/write
● First we can dlsym needed functions from /vendor/lib64/libOpenCL.so and init our GPU r/w function
from gpu_rw.cl file
GPUAF - Combine together on Pixel 8?
● In this case, we need to use OpenCL for GPU memory read/write
● First we can dlsym needed functions from /vendor/lib64/libOpenCL.so and init our GPU r/w function
from gpu_rw.cl file
● Then we can wrap the gpu r/w in .c code
GPUAF - Combine together on Pixel 8?
● But openCL will introduce another problem, it auto open a new mali fd, but
each fd/kbase_context maintains its own GPU address space and also
manages its own GPU page table.
● If we try to use the mali fd we create write to the memory openCL created, it
will generate page fault in GPU side
● And if we force spray in openCL fd, it will break our heap fengshui and can
not reuse our UAF page as PGD and make us to use option 2
GPUAF - Combine together on Pixel 8?
● But what if we still wanna use option 1?
● Our solution is use a hook.so to hook our openCL functions that setup the
device and reserve our spray pages. In this way, our exploit will work.
● In this way, we reserve pages before openCL corrupt our heap fengshui and
can successfully continue our exploit
GPUAF - Other vendors?
● The memory object itself represents a region of memory and it’s reference
counted object
● Besides causing by shrinker mechanism, we can also use krefs to achieve
PUAF
● Qualcomm Adreno /PowerVR GPU should have similar memory object as
Mali GPU
GPUAF - Where is MTE?
● Except for the first OOB for PUAF, the whole exploit didn’t touch MTE, we
take use of the legitimate shrinker mechanism to get PUAF
● For the first OOB, even if detected, it will throw a KASAN in dmesg and stop
our exploit flow other than panic
● And the chance of detecting the OOB is low from the test, less than 50%
● Which means we just run it twice at most, it will give us the root shell and
clean the warning in dmesg
Demo
Demo
Agenda
● Introduction
● Bug analysis
● GPUAF exploit
● Conclusion
Conclusions
● Mitigations sometimes hard, but it might be weak from another level, think
outside the box and defeat mitigations by abusing features
● Targets not only can have vulns but also can be part of exploit path
● With more and more software/hardware mitigations, exploit with one bug is
harder, but with good exploit tech, it’s still possible
References
● Root Cause Analyses | 0-days In-the-Wild
● corrupting-memory-without-memory-corruption
● MTE As Implemented, Part 1: Implementation Testing
● Make KSMA Great Again: The Art of Rooting Android devices by GPU MMU
features
● Towards the next generation of XNU memory safety: kalloc_type - Apple
Security Research
● https://github.com/thejh/linux/commit/bc52f973a53d0b525892088dfbd251bc9
34e3ac3
● Racing Against the Lock: Exploiting Spinlock UAF in the Android Kernel
Q&A
Thanks for listening