CVE-2015-5774

IOHIDResourceDeviceUserClient::_postReportResult堆溢出漏洞

漏洞描述

该内核漏洞被用于太极越狱v2.0,并于iOS 8.4.1版本中被修补。Apple的安全公告中给出的漏洞编号为CVE-2015-5774,但并未提及漏洞的细节。

通过查看IOHIDFamily的代码,检查IOHIDResourceDeviceUserClient的3号处理函数_postReportResult。该函数的入参是2个uint64(分别对应result和token),以及任意长度的一个struct数据(report数据)。

typedef enum {
    kIOHIDResourceUserClientResponseIndexResult = 0,
    kIOHIDResourceUserClientResponseIndexToken,
    kIOHIDResourceUserClientResponseIndexCount
} IOHIDResourceUserClientResponseIndex;

    {   // kIOHIDResourceDeviceUserClientMethodPostReportResult
        (IOExternalMethodAction) &IOHIDResourceDeviceUserClient::_postReportResult,
        kIOHIDResourceUserClientResponseIndexCount, -1, /* 1 scalar input: the result, 1 struct input : the buffer */
        0, 0
    }

_postReportResult函数的用途是,如果有在等待结果的队列(通过token来找到对应的结构),则将输入的struct数据作为结果复制到该队列中,并唤醒等待。

typedef struct {
    IOReturn                ret;
    IOMemoryDescriptor *    descriptor;
} __ReportResult;

IOReturn IOHIDResourceDeviceUserClient::postReportResult(IOExternalMethodArguments * arguments)
{
    OSObject * tokenObj = (OSObject*)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexToken];

    if ( tokenObj && _pending->containsObject(tokenObj) ) {
        OSData * data = OSDynamicCast(OSData, tokenObj);  // 通过输入的token查找对应数据结构
        if ( data ) {
            __ReportResult * pResult = (__ReportResult*)data->getBytesNoCopy();

            if ( pResult->descriptor && arguments->structureInput ) {
                // 向对象写入数据,该写入行为是安全的,会根据buffer的长度判断最多能够写入的数据长度
                pResult->descriptor->writeBytes(0, arguments->structureInput, arguments->structureInputSize);

                IOBufferMemoryDescriptor * buffer = OSDynamicCast(IOBufferMemoryDescriptor, pResult->descriptor);
                if (buffer)
                    // 修改IOBufferMemoryDescriptor的长度,该长度修改函数内部缺少对长度的检查,从而导致可能的溢出!
                    buffer->setLength((vm_size_t)arguments->structureInputSize);

            }
            pResult->ret = (IOReturn)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexResult];
            _commandGate->commandWakeup(data);
        }       
    }
    return kIOReturnSuccess;
}

分析上述代码,首先IOMemoryDescriptor::writeBytes函数是写入安全的,但前提是_length成员不超过buffer的分配长度。而IOBufferMemoryDescriptor::setLength函数的代码是有问题的,release版本中由于去掉了assert判断,当设置的length大于buffer的分配长度时将会使得writeBytes不再安全,从而造成堆溢出(可控制溢出长度及覆盖数据)。

void IOBufferMemoryDescriptor::setLength(vm_size_t length)
{
    assert(length <= _capacity);  // 该行代码在release版本内核中不存在!

    _length = length;
    _ranges.v64->length = length;
}

通过两次调用_postReportResult方法,第一次将memory descriptor对象的长度设置为structureInputSize(超过buffer的分配长度),第二次再调用时即可导致堆溢出。

触发路径

  • 首先要创建一个IOHID的设备,通过”IOHIDResource”服务的0号调用函数来完成,与CVE-2014-4487相同。
  • 创建一个线程并调用IOHIDLibUserClient::_updateElementValues方法,该函数会等待获取report result数据后返回。
    • IOHIDDevice::updateElementValues函数分配IOBufferMemoryDescriptor,并调用getReport等待返回。
    • IOHIDResourceDeviceUserClient::getReportGated等待report数据(最长等待时间_maxClientTimeoutUS)
  • 主线程中两次调用IOHIDResourceDeviceUserClient::_postReportResult方法触发漏洞

发表评论

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