Я использую IOKit Framework для связи с моим драйвером, используя IOConnectCallMethod
от клиента пространства пользователя и IOExternalMethodDispatch
на стороне водителя.
До сих пор я мог отправлять команды фиксированной длины, и теперь я хочу отправить массив символов различного размера (то есть fullpath).
Тем не менее, кажется, что длина команд на стороне клиента и на стороне клиента связана, что означает, что checkStructureInputSize
от IOExternalMethodDispatch
в драйвере должно быть равно inputStructCnt
от
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
а также IOExternalMethodDispatch *dispatch
но это требует точной длины данных, которые я передаю от клиента, которая является динамической.
это не сработает, если я не установлю диспетчерскую функцию с точной длиной данных, которую она должна ожидать.
Любая идея, как решить эту проблему или, возможно, есть другой API, который я должен использовать в этом случае?
Как вы уже обнаружили, ответом для принятия «структурированных» входов и выходов переменной длины является указание специального 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
или же 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
страдает от той же проблемы, поэтому рассматривайте ее как доступную только для записи с точки зрения ядра. Никогда не читайте никаких данных, которые вы там написали. (Отображение при копировании при записи не имеет смысла для структуры вывода.)
После повторения соответствующего руководства, я нашел соответствующий параграф:
Поля checkScalarInputCount, checkStructureInputSize, checkScalarOutputCount и checkStructureOutputSize позволяют проверять правильность списка аргументов перед передачей его целевому объекту. Число скаляров должно быть установлено равным числу скалярных (64-битных) значений, которые целевой метод ожидает для чтения или записи. Размеры структуры должны быть равны размеру любых структур, которые целевой метод ожидает для чтения или записи. Для любого из полей размера структуры, если размер структуры не может быть определен во время компиляции, укажите kIOUCVariableStructureSize вместо фактического размера.
Так что все, что мне нужно было сделать, чтобы избежать проверки размера, это установить поле checkStructureInputSize
ценить, оценивать kIOUCVariableStructureSize
в IoExternalMethodDispatch
и команда передана водителю правильно.