Coordinated Disclosure Timeline
- 11/16/2021: Reported issue to Qualcomm
- 11/18/2021: Qualcomm confirmed the report and assigned severity to High
- 05/03/2021: Qualcomm publicly disclosed the issue in the May 2022 Security Bulletin as CVE-2022-22057
Summary
There is a use-after-free vulnerability in the Qualcomm kgsl driver.
Product
msm_kernel
Tested Version
v5.4 before May 2022
Details
Issue 1: Use-after-free in Qualcomm kgsl driver (GHSL-2022-037
)
The source code references are to the msm-5.4.r5
branch.
The structure kgsl_timeline
contains a list of kgsl_timeline_fence
objects in kgsl_timeline::fences
. The kgsl_timeline_fence
objects can be added to the fences
using kgsl_timeline_add_fence
. While kgsl_timeline_fence
are dma_fence
objects that are reference counted objects, kgsl_timeline::fences
does not increase the refcount on the kgsl_timeline_fence
objects that it holds. In particular, the kgsl_ioctl_timeline_fence_get
ioctl can be used to create and add kgsl_timeline_fence
to kgsl_timeline::fences
and return a file handle back to the user.
long kgsl_ioctl_timeline_fence_get(struct kgsl_device_private *dev_priv,
unsigned int cmd, void *data)
{
...
fence = kgsl_timeline_fence_alloc(timeline, param->seqno); //<------ allocate and add `fence` to `timeline`, refcount = 1
if (IS_ERR(fence)) {
kgsl_timeline_put(timeline);
return PTR_ERR(fence);
}
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0) {
ret = fd;
goto out;
}
sync_file = sync_file_create(fence); //<--------- `sync_file` increases the refcount of `fence`, refcount = 2
if (sync_file) {
fd_install(fd, sync_file->file);
param->handle = fd; //<--------- file handle returns to user
} else {
put_unused_fd(fd);
ret = -ENOMEM;
}
out:
dma_fence_put(fence); //<------ refcount of `fence` is 1.
kgsl_timeline_put(timeline);
return ret;
}
If the user closes the file descriptor, then the refcount of fence
will decrease to zero, which will trigger timeline_fence_release
:
static void timeline_fence_release(struct dma_fence *fence)
{
struct kgsl_timeline_fence *f = to_timeline_fence(fence);
struct kgsl_timeline *timeline = f->timeline;
struct kgsl_timeline_fence *cur, *temp;
unsigned long flags;
spin_lock_irqsave(&timeline->fence_lock, flags);
/* If the fence is still on the active list, remove it */
list_for_each_entry_safe(cur, temp, &timeline->fences, node) {
if (f != cur)
continue;
list_del_init(&f->node);
break;
}
spin_unlock_irqrestore(&timeline->fence_lock, flags);
trace_kgsl_timeline_fence_release(f->timeline->id, fence->seqno);
kgsl_timeline_put(f->timeline);
dma_fence_free(fence);
}
Although timeline_fence_release
removes fence
from timeline->fences
, there is a time window between the triggering of timeline_fence_release
and its removal from timeline->fences
where another thread may get hold of a copy of fence
from timeline->fences
and use it after dma_fence_free
is called. This will trigger a use-after-free. For example, if another thread calls kgsl_ioctl_timeline_destroy
and the code marked as “a” below is triggered before its removal from timeline->fences
, then a use-after-free will occur:
long kgsl_ioctl_timeline_destroy(struct kgsl_device_private *dev_priv,
unsigned int cmd, void *data)
{
struct kgsl_device *device = dev_priv->device;
struct kgsl_timeline_fence *fence, *tmp;
struct kgsl_timeline *timeline;
struct list_head temp;
u32 *param = data;
if (*param == 0)
return -ENODEV;
spin_lock(&device->timelines_lock);
timeline = idr_find(&device->timelines, *param);
if (timeline == NULL) {
spin_unlock(&device->timelines_lock);
return -ENODEV;
}
/*
* Validate that the id given is owned by the dev_priv
* instance that is passed in. If not, abort.
*/
if (timeline->dev_priv != dev_priv) {
spin_unlock(&device->timelines_lock);
return -EINVAL;
}
idr_remove(&device->timelines, timeline->id);
spin_unlock(&device->timelines_lock);
INIT_LIST_HEAD(&temp);
spin_lock(&timeline->fence_lock); //<----------- a. triggers before timeline_fence_release removes `fence` from `timeline->fences`
list_for_each_entry_safe(fence, tmp, &timeline->fences, node)
dma_fence_get(&fence->base);
list_replace_init(&timeline->fences, &temp);
spin_unlock(&timeline->fence_lock);
//<------------- `timeline_fence_release` tries to remove `fence` from `timeline->fences` but couldn't find it
// it then proceeds to and free `fence`
spin_lock_irq(&timeline->lock);
list_for_each_entry_safe(fence, tmp, &temp, node) { //<------------ `use-after-free`
dma_fence_set_error(&fence->base, -ENOENT);
dma_fence_signal_locked(&fence->base);
dma_fence_put(&fence->base);
}
spin_unlock_irq(&timeline->lock);
kgsl_timeline_put(timeline);
return timeline ? 0 : -ENODEV;
}
The issue can also be triggered using the kgsl_ioctl_timeline_wait
as follows.
In the kgsl_timelines_to_fence_array
function, kgsl_timeline_fence
objects are created and added to a kgsl_timeline
by calling the kgsl_timeline_fence_alloc
function:
struct dma_fence *kgsl_timelines_to_fence_array(struct kgsl_device *device,
u64 timelines, u32 count, u64 usize, bool any)
{
...
for (i = 0; i < count; i++) {
...
fences[i] = kgsl_timeline_fence_alloc(timeline, val.seqno); //<------ kgsl_timeline_fence created and adds to timeline
...
uptr += usize;
}
...
err:
for (i = 0; i < count; i++) {
if (!IS_ERR_OR_NULL(fences[i]))
dma_fence_put(fences[i]); //<-------- this will free fences
}
...
}
However, if there is an error, the created kgsl_timeline_fence
objects will have their references removed, causing the timeline_fence_release
method to be called to free them. Although timeline_fence_release
will remove the kgsl_timeline_fence
from timeline
before freeing them, it is possible that another thread will get hold of a reference of the kgsl_timeline_fence
before timeline_fence_release
removes them, causing a use-after-free. For example, the kgsl_ioctl_timeline_destroy
can race with the kgsl_ioctl_timeline_wait
and get a reference of the kgsl_timeline_fence
created in kgsl_timelines_to_fence_array
(called by kgsl_ioctl_timeline_wait
) before it gets freed by an error path in kgsl_ioctl_timeline_wait
in the follow order of events:
Thread 1: kgsl_ioctl_timeline_wait
kgsl_timelines_to_fence_array
kgsl_timeline_fence_alloc
dma_fence_put
timeline_fence_release
--------------------------------------------
Thread 2: (kgsl_ioctl_timeline_destroy)
list_for_each_entry_safe(fence, tmp, &timeline->fences, node)
dma_fence_get(&fence->base);
------------------------------------------------
Thread 1: (timeline_fence_release)
dma_fence_free(fence)
------------------------------------------------
Thread 2: (kgsl_ioctl_timeline_destroy)
list_for_each_entry_safe(fence, tmp, &temp, node) {
dma_fence_set_error(&fence->base, -ENOENT); //<------ use-after-free
dma_fence_signal_locked(&fence->base);
dma_fence_put(&fence->base);
}
In the above scenario, a use-after-free can happen.
Impact
This vulnerability can be exploited to gain arbitrary kernel read and write, enabling untrusted apps on Android devices to gain root privileges.
Resources and links:
- https://android.googlesource.com/kernel/msm/+/refs/heads/android-msm-coral-4.14-android12/drivers/media/platform/msm/npu/npu_mgr.c#831
- https://android.googlesource.com/kernel/msm/+/refs/heads/android-msm-coral-4.14-android12/drivers/media/platform/msm/npu/npu_mgr.c#688
- https://android.googlesource.com/kernel/msm/+/refs/heads/android-msm-coral-4.14-android12/drivers/media/platform/msm/npu/npu_dev.c#1579
CVE
- CVE-2022-22057
Credit
These issues were 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-2022-037
in any communication regarding these issues.