Projects STRLCPY CVE-2022-42864 Commits a8dbd6b2
🤬
  • ■ ■ ■ ■ ■ ■
    README.md
    skipped 11 lines
    12 12  ## The bug
    13 13  [Apple's comment](https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDFamily/IOHIDDevice.cpp#L1601) from the source code when this issue was fixed sums this up nicely:
    14 14   
     15 +```cpp
    15 16   // Find the number of cookies in the data. The data from elementData is shared with user space and may change at any time.
     17 +```
    16 18   
    17 19  Let us have a look at the function before the patch (I have tried to label relevant lines):
    18  - 
    19  - IOReturn IOHIDDevice::postElementTransaction(const void* elementData, UInt32 dataSize, UInt32 completionTimeout, IOHIDCompletion * completion)
    20  - {
    21  - IOReturn ret = kIOReturnError;
    22  - uint32_t cookies_[kMaxLocalCookieArrayLength];
    23  - uint32_t *cookies = cookies_;
    24  - uint32_t cookieCount = 0;
    25  - uint32_t cookieSize = 0;
    26  - uint32_t dataOffset = 0;
    27  - uint8_t *data = (uint8_t*)elementData;
    28  - IOMemoryDescriptor *elementDesc = getMemoryWithCurrentElementValues();
    29  - require(_elementArray && elementDesc, fail);
    30  - 
    31  - WORKLOOP_LOCK;
     20 +```cpp
     21 +IOReturn IOHIDDevice::postElementTransaction(const void* elementData, UInt32 dataSize, UInt32 completionTimeout, IOHIDCompletion * completion)
     22 +{
     23 + IOReturn ret = kIOReturnError;
     24 + uint32_t cookies_[kMaxLocalCookieArrayLength];
     25 + uint32_t *cookies = cookies_;
     26 + uint32_t cookieCount = 0;
     27 + uint32_t cookieSize = 0;
     28 + uint32_t dataOffset = 0;
     29 + uint8_t *data = (uint8_t*)elementData;
     30 + IOMemoryDescriptor *elementDesc = getMemoryWithCurrentElementValues();
     31 + require(_elementArray && elementDesc, fail);
    32 32   
    33  - // Find the number of cookies in the data. Check that all cookies are valid elements. [1]
    34  - while (dataOffset < dataSize) {
    35  - const IOHIDElementValueHeader *headerPtr = (const IOHIDElementValueHeader *)(data + dataOffset);
    36  - IOHIDElementPrivate *element = GetElement(headerPtr->cookie);
    37  - if (!element) {
    38  - HIDDeviceLogError("Could not find element for cookie: %d", headerPtr->cookie);
    39  - ret = kIOReturnAborted;
    40  - goto fail;
    41  - }
    42  - cookieCount++;
     33 + WORKLOOP_LOCK;
    43 34   
    44  - require_noerr_action(os_add3_overflow(dataOffset, headerPtr->length, sizeof(IOHIDElementValueHeader), &dataOffset), fail, HIDDeviceLogError("Overflow iterating cookie data buffer %u %u", dataOffset, headerPtr->length));
    45  - }
    46  - // Data isn't as large as expected, don't overrun, just abort
    47  - if (dataOffset != dataSize) { // [2]
    48  - HIDDeviceLogError("Cookie data buffer is smaller than expected. %u vs. %u",
    49  - (unsigned int)dataSize, (unsigned int)dataOffset);
     35 + // Find the number of cookies in the data. Check that all cookies are valid elements. [1]
     36 + while (dataOffset < dataSize) {
     37 + const IOHIDElementValueHeader *headerPtr = (const IOHIDElementValueHeader *)(data + dataOffset);
     38 + IOHIDElementPrivate *element = GetElement(headerPtr->cookie);
     39 + if (!element) {
     40 + HIDDeviceLogError("Could not find element for cookie: %d", headerPtr->cookie);
    50 41   ret = kIOReturnAborted;
    51 42   goto fail;
    52 43   }
    53  - dataOffset = 0;
     44 + cookieCount++;
    54 45   
    55  - require_noerr_action(os_mul_overflow(cookieCount, sizeof(uint32_t), &cookieSize),
    56  - fail,
    57  - HIDDeviceLogError("Overflow calculating cookieSize"));
     46 + require_noerr_action(os_add3_overflow(dataOffset, headerPtr->length, sizeof(IOHIDElementValueHeader), &dataOffset), fail, HIDDeviceLogError("Overflow iterating cookie data buffer %u %u", dataOffset, headerPtr->length));
     47 + }
     48 + // Data isn't as large as expected, don't overrun, just abort
     49 + if (dataOffset != dataSize) { // [2]
     50 + HIDDeviceLogError("Cookie data buffer is smaller than expected. %u vs. %u",
     51 + (unsigned int)dataSize, (unsigned int)dataOffset);
     52 + ret = kIOReturnAborted;
     53 + goto fail;
     54 + }
     55 + dataOffset = 0;
    58 56   
    59  - cookies = (cookieCount <= kMaxLocalCookieArrayLength) ? cookies : (uint32_t*)IOMallocData(cookieSize); // [3]
     57 + require_noerr_action(os_mul_overflow(cookieCount, sizeof(uint32_t), &cookieSize),
     58 + fail,
     59 + HIDDeviceLogError("Overflow calculating cookieSize"));
    60 60   
    61  - if (cookies == NULL) {
    62  - ret = kIOReturnNoMemory;
    63  - goto fail;
    64  - }
     61 + cookies = (cookieCount <= kMaxLocalCookieArrayLength) ? cookies : (uint32_t*)IOMallocData(cookieSize); // [3]
    65 62   
    66  - // Update the elements, this replaced the shared kernel-user shared memory.
    67  - for (size_t index = 0; dataOffset < dataSize; ++index) { // [4]
    68  - const IOHIDElementValueHeader *headerPtr;
    69  - IOHIDElementPrivate *element;
    70  - OSData *elementVal;
     63 + if (cookies == NULL) {
     64 + ret = kIOReturnNoMemory;
     65 + goto fail;
     66 + }
    71 67   
    72  - headerPtr = (const IOHIDElementValueHeader *)(data + dataOffset);
    73  - element = GetElement(headerPtr->cookie);
    74  - dataOffset += headerPtr->length + sizeof(IOHIDElementValueHeader);
     68 + // Update the elements, this replaced the shared kernel-user shared memory.
     69 + for (size_t index = 0; dataOffset < dataSize; ++index) { // [4]
     70 + const IOHIDElementValueHeader *headerPtr;
     71 + IOHIDElementPrivate *element;
     72 + OSData *elementVal;
    75 73   
    76  - elementVal = OSData::withBytesNoCopy((void*)headerPtr->value,
    77  - headerPtr->length); // [5]
    78  - require_action(elementVal, fail, ret = kIOReturnNoMemory);
    79  - element->setDataBits(elementVal);
    80  - elementVal->release();
     74 + headerPtr = (const IOHIDElementValueHeader *)(data + dataOffset);
     75 + element = GetElement(headerPtr->cookie);
     76 + dataOffset += headerPtr->length + sizeof(IOHIDElementValueHeader);
    81 77   
    82  - cookies[index] = headerPtr->cookie; // [6]
    83  - }
     78 + elementVal = OSData::withBytesNoCopy((void*)headerPtr->value,
     79 + headerPtr->length); // [5]
     80 + require_action(elementVal, fail, ret = kIOReturnNoMemory);
     81 + element->setDataBits(elementVal);
     82 + elementVal->release();
    84 83   
    85  - // Actually post elements
    86  - ret = postElementValues((IOHIDElementCookie *)cookies, (UInt32)cookieCount, 0, completionTimeout, completion);
     84 + cookies[index] = headerPtr->cookie; // [6]
     85 + }
    87 86   
    88  - fail:
    89  - WORKLOOP_UNLOCK;
    90  - if (cookies != &cookies_[0]) {
    91  - IOFreeData(cookies, cookieSize);
    92  - }
     87 + // Actually post elements
     88 + ret = postElementValues((IOHIDElementCookie *)cookies, (UInt32)cookieCount, 0, completionTimeout, completion);
    93 89   
    94  - return ret;
     90 +fail:
     91 + WORKLOOP_UNLOCK;
     92 + if (cookies != &cookies_[0]) {
     93 + IOFreeData(cookies, cookieSize);
    95 94   }
     95 + 
     96 + return ret;
     97 +}
     98 +```
    96 99   
    97 100  - The loop at `[1]` counts the number of `IOHIDElementValue`s in the buffer, and stores this count in `cookieCount`.
    98 101  - The check at `[2]` (combined with the condition of the while loop) will make sure that the `length` field of each header does not extend out of the bounds of the `elementData` buffer (nor can it fall short of the end of the buffer, although this is less relevant).
    skipped 4 lines
    103 106   
    104 107  So what's the issue? This function behaves entirely correctly when `elementData` is non-volatile, the issue comes when the method is called with shared memory. Enter the `IOHIDInterface::SetElementValues_Impl` DriverKit method:
    105 108   
    106  - kern_return_t
    107  - IMPL(IOHIDInterface, SetElementValues)
    108  - {
    109  - IOReturn ret = kIOReturnError;
    110  - UInt8 *values = NULL;
    111  - IOBufferMemoryDescriptor *md = NULL;
     109 +```cpp
     110 +kern_return_t
     111 +IMPL(IOHIDInterface, SetElementValues)
     112 +{
     113 + IOReturn ret = kIOReturnError;
     114 + UInt8 *values = NULL;
     115 + IOBufferMemoryDescriptor *md = NULL;
    112 116  
    113  - md = OSDynamicCast(IOBufferMemoryDescriptor, elementValues);
    114  - require_action(md && count, exit, ret = kIOReturnBadArgument);
     117 + md = OSDynamicCast(IOBufferMemoryDescriptor, elementValues);
     118 + require_action(md && count, exit, ret = kIOReturnBadArgument);
    115 119   
    116  - values = (UInt8 *)md->getBytesNoCopy();
     120 + values = (UInt8 *)md->getBytesNoCopy();
    117 121  
    118  - // Post the data to the device
    119  - ret = _owner->postElementTransaction(values, (UInt32)md->getLength());
    120  - require_noerr_action(ret, exit, HIDServiceLogError("postElementValues failed: 0x%x", ret));
     122 + // Post the data to the device
     123 + ret = _owner->postElementTransaction(values, (UInt32)md->getLength());
     124 + require_noerr_action(ret, exit, HIDServiceLogError("postElementValues failed: 0x%x", ret));
    121 125   
    122  - exit:
    123  - return ret;
    124  - }
     126 +exit:
     127 + return ret;
     128 +}
     129 +```
    125 130   
    126 131  Here, `postElementTransaction` is called with `md->getBytesNoCopy()`, memory shared with userspace, violating the assumption that `elementData` is non-volatile. The content of the `elementData` buffer can change after the loop at `[1]`, but before the loop at `[4]`, so what does this mean for an attacker?
    127 132   
    skipped 41 lines
Please wait...
Page is in error, reload to recover