CVE-2022-46702: Remember to Clean Up the Memory
As I tinkered with my first iPhone and jailbroke it, I was struck by the endless possibilities for customization and exploration. That’s when my interest in security research truly took off. I immersed myself in the study of reverse engineering and even became an iOS tweak developer. My curiosity and passion for software security only continued to grow as I delved deeper into this field. And I am excited to share the details of my first CVE, CVE-2022-46702: a kernel information leak in the GPU driver. This discovery has been a highlight of my security research career so far, and I can’t wait to see what other exciting challenges and opportunities lie ahead.
IOKit
Before we start, I’d like to go through the concept of IOKit briefly. Since there are many detailed documents about IOKit, I will only cover the basic idea of it here.
IOKit is a collection of low-level frameworks, libraries, tools, and other things for developing drivers in macOS, iOS, iPadOS, and so on.
In userland, we can interact with IOKit kernel extensions with their user clients. You need to get a “handle” of it first with IOServiceOpen()
, which will return a Mach Port. User clients can expose the interface to userland with external methods, and in most of cases, the external methods will be stored in an external methods table. Then just like ioctl()
, you can use IOConnectCallMethod()
to call the external methods implemented in user clients.
1 | kern_return_t |
Apple also provides some variants, such as IOConnectCallAsyncMethod()
, and they share the same idea.
Dispatch table
struct IOExternalMethodDispatch
is used to manage the implemented external methods.
1 | typedef IOReturn (*IOExternalMethodAction)(OSObject * target, void * reference, |
function
is a function pointer to an external method, and the following fields describe the input and output data.
The kernel info leak
The vulnerability is in the GPU driver of iOS. AGXDeviceUserClient::performanceCounterSamplerControl()
handles many commands about sampling. In command 0xF
1 | __int64 __fastcall AGXDeviceUserClient::performanceCounterSamplerControl( |
Notice that
1 | v32 = AGXShared::createMappedBuffer( |
Pointer v32
was then copied to *(_QWORD *)(structInput + 16)
. structInput
was passed to a1->IOGPUDevice.io_gpu->AGXAccelerator.pref_ctl->vtable->AGXPerfCtrSamplerGen11.processControlCommand)();
as an argument.
When the function return,
1 | if ( v34 ) |
*(_OWORD *)(structInput + 16);
was then assigned to structOutput
. So we can directly retrieve a kernel pointer in userland.
Exploitation
The exploitation is pretty straightforward. To exploit this vulnerability in iOS 16, you need an entitlement first. But in iOS 14, we can invoke this method without special entitlement.
1 | if ( IOGPUDevice::doesEntitlementExist(io_gpu_device, "com.apple.private.agx.performance-spi") |
1 | io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, |
Simply open the user client first.
1 | kr = IOConnectCallStructMethod(conn, getindex_method_start + 5, struct_input, sizeof(struct_input), struct_output, &struct_output_size); |
Then enjoy your kernel pointer.