1. Overview

  • Binaries: ndis_unpatched.sys vs. ndis_patched.sys
  • Overall Similarity: 98.66%
  • Diff Statistics: 3451 matched functions (3409 identical, 42 changed), 0 unmatched functions in either direction.
  • Verdict: The patch fixes the same out-of-bounds read (CWE-125) in two NDIS receive-path Ethernet/SNAP parsers and adds a NULL guard on the MDL pointer in each. ndisSortNetBufferLists classifies/sorts received NET_BUFFER_LISTs and is reached from the standard miniport receive-indication dispatch (ndisMDispatchReceiveNetBufferLists and related), so its instance is reachable on the ordinary receive path. ndisParseReceivedNBL is the same parser reached only from the datapath receive verifier, which is enabled per adapter, so that instance is not reachable on the default path. In both, the LLC/SNAP branch reads a 16-bit field at data offset 0x14 (bytes 20-21) while the preceding length check only guaranteed the remaining mapped length was at least 0xE (14); the patch adds a remaining >= 0x16 (22) check before that read. The out-of-bounds value is used only for the driver's internal EtherType/protocol classification and is not returned to the frame sender. The patch also tightens embedded-descriptor validation on the NDIS user-mode PnP/IOCTL path (ndisValidateEmbeddedBufferBounds gains a minimum-offset check); that change is input-validation hardening, not a memory-safety fix, because the sub-buffer was already bounded to the parent buffer in both builds. All remaining changed functions are WIL feature-staging, tracing, or register-allocation churn.

2. Vulnerability Summary

Finding 1: Out-of-bounds read in the receive-verifier Ethernet/SNAP parser

  • Severity: Low
  • Vulnerability Class: Out-of-bounds read (CWE-125); added NULL-pointer guard (defense in depth)
  • Affected Function: ndisParseReceivedNBL @ 0x1C006E370 (unpatched) → 0x1C00141EC (patched)
  • Root Cause: On the 802.3 path the routine loads CurrentMdl = FirstNetBuffer->CurrentMdl and reads CurrentMdl->ByteCount with no NULL check, then verifies that the remaining mapped data length (ByteCount - CurrentMdlOffset) is at least 0xE (14). On the LLC/SNAP branch (data bytes 0xAA, 0xAA, 0x03 at offsets 14/15/16) it reads a 16-bit field at data offset 0x14 (bytes 20-21) without first requiring the remaining length to be at least 0x16 (22). A truncated SNAP frame whose remaining length is between 14 and 21 bytes therefore causes a read of up to ~8 bytes past the valid MDL data.
  • Reachability: ndisParseReceivedNBL has exactly one caller, ndisNblVerifyRxIndication (the NDIS datapath receive verifier). NdisFIndicateReceiveNetBufferLists and NdisMIndicateReceiveNetBufferLists invoke that verifier only when the adapter's datapath-verification flag is set (filter handle +0x38 bit 0x200; miniport handle +0xE68 bit 0x800). The datapath verifier is a diagnostic feature (ndisDatapathVerifierMode / MmIsVerifierEnabled) that is off by default. The normal receive fast path does not call this parser.
  • Data Flow (Call Chain):
    1. NdisMIndicateReceiveNetBufferLists / NdisFIndicateReceiveNetBufferLists (only when datapath verification is enabled for the adapter)
    2. ndisNblVerifyRxIndication (0x1C00722BC)
    3. ndisParseReceivedNBL (0x1C006E370)

Finding 3: Out-of-bounds read in the receive NBL-sort Ethernet/SNAP parser

  • Severity: Low
  • Vulnerability Class: Out-of-bounds read (CWE-125); added NULL-pointer guard (defense in depth)
  • Affected Function: ndisSortNetBufferLists @ 0x1C0012960 (unpatched) → 0x1C0012B00 (patched)
  • Root Cause: This is the same defect as Finding 1 in a second, separately compiled parser. When it classifies a received NET_BUFFER_LIST it loads CurrentMdl = FirstNetBuffer->CurrentMdl and reads CurrentMdl->ByteCount with no NULL check, then requires the remaining mapped length (ByteCount - CurrentMdlOffset) to be at least 0xE (14). On the LLC/SNAP branch (data bytes 0xAA, 0xAA, 0x03 at offsets 14/15/16) it reads a 16-bit field at data offset 0x14 (bytes 20-21) without first requiring the remaining length to be at least 0x16 (22). A truncated SNAP frame whose remaining mapped length is between 14 and 21 bytes therefore causes a read of up to ~8 bytes past the valid MDL data.
  • Reachability: Unlike Finding 1, this parser is not verifier-gated. ndisSortNetBufferLists is called from the standard miniport receive-indication dispatch: ndisMTopReceiveNetBufferLists (0x1C0011EA0), ndisMDispatchReceiveNetBufferLists (0x1C009DD90), ndisMDispatchReceiveNetBufferListsWithLock (0x1C00322D0), ndisDoPeriodicReceivesIndication (0x1C00A99D4), and ndisMIndicateSplitNblChain (0x1C00AA6B0). The L2 header is parsed to classify/sort the indicated NBLs; the parse is taken unless a precomputed protocol value is already present (*(NDIS_NBL_RCV_TRACKER->field0 + 0x1D0) != 0 selects the precomputed value at NET_BUFFER_LIST+0xC8 instead). The out-of-bounds value becomes the NBL's internal EtherType/protocol classification; it is not returned to the frame sender. On a typical hardware receive buffer (a fixed-size DMA buffer larger than the frame) the read stays inside the buffer and merely misreads stale bytes; it crosses an allocation boundary only when the MDL is sized to the frame with no mapped page following, in which case a boundary-crossing read under Special Pool bugchecks.
  • Data Flow (Call Chain):
    1. Miniport receive indication (ndisMTopReceiveNetBufferLists / ndisMDispatchReceiveNetBufferLists / ndisMDispatchReceiveNetBufferListsWithLock / ndisDoPeriodicReceivesIndication / ndisMIndicateSplitNblChain)
    2. ndisSortNetBufferLists (0x1C0012960)

Finding 2: Missing minimum-offset check in embedded-descriptor validation

  • Severity: Low (input-validation hardening)
  • Vulnerability Class: Improper input validation (CWE-20); no demonstrated out-of-bounds access
  • Affected Function: ndisValidateEmbeddedBufferBounds @ 0x1C0116760 (unpatched) → 0x1C010C3CC (patched), plus its wrappers.
  • Root Cause / What Changed: In both builds the validator already enforced that the embedded sub-buffer [base+Offset, base+Offset+Length) lies entirely within the parent input buffer [base, base+parent_len), so downstream reads never exceeded the parent allocation. The patch adds one parameter (a minimum) and rejects the request when Offset < minimum. The NDIS_VAR_DATA_DESC wrappers now pass that minimum (0x28 for the 32-bit thunk descriptor variant, 0x48 for the native variant), forcing the descriptor's variable data to begin beyond the fixed request header rather than overlapping it. This prevents an attacker-supplied descriptor from aliasing the fixed structure fields; it is not an out-of-bounds read, because containment was, and remains, enforced in both builds and the calling handler's own size and overflow checks (0x48 minimum, offset+size overflow rejection) are present in both builds.
  • Reachability: NDIS user-mode PnP operation path, exposed locally via IRP_MJ_DEVICE_CONTROL to the NDIS device.
  • Data Flow (Call Chain):
    1. IRP_MJ_DEVICE_CONTROL dispatch
    2. ndisHandlePnPRequest (0x1C0144008)
    3. ndisValidateNdisVarDataDesc32InputString (0x1C012DF58) / ndisValidateNdisVarDataDescInputString (0x1C01166DC)
    4. ndisValidateEmbeddedBufferBounds (0x1C0116760)

3. Decompilation Diff

Finding 1: Parser (ndisParseReceivedNBL)

Unpatched (ndis_unpatched.sys, 0x1C006E370), 802.3 path:

  FirstNetBuffer = a2->FirstNetBuffer;
  CurrentMdl = FirstNetBuffer->CurrentMdl;       // no NULL check
  ByteCount = CurrentMdl->ByteCount;
  if ( ByteCount < 0xE
    || (CurrentMdlOffset = FirstNetBuffer->CurrentMdlOffset, ByteCount <= (unsigned int)CurrentMdlOffset)
    || ByteCount - (unsigned int)CurrentMdlOffset < 0xE )   // remaining only checked >= 0xE
  { ... return 0; }
  ...
  v14 = &MappedSystemVa[CurrentMdlOffset];
  ...
  if ( v14[14] == 0xAA && v14[15] == 0xAA && v14[16] == 3 )
  {
    v7 = *((_WORD *)v14 + 10);   // OOB: reads data offset 0x14 with only remaining >= 0xE guaranteed
    ...
  }

Patched (ndis_patched.sys, 0x1C00141EC):

  FirstNetBuffer = a2->FirstNetBuffer;
  CurrentMdl = FirstNetBuffer->CurrentMdl;
  if ( CurrentMdl == nullptr )         // FIX 1: added NULL guard
    goto LABEL_4;
  ByteCount = CurrentMdl->ByteCount;
  ...
  v13 = ByteCount - CurrentMdlOffset;
  ...
  if ( v13 >= 0x16 )                    // FIX 2: require remaining >= 0x16 before the SNAP read
  {
    if ( v15[14] != 0xAA || v15[15] != 0xAA || v15[16] != 3 )
      goto LABEL_24;
    v7 = *((_WORD *)v15 + 10);          // data offset 0x14, now reached only when safe
    ...
  }
  else { result = 0; }

Note: the 802.1Q VLAN branch (v17 == 0x8100) already re-validated ByteCount >= 0x12 and remaining >= 0x12 before its offset-0x10 read in both builds; only the SNAP branch lacked the corresponding size check.

Finding 3: NBL-sort parser (ndisSortNetBufferLists)

Unpatched (ndis_unpatched.sys, 0x1C0012960), SNAP branch:

  CurrentMdl = FirstNetBuffer->CurrentMdl;         // no NULL check
  ByteCount  = CurrentMdl->ByteCount;
  if ( ByteCount < 0xE
    || ByteCount <= CurrentMdlOffset
    || ByteCount - CurrentMdlOffset < 0xE )         // remaining only checked >= 0xE
  { ... }
  ...
  if ( data[14] == 0xAA && data[15] == 0xAA && data[16] == 3 )
    protocol = *((_WORD *)data + 10);               // OOB: reads data offset 0x14 with only remaining >= 0xE guaranteed

Patched (ndis_patched.sys, 0x1C0012B00):

  CurrentMdl = FirstNetBuffer->CurrentMdl;
  if ( CurrentMdl == nullptr )                       // FIX 1: added NULL guard
    goto reject;
  ByteCount = CurrentMdl->ByteCount;
  ...
  remaining = ByteCount - CurrentMdlOffset;
  ...
  if ( remaining < 0x16 )                            // FIX 2: require remaining >= 0x16 before the SNAP read
    goto reject;
  if ( data[14] == 0xAA && data[15] == 0xAA && data[16] == 3 )
    protocol = *((_WORD *)data + 10);               // data offset 0x14, now reached only when safe

As in Finding 1, the 802.1Q VLAN branch (0x8100) already re-validated ByteCount >= 0x12 and remaining >= 0x12 in both builds; only the SNAP branch lacked the check.

Finding 2: Buffer Validation (ndisValidateEmbeddedBufferBounds)

Unpatched (0x1C0116760) — containment only, no minimum:

  v8 = a1 + a2;          // parent_end
  v9 = a1 + a5;          // sub_start = base + Offset
  v10 = v9 + a6;         // sub_end   = sub_start + Length
  v11 = a3 + a4;
  if ( v8 < a1 || v9 < a1 || v10 < v9 || v10 > v8   // overflow + containment
    || a7 != 0 && a6 != 0 && ((a7 - 1) & (unsigned int)v9) != 0
    || v9 <= a3 && v10 > a3
    || v9 <= v11 && v10 > v11 )
  { return 0; }
  *a8 = v9;
  return 1;

Patched (0x1C010C3CC) — adds the Offset < minimum rejection (a5 is the new minimum, a6 is Offset):

  v10 = a1 + a2;
  v11 = a1 + a6;         // sub_start = base + Offset
  v12 = v11 + a7;        // sub_end   = sub_start + Length
  v13 = a3 + a4;
  if ( v10 < a1
    || a5 != 0 && a6 < a5              // NEW: reject when Offset < minimum
    || v11 < a1
    || v12 < v11
    || v12 > v10                       // containment (unchanged intent)
    || a8 != 0 && a7 != 0 && ((a8 - 1) & (unsigned int)v11) != 0
    || v11 <= a3 && v12 > a3
    || v11 <= v13 && v12 > v13 )
  { return 0; }
  *a9 = v11;
  return 1;

The wrappers supply the minimum: ndisValidateNdisVarDataDesc32InputString passes 0x28, ndisValidateNdisVarDataDescInputString passes 0x48, and ndisValidateNdisOffsetAndLengthInputBufferBounds passes 0.


4. Assembly Analysis

Finding 1: Parser

Unpatched sequence in ndis_unpatched.sys (ndisParseReceivedNBL):

0x1c006e3a7  mov     rax, [rdx+8]          ; NET_BUFFER_LIST->FirstNetBuffer
0x1c006e3ab  mov     rcx, [rax+8]          ; NET_BUFFER->CurrentMdl -- no NULL check
0x1c006e3af  mov     edi, [rcx+28h]        ; MDL->ByteCount
0x1c006e3b2  cmp     edi, 0Eh              ; ByteCount >= 14
0x1c006e3b5  jnb     0x1c006e3be
...
0x1c006e3c7  sub     esi, ebp              ; remaining = ByteCount - CurrentMdlOffset
0x1c006e3c9  cmp     esi, 0Eh              ; remaining >= 14 (only 14, not 22)
0x1c006e3cc  jb      0x1c006e3b7
...
0x1c006e43f  cmp     byte ptr [rdx+0Eh], 0AAh   ; SNAP DSAP
0x1c006e447  cmp     byte ptr [rdx+0Fh], 0AAh   ; SNAP SSAP
0x1c006e44d  cmp     byte ptr [rdx+10h], 3      ; SNAP Control
0x1c006e453  movzx   eax, word ptr [rdx+14h]    ; OOB read at data offset 0x14
0x1c006e459  mov     [r15], ax                  ; store parsed EtherType

Patched (ndis_patched.sys, ndisParseReceivedNBL):

0x1c0014223  mov     rax, [rdx+8]
0x1c0014227  mov     rcx, [rax+8]          ; CurrentMdl
0x1c001422b  test    rcx, rcx              ; ADDED: NULL check on MDL pointer
0x1c001422e  jnz     0x1c0014237
0x1c0014230  ...                            ; NULL -> bail out
...
0x1c00142c3  cmp     edi, 16h              ; ADDED: require remaining >= 22
0x1c00142c6  jnb     0x1c00142cc
0x1c00142c8  xor     al, al                ; too small -> return 0
0x1c00142cc  cmp     byte ptr [rdx+0Eh], 0AAh
...
0x1c00142e0  movzx   eax, word ptr [rdx+14h]    ; reached only when remaining >= 22

Here edi holds the remaining mapped length (ByteCount - CurrentMdlOffset), so cmp edi, 16h is the guard that bounds the offset-0x14 read.

Finding 3: NBL-sort parser

Unpatched sequence in ndis_unpatched.sys (ndisSortNetBufferLists):

0x1c0012a24  mov     rcx, [rax+8]           ; NET_BUFFER->CurrentMdl -- no NULL check
0x1c0012a2b  mov     esi, [rcx+28h]         ; MDL->ByteCount
0x1c0012a2e  cmp     esi, 0Eh               ; ByteCount >= 14
0x1c0012a3f  mov     ebp, esi
0x1c0012a41  sub     ebp, edi               ; remaining = ByteCount - CurrentMdlOffset
0x1c0012a43  cmp     ebp, 0Eh               ; remaining >= 14 (only 14, not 22)
...
0x1c0012c5c  cmp     byte ptr [r8+0Eh], 0AAh   ; SNAP DSAP
0x1c0012c67  cmp     byte ptr [r8+0Fh], 0AAh   ; SNAP SSAP
0x1c0012c72  cmp     byte ptr [r8+10h], 3      ; SNAP Control
0x1c0012c7d  movzx   r14d, word ptr [r8+14h]   ; OOB read at data offset 0x14

Patched (ndis_patched.sys, ndisSortNetBufferLists):

0x1c0012bc8  mov     rcx, [rax+8]           ; CurrentMdl
0x1c0012bcc  test    rcx, rcx               ; ADDED: NULL check on MDL pointer
0x1c0012bcf  jz      0x1c0012e0f            ; NULL -> reject
0x1c0012bd5  mov     r14d, [rcx+28h]        ; ByteCount
0x1c0012bf0  mov     ebp, r14d
0x1c0012bf3  sub     ebp, r15d              ; remaining = ByteCount - CurrentMdlOffset
...
0x1c0012ca8  cmp     ebp, 16h               ; ADDED: require remaining >= 22
0x1c0012cab  jb      0x1c0012e0f            ; too small -> reject
0x1c0012cb3  cmp     byte ptr [r8+0Eh], 0AAh
...
0x1c0012cc8  movzx   edi, word ptr [r8+14h] ; reached only when remaining >= 22

Here ebp holds the remaining mapped length, so cmp ebp, 16h bounds the offset-0x14 read, mirroring the Finding 1 fix.

Finding 2: Buffer Validation

The added check in ndis_patched.sys (ndisValidateEmbeddedBufferBounds, 0x1C010C3CC):

0x1c010c3f3  mov     eax, [rsp+arg_20]     ; new minimum argument
0x1c010c3f7  test    eax, eax
0x1c010c3f9  jz      0x1c010c401           ; minimum == 0 -> skip
0x1c010c3fb  cmp     [rsp+arg_28], eax     ; Offset vs minimum
0x1c010c3ff  jb      0x1c010c447           ; Offset < minimum -> reject

The unpatched 0x1C0116760 has no equivalent instructions; it performs only the containment and boundary/alignment comparisons before returning the sub-buffer pointer. The parent-buffer containment check (sub_end <= parent_end) is present and identical in both builds.


5. Trigger Conditions

Finding 1 (Receive-verifier OOB read)

  1. NDIS datapath verification must be enabled for the target adapter (e.g. via Driver Verifier's NDIS/datapath verification). Without it, ndisNblVerifyRxIndication and therefore ndisParseReceivedNBL are never called.
  2. A frame indicated on that adapter must have a NET_BUFFER whose remaining mapped length (ByteCount - CurrentMdlOffset) is between 14 and 21 bytes.
  3. The mapped data must present an 802.3 length field <= 0x600 at offset 0xC and the SNAP markers 0xAA, 0xAA, 0x03 at offsets 0xE, 0xF, 0x10, so the offset-0x14 read is reached.
  4. Observable effect: the routine reads up to ~8 bytes past the valid MDL data. The value is written to the parser's 16-bit EtherType output used by the verifier; it is not returned to any external party. When datapath verification is combined with Special Pool, a read that crosses the allocation boundary is caught and bugchecks.

Finding 3 (Receive NBL-sort OOB read)

  1. A miniport indicates a received NET_BUFFER_LIST up the stack (ndisMDispatchReceiveNetBufferLists and the related dispatch routines listed above). No datapath-verification flag is required; the L2 classification/sort parse runs on the ordinary receive path (unless a precomputed protocol value is already set on the tracker).
  2. The indicated NET_BUFFER must have a remaining mapped length (ByteCount - CurrentMdlOffset) between 14 and 21 bytes, present an 802.3 length field <= 0x600 at data offset 0xC, and carry the SNAP markers 0xAA, 0xAA, 0x03 at offsets 0xE, 0xF, 0x10.
  3. Observable effect: the routine reads up to ~8 bytes past the valid MDL data at offset 0x14. The value becomes the NBL's internal protocol classification and is not returned to the frame sender. On a typical DMA receive buffer larger than the frame the read stays inside the buffer (a stale-byte misread); it crosses the allocation boundary only when the MDL is sized to the frame with no following mapped page, in which case a boundary-crossing read under Special Pool bugchecks.

Finding 2 (Local descriptor-offset validation)

  1. Open a handle to the NDIS device and issue an IRP_MJ_DEVICE_CONTROL request routed to ndisHandlePnPRequest.
  2. The input buffer must contain an NDIS_VAR_DATA_DESC whose Offset field points before the fixed request header (Offset < 0x28 for the 32-bit thunk variant, < 0x48 for the native variant) while still lying inside the parent buffer.
  3. In the unpatched build the validator accepts such a descriptor, so the resulting UNICODE_STRING overlaps the fixed structure fields. All reads remain inside the parent input buffer (containment is enforced), so this is a structural/aliasing issue, not an out-of-bounds access. The patched build rejects it.

6. Exploit Primitive & Development Notes

  • Finding 1 (receive-verifier OOB read): A bounded read of up to ~8 bytes immediately following the packet data in the MDL-mapped region. The value influences only the verifier's internal EtherType classification and is not disclosed to the frame sender. Its practical effect is a possible bugcheck when datapath verification runs with Special Pool, and otherwise a benign misread. It is gated behind a non-default diagnostic mode, so it is not a remote information-disclosure or denial-of-service primitive against a default configuration.
  • Finding 1 (NULL guard): The added CurrentMdl == NULL check is defense in depth. A NULL CurrentMdl on an indicated NET_BUFFER would be a miniport defect rather than attacker-controlled input.
  • Finding 3 (receive NBL-sort OOB read): The same bounded read of up to ~8 bytes following the packet data, but on the standard receive path rather than the verifier-only path. The value influences only the NBL's internal protocol classification and is not disclosed to the frame sender. On typical hardware receive buffers the read stays within the DMA buffer; its practical worst case is a bugcheck under Special Pool with a frame-sized MDL. The added CurrentMdl == NULL guard is defense in depth, as in Finding 1.
  • Finding 2: No out-of-bounds primitive is demonstrable; the parent-buffer containment check bounds every access in both builds. The added minimum-offset check prevents an embedded descriptor's variable data from overlapping the fixed request header.

7. Debugger Notes

For a follow-on agent with a kernel debugger attached to ndis_unpatched.sys, and with NDIS datapath verification enabled on the target adapter, Finding 1 can be observed at:

ndis!ndisParseReceivedNBL          (0x1c006e370)  entry; rdx = NET_BUFFER_LIST
ndis!ndisParseReceivedNBL+0x3b     (0x1c006e3ab)  mov rcx,[rax+8] loads CurrentMdl (NULL -> fault at +0x3f)
ndis!ndisParseReceivedNBL+0x42     (0x1c006e3b2)  cmp edi,0xe ; edi = ByteCount
ndis!ndisParseReceivedNBL+0xe3     (0x1c006e453)  movzx eax,word [rdx+0x14] ; the OOB read
  • At +0x3b, if [rax+8] (CurrentMdl) is NULL the following mov edi,[rcx+28h] at +0x3f faults.
  • At +0x42, edi is ByteCount; the SNAP path is entered when the remaining length is in [0xE, 0x15].
  • At +0xe3, rdx points at the mapped data; rdx+0x14 is the 2-byte read that can exceed the valid data when the remaining length is below 0x16. Under Special Pool a boundary-crossing read bugchecks (PAGE_FAULT_IN_NONPAGED_AREA, 0x50).

A 20-byte 802.3 + LLC/SNAP frame (6-byte destination MAC, 6-byte source MAC, a length field <= 0x600, AA AA 03, then padding) indicated on a verification-enabled adapter reaches +0xe3 with rdx+0x14 at the last valid byte and one byte beyond.


8. Changed Functions — Full Triage

The patch diff contains 42 changed functions.

  • Security-relevant logic change:
    • ndisSortNetBufferLists (0x1C00129600x1C0012B00): added NULL guard on CurrentMdl and a remaining >= 0x16 check before the SNAP offset-0x14 read (Finding 3); reachable on the ordinary receive path.
    • ndisParseReceivedNBL (0x1C006E3700x1C00141EC): added NULL guard on CurrentMdl and a remaining >= 0x16 check before the SNAP offset-0x14 read (Finding 1); verifier-gated.
    • ndisValidateEmbeddedBufferBounds (0x1C01167600x1C010C3CC): added a minimum-offset parameter and Offset < minimum rejection (Finding 2).
  • Receive-dispatch wrappers adjusted for the ndisSortNetBufferLists signature change (not independent fixes): ndisMDispatchReceiveNetBufferLists, ndisMDispatchReceiveNetBufferListsWithLock, ndisMIndicateSplitNblChain, and ndisDoPeriodicReceivesIndication were recompiled to match the changed ndisSortNetBufferLists; their own logic is unchanged.
  • Callers adjusted to pass the new minimum (not independent fixes):
    • ndisValidateNdisVarDataDescInputString (0x1C01166DC0x1C010C4E0): passes minimum 0x48.
    • ndisValidateNdisVarDataDesc32InputString (0x1C012DF580x1C010C458): passes minimum 0x28.
    • ndisValidateNdisOffsetAndLengthInputBufferBounds (0x1C012DEA80x1C012EEF8): passes minimum 0x0.
    • ndisHandlePnPRequest (0x1C01440080x1C0145008): recompiled/relocated (WPP tracing and block reordering). Its input-buffer minimum-size (0x48), integer-overflow, and IoIs32bitProcess thunk checks are present in both builds; only the wrapper calls now carry the new minimum.
    • ndisNblVerifyRxIndication (0x1C00722BC): register-allocation change following the ndisParseReceivedNBL signature change.
  • WIL feature-staging migration (not security fixes): a cluster of functions (e.g. 0x1C0033984, 0x1C0035D0C, 0x1C00751E0, 0x1C007F214, 0x1C0134C10, 0x1C0152E1C, 0x1C01445F8) moved from querying global feature configuration (RtlQueryFeatureConfiguration) to a per-adapter atomic field check.
  • Tracing / register-reallocation churn: 0x1C00B7CBC, 0x1C0002B08, 0x1C0002AE0, 0x1C0160A20, and similar exhibit recompilation noise with no security-relevant logical change.

9. Unmatched Functions

There are no added or removed functions in the provided diff. The patch modifies existing logic and relocates several functions without introducing or removing dispatch routines.


10. Confidence & Caveats

  • Confidence: High. All three changes are confirmed against the decompilation and disassembly of both builds. The reachability of ndisParseReceivedNBL (single verifier-only caller, gated by the datapath-verification flag) and of ndisSortNetBufferLists (called from the standard miniport receive-indication dispatch) is confirmed at every call site.
  • Caveats:
    1. Findings 1 and 3 are the same out-of-bounds read fixed in two parsers. Finding 1 (ndisParseReceivedNBL) is reachable only when NDIS datapath verification is enabled and is not exposed on the default receive path. Finding 3 (ndisSortNetBufferLists) is on the ordinary receive path but the out-of-bounds value is used only for internal NBL classification, is not disclosed to the sender, and typically stays within the DMA receive buffer; its worst realistic impact is a bugcheck under Special Pool with a frame-sized MDL. Both are rated Low.
    2. Finding 2 is input-validation hardening. The parent-buffer containment check bounds every access in both builds, so no out-of-bounds read is demonstrable; the added check prevents an embedded descriptor's variable data from overlapping the fixed request header.