发送动态长度的IOKit命令

时间:2017-08-01 08:17:18

标签: c++ c iokit kernel-extension xnu

我正在使用IOKit框架使用来自用户空间客户端的IOConnectCallMethod和驱动程序端的IOExternalMethodDispatch与我的驱动程序进行通信。

到目前为止,我能够发送固定长度命令,现在我希望发送一个不同大小的字符数组(即完整路径)。

但是,似乎驱动程序和客户端命令长度是耦合的,这意味着驱动程序中checkStructureInputSize的{​​{1}}必须等于IOExternalMethodDispatch 客户端inputStructCnt

以下是双方的结构内容:

DRIVER:

IOConnectCallMethod

客户:

struct IOExternalMethodDispatch
{
    IOExternalMethodAction function;
    uint32_t           checkScalarInputCount;
    uint32_t           checkStructureInputSize;
    uint32_t           checkScalarOutputCount;
    uint32_t           checkStructureOutputSize;
};

这是我尝试使用各种大小的命令失败:

kern_return_t IOConnectCallMethod(
    mach_port_t  connection,        // In
    uint32_t     selector,      // In
    const uint64_t  *input,         // In
    uint32_t     inputCnt,      // In
    const void      *inputStruct,       // In
    size_t       inputStructCnt,    // In
    uint64_t    *output,        // Out
    uint32_t    *outputCnt,     // In/Out
    void        *outputStruct,      // Out
    size_t      *outputStructCnt)   // In/Out

从驱动程序命令处理程序端,我使用std::vector<char> rawData; //vector of chars // filling the vector with filePath ... kr = IOConnectCallMethod(_connection, kCommandIndex , 0, 0, rawData.data(), rawData.size(), 0, 0, 0, 0); IOUserClient::ExternalMethod调用IOExternalMethodArguments *arguments,但这需要我从客户端传递的数据的确切长度,这是动态的。

这不起作用,除非我将调度函数设置为它应该期望的确切数据长度。

知道如何解决这个问题,或者在这种情况下我应该使用不同的API吗?

2 个答案:

答案 0 :(得分:3)

正如您已经发现的那样,接受可变长度&#34;结构&#34;输入和输出用于指定kIOUCVariableStructureSize中输入或输出结构大小的特殊IOExternalMethodDispatch值。

这将允许方法调度成功并调用您的方法实现。然而,一个令人讨厌的缺陷是结构输入和输出不一定通过structureInput结构中的structureOutputIOExternalMethodArguments指针字段提供。在结构定义(IOKit / IOUserClient.h)中,请注意:

struct IOExternalMethodArguments
{
    …

    const void *    structureInput;
    uint32_t        structureInputSize;

    IOMemoryDescriptor * structureInputDescriptor;

    …

    void *      structureOutput;
    uint32_t        structureOutputSize;

    IOMemoryDescriptor * structureOutputDescriptor;

    …
};

根据实际尺寸,内容区域可能会被structureInput structureInputDescriptor(以及structureOutput {引用{1}}) - 交叉点通常为8192个字节,或2个内存页。任何较小的东西都将作为指针进入,任何更大的东西都将被内存描述符引用。不过指的是特定的交叉点,这是一个实现细节,原则上可以改变。

如何处理这种情况取决于您需要对输入或输出数据执行的操作。通常,您希望直接在kext中读取它 - 因此如果它作为内存描述符出现,则需要先将其映射到内核任务的地址空间。像这样:

structureOutputDescriptor

输出描述符可以被类似地处理,当然除了没有static IOReturn my_external_method_impl(OSObject* target, void* reference, IOExternalMethodArguments* arguments) { IOMemoryMap* map = nullptr; const void* input; size_t input_size; if (arguments->structureInputDescriptor != nullptr) { map = arguments->structureInputDescriptor->createMappingInTask(kernel_task, 0, kIOMapAnywhere | kIOMapReadOnly); if (map == nullptr) { // insert error handling here return …; } input = reinterpret_cast<const void*>(map->getAddress()); input_size = map->getLength(); } else { input = arguments->structureInput; input_size = arguments->structureInputSize; } // … // do stuff with input here // … OSSafeReleaseNULL(map); // make sure we unmap on all function return paths! return …; } 选项之外!

注意:减轻安全风险:

解释内核中的用户数据通常是一项对安全性敏感的任务。直到最近,结构输入机制特别容易受到攻击 - 因为输入结构是从用户空间到内核空间的内存映射,另一个用户空间线程仍然可以在内核读取时修改该内存。您需要非常小心地制作内核代码,以避免向恶意用户客户端引入漏洞。例如,边界检查映射内存中的用户空间提供的值,然后在假设它仍然在有效范围内的情况下重新读取它是错误的。

避免这种情况最直接的方法是复制一次内存,然后只使用复制的数据版本。要采用这种方法,您甚至不需要对描述符进行内存映射:您可以使用kIOMapReadOnly成员函数。对于大量数据,您可能不希望这样做是为了提高效率。

最近(在10.12.x周期期间)Apple更改了readBytes(),因此使用structureInputDescriptor选项创建了它。 (据我所知,这是专为此目的而创建的。)这样做的结果是,如果用户空间修改了内存范围,它不会修改内核映射,而是透明地创建它写入的页面的副本。依赖于此假设您的用户系统已完全打补丁。 即使在完全修补的系统上,kIOMemoryMapCopyOnWrite也会遇到同样的问题,因此请从内核的角度将其视为只写。 永远不要回读你在那里写的任何数据。 (写时复制映射对输出结构没有意义。)

答案 1 :(得分:1)

再次阅读相关手册后,我找到了相关段落:

  

checkScalarInputCount,checkStructureInputSize,checkScalarOutputCount和checkStructureOutputSize字段允许在将参数列表传递给目标对象之前对其进行完整性检查。标量计数应设置为目标方法期望读取或写入的标量(64位)值的数量。结构大小应设置为目标方法期望读取或写入的任何结构的大小。对于任何struct size字段,如果在编译时无法确定结构的大小,请指定kIOUCVariableStructureSize而不是实际大小。

因此,为了避免大小验证,我必须做的就是在checkStructureInputSize中将字段kIOUCVariableStructureSize设置为值IoExternalMethodDispatch,并将命令正确传递给驱动程序。