Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Одна из наиболее распространенных ошибок в любом драйвере связана с обработкой буферов, где буферы недопустимы или слишком малы. Эти ошибки могут позволять переполнение буфера или вызывать сбои в системе, что может скомпрометировать безопасность системы. В этой статье рассматриваются некоторые распространенные проблемы с обработкой буферов и их избежание. Он также определяет пример кода WDK, демонстрирующий правильные методы обработки буферов.
Типы буферов и недопустимые адреса
С точки зрения водителя, буферы бывают двух видов.
Страничные буферы, которые могут находиться в памяти или не находиться в ней.
Непакетные буферы, которые должны находиться в памяти.
Недопустимый адрес памяти не отображается и не поддерживается. Так как операционная система работает для устранения ошибки страницы, вызванной неправильной обработкой буфера, необходимо выполнить следующие действия.
Он изолирует недопустимый адрес в одном из "стандартных" диапазонов адресов (страничные адреса ядра, некэшированные адреса ядра или пользовательские адреса).
Он вызывает соответствующий тип ошибки. Система всегда обрабатывает ошибки буфера либо с помощью проверки ошибок, например PAGE_FAULT_IN_NONPAGED_AREA, либо по исключению, например STATUS_ACCESS_VIOLATION. Если ошибка является проверкой ошибок, система остановит операцию. В случае исключения система вызывает обработчики исключений на основе стека. Если ни один из обработчиков исключений не обрабатывает исключение, система вызывает проверку ошибок.
Независимо от того, какой путь доступа может вызвать программа приложения, который приводит к возникновению ошибки, является нарушением безопасности в драйвере. Такое нарушение позволяет приложению вызывать атаки типа "отказ в обслуживании" всей системе.
Распространенные предположения и ошибки
Одна из наиболее распространенных проблем в этой области заключается в том, что авторы драйверов предполагают слишком много о операционной среде. Ниже приведены некоторые распространенные предположения и ошибки:
Драйвер просто проверяет, задан ли высокий бит в адресе. Использование фиксированного битового шаблона для определения типа адреса не работает во всех системах или сценариях. Например, эта проверка не работает на компьютерах на основе x86, если система использует Четырёхгигабайтная настройка (4GT). При использовании 4GT адреса пользовательского режима задают высокий бит для третьего гигабайта адресного пространства.
Драйвер использует исключительно функции ProbeForRead и ProbeForWrite для проверки адреса. Эти вызовы гарантируют, что адрес является допустимым адресом пользовательского режима во время пробы. Тем не менее, нет никаких гарантий, что этот адрес останется действительным после операции зондирования. Таким образом, этот метод вводит тонкое состояние гонки, которое может привести к периодическим невоспродуцируемым авариям.
Вызовы ProbeForRead и ProbeForWrite по-прежнему необходимы. Если драйвер пропускает пробу, пользователи могут передавать допустимые адреса в режиме ядра, которые
__tryне будут перехватывать и__exceptблокировать (структурированную обработку исключений) и таким образом открывать большое отверстие безопасности.Итог заключается в том, что и проверка, и обработка структурированных исключений необходимы:
Проверка проверяет, является ли адрес адресом в пользовательском режиме, а длина буфера находится в диапазоне адресов пользователя.
Блок
__try/__exceptзащищает от доступа.
Обратите внимание, что ProbeForRead проверяет, что адрес и длина соответствуют возможному диапазону адресов в пользовательском режиме (чуть менее 2 ГБ для системы без 4GT, например), а не является ли адрес памяти допустимым. Напротив, ProbeForWrite пытается получить доступ к первому байту на каждой странице указанной длины, чтобы убедиться, что эти байты являются допустимыми адресами памяти.
Драйвер, полагающийся на функции диспетчера памяти, такие как MmIsAddressValid, чтобы убедиться, что адрес действителен. Как описано для функций проверки, эта ситуация создает состояние гонки, которое может привести к трудновоспроизводимым сбоям.
Драйвер не использует структурированную обработку исключений. Функции
__try/exceptкомпилятора используют поддержку на уровне операционной системы для обработки исключений. Исключения уровня ядра возвращаются в систему через вызов ExRaiseStatus или одну из связанных функций. Если драйвер не использует структурированную обработку исключений вокруг любого вызова, который может вызвать исключение, это приведет к проверке на наличие ошибок (обычно KMODE_EXCEPTION_NOT_HANDLED).Это ошибка в использовании структурированной обработки исключений вокруг кода, который, как ожидается, не вызывает ошибок. Это использование будет просто маскировать реальные ошибки, которые будут найдены в противном случае.
__try/__exceptРазмещение оболочки на верхнем уровне диспетчера вашей подпрограммы не является правильным решением этой проблемы, хотя иногда это рефлекторное решение, попробованное авторами драйверов.Драйвер предполагает, что содержимое памяти пользователя останется стабильным. Например, предположим, что драйвер написал значение в расположение памяти в пользовательском режиме, а затем позже в той же подпрограмме ссылается на это расположение памяти. Вредоносное приложение может активно изменить память после записи и, как следствие, привести к сбою драйвера.
Для файловых систем эти проблемы являются серьезными, так как файловые системы обычно полагаются непосредственно на доступ к буферам пользователей (метод передачи METHOD_NEITHER). Такие драйверы напрямую управляют буферами пользователей и поэтому должны включать методы предосторожности для обработки буферов, чтобы избежать сбоев на уровне операционной системы. Быстрый ввод-вывод всегда передает необработанные указатели памяти, поэтому драйверы должны защититься от аналогичных проблем, если поддерживается быстрый ввод-вывод.
Пример кода для обработки буфера
WDK содержит множество примеров проверки буфера в примере кода драйвера файловой системы CDFS и fastfat, включая:
Функция FatLockUserBuffer в fastfat\deviosup.c использует MmProbeAndLockPages для блокировки физических страниц за пользовательским буфером и MmGetSystemAddressForMdlSafe в FatMapUserBuffer для создания виртуального сопоставления страниц, заблокированных.
Функция FatGetVolumeBitmap в fastfat\fsctl.c использует ProbeForRead и ProbeForWrite для проверки буферов пользователей в API дефрагментации.
Функция CdCommonRead в cdfs\read.c использует
__tryи__exceptвокруг кода для обнуления пользовательских буферов. Пример кода в CdCommonRead, как представляется, использует ключевые словаtryиexcept. В среде WDK эти ключевые слова в C определяются с точки зрения расширений__tryкомпилятора и__except. Любой пользователь, использующий код C++, должен использовать встроенные типы компилятора для правильной обработки исключений, поскольку__tryявляется ключевым словом C++, но не ключевым словом C, и предоставляет форму обработки исключений C++, которая не подходит для драйверов ядра.