Apple Mac OSX / iOS - Multiple Kernel Uninitialized Variable Bugs Leading to Code Execution Vulnerabilities

EDB-ID:

39358




Platform:

Multiple

Date:

2016-01-28


Source: https://code.google.com/p/google-security-research/issues/detail?id=618

The _ool variations of the IOKit device.defs functions all incorrectly deal with error conditions.

If you run the mig tool on device.defs you can see the source of the kernel-side MIG handling code; here
is the relevant generated code for io_service_get_matching_services_ool:

mig_internal novalue _Xio_service_get_matching_services_ool
  (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP)
{

  ... // some typedefs

  Request *In0P = (Request *) InHeadP;
  Reply *OutP = (Reply *) OutHeadP;

  kern_return_t RetCode;
  io_object_t existing;                   <-- (a)

  ... // check the input types

  RetCode = is_io_service_get_matching_services_ool(In0P->Head.msgh_request_port, (io_buf_ptr_t)(In0P->matching.address), In0P->matchingCnt, &OutP->result, &existing);  <-- (b)

  if (RetCode != KERN_SUCCESS) {
    MIG_RETURN_ERROR(OutP, RetCode);
  }

  OutP->existing.name = (mach_port_t)iokit_make_object_port(existing);   <-- (c)


At (a) it declares an io_object_t existing on the stack (io_object_t is just a pointer.) It then passes the address of that local to is_io_service_get_matching_services_ool, and if that
function succeeds passes the value of existing to iokit_make_object_port. Here's is_io_service_get_matching_services_ool (which importantly is NOT generated code):

    /* Routine io_service_get_matching_services_ool */
    kern_return_t is_io_service_get_matching_services_ool(
                                                          mach_port_t master_port,
                                                          io_buf_ptr_t matching,
                                                          mach_msg_type_number_t matchingCnt,
                                                          kern_return_t *result,
                                                          io_object_t *existing )
    {
        kern_return_t kr;
        vm_offset_t   data;
        vm_map_offset_t map_data;
        
        kr = vm_map_copyout( kernel_map, &map_data, (vm_map_copy_t) matching );
        data = CAST_DOWN(vm_offset_t, map_data);
        
        if( KERN_SUCCESS == kr) {
            // must return success after vm_map_copyout() succeeds
            *result = internal_io_service_get_matching_services(master_port,
                                                                (const char *) data, matchingCnt, existing);
            vm_deallocate( kernel_map, data, matchingCnt );
        }
        
        return( kr );
    }

Note here that it returns kr which *only* indicates if the vm_map_copyout failed. This will of course succeed so the return value of this function
will always be KERN_SUCCESS, even if internal_io_service_get_matching_services fails... Let's look at that function:

    static kern_return_t internal_io_service_get_matching_services(
                                                                   mach_port_t master_port,
                                                                   const char * matching,
                                                                   mach_msg_type_number_t matching_size,
                                                                   io_iterator_t *existing )
    {
        kern_return_t kr;
        OSObject *    obj;
        OSDictionary *  dict;
        
        if( master_port != master_device_port)
            return( kIOReturnNotPrivileged);
        
        obj = matching_size ? OSUnserializeXML(matching, matching_size)
        : OSUnserializeXML(matching);
        if( (dict = OSDynamicCast( OSDictionary, obj))) {
            *existing = IOService::getMatchingServices( dict );
            kr = kIOReturnSuccess;
        } else
            kr = kIOReturnBadArgument;
        
        if( obj)
            obj->release();
        
        return( kr );
    }

Indeed, if this function fails it doesn't set existing to a safe value but does return an error code. However, the _ool variation ignores this error code (it
just returns it to userspace via the result parameter.) This means that the generated code thinks that is_io_service_get_matching_services_ool succeed
and it therefore pass existing in iokit_make_object_port which will eventually (if the uninitialized value wasn't NULL) call a virtual function on it
(taggedRetain) when adding the object to the dictionary storing all iokit user objects.

All of the _ool variations of IOKit API's have this problem; PoCs are included for all of them but they may or may not crash depending on the
state of the stack.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39358.zip