2017-08-01 137 views
6

发送由于IOKit命令我使用由于IOKit框架与使用从用户空间客户IOConnectCallMethodIOExternalMethodDispatch驾驶员侧我的驱动程序通信。具有动态长度

到目前为止我能够发送固定长度指令,现在我想发送字符的不同大小的阵列(即FULLPATH)。

然而,似乎是,驱动器和所述的客户端侧命令长度耦合,这意味着checkStructureInputSizeIOExternalMethodDispatch在驾驶员必须在客户端等于inputStructCntIOConnectCallMethod

这里有结构内容两侧:

DRIVER:

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::ExternalMethodIOExternalMethodArguments *argumentsIOExternalMethodDispatch *dispatch但是这需要我从客户端,它是动态的传递数据的精确长度。

这不起作用,除非我应该期待数据的准确长度设置的调度功能。

任何想法如何解决这个或许有不同的API,我应该在这种情况下使用?

回答

2

正如您已经发现的那样,接受可变长度“结构”输入和输出的答案是在IOExternalMethodDispatch中为输入或输出结构大小指定特殊的kIOUCVariableStructureSize值。

这将使方法调度成功,招呼一下,你的方法实现。然而令人讨厌的是,结构输入和输出不一定通过IOExternalMethodArguments结构中的structureInputstructureOutput指针字段来提供。在结构体定义(IOKit/IOUserClient。h)中,通知: - 交叉点而言通常是8192个字节2,或

struct IOExternalMethodArguments 
{ 
    … 

    const void * structureInput; 
    uint32_t  structureInputSize; 

    IOMemoryDescriptor * structureInputDescriptor; 

    … 

    void *  structureOutput; 
    uint32_t  structureOutputSize; 

    IOMemoryDescriptor * structureOutputDescriptor; 

    … 
}; 

根据实际大小,存储区可能被structureInputstructureInputDescriptor(和structureOutputstructureOutputDescriptor)引用内存页面。任何更小的内容都将作为指针出现,任何更大的内容都将被内存描述符引用。不要指望具体的交叉点,这是一个实现细节,原则上可以改变。

您如何处理这种情况取决于您需要如何处理输入或输出数据。通常情况下,你需要直接在你的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也会遇到同样的问题,所以从内核的角度来看它只能写入。 永远不要读回你在那里写的任何数据。(写时复制映射对输出结构没有意义。)

1

通过相关的手册会再次后,我找到了相关段落:

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

所以我不得不以避免大小验证做的,就是设置现场checkStructureInputSize重视在IoExternalMethodDispatchkIOUCVariableStructureSize并传递到驱动程序正确的命令。

+1

是的,这将做到这一点。请注意,如果“结构”超出了特定的大小(如果我没有记错的话,2页/ 8K),您将在内核中将其作为指针接收,而不是作为内存描述符。 'IOExternalMethodArguments'中的'structureInput'字段将是一个'nullptr',你需要看一下'structInputDescriptor'字段。让我知道你是否希望我在正确的答案中充实确切的程序。 – pmdj

+0

@pmdj,是的,代码示例的正确答案确实值得欢迎。谢谢 – Zohar81

+0

完成!我已经深入了解更多细节,特别是安全隐患。 – pmdj