понедельник, 27 июля 2020 г.

Может ли колбек PsSetCreateThreadNotifyRoutine выполняться на APC_LEVEL?

Msdn утверждает, что нет: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreatethreadnotifyroutine

Реальность утверждает, что да. По крайней мере на windows 8.

Кратенько суть: есть установленный колбек на создание новых потоков в драйвере, в нем среди прочего вызывается ZwClose, и, внезапно, колбек вызывается на IRQL == APC_LEVEL.

И это дело ловит верифаер:

DRIVER_VERIFIER_DETECTED_VIOLATION (c4)
A device driver attempting to corrupt the system has been caught.  This is
because the driver was specified in the registry as being suspect (by the
administrator) and the kernel has enabled substantial checking of this driver.
If the driver attempts to corrupt the system, bugchecks 0xC4, 0xC1 and 0xA will
be among the most commonly seen crashes.
Arguments:
Arg1: 000000000002001f, ID of the 'IrqlZwPassive' rule that was violated.
Arg2: fffff8800109ba10, A pointer to the string describing the violated rule condition.
Arg3: 0000000000000000, Reserved (unused).
Arg4: 0000000000000000, Reserved (unused).

Debugging Details:
------------------
DV_VIOLATED_CONDITION:  ZwClose should only be called at IRQL = PASSIVE_LEVEL.
LAST_CONTROL_TRANSFER:  from fffff803b91ed0ea to fffff803b90ec930

STACK_TEXT: 
fffff880`049cf4d8 fffff803`b91ed0ea : 00000000`00000000 00000000`000000c4 fffff880`049cf640 fffff803`b91714b8 : nt!DbgBreakPointWithStatus
fffff880`049cf4e0 fffff803`b91ec742 : 00000000`00000003 fffff880`049cf640 fffff803`b9171e90 00000000`000000c4 : nt!KiBugCheckDebugBreak+0x12
fffff880`049cf540 fffff803`b90f2144 : 00000000`00000000 fffff880`0109925d 00000000`00000000 00000000`00000000 : nt!KeBugCheck2+0x79f
fffff880`049cfc60 fffff880`0109921f : 00000000`000000c4 00000000`0002001f fffff880`0109ba10 00000000`00000000 : nt!KeBugCheckEx+0x104
fffff880`049cfca0 fffff880`01090529 : ffffffff`80000498 fffff803`b96d4180 fffff980`032c4ee0 fffffa80`1c505b00 : VerifierExt!SLIC_abort+0x47
fffff880`049cfce0 fffff880`0109054e : fffff880`049cfdb0 fffff880`049cfdf0 fffffa80`1c505b00 00000000`00000000 : VerifierExt!SLIC_ZwClose_entry_irqlzwpassive+0x1d
fffff880`049cfd10 fffff880`08f6c9be : 00000000`000010a0 fffff880`049d0180 00000000`00000004 fffff880`049d0180 : VerifierExt!ZwClose_wrapper+0x1a
fffff880`049cfe40 fffff803`b94a668d : fffff803`b930d7a8 fffffa80`1bb3d680 fffff803`b930d7a8 fffff880`049d03d0 : xxx!ThreadNotify+0x73
fffff880`049cfea0 fffff803`b94a9b3a : fffffa80`18f68b00 00000000`00000000 fffff6fc`01dca000 fffff880`049d00e0 : nt!PspInsertThread+0x61d
fffff880`049d0080 fffff803`b940f8c0 : 00001000`18f68b00 00000000`00000000 fffff803`b93c6a80 00000000`00000000 : nt!PspCreateThread+0x216
fffff880`049d0320 fffff803`b940ed7a : 00000000`00000000 00000000`00000001 fffffa80`1c638150 fffff880`049d0679 : nt!PsCreateSystemThreadEx+0x134
fffff880`049d0590 fffff803`b93d5702 : 00000000`00000006 00000000`00000000 00000000`00000000 fffff880`049d0820 : nt!PsCreateSystemThread+0x3a
fffff880`049d05f0 fffff803`b93d7e21 : 00000000`00000001 00000000`00000000 00000000`00000000 00000000`00000002 : nt!PopFlushVolumes+0x2c2
fffff880`049d06e0 fffff803`b90f1053 : fffffa80`18f68b00 fffff803`b90efb00 00000000`c0000004 00000000`00000001 : nt!NtSetSystemPowerState+0x495
fffff880`049d0820 fffff803`b90f6230 : fffff803`b93e983c 00000000`00534310 00000000`00000006 00000000`00000005 : nt!KiSystemServiceCopyEnd+0x13
fffff880`049d09b8 fffff803`b93e983c : 00000000`00534310 00000000`00000006 00000000`00000005 00000000`00000004 : nt!KiServiceLinkage
fffff880`049d09c0 fffff803`b90f1053 : fffffa80`18f68b00 fffffa80`18f68b00 fffff880`c0000004 0000003e`e9aafac8 : nt! ?? ::OKHAJAOM::`string'+0x3584
fffff880`049d0b00 000007fc`fed7447b : 000007f7`8f091f3c 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
0000003e`e9aafae8 000007f7`8f091f3c : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`000003ea : ntdll!NtShutdownSystem+0xa

Текущий IRQL == APC_LEVEL, а ZwClose действительно должна быть вызвана на PASSIVE_LEVEL, так что вердикт от верифаера справедлив.

Кто задирает IRQL до APC_LEVEL? Функция NtSetSystemPowerState:

NtSetSystemPowerState:
PAGELK:0000000140360C2E mov rbx, cr8
PAGELK:0000000140360C32 mov cr8, r13 <== r13 равна единице, это APC_LEVEL

На x64 регистр CR8 содержит информацию о приоритизации внешних прерываний (0-3биты), то есть текущий irq level.

Почему так происходит:

При выполнении NtShutdownSystem система создает до 8ми системных потоков, которые скидывают данные томов на диск. Подсчитывается количество томов, обходом списка PopVolumeDevices, затем создаются системные потоки (кол-во равно кол-ву томов, но не более 8ми). Для синхронизации доступа к списку томов используется FastMutex, который задирает IRQL до APC_LEVEL. Код выше с CR8 это просто заинлайненный код захвата fast mutex.

Псевдокод примерно такой(очень упрощенно):

ExAcquireFastMutex() <= IRQL становится равным APC_LEVEL

1) обход списка томов, для их подсчета
2) создание системных потоков которые flush'ат данные томов на диск через ZwFlushBuffersFile(кеш файловой системы скидывается на диск)
3) системные потоки при создании триггерят колбеки, установленные через PsSetCreateThreadNotifyRoutine(IRQL все еще равен APC_LEVEL), в колбеках могут вызываться функции, которые работают только на PASSIVE_LEVEL.

ExReleaseFastMutex()

Это является багом в ядре ОС, т.к. правильная реализация должна использовать guarded mutex.
Несмотря на то, что в msdn пишут, что с win8 фаст и гвардед мьютексы реализованы одинаково, видно, что это не так.