NdisFSendNetBufferLists 在禁用适配器时导致 BSoD

NdisFSendNetBufferLists causes BSoD when the adapter is being disabled

我有一个 NDIS 6.x LWF 驱动程序可以在 Windows 上捕获和发送数据包。这是 WinPcap 从 NDIS 5 到 NDIS 6 的更新。

此驱动程序从用户模式应用程序接收数据包并使用 NdisFSendNetBufferLists 将它们发送出去(参见 https://github.com/nmap/npcap/blob/master/packetWin7/npf/npf/Write.c 中的第 631 行)。我发现如果在发送数据包时,我禁用了 Network Connections(又名 ncpa.cpl)中的相应适配器。然后系统蓝屏了。我分析了小型转储文件,输出如下:

0: kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: fffff80745e9de30, Address of the instruction which caused the bugcheck
Arg3: ffffa38002702de0, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.

Debugging Details:
------------------

*** WARNING: Unable to verify timestamp for npf.sys

DUMP_CLASS: 1

DUMP_QUALIFIER: 400

BUILD_VERSION_STRING:  14267.1000.amd64fre.rs1_release.160213-0213

SYSTEM_MANUFACTURER:  Dell Inc.

SYSTEM_PRODUCT_NAME:  OptiPlex 7010

SYSTEM_SKU:  OptiPlex 7010

SYSTEM_VERSION:  01

BIOS_VENDOR:  Dell Inc.

BIOS_VERSION:  A14

BIOS_DATE:  06/10/2013

BASEBOARD_MANUFACTURER:  Dell Inc.

BASEBOARD_PRODUCT:  09PR9H

BASEBOARD_VERSION:  A01

DUMP_TYPE:  2

BUGCHECK_P1: c0000005

BUGCHECK_P2: fffff80745e9de30

BUGCHECK_P3: ffffa38002702de0

BUGCHECK_P4: 0

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

FAULTING_IP: 
ndis!NdisFSendNetBufferLists+c0
fffff807`45e9de30 4c8b5818        mov     r11,qword ptr [rax+18h]

CONTEXT:  ffffa38002702de0 -- (.cxr 0xffffa38002702de0)
rax=6b49534e02130018 rbx=6b49534e02130019 rcx=0000000000000001
rdx=0000000000000000 rsi=ffffd50728240030 rdi=ffffd5072c4ac8d0
rip=fffff80745e9de30 rsp=ffffa380027037e0 rbp=0000000000000000
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=0000000000060001 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
ndis!NdisFSendNetBufferLists+0xc0:
fffff807`45e9de30 4c8b5818        mov     r11,qword ptr [rax+18h] ds:002b:6b49534e`02130030=????????????????
Resetting default scope

CPU_COUNT: 4

CPU_MHZ: c79

CPU_VENDOR:  GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 3a

CPU_STEPPING: 9

CPU_MICROCODE: 6,3a,9,0 (F,M,S,R)  SIG: 1B'00000000 (cache) 1B'00000000 (init)

CUSTOMER_CRASH_COUNT:  1

DEFAULT_BUCKET_ID:  WIN8_DRIVER_FAULT

BUGCHECK_STR:  0x3B

PROCESS_NAME:  EapolLogin.exe

CURRENT_IRQL:  0

ANALYSIS_SESSION_HOST:  AKISN0W-PC

ANALYSIS_SESSION_TIME:  02-26-2016 13:42:06.0762

ANALYSIS_VERSION: 10.0.10586.567 amd64fre

LAST_CONTROL_TRANSFER:  from fffff807476f67f8 to fffff80745e9de30

STACK_TEXT:  
ffffa380`027037e0 fffff807`476f67f8 : 00000000`00000000 00000000`00000000 00000000`00000001 ffffd507`3a613570 : ndis!NdisFSendNetBufferLists+0xc0
ffffa380`02703860 fffff803`8c698c05 : ffffd507`3a6134a0 00000000`00000000 00000000`00000001 fffff680`00003140 : npf!NPF_Write+0x214 [j:\npcap\packetwin7\npf\npf\write.c @ 324]
ffffa380`027038d0 fffff803`8c69840a : ffffd507`39edba60 ffffd507`3a6134a0 ffffd507`2871aef0 ffffa380`02703b80 : nt!IopSynchronousServiceTail+0x1a5
ffffa380`02703990 fffff803`8c3d2f83 : ffff8208`1164b160 00000000`00000000 00000000`00000000 00000000`00000000 : nt!NtWriteFile+0x67a
ffffa380`02703a90 00007fff`94c21034 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
00000000`0014e248 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x00007fff`94c21034


THREAD_SHA1_HASH_MOD_FUNC:  8de63a100febe6f9f89153a5a9abc9ba86d452de

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  c12fe9b8d789ae102dec8036452ef91cdcd180b3

THREAD_SHA1_HASH_MOD:  bccfea03237cfde6486a55b63bb95e3341833378

FOLLOWUP_IP: 
npf!NPF_Write+214 [j:\npcap\packetwin7\npf\npf\write.c @ 324]
fffff807`476f67f8 8b6c2478        mov     ebp,dword ptr [rsp+78h]

FAULT_INSTR_CODE:  78246c8b

FAULTING_SOURCE_LINE:  j:\npcap\packetwin7\npf\npf\write.c

FAULTING_SOURCE_FILE:  j:\npcap\packetwin7\npf\npf\write.c

FAULTING_SOURCE_LINE_NUMBER:  324

FAULTING_SOURCE_CODE:  
   320:                         NDIS_DEFAULT_PORT_NUMBER,
   321:                         SendFlags);
   322:                 }
   323: 
>  324:             numSentPackets ++;
   325:         }
   326:         else
   327:         {
   328:             //
   329:             // no packets are available in the Transmit pool, wait some time. The 


SYMBOL_STACK_INDEX:  1

SYMBOL_NAME:  npf!NPF_Write+214

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: npf

IMAGE_NAME:  npf.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  56c2d58e

STACK_COMMAND:  .cxr 0xffffa38002702de0 ; kb

BUCKET_ID_FUNC_OFFSET:  214

FAILURE_BUCKET_ID:  0x3B_npf!NPF_Write

BUCKET_ID:  0x3B_npf!NPF_Write

PRIMARY_PROBLEM_CLASS:  0x3B_npf!NPF_Write

TARGET_TIME:  2016-02-26T02:30:30.000Z

OSBUILD:  14267

OSSERVICEPACK:  0

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

SUITE_MASK:  272

PRODUCT_TYPE:  1

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 10

OSEDITION:  Windows 10 WinNt TerminalServer SingleUserTS

OS_LOCALE:  

USER_LCID:  0

OSBUILD_TIMESTAMP:  2016-02-13 20:56:11

BUILDDATESTAMP_STR:  160213-0213

BUILDLAB_STR:  rs1_release

BUILDOSVER_STR:  10.0.14267.1000.amd64fre.rs1_release.160213-0213

ANALYSIS_SESSION_ELAPSED_TIME: 127c9

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:0x3b_npf!npf_write

FAILURE_ID_HASH:  {2eb5e15e-9853-313b-618d-2ac277a2bfb5}

Followup:     MachineOwner

上述转储分析报告中指向的源代码行不是很准确。实际上是numSentPackets ++;

上面那一行

所以下面的代码会触发 BSoD。

NdisFSendNetBufferLists(Open->AdapterHandle,
                    pNetBufferList,
                    NDIS_DEFAULT_PORT_NUMBER,
                    SendFlags);

适配器的禁用行为导致此 BSoD 是可以理解的,就像您禁用了适配器一样,您应该无法向其发送数据包。我无法阻止用户在使用我的驱动程序时禁用他的适配器。但我认为在这种情况下唯一应该发生的事情就是发送操作失败。 BSoD 太多了。

所以,我想知道根据 NDIS 的设计,让我的驱动程序阻止这种情况的正确方法是什么?谢谢!


更新

我按照 Jeffrey 的建议修改了我的代码,但同样的 BSoD 仍然发生。我的代码位于:https://github.com/nmap/npcap/commit/f68b20fca345ca195d0862856ed8ac6c0f65c957


更新 2

嗨。我还有两个问题。

1) 为什么不在FilterPause的else分支中设置me->PausePending = TRUE;?在 FilterPause 中返回 NDIS_STATUS_SUCCESS 有什么意义吗?如果我的驱动程序在 FilterPause 中返回 NDIS_STATUS_SUCCESS 后仍然调用 NdisFSendNetBufferLists(这是由用户模式应用程序控制的)怎么办?

2) 我应该在 NPF_Restart (FilterRestart) 例程中设置 Open->PausePending = TRUE; 吗?

禁用网卡时:

  1. TCPIP(和其他)将停止向您的过滤器驱动程序发送数据包。
  2. 您的过滤器驱动程序收到 FilterPause 调用。
  3. 您的过滤器驱动程序收到 FilterDetach 调用。

完成第 2 步后,您将无法创建新的 NBL。 完成第 3 步后,您将无法发起 任何东西(OID、状态指示等),您可以删除过滤器模块上下文。

我看了一眼 GitHub 代码,我注意到 NPF_Pause 例程是空操作。这就是这个错误的原因。发起 NBL 的 LWF 驱动程序必须 处理暂停。 FilterPause 的一种典型实现是这样的:

NDIS_STATUS FilterPause(. . .)
{
    NDIS_STATUS status;

    AcquireDatapathLock();

    if (me->NumberOfOriginatedSendPackets > 0 ||
        me->NumberOfOriginatedReceivePackets > 0)
    {
         me->PausePending = TRUE;
         status = NDIS_STATUS_PENDING;
    }
    else
    {
         status = NDIS_STATUS_SUCCESS;
    }

    ReleaseDatapathLock();
    return status;
}

当发起的 NBL 数量下降到零时,您的 NBL 完成处理程序应该完成暂停:

VOID CompleteSendNbl(. . .)
{
    BOOLEAN CompletePause = FALSE;
    AcquireDatapathLock();

    me->NumberOfOriginatedSendPackets -= 1;

    if (me->NumberOfOriginatedSendPackets == 0 &&
        me->PausePending)
    {
        CompletePause = TRUE;
    }

    ReleaseDatapathLock();

    if (CompletePause)
    {
        NdisFPauseComplete(. . .);
    }
}

最后,请确保在暂停后不要创建新的 NBL:

NDIS_STATUS TrySendPacket(. . .)
{
    NDIS_STATUS status;
    AcquireDatapathLock();

    if (me->PausePending)
    {
         status = NDIS_STATUS_PAUSED;
    }
    else
    {
         status = NDIS_STATUS_SUCCESS;
         me->NumberOfOriginatedSendPackets++;
    }

    ReleaseDatapathLock();

    if (status == NDIS_STATUS_SUCCESS)
    {
         . . .
         NdisFSendNetBufferLists(. . .);
    }
}

当然,上面有几种方法可以优化。但首先让它工作。如果它对您的需求来说太慢了,也许可以让它更快。