我正在使用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吗?
答案 0 :(得分:3)
正如您已经发现的那样,接受可变长度&#34;结构&#34;输入和输出用于指定kIOUCVariableStructureSize
中输入或输出结构大小的特殊IOExternalMethodDispatch
值。
这将允许方法调度成功并调用您的方法实现。然而,一个令人讨厌的缺陷是结构输入和输出不一定通过structureInput
结构中的structureOutput
和IOExternalMethodArguments
指针字段提供。在结构定义(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
,并将命令正确传递给驱动程序。