In iOS 8.1.3, Apple made the following patch to the _getElements method in IOHIDFamily:
IOReturn IOHIDLibUserClient::getElements(uint32_t elementType, IOMemoryDescriptor * mem, uint32_t *elementBufferSize)
{
IOReturn ret = kIOReturnNoMemory;
if (!fNub || isInactive())
return kIOReturnNotAttached;
ret = mem->prepare();
if(ret == kIOReturnSuccess)
{
void * elementData;
uint32_t elementLength;
+ uint32_t allocationSize;
- elementLength = mem->getLength();
+ allocationSize = elementLength = (uint32_t)mem->getLength();
if ( elementLength )
{
elementData = IOMalloc( elementLength );
if ( elementData )
{
bzero(elementData, elementLength);
ret = getElements(elementType, elementData, &elementLength);
if ( elementBufferSize )
*elementBufferSize = elementLength;
mem->writeBytes( 0, elementData, elementLength );
- IOFree( elementData, elementLength );
+ IOFree( elementData, allocationSize );
}
else
ret = kIOReturnNoMemory;
}
else
ret = kIOReturnBadArgument;
mem->complete();
}
return ret;
}
This was to fix the bug TaiG used for their iOS 8.1.2 jailbreak. I am not going to reexplain it here, you can find a pretty good version of it in Levin's OS Internals series.
However, what if I told you that, believe it or not, this function still had a bug that affected it that wasn't noticed until iOS 12?
The New Vulnerable Path
So yes, the method has been changed so that even if you manage to have elementLength change in the getElements call to have a different length than before to affect the IOFree call. But, they still use elementLength for the next writeBytes call in mem->writeBytes( 0, elementData, elementLength );
. This means that prior to iOS 12, you can have this method change the elementLength to be larger than the allocated size and it would in turn grant you a heap overflow.
I cannot find any specific writeups for this, so I don't know who eventually discovered this. Apple would later patch this in IOHIDFamily-1090.200.63 by adding an elementLength check to the method.
IOReturn IOHIDLibUserClient::getElements(uint32_t elementType, IOMemoryDescriptor * mem, uint32_t *elementBufferSize)
{
IOReturn ret = kIOReturnNoMemory;
if (!fNub || isInactive())
return kIOReturnNotAttached;
ret = mem->prepare();
if(ret == kIOReturnSuccess)
{
void * elementData;
uint32_t elementLength;
uint32_t allocationSize;
allocationSize = elementLength = (uint32_t)mem->getLength();
if ( elementLength )
{
elementData = IOMalloc( elementLength );
if ( elementData )
{
bzero(elementData, elementLength);
ret = getElements(elementType, elementData, &elementLength);
if ( elementBufferSize )
*elementBufferSize = elementLength;
+ if (elementLength <= mem->getLength()) {
mem->writeBytes( 0, elementData, elementLength );
+ } else {
+ ret = kIOReturnBadArgument;
+ }
IOFree( elementData, allocationSize );
}
else
ret = kIOReturnNoMemory;
}
else
ret = kIOReturnBadArgument;
mem->complete();
}
return ret;
}