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.