отправка команды IOKit с динамической длиной

Я использую 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, который я должен использовать в этом случае?

6

Решение

Как вы уже обнаружили, ответом для принятия «структурированных» входов и выходов переменной длины является указание специального 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 страдает от той же проблемы, поэтому рассматривайте ее как доступную только для записи с точки зрения ядра. Никогда не читайте никаких данных, которые вы там написали. (Отображение при копировании при записи не имеет смысла для структуры вывода.)

3

Другие решения

После повторения соответствующего руководства, я нашел соответствующий параграф:

Поля checkScalarInputCount, checkStructureInputSize, checkScalarOutputCount и checkStructureOutputSize позволяют проверять правильность списка аргументов перед передачей его целевому объекту. Число скаляров должно быть установлено равным числу скалярных (64-битных) значений, которые целевой метод ожидает для чтения или записи. Размеры структуры должны быть равны размеру любых структур, которые целевой метод ожидает для чтения или записи. Для любого из полей размера структуры, если размер структуры не может быть определен во время компиляции, укажите kIOUCVariableStructureSize вместо фактического размера.

Так что все, что мне нужно было сделать, чтобы избежать проверки размера, это установить поле checkStructureInputSize ценить, оценивать kIOUCVariableStructureSize в IoExternalMethodDispatch и команда передана водителю правильно.

1

По вопросам рекламы [email protected]