Драйверы - фильтры
Система ввода/вывода Windows NT является расширяемой.
Один из методов расширения возможностей системы ввода/вывода - разработка
и применение драйверов-фильтров.
Драйвер-фильтр - это промежуточный драйвер, перехватывающий запросы, предназначенные
некоторым программным модулям (например, драйверу файловой системы или
драйверу диска). Перехватив запрос, драйвер-фильтр имеет возможность либо
расширения, либо замещения функциональности, обеспечиваемой первоначальным
получателем запроса. Драйвер-фильтр может, либо использовать сервисы драйвера,
которому изначально предназначался запрос, либо использовать сервисы других
программных модулей уровня ядра для обеспечения дополнительной функциональности.
Драйверы-фильтры могут встраиваться в любое место в стеке драйверов, их
может быть несколько, в том числе расположенных подряд.
Примером использования драйвера-фильтра может послужить реализация прозрачного
зашифрования/расшифрования данных, хранимых на диске или в сети. Ни одна
из поставляемых с ОС Windows NT файловых систем (FASTFAT, NTFS, CDFS,
LAN Manager Redirector) не обеспечивает поддержки прозрачного зашифрования
данных перед записью их на диск (или отправкой в сеть) и расшифрования
данных перед предоставлением их авторизированному пользователю. Но для того, чтобы выполнять шифрование, необязательно реализовать свой собственный драйвер файловой системы или специальный драйвер диска. Достаточно разработать драйвер-фильтр, располагающийся либо выше драйвера файловой системы (в этом случае драйвер-фильтр сможет обработать запрос перед тем, как драйвер файловой системы получит возможность его увидеть), либо ниже драйвера файловой системы (этот случай позволяет драйверу-фильтру выполнить любые требуемые операции после того, как драйвер файловой системы завершит свою задачу, и перед тем, как запрос будет получен драйвером диска (или сетевым драйвером)).
Драйвер-фильтр также может выполнять автоматическое обнаружение в реальном времени сигнатур вирусов в файлах (в том числе и файлах, полученных из сети). Фундаментальные шаги в разработке драйверов-фильтров:
- Присоединение к нужному объекту-устройству. Диспетчер ввода/вывода включает возможность присоединения к объекту-устройству (1), созданному некоторым драйвером (1), объекта-устройства (2), созданного другим драйвером(2). В результате такого присоединения пакеты IRP, направленные драйверу (1), ассоциированному с объектом-устройством (1), будут перенаправляться драйверу (2), ассоциированному с присоединенным объектом-устройством (2). Этот «присоединенный» драйвер (2) и является драйвером-фильтром.
Каждый объект-устройство имеет поле, называемое AttachedDevice
и содержащее указатель на объект-устройство, созданное драйвером-фильтром,
который первым присоединил свое устройство. Если это поле содержит NULL,
значит нет присоединенных устройств. В процессе присоединения нового объекта-устройства,
процедура присоединения пройдет по связанному списку присоединенных устройств
до конца и выполнит присоединение нового объекта-устройства к последнему
объекту-устройству в этом списке. В результате, любой запрос на создание/открытие,
предназначающийся некоторому объекту-устройству, будет перенаправляться
последнему (в его списке присоединенных устройств) объекту-устройству.
То есть последнему драйверу-фильтру, ассоциированному с последним объектом-устройством.
Аналогично, при вызове функции, которая по имени объекта-устройства возвращает
его указатель, будет возвращен указатель на объект-устройство, являющийся
последним в списке присоединенных устройств.
Но важно заметить, что диспетчер ввода/вывода проходит до конца списка
присоединенных устройств не во всех случаях. Например, никакого перенаправления
не будет, если будет вызвана функция IoCallDriver(). Следовательно, чтобы
обращение к драйверу назначения не миновало драйвер-фильтр, этот драйвер-фильтр
должен присоединить свой объект-устройство к объекту-устройству драйвера-назначения,
прежде, чем вышележащий драйвер вызовет процедуру определения указателя
нижележащего объекта-устройства по его имени (это делается во время запроса
на открытие), а затем будет использовать этот указатель (в частности при
вызове функции IoCallDriver()).
- Драйвер-фильтр может исследовать, модифицировать, завершать, или передавать дальше полученный пакет запроса драйверу назначения. Для того чтобы передать драйверу назначения полученный запрос, драйвер-фильтр должен создать собственный пакет IRP.
- Драйвер-фильтр должен реализовать завершающие процедуры для завершения обработки IRP, после того, как IRP завершит драйвер назначения.
- Отсоединение от объекта-устройства назначения.
Ниже рассмотрены основные принципы, которых необходимо придерживаться при разработке драйвера-фильтра, а также недостатки, из-за которых применение драйверов-фильтров становится нежелательным:
- 1. Разработчик драйвера-фильтра должен четко знать, как работает драйвер, создавший устройство, к которому будет присоединяться его драйвер. Драйвер-фильтр должен уметь обрабатывать все получаемые им запросы, адресованные на самом деле первоначальному драйверу, к которому он присоединился.
Несмотря на то, что все драйверы должны отвечать на
стандартное множество запросов, выдаваемых менеджером ввода/вывода, в
реальности, в случае разработки собственного драйвера-фильтра, нужно знать
очень тонкие взаимосвязи, которые проявляются в зависимости от того, на
каком уровне иерархии находится драйвер-фильтр. Возможны случаи, когда
драйвер одной файловой системы запрашивает сервисы у драйвера другой файловой
системы, или когда промежуточный драйвер запрашивает сервисы вышележащего
драйвера файловой системы.
Например, в случае фильтрации всех запросов, предназначенных некоторому
логическому тому, управляемому драйвером файловой системы (NTFS), необходимо
понимать всевозможные пути вовлечения драйвера NTFS в обработку запроса,
так как во всех этих случаях будут вовлекаться процедуры распределения
драйвера-фильтра. Процедуры обработки запроса на чтения/запись драйвера
файловой системы могут вовлекаться несколькими путями, например: из потока
режима ядра, вызвавшего системные сервисы ZwReadFile(), ZwWriteFile();
из потока пользовательского режима, вызвавшего системные сервисы NtReadFile(),
NtWriteFile(); из диспетчера памяти из-за обращения к отсутствующей странице
спроецированного файла; из диспетчера кэша в результате асинхронного сброса
буферов диспетчера кэша на диск; и так далее.
В некоторых ситуациях может быть приемлемым то, что драйвер - фильтр отправляет
запрос ввода/вывода на асинхронное выполнение, но в других ситуациях (например,
при обслуживании обращения к отсутствующей странице) драйвер-фильтр не
должен этого делать, иначе это приведет к взаимной блокировке или зависанию.
В случае, когда драйвер-фильтр привязывается к объекту-устройству нижнего
уровня (например, к устройству, представляющему физический диск), то возникают
другие проблемы, с которыми сталкивается подобный драйвер-фильтр. Например,
пакеты IRP, посланные драйверу диска, могут быть чаще всего ассоциированными
пакетами, созданными драйвером файловой системы, и поэтому драйвер-фильтр
не должен пытаться в свою очередь создавать ассоциированные пакеты. Обычно
от драйверов дисков ожидают, что они будут выполнять операции асинхронно,
поэтому драйвер-фильтр должен удовлетворять этим ожиданиям.
- 2. Драйвер-фильтр должен корректно работать с другими возможными драйверами-фильтрами, присоединенными до или после него. Никогда нельзя полагаться на то, что запрос, выпущенный драйвером-фильтром, пойдет напрямую нужному драйверу, так как он может быть перехвачен другим драйвером-фильтром.
- 3. Драйвер-фильтр должен знать, к чему он привязывается. Например, может получиться так, что когда драйвер-фильтр попытается привязаться к объекту-устройству, представляющему логический том файловой системы, он в действительности привяжется к объекту-устройству, представляющему физический диск, если том еще не был вмонтирован.
- 4. Драйвер-фильтр должен избегать поддержки ненужных ссылок. Если по небрежности драйвер-фильтр поддерживает лишнюю ссылку на объект-устройство назначения, то, возможно, что это будет препятствовать всем дальнейшим запросам на открытие и выполнение операций над этим устройством.
- 5. Драйвер-фильтр должен следить за контекстом потока, в котором выполняется его процедура распределения. В первую очередь это касается запросов ввода/ вывода к драйверам файловых систем, так как эти запросы должны передаваться в контексте потока, инициировавшего запрос, поэтому если драйвер-фильтр вызвал переключение контекста, например, послав запрос на исполнение рабочему потоку, то дальнейшая обработка этого запроса драйвером файловой системы приведет к непредсказуемым последствиям.
На всех уровнях, которые будут обсуждаться в дальнейшем, возможны реализация и применение драйверов-фильтров.