1. Overview

  • Unpatched Binary: netio_unpatched.sys
  • Patched Binary: netio_patched.sys
  • Overall Similarity Score: 0.6138 (far-apart builds; a major version leap, so the diff is dominated by recompilation churn)
  • Diff Statistics:
  • Matched Functions: 1582
  • Changed Functions: 1250
  • Identical Functions: 332
  • Unmatched (Unpatched / Patched): 0 / 0
  • Verdict: No security-relevant change was delivered. The two changes that were examined in detail are both behavior-preserving: one is the inlining of an already-atomic reference-count helper, the other is an IPv4 fragment-flag parse that produces byte-for-byte identical output in both builds. The remainder of the diff is compiler churn plus broad integrity hardening (magic-number and bug-check additions) that is characteristic of a servicing rebuild rather than a targeted fix.

2. Findings Summary

Finding 1: NBL information copy — reference handling (No security-relevant change)

  • Severity: None
  • Nature: Compiler inlining of an already-atomic reference operation, plus one added struct-field copy
  • Affected Function: NetioCopyNetBufferListInformation (unpatched @0x1C001BBB0, patched @0x1400167E0)

The reference count that this routine touches is the global QoS provider reference counter (QoSProviderReference), not a per-NBL reference count. It is incremented atomically in both builds:

  • Unpatched: the routine calls QosReference (@0x1C003FE9C), which calls RoReferenceEx (@0x1C001C39C). RoReferenceEx is an atomic compare-and-exchange loop (lock cmpxchg [rcx], edx).
  • Patched: the same compare-and-exchange loop is inlined directly into the routine (lock cmpxchg cs:QoSProviderReference, ecx at 0x14001682D).

There is no non-atomic inc/add on any reference count in the unpatched build, so there is no race condition and no use-after-free to fix. The change is that the compiler inlined the reference helper. The one genuine functional delta is an added copy of the 8-byte field at structure offset 0x118 (dest+0x118 = src+0x118, at 0x1400168F6); this copies a field from the source structure and is consistent with a structure-layout change across the version leap. No reachable use of an uninitialized destination field is demonstrable, so this is not an information-leak fix.

Finding 2: IPv4 fragment-flag classification (No security-relevant change)

  • Severity: None
  • Nature: Behavior-preserving rewrite of a flag test
  • Affected Function: NetioParseIpFieldsFromIpHeader (unpatched @0x1C003E5AC, patched @0x140018AB8)

For the IPv4 branch (address family == 2), the routine writes a one-byte fragment classification value to output offset 0x0D. The unpatched and patched code produce identical output for every possible flags byte:

  • Word at IPv4 header offset 6 (flags + fragment offset) is zero → 0
  • MF flag (0x20) set → 1
  • MF clear, DF flag (0x40) set → 0
  • MF clear, DF clear (any other nonzero) → 2

The unpatched code computes the value 2 via ~(flags>>5) & 2, which equals 2 exactly when DF is clear and 0 when DF is set. The patched code computes the same values with an explicit test al, 0x40 and sources the constant 2 from the address-family register cl (which is always 2 in this IPv4-only branch). No flag combination yields a different result between the two builds, so no packet can be classified differently. This is a refactor, not a filtering-bypass fix.


3. Pseudocode Diff

NetioCopyNetBufferListInformation

// --- UNPATCHED (0x1C001BBB0) ---
v2 = *(_QWORD *)(a2 + 168);            // src->0xA8 (QoS provider pointer)
a1[18] = *(_QWORD *)(a2 + 144);
if ( v2 == 0 || (unsigned int)QosReference(v2) != 0 )   // QosReference -> RoReferenceEx (atomic)
    v2 = 0;
a1[21] = v2;                           // dest->0xA8
// ... field copies ...
// (no copy of offset 0x118)

// --- PATCHED (0x1400167E0) ---
v2 = *(_QWORD *)(a2 + 168);
a1[18] = *(_QWORD *)(a2 + 144);
if ( v2 != 0 ) {
    // RoReferenceEx inlined: atomic compare-and-exchange loop on the
    // GLOBAL QoSProviderReference counter, then provider callback
    while (1) {
        v5 = QoSProviderReference;
        if ( (QoSProviderReference & 1) != 0 ) break;
        if ( v5 == _InterlockedCompareExchange(&QoSProviderReference,
                                               QoSProviderReference + 2,
                                               QoSProviderReference) ) {
            /* provider callback + QospDereferenceContext */
            if ( callback_result == 0 ) goto keep;
            break;
        }
    }
    v2 = 0;
}
keep:
a1[21] = v2;
// ... same field copies ...
a1[35] = *(_QWORD *)(a2 + 280);        // ADDED: copy of field at offset 0x118

Both builds reference QoSProviderReference atomically. The patched version merely inlines what the unpatched version reached through QosReferenceRoReferenceEx.

NetioParseIpFieldsFromIpHeader (IPv4 branch)

// --- UNPATCHED (0x1C003E5AC) ---
v3 = *(_WORD *)(v2 + 6);
if ( v3 != 0 ) {
    if ( (v3 & 0x20) != 0 )                       // MF
        *((_BYTE *)a2 + 13) = 1;
    else
        *((_BYTE *)a2 + 13) = ~((unsigned __int8)v3 >> 5) & 2;  // == 2 iff DF clear, else 0
    return;
}
*((_BYTE *)a2 + 13) = 0;

// --- PATCHED (0x140018AB8) ---
v3 = *(_WORD *)(v2 + 6);
if ( v3 != 0 ) {
    if ( (v3 & 0x20) != 0 ) { *((_BYTE *)a2 + 13) = 1; return; }   // MF
    if ( (v3 & 0x40) == 0 ) { *((_BYTE *)a2 + 13) = 2; return; }   // DF clear -> 2 (== cl == AF_INET)
}
*((_BYTE *)a2 + 13) = 0;                                            // DF set / word 0 -> 0

The two implementations are equivalent for all inputs.


4. Assembly Evidence

Atomic reference is present in BOTH builds

Unpatched RoReferenceEx (@0x1C001C39C), reached via QosReference:

00000001C001C39E  mov     eax, [rcx]
00000001C001C3A0  test    al, 1
00000001C001C3A2  jnz     short loc_1C001C3B1
00000001C001C3A4  lea     edx, [rax+2]
00000001C001C3A7  lock cmpxchg [rcx], edx
00000001C001C3AB  jnz     short loc_1C001C39E

Patched NetioCopyNetBufferListInformation (@0x1400167E0), same loop inlined on the global counter:

0000000140016820  mov     eax, cs:QoSProviderReference
0000000140016826  test    al, 1
0000000140016828  jnz     short loc_14001686A
000000014001682A  lea     ecx, [rax+2]
000000014001682D  lock cmpxchg cs:QoSProviderReference, ecx
0000000140016835  jnz     short loc_140016820

Added field copy in the patched routine:

00000001400168F6  mov     rax, [rsi+118h]
00000001400168FD  mov     [rbx+118h], rax

IPv4 fragment classification is equivalent

Unpatched NetioParseIpFieldsFromIpHeader (@0x1C003E5AC):

00000001C003E602  movzx   eax, word ptr [r8+6]
00000001C003E607  test    ax, ax
00000001C003E60A  jz      short loc_1C003E65E     ; -> [rdx+0Dh] = 0
00000001C003E60C  test    al, 20h                 ; MF
00000001C003E60E  jz      short loc_1C003E616
00000001C003E610  mov     byte ptr [rdx+0Dh], 1
00000001C003E614  retn
00000001C003E616  shr     al, 5
00000001C003E619  not     al
00000001C003E61B  and     al, 2                   ; == 2 iff DF clear, else 0
00000001C003E61D  mov     [rdx+0Dh], al

Patched NetioParseIpFieldsFromIpHeader (@0x140018AB8):

0000000140018B0E  movzx   eax, word ptr [r8+6]
0000000140018B13  test    ax, ax
0000000140018B16  jz      short loc_140018B61     ; -> [rdx+0Dh] = 0
0000000140018B18  test    al, 20h                 ; MF
0000000140018B1A  jnz     short loc_140018B67     ; -> [rdx+0Dh] = 1
0000000140018B1C  test    al, 40h                 ; DF
0000000140018B1E  jnz     short loc_140018B61     ; DF set -> [rdx+0Dh] = 0
0000000140018B20  mov     [rdx+0Dh], cl           ; DF clear -> cl (== 2, AF_INET)

The output byte is 0, 1, or 2 for the same inputs in both builds.


5. Reachability

NetioCopyNetBufferListInformation is reachable on the NBL clone/copy path during network processing, and NetioParseIpFieldsFromIpHeader is reachable from the IPv4 receive/parse path. Reachability is not in question; the point is that neither routine's observable behavior changed in a security-relevant way between the two builds, so there is no attacker-triggerable state difference to reach.


6. No Exploit Primitive

Neither change yields a memory-safety or filtering primitive:

  • The QoS provider reference is atomic in both builds, so there is no race window, no premature free, and no dangling pointer. The added 0x118 field copy does not create a use-after-free or an information leak that is demonstrable from the code.
  • The IPv4 fragment classification value is identical in both builds for every flags byte, so no packet can slip past a filter in one build that it would not slip past in the other.

No proof-of-concept, pool-grooming, or control-flow-hijack primitive follows from these changes.


7. Changed Functions — Triage

The 1250 changed functions are dominated by recompilation churn (register allocation, inlining, control-flow restructuring) and broad integrity hardening consistent with a version leap.

  • NetioCopyNetBufferListInformation (No security change): Inlined the already-atomic QoS provider reference (RoReferenceEx); added a copy of the field at offset 0x118. No non-atomic reference count exists in either build.
  • NetioParseIpFieldsFromIpHeader (No security change): Rewrote the IPv4 fragment-flag test into an explicit DF check; produces identical output for all inputs.
  • WfpNblInfoGet / WfpNblInfoSet / WfpNblInfoClone (Hardening): Added WFP_NBL_INFO magic-number checks (0x6E706657) and KeBugCheckEx on corruption. The magic constant appears 2 times in the unpatched build and 67 times in the patched build; KeBugCheckEx sites rise from 19 to 58. This is broad integrity/self-check hardening across the subsystem, not a targeted runtime fix.
  • NetioPhIsIcmpErrorForIcmpMessage (Optimization): Replaced the NetioPhGetDefaultIpHeaderSize call with hardcoded header sizes (0x14 IPv4, 0x28 IPv6) and an inline contiguous-buffer advance. The bounds check DataLength >= header_size + 16 is present and identical in both builds.
  • NetioPhGetIpUlProtocol (Refactor): Restructured extraction of the upper-layer protocol from IP headers. No semantic change.
  • NetioPhFindTcpOption (Refactor): Loop restructuring; both builds guard every option-byte dereference against the end pointer.
  • FwppStreamInject (Telemetry): Added tracing and error-path cleanup. Core injection validation unchanged.
  • NetioPhParseArpPacket (New feature, patched @0x140052C00): A new ARP-inspection routine that exists only in the patched build (the unpatched build has no ARP parser and no in-module caller). It validates its inputs (NetBuffer->DataLength >= 2*hwaddrlen + 16 and hwaddrlen <= 0x20 before memmove). Because it is a brand-new function rather than a check added to a pre-existing parsing path, it is a feature addition, not a fix to a prior vulnerability.

8. Unmatched Functions

There are 0 functions reported as unmatched in either direction by the diff. New source-level routines such as NetioPhParseArpPacket appear within the matched/changed accounting of this far-apart comparison rather than as unmatched entries.


9. Confidence & Caveats

  • Confidence Level: High that there is no security-relevant change in the two examined functions. The atomic reference operation is present in both builds (matching RoReferenceEx), and the IPv4 fragment classification is byte-for-byte equivalent across all flag values.
  • Caveats: This is a large version-leap comparison. The audit focused on the network-facing packet-parsing and NBL-lifecycle surface. The dominant categories of change are recompilation churn and integrity/self-check hardening, neither of which constitutes a delivered security fix.