Далее по тексту я хочу поделиться своим опытом разбора одного BSOD'а. Падение достаточно банальное: разыменование NULL-указателя. А вот стек падения достаточно странный:
- nt!ExpLookupHandleTableEntry
- nt!ExMapHandleToPointerEx
- nt!ObReferenceObjectByHandle
Посмотрев в WRK несложно догадать, что поле ObjectTable структуры nt!_EPROCESS у процесса, с описателем которого работает драйвер, равно 0. Важная особенность: драйвер работает не с текущим процессом, поэтому для корректного referenc'а описателя используется вызов функции KeStackAttachProcess(...).
Но возникает вопрос: когда у процесса поле ObjectTable может содержать 0? Обратившись к WRK несложно выяснить, что это может происходить в момент, когда процесс уже сигнальный, т.е. уничтожается. Написав небольшой тест я добился стабильного воспроизведения BSOD'а. Шаги теста следующие:
- Процесс обращается к драйверу, передавая ему значение своего описателя.
- Драйвер инкрементирует количество ссылок на объект процесса-источника, передавая указатель на процесс и описатель в WorkItem. Запрос от процесса завершается.
- Процесс спокойно уничтожается.
- WorkItem ожидает пока целевой процесс станет сигнальным (уничтожится).
- WorkItem вызывает KeStackAttachProcess(...), что бы переключить текущий процесс, а, соответственно, и таблицу описателей.
- WorkItem вызывает ObReferenceObjectByHandle(...) для попытки получения указателя на объект.
Если посмотреть на код ядра, то можно обнаружить неэкспортируемую функцию nt!ObReferenceProcessHandleTable, которая безопасно проверяет поле ObjectTable объекта процесса. Но этой функцией пользуется достаточно ограниченный набор экспортируемых API-функций ядра. В частности: ObFindHandleForObject и ObDuplicateObject, вызываемая из ZwDuplicateObject(...). Первая функция, в нашем случае, никак не поможет. А вот вызов дублирования описателя в безопасный процесс решает проблему. Кстати, в вышеописанном случае вернется соответствующий говорящий статус ошибки: STATUS_PROCESS_IS_TERMINATING.
Вывод достаточно прост: связка вызовов KeStackAttachProcess + ObReferenceObjectByHandle небезопасна, если нельзя гарантировано обеспечить то, что целевой процесс не уничтожился. Нужно гарантировать либо то, что процесс не будет сигнальным, например Kernel-ожиданием в одной из его нитей, либо использовать вызов ZwDuplicateObject(...).
Еще хочу немного расписать диагностирование ошибки. С включенным verifier XP Sp2 пропускает этот вызов без проблем. А вот Windows 8 (проверено на 9200) выдает опережащий BSOD:
...
Arguments:
Arg1: 0000003c, ObReferenceObjectByHandle is being called with a bad handle.
Информативность сообщения, честно говоря, не радует. Но вот если запустить это на checked-сборке той же XP Sp2, то перед BSOD'ом будет assert:
*** Source File: d:\xpsprtm\base\ntos\ob\obref.c, line 1214
Break repeatedly, break Once, Ignore, terminate Process, or terminate Thread (boipt)?
Я считаю, это еще один повод прогонять свои драйвера на checked-сборках.
ΞρεΤΙκ