1. Overview

  • Unpatched Binary: netio_unpatched.sys
  • Patched Binary: netio_patched.sys
  • Overall Similarity Score: 0.9717
  • Diff Statistics: 1856 matched functions, 1800 identical functions, and 0 unmatched functions in either direction. The remaining functions differ mostly through WPP/ETW tracing-helper churn, function relocation, and WIL feature-staging (Feature_*__private_IsEnabled*) graduation.
  • Verdict: No security-relevant change was delivered. The three areas examined below are explained entirely by (a) the compiler inlining an out-of-line reference-release helper into NsiEnumerateObjectsAllParametersEx, and (b) several WIL staged features (Feature_Firewall_Rules_Filters_Performance_Counters, Feature_Netsec_BugFixes_25C_Loopback_Checksum_Injection, Feature_TCPIP_2025_Wave2_NsiVersioning) being turned from gated to always-on. In every case the object-lifecycle validation, locking, and buffer-size checks are present in BOTH builds; no check was added that did not already exist, and no check was removed.

2. Change Summary

Finding 1: NsiEnumerateObjectsAllParametersEx — reference-release helper inlined (no lifecycle check added)

  • Severity: None (No security-relevant change)
  • Vulnerability Class: Not applicable (originally alleged CWE-416 Use-After-Free — not supported by the binaries)
  • Affected Function: NsiEnumerateObjectsAllParametersEx (unpatched @ 0x14002F8D0, patched @ 0x14002F5C0)
  • What actually changed: The unpatched function, on its context-release path (LABEL_55), calls the out-of-line helper NsipDereferenceNmpContext (0x140030330). That helper performs the interlocked refcount decrement and, when the count reaches zero, walks the provider array and calls NmrClientDetachProviderCompleteNmrpGetBindingFromHandleNmrpDetachProviderCompleteNmrpDereferenceBinding. In the patched build the compiler inlined this entire release chain directly into NsiEnumerateObjectsAllParametersEx. The object-magic check (0x6c43), the interlocked refcount operation, the two KeBugCheckEx(0x125, …) assertions, and the NmrpDereferenceBinding cleanup all already exist in the unpatched build inside the callee functions listed above. Nothing was added and nothing was removed; the code was relocated inline.
  • Note on the original claim: The functions sub_1400759d0 and sub_1400742a0 cited by the earlier draft do not exist at any address in either binary, and there is no while(true) object-iteration loop in this function. Those details are removed as unverifiable.

Finding 2: DeleteFilterFromIndex — layer-statistics lock behind a WIL feature flag

  • Severity: None (No security-relevant change)
  • Vulnerability Class: Not applicable (originally alleged CWE-662 Race Condition — not supported by the binaries)
  • Affected Function: DeleteFilterFromIndex (unpatched @ 0x140040F80, patched @ 0x14004C8DC)
  • What actually changed: In the unpatched build the acquisition of gLayerStatsLock (and the AreAllIpv4InLayersEmpty update) is gated behind the WIL staged features Feature_Firewall_Rules_Filters_Performance_Counters and Feature_Netsec_BugFixes_25C_Loopback_Checksum_Injection. When the performance-counter feature is enabled, the lock is taken and per-processor statistics are updated; when it is disabled, the same statistics decrement runs without that lock. The patched build simply graduates the feature to always-on: it always takes gLayerStatsLock around the statistics update. The lock protects layer-statistics counters, not the filter object itself, and the counter decrement (_InterlockedDecrement) is atomic in both builds. This is a staged-feature rollout, not a synchronization fix to shared filter data.
  • Note on the original claim: The gate is a WIL feature flag, not a global data_14009b248 flag, and the helper sub_140044424 does not exist. Those details are removed.

Finding 3: NsipValidateEnumerateObjectsAllParametersRequest — versioned-request validation behind a WIL feature flag

  • Severity: None (No security-relevant change)
  • Vulnerability Class: Not applicable (originally alleged CWE-20 Missing Input Validation — not supported by the binaries)
  • Affected Function: NsipValidateEnumerateObjectsAllParametersRequest (unpatched @ 0x140030390, patched @ 0x14004D590)
  • What actually changed: The set of parameter-buffer size checks (request fields at offsets 0x38/0x40, 0x48/0x50, 0x58/0x60 compared against the module descriptor's expected sizes) is gated in the unpatched build behind the WIL staged feature Feature_TCPIP_2025_Wave2_NsiVersioning. When the versioning feature is disabled, the function returns success without performing those checks; when enabled, it performs them. The patched build makes the same checks unconditional, i.e. it graduates the NSI-versioning feature to always-on. This is a staged rollout of a new feature's request validation, not the removal of a gate on validation of an already-shipping code path.
  • Note on the original claim: The gate is the WIL feature Feature_TCPIP_2025_Wave2_NsiVersioning, not a global data_14009b200 flag or sub_14005ea18. Those details are removed.

3. Pseudocode Evidence

NsiEnumerateObjectsAllParametersEx — release path

// UNPATCHED (0x14002F8D0): release path calls the out-of-line helper
LABEL_55:
    NsipDereferenceNmpContext(v49);   // 0x140030330

// NsipDereferenceNmpContext (0x140030330), unpatched:
if ( _InterlockedExchangeAdd((volatile signed __int32 *)a1, 0xFFFFFFFF) == 1 )
{
    // ... walks provider array, invokes detach callbacks ...
    NmrClientDetachProviderComplete(*(HANDLE *)(a1 + 56));
}

// NmrClientDetachProviderComplete (0x14005CA20) -> NmrpGetBindingFromHandle (0x14004BB54):
//   v3 = (*binding == 27715);            // magic 0x6c43 check ALREADY PRESENT
//   if (!v3) KeBugCheckEx(0x125u, 1u, binding, result, 0);
// -> NmrpDetachProviderComplete (0x140048C84):
//   if (_InterlockedIncrement(binding + 20) != 1) KeBugCheckEx(0x125u, 3u, binding, 0, 0);
//   return NmrpDereferenceBinding(binding);
// PATCHED (0x14002F5C0): the same release chain is inlined into the function
if ( _InterlockedExchangeAdd(v3, 0xFFFFFFFF) == 1 )
{
    // ... walks provider array, invokes detach callbacks ...
    v75 = *((_QWORD *)v3 + 7);
    if ( *(_WORD *)v75 != 27715 )                  // magic 0x6c43 check (same as unpatched callee)
        KeBugCheckEx(0x125u, 1u, v75, v75 - 76, 0);
    if ( _InterlockedIncrement((volatile signed __int32 *)(v75 + 4)) != 1 )
        KeBugCheckEx(0x125u, 3u, v75 - 76, 0, 0);
    NmrpDereferenceBinding((PVOID)(v75 - 76));      // same cleanup as unpatched callee
}

The magic value (27715 = 0x6c43), both KeBugCheckEx(0x125, …) assertions, and NmrpDereferenceBinding are present in both builds; the patch only changes where the code physically lives.

DeleteFilterFromIndex — statistics lock

// UNPATCHED (0x140040F80): lock gated by a WIL feature flag
if ( (Feature_Firewall_Rules_Filters_Performance_Counters__private_featureState & 0x10) != 0 )
    v24 = Feature_..._featureState & 1;
else
    v24 = Feature_Firewall_Rules_Filters_Performance_Counters__private_IsEnabledDeviceUsageNoInline();
if ( v24 != 0 )
{
    WfpAcquireFastWriteLock(&gLayerStatsLock, &LockState);
    *(_QWORD *)(v20 + 96) -= v16[10];
    _InterlockedDecrement((volatile signed __int32 *)(v20 + 112));   // atomic in both builds
    // ... per-processor stats ...
    NdisReleaseRWLock(gLayerStatsLock, &LockState);
}
else
{
    *(_QWORD *)(v20 + 96) -= v16[10];
    _InterlockedDecrement((volatile signed __int32 *)(v20 + 112));   // atomic in both builds
}

// PATCHED (0x14004C8DC): the same statistics update, feature graduated to always-on
WfpAcquireFastWriteLock(&gLayerStatsLock, &v28);
*(_QWORD *)(v20 + 96) -= v16[10];
_InterlockedDecrement((volatile signed __int32 *)(v20 + 112));
WfpReleaseFastWriteLock(&gLayerStatsLock, &v28);

4. Assembly Evidence

Object-lifecycle checks exist in the UNPATCHED build (callees of the release chain)

NmrpGetBindingFromHandle @ 0x14004BB54 (unpatched) — magic-value check:

000000014004BB60  mov     edx, 6C43h
000000014004BB65  cmp     ax, dx
000000014004BBA5  call    cs:__imp_KeBugCheckEx        ; KeBugCheckEx(0x125, 1, ...)

NmrpDetachProviderComplete @ 0x140048C84 (unpatched) — refcount check + cleanup:

0000000140048C8D  lock xadd [rcx+50h], eax
0000000140048C92  inc     eax
0000000140048CB1  call    cs:__imp_KeBugCheckEx        ; KeBugCheckEx(0x125, 3, ...)
0000000140048CBE  call    NmrpDereferenceBinding

The same checks, inlined, in the PATCHED NsiEnumerateObjectsAllParametersEx @ 0x14002F5C0

000000014002FF29  lock xadd [r13+0], eax             ; interlocked refcount decrement (release)
000000014002FFB8  mov     ecx, 6C43h                 ; magic-value check (inlined)
000000014002FFC3  lock xadd [r8+4], r12d             ; interlocked refcount increment
000000014002FFEC  call    cs:__imp_KeBugCheckEx      ; KeBugCheckEx(0x125, ...)
000000014002FFFD  call    NmrpDereferenceBinding      ; cleanup (inlined)
000000014003001C  call    cs:__imp_KeBugCheckEx      ; KeBugCheckEx(0x125, ...)

The patched function still calls the same core helpers as the unpatched one (NsipGetNmpContext @ 0x14002F7B3/0x14002F904, NsipValidateEnumerateObjectsAllParametersRequest @ 0x14002F929, NsipEnumeratePersistentData @ 0x14002F8B9), confirming the logic is unchanged apart from the inlined release chain.

DeleteFilterFromIndex @ 0x140040F80 (unpatched) — lock gated by a WIL feature flag

000000014004117A  mov     eax, cs:Feature_Netsec_BugFixes_25C_Loopback_Checksum_Injection__private_featureState
0000000140041189  call    Feature_Netsec_BugFixes_25C_Loopback_Checksum_Injection__private_IsEnabledDeviceUsageNoInline
00000001400411B3  call    Feature_Firewall_Rules_Filters_Performance_Counters__private_IsEnabledDeviceUsageNoInline
00000001400411E0  call    WfpAcquireFastWriteLock
0000000140041237  call    cs:__imp_NdisReleaseRWLock

5. Direction Check

For all three areas the patched build is at most equal-or-stricter, and the difference is explained by feature staging or inlining rather than a removed or added security check:

  • Finding 1: No check was added by the patch. The magic/refcount/cleanup logic is byte-for-byte equivalent in both builds; it is out-of-line (in NmrpGetBindingFromHandle/NmrpDetachProviderComplete/NmrpDereferenceBinding) in the unpatched build and inlined in the patched build.
  • Finding 2: The unpatched "no-lock" path runs only when the performance-counter WIL feature is disabled; both paths perform the same atomic decrement. The patched build graduates the feature to always-on.
  • Finding 3: The unpatched build skips the versioned-request size checks only when the NsiVersioning WIL feature is disabled; the patched build graduates the feature to always-on.

6. Reachability

NsiEnumerateObjectsAllParametersEx is reachable from user mode via the NSI device IOCTL path, and DeleteFilterFromIndex is reachable from the WFP filter-deletion path. However, because no security-relevant behavioral difference exists between the two builds in these functions, there is no vulnerable primitive to reach. No exploit chain, PoC, or mitigation-bypass sequence is asserted, as none is supported by the binaries.


7. Independent Diff Note

An independent content-matched diff of the two builds (matching functions by content across relocations rather than by reused address) confirms the changed set is dominated by:

  • WPP/ETW tracing helpers (WPP_SF_*, McTemplate*, McGen*, _tlg*) — servicing/tracing churn.
  • WIL feature-staging plumbing (Feature_*__private_IsEnabled*, wil_details_*) and graduation of several staged features to always-on.
  • New staged features rather than fixes to existing code (e.g. audit-mode support: IoctlEnableAuditMode, Feature_Netsec_AuditMode_Hardening; NSI versioning: Feature_TCPIP_2025_Wave2_NsiVersioning).
  • Function relocation and compiler inlining (as in Finding 1).

No changed function outside the three examined above was found to deliver a security-relevant fix that this report omits.


8. Confidence & Caveats

  • Confidence Level: High that the originally alleged Use-After-Free, race-condition, and missing-validation findings are not supported by the binaries. In each case the relevant checks are present in both builds, and the observed differences are compiler inlining and WIL feature-staging graduation.
  • Assumptions Made: None required. The determinations rest directly on the decompilation and disassembly of both builds at the addresses cited above.
  • Manual Verification Performed: The object-magic (0x6c43) and refcount KeBugCheckEx(0x125, …) assertions were located in the unpatched build's NmrpGetBindingFromHandle/NmrpDetachProviderComplete/NmrpDereferenceBinding; the feature-flag gates were confirmed in the unpatched DeleteFilterFromIndex and NsipValidateEnumerateObjectsAllParametersRequest.