4

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

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

但是,驱动程序和客户端的命令长度似乎是耦合的,这意味着驱动程序中的checkStructureInputSizefromIOExternalMethodDispatch必须等于客户端中的inputStructCntfrom 。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 *argumentsIOExternalMethodDispatch *dispatch

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

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

4

2 回答 2

3

正如您已经发现的那样,接受可变长度“结构”输入和输出的答案是kIOUCVariableStructureSizeIOExternalMethodDispatch.

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

struct IOExternalMethodArguments
{
    …

    const void *    structureInput;
    uint32_t        structureInputSize;

    IOMemoryDescriptor * structureInputDescriptor;

    …

    void *      structureOutput;
    uint32_t        structureOutputSize;

    IOMemoryDescriptor * structureOutputDescriptor;

    …
};

根据实际大小,内存区域可能由structureInput or structureInputDescriptor (and structureOutput or structureOutputDescriptor ) 引用 - 交叉点通常是 8192 字节或 2 个内存页。任何较小的都将作为指针进入,任何较大的都将由内存描述符引用。不过不要指望特定的交叉点,这是一个实现细节,原则上可能会改变。

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

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,当然除了没有选项!

注意:细微的安全风险:

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

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

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

于 2017-08-01T19:57:36.783 回答
1

再次浏览相关手册后,我找到了相关段落:

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

因此,为了避免尺寸验证,我所要做的就是将字段设置checkStructureInputSize为 value kIOUCVariableStructureSize inIoExternalMethodDispatch并将命令正确传递给驱动程序。

于 2017-08-01T12:52:55.460 回答