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

Я использую инфраструктуру IOKit для связи с моим драйвером, используя IOConnectCallMethod из клиента пользовательского пространства и IOExternalMethodDispatch на стороне драйвера.

До сих пор я мог отправлять команды фиксированной длины, и теперь я хочу отправить массив символов различного размера (то есть полный путь).

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


person Zohar81    schedule 01.08.2017    source источник


Ответы (2)


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

person pmdj    schedule 01.08.2017

Еще раз просмотрев соответствующее руководство, я нашел соответствующий абзац:

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

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

person Zohar81    schedule 01.08.2017
comment
Да, это сработает. Обратите внимание, что если структура превышает определенный размер (2 страницы / 8 КБ, если я правильно помню), вы получите ее в ядре не как указатель, а как дескриптор памяти. Поле structureInput в IOExternalMethodArguments будет nullptr, и вам нужно вместо этого посмотреть на поле structureInputDescriptor. Дайте мне знать, если вы хотите, чтобы я подробно изложил точную процедуру в правильном ответе. - person pmdj; 01.08.2017
comment
@pmdj, да, правильный ответ с примером кода действительно приветствуется. Благодарность - person Zohar81; 01.08.2017
comment
Выполнено! Я подробно остановился на деталях, особенно о подводных камнях. - person pmdj; 01.08.2017