Ограничение функциональных возможностей драйвера
Ядро экспортирует ограниченный набор функций, которые можно вызывать извне. Этот ядерный API представляет собой единственный способ взаимодействия драйвера с ядром. Однако не каждому драйверу разрешается использовать любой вызов ядра. Для каждого драйвера в ядре (в таблице процессов) поддерживается битовый массив, показывающий, какие вызовы ядра может производить этот драйвер. Гранулярность вызовов ядра является достаточно мелкой. Отсутствует мультиплексирование вызовов в один и тот же номер функции. Каждый вызов индивидуально защищается собственным битом в битовом массиве. Однако на внутреннем уровне несколько вызовов может обрабатываться одной и той же ядерной функцией. Этот метод позволяет реализовать детальное управление доступом к ядру.
Например, некоторым драйверам требуется доступ по чтению и записи к данным, находящимся в пользовательских адресных пространствах, но вызовы для чтения и записи в этих пространствах являются разными. Так что мы не мультиплексируем чтение и запись в один вызов с использованием параметра «направление». Соответственно, можно разрешить, например, драйверу принтера выполнять вызов ядра для чтения данных из пользовательских процессов, но не разрешать выполнение вызовов для записи. Вследствие этого ошибка в драйвере, которому разрешено только чтение, не может привести к случайному повреждению пользовательского адресного пространства.
Сравним эту ситуацию с возможным поведением драйвера устройства в монолитном ядре. Ошибка в коде может привести к записи в адресное пространство пользовательского процесса вместо чтения из него, что разрушит процесс. Кроме того, ядерный драйвер может вызвать любую функцию во всем ядре, включая функции, которые не должны вызываться драйверами. Поскольку отсутствует какая-либо внутриядерная защита, это практически невозможно предотвратить. В нашей разработке ни один драйвер не может вызвать ядерную функцию, которая не была явно экспортирована как часть интерфейса между ядром и этим драйвером.