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