Coordinated Disclosure Timeline

  • 16/07/2020 Reported to Android security team as Issue 161467620, later assigned Android ID 161544755
  • 20/07/2020 Realised it was a Qualcomm issue and asked if I should report directly it to Qualcomm instead, but was told not to.
  • 22/07/2020 Was told the ticket was forwarded to Qualcomm.
  • 11/08/2020 Was told that Qualcomm had successfully reproduced the issue.
  • 01/01/2021 Fixed in January bulletin as CVE-2020-11239 and Android ID A-168722551. No credit given.
  • 05/01/2021 As both the reported date from Qualcomm (26/07/2020) and the Android ID from the bulletin (168722551) indicated that we reported the issue before the one acknowledged in the bulletin, I asked if Android security team can confirm that it is the case.
  • 05/01/2021 Android security team responded saying that they would investigate.
  • 25/01/2021 Android security team confirmed that I will be acknowledged as the original reporter of the issue.

Summary

Use-after-free in kgsl_ioctl_gpuobj_import and kgsl_ioctl_map_user_mem of the Qualcomm kgsl driver

Product

msm kernel

Tested Version

Pixel 4 QQ3A.200705.002 build

Details

The code references here is in the coral-4.14 branch of the kernel. The use-after-free issue seems to only affect this branch (after the new ION ABI is introduced in 4.12). In the kgsl_ioctl_gpuobj_import function, if the parameter type is set to KGSL_USER_MEM_TYPE_ADDR, then the function _gpuobj_map_useraddr will be used to create the memory mapping [1]. This function will call kgsl_setup_useraddr [2] and try to create a mapping with dma first [3]. If a valid dma buffer is found, it will then use it to create the mapping, and attach the dma buffer to the device [4], [5]

static int kgsl_setup_dma_buf(struct kgsl_device *device,
                                struct kgsl_pagetable *pagetable,
                                struct kgsl_mem_entry *entry,
                                struct dma_buf *dmabuf)
{
        int ret = 0;
        struct scatterlist *s;
        struct sg_table *sg_table;
        struct dma_buf_attachment *attach = NULL;
        struct kgsl_dma_buf_meta *meta;
        meta = kzalloc(sizeof(*meta), GFP_KERNEL);
        if (!meta)
                return -ENOMEM;
        attach = dma_buf_attach(dmabuf, device->dev);  //<------- a.
    ...
        sg_table = dma_buf_map_attachment(attach, DMA_TO_DEVICE);
    ...
        meta->table = sg_table;
        entry->priv_data = meta;
        entry->memdesc.sgt = sg_table;           //<------- b.

This will create a sg_table for the attachment by duplicating the one from the dma_buf (a. and [6], see below)

The ion implementation of dma_buf_attach in (a.) is as follows:

static int ion_dma_buf_attach(struct dma_buf *dmabuf, struct device *dev,
                                struct dma_buf_attachment *attachment)
{
    ...
        table = dup_sg_table(buffer->sg_table);
    ...
        a->table = table;                          //<---- c. duplicated table stored in attachment, which is the output of dma_buf_attach in a.
    ...
        mutex_lock(&buffer->lock);
        list_add(&a->list, &buffer->attachments);  //<---- d. attachment got added to dma_buf::attachments
        mutex_unlock(&buffer->lock);
        return 0;
}

This stores the duplicated table in an ion_dma_buf_attachment as raw pointer, while at the same time, the table is also stored in entry->memdesc.sgt in (b) and the ion_dma_buf_attachment is also stored in the attachment list of dma_buf.

If the ioctl call then failed at the kgsl_mem_entry_attach_process call, it will go to unmap [7]
unmap:
        if (param->type == KGSL_USER_MEM_TYPE_DMABUF) {
                kgsl_destroy_ion(entry->priv_data);
                entry->memdesc.sgt = NULL;
        }
        kgsl_sharedmem_free(&entry->memdesc);  //<---- deletes |table| in c.

As param->type in this case is KGSL_USER_MEM_TYPE_ADDR, kgsl_destroy_ion will not be called, which means that the dma_buf will remain attached to the gpu, and more importantly, the ion_dma_buf_attachment created in ion_dma_buf_attach will remain in the attachment list of the buffer. Now kgsl_sharedmem_free that follows will delete memdesc.sgt, which is the same as table in (c):

void kgsl_sharedmem_free(struct kgsl_memdesc *memdesc)
{
    ....
        if (memdesc->sgt) {
                sg_free_table(memdesc->sgt);
                kfree(memdesc->sgt);
        }
    ...
}

After this point, the dma_buf, which the user holds a reference to and can call its ioctl at any time, will contain a reference to an ion_dma_buf_attachment in its attachments list, and this attachment holds a reference to a free’d sg_table. A use-after-free can then be triggered, for example, by using the DMA_BUF_IOCTL_SYNC ioctl call on this dma_buf, which is implemented by __ion_dma_buf_begin_cpu_access:

static int __ion_dma_buf_begin_cpu_access(struct dma_buf *dmabuf,
                                          enum dma_data_direction direction,
                                          bool sync_only_mapped)
{
    ...
        list_for_each_entry(a, &buffer->attachments, list) {
        ...
                if (sync_only_mapped)
                        tmp = ion_sgl_sync_mapped(a->dev, a->table->sgl,        //<--- use-after-free of a->table
                                                  a->table->nents,
                                                  &buffer->vmas,
                                                  direction, true);
                else
                        dma_sync_sg_for_cpu(a->dev, a->table->sgl,              //<--- use-after-free of a->table
                                            a->table->nents, direction);
            ...
                }
        }
...
}

The kgsl_ioctl_map_user_mem system call also has a similar problem.

CVE

  • CVE-2020-11239

Impact

Can be exploited from the application sandbox to achieve arbitrary kernel code execution in many devices.

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 the GHSL-2020-375 in any communication regarding this issue.