Lucene search
K

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

🗓️ 28 Jan 2016 00:00:00Reported by Google Security ResearchType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 25 Views

Apple Mac OSX / iOS - Uninitialized Variable Bugs Leading to Code Executio

Code
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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation