Windows Kernel Drivers — Стандартные ошибки – IRQL

от автора

Данная статья нацелена на тех, кто только недавно начал разрабатывать kernel-драйвера под ОС Windows. В 100-ый раз видишь ненавистную надпись IRQL_NOT_LESS_OR_EQUAL и этот грустный смайлик? Тогда прошу пройти под кат.

Одной из основных ошибок, которую я и сам совершал, является жонглирование IRQL так, как душе угодно, и неполное понимание внутреннего устройства работы приоритетов потоков в ядре Windows.

К примеру, у вас есть кусок кода, который генерирует какое-либо событие по PID-процесса.

KSPIN_LOCK SharedDataLock;  NTSTATUS SomeFunction(_In_ HANDLE ProcessId) {     KIRQL OriginalIrql;      NTSTATUS status = STATUS_UNSUCCESSFUL;      // lock shared data access     KeAcquireSpinLock(&SharedDataLock, &OriginalIrql);      // some code, which works with shared data     PVOID data = GetProcessSharedData(ProcessId);     NT_ASSERT("GetProcessSharedData() must always return shared data.", data);      PEPROCESS Process = NULL;     // next we need to get process image name     status = PsLookupProcessByProcessId( 									ProcessId, 									&Process                                 );      if ( !NT_SUCCESS( status ) )     {         goto LOCK_RELEASE;     }      PUNICODE_STRING ProcessImage = NULL;     status = GetProcessImagename(Process, &ProcessImage);     if ( !NT_SUCCESS( status ) )     {         goto PROCESS_LINK_DEREF;     }      // generate some event for our log     GenerateEvent( ProcessImage, data );  PROCESS_LINK_DEREF: 	ObDereferenceObject(Process); LOCK_RELEASE:     // release lock      	KeReleaseSpinLock(&SharedDataLock, OriginalIrql); 	return status; }

Внутри данного кода, используются разделяемые данные, синхронизация которых, обеспечивается спинлоком. Также нам нужно получить имя процесса, чтобы залогировать событие.

Уже увидели ошибку в данном фрагменте?

PsLookupProcessByProcessId() – требует соблюдения условия: IRQL <= APC_LEVEL.

Так что, подобный код будет часто выдавать BSOD с кодом ошибки IRQL_NOT_LESS_OR_EQUAL.

И вот дальше, начинается самое интересное. Первое решение, которое придёт в голову новичкам, будет менять уровень IRQL перед вызовом данной функции так, чтобы условие соблюдалось.

То есть, переписать код — вот так:

KSPIN_LOCK SharedDataLock;  VOID SetIrql(_In_ KIRQL Irql) { 	if ( KeGetCurrentIrql() > Irql ) 		KeLowerIrql(Irql); 	else if ( KeGetCurrentIrql() < Irql ) 		KzRaiseIrql(Irql); }  NTSTATUS SomeFunction(_In_ HANDLE ProcessId) {     KIRQL OriginalIrql;      NTSTATUS status = STATUS_UNSUCCESSFUL;      // lock shared data access     KeAcquireSpinLock(&SharedDataLock, &OriginalIrql);      // some code, which works with shared data     PVOID data = GetProcessSharedData(ProcessId);     NT_ASSERT("GetProcessSharedData() must always return shared data.", data);      SetIrql(APC_LEVEL);      PEPROCESS Process = NULL;     // next we need to get process image name     status = PsLookupProcessByProcessId(                                      ProcessId,                                      &Process                                 );      SetIrql(DISPATCH_LEVEL);      if ( !NT_SUCCESS( status ) )     {         goto LOCK_RELEASE;     }      PUNICODE_STRING ProcessImage = NULL;     status = GetProcessImagename(Process, &ProcessImage);     if ( !NT_SUCCESS( status ) )     {         goto PROCESS_LINK_DEREF;     }      // generate some event for our log     GenerateEvent( ProcessImage, data );  PROCESS_LINK_DEREF: 	ObDereferenceObject(Process); LOCK_RELEASE:   // release lock      	KeReleaseSpinLock(&SharedDataLock, OriginalIrql); 	return status; }

Вот теперь то, всё работает достаточно стабильно. Но, на самом деле это не так. Данный код только хорошо маскирует проблему, снижая шансы её проявления до минимума, но в 1 из 1000 случаев, она всё же всплывёт, а вы будет рвать на себе волосы, пытаясь понять в чём же ошибка.

И тут нужно вспомнить одно из правил написания драйверов, а именно:
«Понижать IRQL можно только в том случае, если вы его собственноручно повышали, и только до его предыдущего значения!»

Для примера:

Если какой-либо код вызвал вашу функцию на IRQL = APC_LEVEL, то вы не имеете права опустить его ниже данного уровня. Вы можете поднять IRQL до DISPATCH_LEVEL, потом опустить обратно до APC_LEVEL, но не ниже.

Таким образом, более приемлемым вариантом кода, будет:

KSPIN_LOCK SharedDataLock;  _IRQL_requires_max_(APC_LEVEL) NTSTATUS SomeFunction(_In_ HANDLE ProcessId) {     NTSTATUS status = STATUS_UNSUCCESSFUL;      PEPROCESS Process = NULL;     // next we need to get process image name     status = PsLookupProcessByProcessId(                                    ProcessId,                                    &Process                                 );      if ( !NT_SUCCESS( status ) )     {         goto EXIT_ROUTINE;     }      PUNICODE_STRING ProcessImage = NULL;     status = GetProcessImagename(Process, &ProcessImage);     if ( !NT_SUCCESS( status ) )     {         goto PROCESS_LINK_DEREF;     }      // lock shared data access     KeAcquireSpinLock(&SharedDataLock, &OriginalIrql);      // some code, which works with shared data     PVOID data = GetProcessSharedData(ProcessId);     NT_ASSERT("GetProcessSharedData() must always return shared data.", data);      // generate some event for our log     GenerateEvent( ProcessImage, data );      // release lock     KeReleaseSpinLock(&SharedDataLock, OriginalIrql); 		 PROCESS_LINK_DEREF: 	ObDereferenceObject(Process); EXIT_ROUTINE: 	return status; }	

А вспомогательные функции по типу SetIrql() из 2-го примера, в принципе не являются адекватными с точки зрения интерфейса, т.к. при проектировании отдельных методов в вашем драйвере, важно продумывать ограничения накладываемые на предусловия вызова вашей функции.

Для описания данных предусловий, удобно использовать аннотации SAL, их список вы можете посмотреть тут:

MSDN SAL 2.0

Также Microsoft предоставляет небольшой whitepaper(в самом низу статьи) по управлению приоритетами потоков в ядре, и более подробно рассказывает некоторые тонкости по работе с ними:

MSDN Managing Hardware Priorities

Если же, вам всё-таки нужно каким-либо образом вызвать какое-либо Api, требующее более низких значений IRQL, то одним из вариантов решения данной проблемы могут стать WorkItem’ы. Но о них, я расскажу уже в другой статье.

ссылка на оригинал статьи https://habr.com/ru/post/548732/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *