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


Обработка буферов

Одна из наиболее распространенных ошибок в любом драйвере связана с обработкой буферов, где буферы недопустимы или слишком малы. Эти ошибки могут разрешать переполнение буфера или вызывать сбои системы, что может скомпрометировать системную безопасность. В этой статье рассматриваются некоторые распространенные проблемы с обработкой буферов и их избежание. Он также определяет пример кода WDK, демонстрирующий правильные методы обработки буферов.

Типы буферов и недопустимые адреса

С точки зрения водителя буферы приходят в один из двух разновидностей:

  • Страничные буферы, которые могут или не могут быть резидентами в памяти.

  • Непакетные буферы, которые должны находиться в памяти.

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

  • Он изолирует недопустимый адрес в одном из диапазонов адресов "стандартный" (адрес страницы ядра, непагаченные адреса ядра или адреса пользователей).

  • Он вызывает соответствующий тип ошибки. Система всегда обрабатывает ошибки буфера либо с помощью проверки ошибок, например PAGE_FAULT_IN_NONPAGED_AREA, либо по исключению, например STATUS_ACCESS_VIOLATION. Если ошибка является проверкой ошибок, система остановит операцию. В случае исключения система вызывает обработчики исключений на основе стека. Если ни один из обработчиков исключений не обрабатывает исключение, система вызывает проверку ошибок.

Независимо от того, какой путь доступа может вызвать программа приложения, который приводит к возникновению ошибки, является нарушением безопасности в драйвере. Такое нарушение позволяет приложению вызывать атаки типа "отказ в обслуживании" всей системе.

Распространенные предположения и ошибки

Одна из наиболее распространенных проблем в этой области заключается в том, что авторы драйверов предполагают слишком много о операционной среде. Ниже приведены некоторые распространенные предположения и ошибки:

  • Драйвер просто проверяет, задан ли высокий бит в адресе. Использование фиксированного битового шаблона для определения типа адреса не работает во всех системах или сценариях. Например, эта проверка не работает на компьютерах на основе x86, если система использует четыре Gigabyte Configuration (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++, которая не является допустимой для драйверов ядра.