Поделиться через


Ошибки в прямых ввода-выводах

Наиболее распространенная проблема прямого ввода-вывода заключается в том, что не удается правильно обрабатывать буферы нулевой длины. Так как диспетчер операций ввода-вывода не создает многомерные выражения для передачи нулевой длины, буфер нулевой длины приводит к значению NULL в Irp-MdlAddress>.

Чтобы сопоставить адресное пространство, драйверы должны использовать MmGetSystemAddressForMdlSafe, который возвращает значение NULL, если сопоставление завершается ошибкой, так как драйвер передает значение NULL MdlAddress. Драйверы всегда должны проверять возврат NULL , прежде чем пытаться использовать возвращенный адрес.

Прямой ввод-вывод включает двойное сопоставление адресного пространства пользователя с буфером системных адресов, чтобы два разных виртуальных адреса имели одинаковый физический адрес. Двойное сопоставление имеет следующие последствия, которые иногда могут вызвать проблемы для драйверов:

  • Смещение на виртуальную страницу адреса пользователя становится смещением на системную страницу.

    Доступ за пределами этих системных буферов может быть незамечен в течение длительного периода времени в зависимости от детализации страницы сопоставления. Если буфер вызывающего объекта не выделяется ближе к концу страницы, данные, записанные за пределами буфера, тем не менее будут отображаться в буфере, и вызывающий объект не будет знать, что произошла любая ошибка. Если конец буфера совпадает с концом страницы, системные виртуальные адреса за пределами конца могут указывать на что-либо или может быть недопустимым. Такие проблемы могут быть чрезвычайно трудными для поиска.

  • Если вызывающий процесс имеет другой поток, который изменяет сопоставление памяти пользователя, содержимое системного буфера изменится при изменении сопоставления памяти пользователя.

    В этой ситуации использование системного буфера для хранения данных с нуля может вызвать проблемы. Две выборки из одного расположения памяти могут привести к разным значениям.

    Следующий фрагмент кода получает строку в прямом запросе ввода-вывода, а затем пытается преобразовать ее в прописные символы:

    PWCHAR  PortName = NULL;
    
    PortName = (PWCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
    
    //
    // Null-terminate the PortName so that RtlInitUnicodeString will not
    // be invalid.
    //
    PortName[Size / sizeof(WCHAR) - 1] = UNICODE_NULL;
    
    RtlInitUnicodeString(&AdapterName, PortName);
    

    Так как буфер может быть неправильно сформирован, код пытается принудительно принудить Юникод NULL в качестве последнего символа буфера. Однако если базовая физическая память сопоставляется как с адресом пользовательского, так и с адресом режима ядра, другой поток в процессе может перезаписать буфер, как только эта операция записи завершится.

    И наоборот, если значение NULL отсутствует, вызов RtlInitUnicodeString может превышать диапазон буфера и, возможно, вызвать ошибку, если она выходит за пределы системного сопоставления.

Если драйвер создает и сопоставляет собственный MDL, он должен убедиться, что он обращается к MDL только с помощью метода, для которого она пробовалась. То есть, когда драйвер вызывает MmProbeAndLockPages, он задает метод доступа (IoReadAccess, IoWriteAccess или IoModifyAccess). Если драйвер указывает IoReadAccess, он не должен позже пытаться записать в системный буфер, доступный MmGetSystemAddressForMdl или MmGetSystemAddressForMdlSafe.