Today I read a very interesting blogpost by Brandon Azad from Google Project 0 (https://googleprojectzero.blogspot.com/2020/11/oops-i-missed-it-again.html) , which disclosed an incorrect bound-check in the function
H11ANEInDirectPathClient::externalMethod, allowing to invoke (more privileged)
H11ANEInUserClient’s external methods in the context of (less privileged)
H11ANEInDirectPathClient, eventually leading to type confusions or other security issues.
It is very likely a copy-paste bug. Similar to Brandon, I also missed the vulnerability! It’s kind of unacceptable. Following Brandon’s blogpost, we will share another bug in the
H11ANEIn driver was first introduced in iOS 12 for A12 devices (e.g., iPhone XS and XS Max). I think that “ANE” stands for “Apple Neural Engine”.
H11ANEIn driver offers two types of IOUserClients,
H11ANEInUserClient. In particular,
H11ANEInUserClient is more powerful than
H11ANEInUserClient has more external methods than
H11ANEInDirectPathClient. However, creating
H11ANEInUserClient requires a special entitlement,
com.apple.ane.iokit-user-access. Only a few executables on iOS have the entitlements, such as
Fortunately, the container sandbox allows to open the
H11ANEInDirectPathClient IOUserClient. So third-party apps can open and communicate with
H11ANEInDirectPathClient IOUserClients. Looking at the function
H11ANEInDirectPathClient::externalMethod, (Ok, please forget the incorrect bound-check here), we can find that
H11ANEInDirectPathClient actually has the following interfaces:
Now let’s take a look at the interface
_ANE_ProgramSendRequest accepts a structural input with length 16.
In fact, at the very beginning of function _ANE_ProgramSendRequest, it would treat the structural input as two uint64 values, and pass them into
Clearly, the first value is a userspace address, and the second value is the length of the userspace buffer. After creating the
_ANE_ProgramSendRequest continues to call the
map functions, and map the memory into the kernel space. In other words,
_ANE_ProgramSendRequest creates a shared memory now.
More importantly, the
_ANE_ProgramSendRequest interface should be called through the
IOConnectCallAsyncMethod allows the IOKit extension to asynchronously process requests from the users-pace. By supplying a notification/wakeup port, a user-space program will receive a notification message when the request is done. It implies that the kernel or the extension should store the wakeup port somewhere so that the kernel or the extension can send the notification later.
The vulnerability and exploit
The vulnerability is that,
_ANE_ProgramSendRequest records the notification context, including the port pointer, in the shared memory!
As we can see above, before invoking
H11ANEIn::ANE_ProgramSendRequest, the wakeup port is stored at the shared memory at offset 2616 (note that the offsets vary across iOS kernel versions ).
It’s quite straightforward to convert the vulnerability into an info leak. We can easily get a port pointer in the shared memory after we trigger the execution of
Beyond the info leak, you may have already realized that there are a lot of chances to further exploit the vulnerability, especially for the readers who can still remember the vulnerability exploited by the very first Pangu jailbreak tool (for iOS 7.4.1).
Just a quick recap. At that time,
IOSharedDataQueue stores a mach message header in shared memory. We achieve the kernel exploit by crafting the mach message header (https://googleprojectzero.blogspot.com/2018/10/deja-xnu.html).
Similar here, we can modify the port pointer in the shared memory so that when a notification is to be sent to a fake (arbitrary) port. This blog won’t go to details of the exploit. There are so many different ways to accomplish a kernel exploit when you have a full control to a port pointer.
Things I haven’t mentioned
It wouldn’t be that easy to reproduce the vulnerability, because you will find that
H11ANEIn::ANE_ProgramSendRequest will not deliver a notification to the user-space app unless you have a properly-filled request.
H11ANEInDirectPathClient::ANE_DeviceOpen needs to be executed before
H11ANEIn::ANE_ProgramSendRequest will eventually go to the function
H11ANEIn::ANE_ProgramSendRequest_gated have a common operation: looking up a program buffer according to a program handle id. This program handle id comes from the user input. Without a correct program handle id,
H11ANEIn::ANE_ProgramSendRequest would simply return. No notification would be sent.
So what is the program buffer and the program handle id? Remember that there is another more privileged IOUserClient,
H11ANEInUserClient has the interfaces to create and destroy ane programs and get the program handle ids:
- H11ANEInUserClient:: _ANE_ProgramCreateInstance
However, container apps cannot create
H11ANEInUserClient, what can we do?
I had to spend some time on machine learning. It’s true. I learned how to use the Core ML framework (https://developer.apple.com/documentation/coreml), and how to integrate machine learning models into my app. I tried all the models on the page https://developer.apple.com/machine-learning/models/ .
Finally, I figured out that our app can talk to
aned, and then ask
aned to create a
H11ANEInUserClient and create an ane program. After
aned returns a program handle id to my app, we can continue to develop our exploit. So, if you want to reproduce the vulnerability, learn machine learning first!
Thank you for your time.