Coordinated Disclosure Timeline
- 2023-11-15: Reported issue to Arm security team
- 2023-11-23: Arm security team confirmed the vulnerability and assigned CVE-2023-6241 to the vulnerability
- 2024-03-04: Vulnerability discloses as CVE-2023-6241 and included in Android security bulletin in March.
Summary
GPU memory in the Arm Mali GPU can be accessed after it is freed
Project
Arm Mali
Tested Version
Tested on Pixel 8 with November patch
Details
Freed GPU memory can be accessed (GHSL-2023-224
)
This vulnerability exists with the MALI_USE_CSF configuration. It is similar to one that I reported earlier this year where the size of jit memory gets changed during the call to kbase_jit_grow
. When allocating jit memory, if some previously freed memory is reused, then kbase_jit_grow
is called from kbase_jit_allocate
to grow the memory if necessary. During kbase_jit_grow
, both the kbase_gpu_vm_lock
and mem_partials_lock
are briefly dropped to grow the memory pool (3a and 3b in the following):
static int kbase_jit_grow(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
struct kbase_va_region *reg,
struct kbase_sub_alloc **prealloc_sas,
enum kbase_caller_mmu_sync_info mmu_sync_info)
{
...
if (!kbase_mem_evictable_unmake(reg->gpu_alloc))
goto update_failed;
...
old_size = reg->gpu_alloc->nents; //<---------1.
/* Allocate some more pages */
delta = info->commit_pages - reg->gpu_alloc->nents; //<---------2.
pages_required = delta;
...
while (kbase_mem_pool_size(pool) < pages_required) {
int pool_delta = pages_required - kbase_mem_pool_size(pool);
int ret;
kbase_mem_pool_unlock(pool);
spin_unlock(&kctx->mem_partials_lock); //<---------- 3a
kbase_gpu_vm_unlock(kctx); //<---------- 3b
ret = kbase_mem_pool_grow(pool, pool_delta);
kbase_gpu_vm_lock(kctx);
...
During this time, a gpu page fault can grow the jit memory via the page_fault_try_alloc
call, which can change the jit region.
void kbase_mmu_page_fault_worker(struct work_struct *data)
{
...
page_fault_retry:
/* so we have a translation fault,
* let's see if it is for growable memory
*/
kbase_gpu_vm_lock(kctx);
...
spin_lock(&kctx->mem_partials_lock);
grown = page_fault_try_alloc(kctx, region, new_pages, &pages_to_grow, &grow_2mb_pool,
prealloc_sas);
spin_unlock(&kctx->mem_partials_lock);
In particular, the committed pages (reg->gpu_alloc->nents
) of the region may change, causing inconsistency when old_size
and delta
(1. and 2. in the first code snippet) are used later in kbase_jit_grow
to allocate pages and grow the gpu mapping (4 and 5 in the following):
static int kbase_jit_grow(struct kbase_context *kctx,
const struct base_jit_alloc_info *info,
struct kbase_va_region *reg,
struct kbase_sub_alloc **prealloc_sas,
enum kbase_caller_mmu_sync_info mmu_sync_info)
{
...
gpu_pages = kbase_alloc_phy_pages_helper_locked(reg->gpu_alloc, pool,
delta, &prealloc_sas[0]); //<---- 4.
...
ret = kbase_mem_grow_gpu_mapping(kctx, reg, info->commit_pages,
old_size, mmu_sync_info); //<---- 5.
This can be exploited using methods similar to the one in this and cause freed gpu pages to be accessible.
Impact
This issue can be exploited to allow untrusted applications to gain arbitrary kernel code execution. This can be exploited even on devices with Memory Tagging Extension (MTE) enabled (both kernel and userspace).
CVE
- CVE-2023-6241
Credit
This issue was discovered and reported by GHSL team member @m-y-mo (Man Yue Mo).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2023-224
in any communication regarding this issue.