Coordinated Disclosure Timeline

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

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.