iOS/macOS - Out-of-Bounds Timestamp Write in IOAccelCommandQueue2::processSegmentKernelCommand()







Become a Certified Penetration Tester

Enroll in Penetration Testing with Kali Linux and pass the exam to become an Offensive Security Certified Professional (OSCP). All new content for 2020.


While investigating possible shared memory issues in AGXCommandQueue::processSegmentKernelCommand(), I noticed that the size checks used to parse the IOAccelKernelCommand in IOAccelCommandQueue2::processSegmentKernelCommand() are incorrect. The IOAccelKernelCommand contains an 8-byte header consisting of a command type and size, followed by structured data specific to the type of command. When verifying that the size of the IOAccelKernelCommand has enough data for the specific command type, it appears that the check excludes the size of the 8-byte header, meaning that processSegmentKernelCommand() will parse up to 8 bytes of out-of-bounds data.

Normally I wouldn't consider this very security-relevant. However, command type 2 corresponds to kIOAccelKernelCommandCollectTimeStamp, which actually *writes* into the OOB memory rather than just parsing data from it. (The IOAccelKernelCommand is being parsed from shared memory, so the write is visible to userspace.) This makes it possible to overwrite the first 1-8 bytes of the subsequent page of memory with timestamp data.

The attached POC should trigger the issue on iOS 13. Tested on iPod9,1 17B111. I haven't tested on macOS, but it looks like the issue is present there as well.

I'll also tack on to this issue that on the whole AGXCommandQueue seems to do a poor job of treating shared memory as volatile, and I suspect that there are further issues here that are worth looking into. For example, when IOAccelKernelCommand's type is 0x10000, AGXCommandQueue::processSegmentKernelCommand() does not use the fourth parameter (which points to the end of the IOAccelKernelCommand as parsed by IOAccelCommandQueue2::processSegmentKernelCommands()) except when passing it to IOAccelCommandQueue2::processSegmentKernelCommand(), instead double-fetching the command size from shared memory to verify that all the command data is in-bounds. Thus, I believe it's possible to make AGXCommandQueue::processSegmentKernelCommand() parse out-of-bounds data, although I have not found a way to turn this into an interesting exploitation primitive. I don't think the shared memory issues are isolated to this function either. For example, there used to be much more readily exploitable double-fetches in AGXAllocationList2::initWithSharedResourceList(), although these were fixed sometime between 16A5288q and 16G77.

Proof of Concept: