Qualcomm chips are common in many phones, such as Samsung's and Google Pixels. Many of these devices have a Graphic Processing Unit (GPU) for performing various actions as well for things like shaders. GPUs tend to have proprietary instruction sets, making them hard to attack.
The kernel drivers have some GPU instructions inside of it though, as demonstrated by a
Project Zero post. Using this, it's possible to somewhat understand the instructions of a GPU, but not totally. While playing around with the IOCTL interface for the GPU, the author of the post ran into some bugs. Errors were occurring constantly, unless they put a delay in. Even the opcode looked wrong when viewing the logs. But why is this? Down the rabbit hole!
The write opcode is structured as follows: opcode|gpu address destination|value to write. Since we were getting an error when executing this instruction with the delay, the author didn't know how to debug this. The workaround was to use two buffers instead of one. By doing this, they could see where the inconsistency was occurring at. Since we can read both buffers, this is easy to do.
On the first test of this, it appears that the values were userland addresses with a prefixed 0x7e. The hunch was that the GPU was reading data from the buffer from the previous use. The author attempted to replicate this by filling the buffers with a magic value. To their shock, their magic value appeared in their output! This means there is a major information disclosure big that can be accessed from Android applications.
When writing to memory, the access is done via the CPU and the content is first written to the CPU cache, then propagated to the physical memory later. In modern CPUs, the cache is the same across all of the different cores. However, the GPU access of the physical memory is slightly different, as it doesn't use the cache. This means it could access physical memory that is stale, since the real version is stored in the cache. The author thought this was a cache coherency problem.
The cache should be flushed prior returning this page for use. Viewing the code, the functions appeared to do so. Except, on ARM, some of these functions were not implemented, which resulted in NO-OPs instead of flushing the cache. Even though a page is marked as dirty, the cache is never flushed. Since the call to return the memory on the write never updates the physical memory, this is the vulnerability! Wow, so crazy.
This bug has no limitations - it can leak all memory from the GPU buffers. The caveat is that we are limited by what was written before (luck) and how fast the flush occurs. To me, a cross in the user boundaries between apps on Android is the worst thing possible.
Leaking kernel memory was trickier. This is because the kernel page allocator allocates pages according to their zones and migration types. For exploitation, this means that a user space call to MIGRATE_UNMOVABLE must overlap with a kernel page. After some poking around, they found something that met their requirements. Some other shenanigans and Android kernel specific knowledge later, and they had a complete KASLR bypass by reading specific objects.
Overall, a super crazy and impactful vulnerability that really wasn't that complicated. In fact, it was seen by other people in the past who simply rode it off as a bug. If something seems off, go down the rabbit hole; you may find a crazy vulnerability in plain sight.