This article provides an in-depth analysis of two kernel vulnerabilities within the Mali GPU, reachable from the default application sandbox, which I independently identified and reported to Google.
It includes a kernel exploit that achieves arbitrary kernel r/w capabilities. Consequently, it disables SELinux and elevates privileges to root on Google Pixel 7 and 8 Pro models running the following Android 14 versions:
google/husky/husky:14/UD1A.231105.004/11010374:user/release-keys
google/cheetah/cheetah:14/UP1A.231105.003/11010452:user/release-keys
google/cheetah/cheetah:14/UP1A.231005.007/10754064:user/release-keys
This exploit leverages two vulnerabilities: an integer overflow resulting from an incomplete patch in the gpu_pixel_handle_buffer_liveness_update_ioctl
ioctl command, and an information leak within the timeline stream message buffers.
Google addressed an integer overflow in the gpu_pixel_handle_buffer_liveness_update_ioctl
ioctl command in this commit.
At first, when I reported this issue, I thought the bug was caused by an issue in the patch described earlier.
After reviewing the report, I came to the realization that my analysis of the a vulnerability was inaccurate. Despite my first assumption of the patch being incomplete, it effectively resolves and prevents an underflow in the calculation.
This lead me to suspect that the change wasn’t applied in the production builds. However, although I can cause an underflow in the calculation, it is not possible to cause an overflow.
This suggests that the ioctl command has been partially fixed, although not with the above patch shown above.
Looking at IDA revealed that another incomplete patch was shipped in the production releases, and this patch is not present in any git branch of the mali gpu kernel module.
This vulnerability was first discovered in the latest Android version and reported on November 19, 2023.
Google later informed me that they had already internally identified it and had assigned it CVE-2023-48409 in the December Android Security Bulletin, labeling it as a duplicate issue.
Although I was able to verify that the bug had been internally identified months prior to my report, (based on the commit date around August 30) there remains confusion.
Specifically, it’s strange that the Security Patch Levels (SPL) for October and November of the most recent devices were still affected by this vulnerability —I haven’t investigated versions prior to these.
Therefore, I am unable to conclusively determine whether this was truly a duplicate issue and if the appropriate patch was indeed scheduled for December prior to my submission or if there was an oversight in addressing this vulnerability.
Anyway, what makes this bug powerful is the following:
info.live_ranges
is fully user-controlled.info.live_ranges
pointer can be at an arbitrary offset prior to the start of the buff
kernel address.This vulnerability shares similarities with the DeCxt::RasterizeScaleBiasData() Buffer underflow vulnerability I found and exploited in the iOS 15 kernel back in 2022.
The GPU Mali implements a custom timeline stream
designed to gather information, serialize it, and subsequently write it to a ring buffer following a specific format.
Users can invoke the ioctl command kbase_api_tlstream_acquire
to obtain a file descriptor, enabling them to read from this ring buffer. The format of the messages is as follows:
__kbase_tlstream_tl_kbase_kcpuqueue_enqueue_fence_wait
function serializes the kbase_kcpu_command_queue
and dma_fence
kernel pointers into the message buffer, resulting in leaking kernel pointers to user space process.void __kbase_tlstream_tl_kbase_kcpuqueue_enqueue_fence_wait(
struct kbase_tlstream *stream,
const void *kcpu_queue,
const void *fence
)
{
const u32 msg_id = KBASE_TL_KBASE_KCPUQUEUE_ENQUEUE_FENCE_WAIT;
const size_t msg_size = sizeof(msg_id) + sizeof(u64)
+ sizeof(kcpu_queue)
+ sizeof(fence)
;
char *buffer;
unsigned long acq_flags;
size_t pos = 0;
buffer = kbase_tlstream_msgbuf_acquire(stream, msg_size, &acq_flags);
pos = kbasep_serialize_bytes(buffer, pos, &msg_id, sizeof(msg_id));
pos = kbasep_serialize_timestamp(buffer, pos);
pos = kbasep_serialize_bytes(buffer,
pos, &kcpu_queue, sizeof(kcpu_queue));
pos = kbasep_serialize_bytes(buffer,
pos, &fence, sizeof(fence));
kbase_tlstream_msgbuf_release(stream, acq_flags);
}
The proof of concept exploit leaks the kbase_kcpu_command_queue
object address by monitoring to the message id KBASE_TL_KBASE_NEW_KCPUQUEUE
which is dispatched by the kbasep_kcpu_queue_new
function whenever a new kcpu queue object is allocated.
Google informed me that the vulnerability was reported in March 2023 and was assigned CVE-2023-26083 in their security bulletin.
Nonetheless, I was able to replicate the issue on the latest Pixel devices shipped with the Security Patch Levels (SPL) for October and November, indicating that the fix had not been applied correctly or at all.
Subsequently, Google quickly addressed the issue in the December Security Update Bulletin without offering credit, and later informed me that the issue was considered a duplicate.
The rationale behind labeling this issue as a duplicate, however, remains questionable.
So I have two interesting vulnerabilities. The first one offers a powerful capability to modify the content of any 16-byte aligned kernel address that comes before the allocated address.
The second vulnerability provides hints into the potential locations of objects within the kernel memory.
With total control over the buffer_count
and live_ranges_count
fields, I have the flexibility to select the target slab and the precise offset I intend to write to.
However, selecting values for buffer_count
and live_ranges_count
requires careful consideration due to several constraints and factors:
0x3004
, the live_ranges
pointer would be set to -0x4000
bytes from the buff
object’s allocated space. The copy_from_user
function would then write 0x7004
bytes, based on the calculation of update->live_ranges_count
times 4. Consequently, this operation would result in user-controlled data overwriting the memory area between the live_ranges
pointer and the buff
allocation. It is essential, therefore, to carefully ensure that no critical system objects within that range are accidentally overwritten. Given that the operation involves a copy_from_user
call, one might consider triggering an EFAULT
by deliberately un-mapping the undesired memory region following the user source buffer to prevent data from being written to sensitive locations. However, this approach is ineffective, that’s because if the raw_copy_from_user
function fails, it will zero out the remaining bytes in the destination kernel buffer. This behavior is implemented to ensure that in case of a partial copy due to an error, the rest of the kernel buffer does not contain uninitialized data.static inline __must_check unsigned long
_copy_from_user(void *to, const void __user *from, unsigned long n)
{
unsigned long res = n;
might_fault();
if (!should_fail_usercopy() && likely(access_ok(from, n))) {
instrument_copy_from_user(to, from, n);
res = raw_copy_from_user(to, from, n);
}
if (unlikely(res))
memset(to + (n - res), 0, res);
return res;
}
For more information click here
Shadow Dumper is a powerful tool used to dump LSASS (Local Security Authority Subsystem Service)…
shadow-rs is a Windows kernel rootkit written in Rust, demonstrating advanced techniques for kernel manipulation…
Extract and execute a PE embedded within a PNG file using an LNK file. The…
Embark on the journey of becoming a certified Red Team professional with our definitive guide.…
This repository contains proof of concept exploits for CVE-2024-5836 and CVE-2024-6778, which are vulnerabilities within…
This took me like 4 days (+2 days for an update), but I got it…