netio.sys — version-leap refactor: QoS-reference inlining and a behavior-preserving IPv4 fragment-parse rewrite
KB5094128
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 callsRoReferenceEx(@0x1C001C39C).RoReferenceExis 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, ecxat0x14001682D).
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 QosReference → RoReferenceEx.
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
0x118field 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 offset0x118. 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): AddedWFP_NBL_INFOmagic-number checks (0x6E706657) andKeBugCheckExon corruption. The magic constant appears 2 times in the unpatched build and 67 times in the patched build;KeBugCheckExsites rise from 19 to 58. This is broad integrity/self-check hardening across the subsystem, not a targeted runtime fix.NetioPhIsIcmpErrorForIcmpMessage(Optimization): Replaced theNetioPhGetDefaultIpHeaderSizecall with hardcoded header sizes (0x14IPv4,0x28IPv6) and an inline contiguous-buffer advance. The bounds checkDataLength >= header_size + 16is 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 + 16andhwaddrlen <= 0x20beforememmove). 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.