Coordinated Disclosure Timeline

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

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.