Kernel Stack Overflows

今天我想谈谈一个常见的错误,我们在日常工作钟许多案例中都看到了这一点。它涉及到驱动程序占用内核堆栈上的过多空间,从而导致内核堆栈溢出,然后将通过以下错误检查之一使系统崩溃:

1. STOP 0x7F: UNEXPECTED_KERNEL_MODE_TRAP 当参数1设置为EXCEPTION_DOUBLE_FAULT时,这是由于覆盖内核堆栈的末尾而导致的。

2. STOP 0x1E: KMODE_EXCEPTION_NOT_HANDLED, 0x7E: SYSTEM_THREAD_EXCEPTION_NOT_HANDLED, or 0x8E: KERNEL_MODE_EXCEPTION_NOT_HANDLED, 异常代码为STATUS_ACCESS_VIOLATION,表示内存访问冲突。

3. STOP 0x2B: PANIC_STACK_SWITCH, 这通常发生在内核模式驱动程序使用太多堆栈空间时。

内核堆栈概述

系统中的每个线程都分配有一个内核模式堆栈。运行在任何内核模式线程(无论是系统线程还是驱动程序创建的线程)上的代码都使用该线程的内核模式堆栈,除非该代码是DPC,在这种情况下,它在某些平台上使用处理器的DPC堆栈。堆栈负增长。这意味着堆栈的开始(底部)的地址高于堆栈的结束(顶部)。例如,让我们保持堆栈的开头是0x80f1000,这是堆栈指针(ESP)指向的位置。如果将一个DWORD值推送到堆栈上,它的地址将是0x80f0ffc。下一个DWORD值将存储在0x80f0ff8,以此类推,直到分配的堆栈的限制(顶部)。堆栈顶部以保护页为边界,以检测溢出。

内核模式堆栈的大小因不同的硬件平台而异。例如:

  • 在基于x86的平台上,内核模式堆栈是12K。
  • 在基于x64的平台上,内核模式堆栈为24K(基于x64的平台包括使用AMD64体系结构的处理器和使用Intel EM64T体系结构的处理器的系统)。
  • 在基于安腾的平台上,内核模式堆栈是32K,有一个32K后备存储。(如果处理器的寄存器文件中的寄存器用完,它将使用后备存储器来保存寄存器的内容,直到分配函数返回为止。这不会直接影响堆栈分配,但操作系统在基于安腾的平台上使用的寄存器比在其他平台上使用的寄存器多,这使得驱动程序可以使用的堆栈相对更多。)
上面列出的堆栈大小是系统施加的硬限制,所有驱动程序都需要保守地使用空间,以便它们能够共存

异常概述

现在,我们已经讨论了内核堆栈,让我们深入研究双错误实际上是如何发生的。当我们到达栈顶时,再推一条指令就会导致异常。这可以是一个简单的push指令,也可以是类似于call指令的东西,它也可以将返回地址推送到堆栈上,等等。push指令将导致第一个异常。这将导致异常处理程序启动,然后将尝试在堆栈上分配陷阱帧和其他变量。这导致了第二个异常。

这一次,操作系统利用了一种特殊的x86结构,称为任务状态段(taskstate Segment,TSS)。操作系统将寄存器的状态存储在TSS中,然后停止。TSS可以通过全局描述符表中的一个条目进行访问,并可用于调试所创建的内存转储。

通常的原因

通常有以下一个或多个设计缺陷:

1、大量使用堆栈。驱动程序编写者应该设计函数来接受指向数据结构的指针,而不是在堆栈上传递大量数据。这些数据结构应该从系统空间内存(分页池或非分页池)中分配。如果需要将大量参数从一个函数传递到另一个函数,请将参数分组到一个结构中,然后传递指向该结构的指针。
2、递归调用函数。在堆栈上传递大量数据的重嵌套或递归函数将占用太多空间并将溢出。尝试设计使用最少递归调用和嵌套函数的驱动程序。

由于x86计算机上的堆栈大小要小得多,因此在x86计算机上遇到这些问题的频率将高于任何其他平台。

调试内核堆栈溢出

完整的内核转储通常足以找到有问题的驱动程序。在这些转储中出现的最常见的错误检查代码是UNEXPECTED_KERNEL_MODE_TRAP(0x7f),第一个参数是EXCEPTION_DOUBLE_FAULT(0x8)。

当您得到这个转储时,您应该运行的第一个命令是!analyze -v。

    0: kd> !analyze -v

    *******************************************************************************

    * Bugcheck Analysis *

    *******************************************************************************

    UNEXPECTED_KERNEL_MODE_TRAP (7f)

    This means a trap occurred in kernel mode, and it's a trap of a kind that the kernel isn't allowed to have/catch (bound trap) or that

    is always instant death (double fault). The first number in the bugcheck params is the number of the trap (8 = double fault, etc)

    Consult an Intel x86 family manual to learn more about what these traps are. Here is a *portion* of those codes:

    If kv shows a taskGate

    use .tss on the part before the colon, then kv.

    Else if kv shows a trapframe

    use .trap on that value

    Else

    .trap on the appropriate frame will show where the trap was taken(on x86, this will be the ebp that goes with the procedure KiTrap)

    Endif

    kb will then show the corrected stack.

    Arguments:

    Arg1: 00000008, EXCEPTION_DOUBLE_FAULT

    Arg2: 80042000

    Arg3: 00000000

    Arg4: 00000000

    Debugging Details:

    ------------------

    BUGCHECK_STR: 0x7f_8

    TSS: 00000028 -- (.tss 0x28)

    eax=87b90328 ebx=87b90328 ecx=8aa3d8c0 edx=87b90328 esi=b8cb7138 edi=8084266a

    eip=f7159c53 esp=b8cb7000 ebp=b8cb7010 iopl=0 nv up ei pl nz na po nc

    cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202

    Ntfs!NtfsInitializeIrpContext+0xc:

    f7159c53 57 push edi

    Resetting default scope

    DEFAULT_BUCKET_ID: DRIVER_FAULT

    PROCESS_NAME: System

    CURRENT_IRQL: 1

    TRAP_FRAME: b8cb8620 -- (.trap 0xffffffffb8cb8620)

    ErrCode = 00000000

    eax=c1587000 ebx=0000000e ecx=0000000f edx=00000000 esi=87dca350 edi=00000000

    eip=8093837b esp=b8cb8694 ebp=b8cb86d0 iopl=0 nv up ei ng nz ac po cy

    cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010293

    nt!CcMapData+0x8c:

    8093837b 8a10 mov dl,byte ptr [eax] ds:0023:c1587000=??

    Resetting default scope

    LAST_CONTROL_TRANSFER: from f7158867 to f7159c53

让我们按照调试器给我们的指令操作。既然调试器给了我们一个.tss命令,让我们运行它。之后,跑 !thread获取线程摘要的线程:

    0: kd> .tss 0x28

    eax=87b90328 ebx=87b90328 ecx=8aa3d8c0 edx=87b90328 esi=b8cb7138 edi=8084266a

    eip=f7159c53 esp=b8cb7000 ebp=b8cb7010 iopl=0 nv up ei pl nz na po nc

    cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202

    Ntfs!NtfsInitializeIrpContext+0xc:

    f7159c53 57 push edi

    0: kd> !thread

    THREAD 87dca350 Cid 0420.0990 Teb: 7ffdf000 Win32Thread: efdbe430 RUNNING on processor 0

    IRP List:

    89cba088: (0006,01fc) Flags: 00000404 Mdl: 00000000

    Not impersonating

    DeviceMap e10008d8

    Owning Process 8ab8e238 Image: System

    Wait Start TickCount 7260638 Ticks: 0

    Context Switch Count 17 LargeStack

    UserTime 00:00:00.000

    KernelTime 00:00:00.015

    Start Address 0x4a6810ea

    Stack Init b8cba000 Current b8cb7c64 Base b8cba000 Limit b8cb7000 Call 0

    Priority 14 BasePriority 13 PriorityDecrement 0

我们正在寻找内核堆栈限制(上面红色部分)。对于这个特定的堆栈,我们看到堆栈从b8cba000开始,到b8cb7000结束。如果您查看上面.tss输出中的ESP寄存器,您将看到我们已经达到堆栈限制。当前尝试的指令是一个push,它溢出堆栈并导致错误检查。现在,我们已经确定确实存在堆栈溢出,让我们找出是什么导致了这种情况,以及谁是违规的驱动程序。我要做的第一件事就是倾倒垃圾。您可能需要增加显示的整个堆栈的帧数。

    0: kd> kb

    *** Stack trace for last set context - .thread/.cxr resets it

    ChildEBP RetAddr

    b8cb7010 f7158867 Ntfs!NtfsInitializeIrpContext+0xc

    b8cb71bc 8083f9c0 Ntfs!NtfsFsdRead+0xb7

    b8cb71d0 f7212c53 nt!IofCallDriver+0x45

    b8cb71f8 8083f9c0 fltmgr!FltpDispatch+0x6f

    b8cb720c ba547bcc nt!IofCallDriver+0x45

    WARNING: Stack unwind information not available. Following frames may be wrong.

    b8cb7214 8083f9c0 tmpreflt!TmpAddRdr+0x7b8

    b8cb7228 ba4e08be nt!IofCallDriver+0x45

    b8cb7430 ba4e09d3 DRIVER_A+0x28be

    b8cb7450 b85fa306 DRIVER_A+0x29d3

    b8cb763c b85fa50d DRIVER_B+0x8306

    b8cb765c 8082f0d7 DRIVER_B+0x850d

    b8cb7674 8082f175 nt!IoPageRead+0x109

    b8cb76f8 80849cd5 nt!MiDispatchFault+0xd2a

    b8cb7754 80837d0a nt!MmAccessFault+0x64a

    b8cb7754 8093837b nt!KiTrap0E+0xdc

    b8cb781c f718c0ac nt!CcMapData+0x8c

    b8cb783c f718c6e6 Ntfs!NtfsMapStream+0x4b

    b8cb78b0 f718c045 Ntfs!NtfsReadMftRecord+0x86

    b8cb78e8 f718c0f4 Ntfs!NtfsReadFileRecord+0x7a

    b8cb7920 f7155c3c Ntfs!NtfsLookupInFileRecord+0x37

    b8cb7a30 f715746a Ntfs!NtfsLookupAllocation+0xdd

    b8cb7bfc f7157655 Ntfs!NtfsPrepareBuffers+0x25d

    b8cb7dd8 f715575e Ntfs!NtfsNonCachedIo+0x1ee

    b8cb7ec4 f71588de Ntfs!NtfsCommonRead+0xaf5

    b8cb8070 8083f9c0 Ntfs!NtfsFsdRead+0x113

    b8cb8084 f7212c53 nt!IofCallDriver+0x45

    b8cb80ac 8083f9c0 fltmgr!FltpDispatch+0x6f

    b8cb80c0 ba547bcc nt!IofCallDriver+0x45

    b8cb80c8 8083f9c0 tmpreflt!TmpAddRdr+0x7b8

    b8cb80dc ba4e08be nt!IofCallDriver+0x45

    b8cb82e4 ba4e09d3 DRIVER_A+0x28be

    b8cb8304 b85fa306 DRIVER_A+0x29d3

    b8cb84f0 b85fa50d DRIVER_B+0x8306

    b8cb8510 8082f0d7 DRIVER_B+0x850d

    b8cb8528 8082f175 nt!IoPageRead+0x109

    b8cb85ac 80849cd5 nt!MiDispatchFault+0xd2a

    b8cb8608 80837d0a nt!MmAccessFault+0x64a

    b8cb8608 8093837b nt!KiTrap0E+0xdc

    b8cb86d0 f718c0ac nt!CcMapData+0x8c

    b8cb86f0 f718ef1b Ntfs!NtfsMapStream+0x4b

    b8cb8720 f7186aa7 Ntfs!ReadIndexBuffer+0x8f

    b8cb8894 f7187042 Ntfs!NtfsUpdateFileNameInIndex+0x62

    b8cb8990 f7186059 Ntfs!NtfsUpdateDuplicateInfo+0x2b0

    b8cb8b98 f7186302 Ntfs!NtfsCommonCleanup+0x1e82

    b8cb8d08 8083f9c0 Ntfs!NtfsFsdCleanup+0xcf

    b8cb8d1c f7212c53 nt!IofCallDriver+0x45

    b8cb8d44 8083f9c0 fltmgr!FltpDispatch+0x6f

    b8cb8d58 ba54809a nt!IofCallDriver+0x45

    b8cb8d80 ba54d01d tmpreflt!TmpQueryFullName+0x454

    b8cb8d90 8083f9c0 tmpreflt!TmpQueryFullName+0x53d7

    b8cb8da4 ba4e08be nt!IofCallDriver+0x45

    b8cb8fac ba4e09d3 DRIVER_A+0x28be

    b8cb8fcc b85fa306 DRIVER_A+0x29d3

    b8cb91b8 b85fa50d DRIVER_B+0x8306

    b8cb91d8 80937f75 DRIVER_B+0x850d

    b8cb9208 8092add4 nt!IopCloseFile+0x2ae

    b8cb9238 8092af7a nt!ObpDecrementHandleCount+0x10a

    b8cb9260 8092ae9e nt!ObpCloseHandleTableEntry+0x131

    b8cb92a4 8092aee9 nt!ObpCloseHandle+0x82

    b8cb92b4 80834d3f nt!NtClose+0x1b

    b8cb92b4 8083c0fc nt!KiFastCallEntry+0xfc

    b8cb9330 bf835765 nt!ZwClose+0x11

    b8cb9608 bf8aa2dd win32k!bCreateSection+0x2ad

    b8cb9660 bf826b45 win32k!EngMapFontFileFDInternal+0xc6

    b8cb96c0 bf82784a win32k!PUBLIC_PFTOBJ::bLoadFonts+0x17f

    b8cb991c bf9bcb67 win32k!PUBLIC_PFTOBJ::bLoadAFont+0x77

    b8cb9af0 bf9bcb16 win32k!bInitOneStockFontInternal+0x42

    b8cb9b0c bf9bb0e8 win32k!bInitOneStockFont+0x3f

    b8cb9cf4 bf9ba845 win32k!bInitStockFontsInternal+0x12a

    b8cb9cfc bf8246ad win32k!bInitStockFonts+0xa

    b8cb9d48 bf8242d5 win32k!InitializeGreCSRSS+0x149

    b8cb9d50 80834d3f win32k!NtUserInitialize+0x66

    b8cb9d50 7c82ed54 nt!KiFastCallEntry+0xfc

    0015fdb0 00000000 0x7c82ed54

下一步是计算每个帧占用的空间。这可以通过手动遍历堆栈来完成。只需从每个帧的当前EBP中减去后续EBP,然后将所有模块使用的空间相加。

Module Stack Usage Percentage
Ntfs 4152 36%
DRIVER_A 1572 14%
win32k 2592 22%
DRIVER_B 1656 14%
tmpreflt 72 1%
fltmgr 120 1%
nt 1420 12%

这很容易责怪NTFS,因为它是最顶级的堆栈用户,但是仔细看一下。尽管在我们的示例中NTFS使用了大部分空间,但这是由于驱动程序A和驱动程序B重复调用NTFS来访问数据。单独来看,很可能两个驱动程序都不会引起问题,但两个驱动程序结合起来会导致错误检查。认真的驱动程序编写和高效的堆栈使用本可以避免这个问题。两个驱动程序都需要优化对NTFS的调用次数。

原文地址:https://www.cnblogs.com/yilang/p/13581248.html