1. Overview

  • Unpatched Binary: afd_unpatched.sys
  • Patched Binary: afd_patched.sys
  • Overall Similarity Score: 0.9621
  • Diff Statistics: 1248 matched, 31 changed, 1217 identical, 0 unmatched functions in either direction.
  • Verdict: The diff is the finalization of several staged WIL (Windows Implementation Library) features. In the unpatched build the connection-creation path is chosen at runtime by the feature gate Feature_1230705977__private_IsEnabledDeviceUsage: when the feature is enabled it calls the new AfdCreateConnection, otherwise it falls back to AfdCreateConnection_Old. The patched build removes the feature gate and the AfdCreateConnection_Old fallback and always calls the new AfdCreateConnection. Both functions already existed side by side in the unpatched build. There is no removed spinlock, no time-of-check/time-of-use race, and no use-after-free. This is staged-feature servicing churn, not a security fix.

2. Change Summary

  • Severity: None (informational)
  • Change Class: Staged WIL feature rollout / servicing churn (no CWE)
  • Primary Functions: AfdCreateConnection_Old (sub_1C0009CD8) (unpatched only), AfdCreateConnection (sub_1C005ABE4) (unpatched) / AfdCreateConnection (sub_1C005AB94) (patched)

What actually changed: The unpatched afd.sys contains two connection-creation functions: - AfdCreateConnection_Old (sub_1C0009CD8) — the legacy fallback. - AfdCreateConnection (sub_1C005ABE4) — the new implementation.

Every call site selects between them at runtime with a WIL feature gate. For example at 0x1C0036386 the caller executes call Feature_1230705977__private_IsEnabledDeviceUsage, then branches to AfdCreateConnection (0x1C0036567) when the feature is enabled or to AfdCreateConnection_Old (0x1C00365B2) when it is not. This pattern is repeated at all 10 call sites. The gate function at 0x1C0009BBC reads the WIL feature state (Feature_1230705977__private_featureState) and tests bit 0x10; this is a feature-staging flag, not a security or compatibility flag.

In the patched build: - AfdCreateConnection_Old is gone (0 references, function removed). - Feature_1230705977 and its gate are gone (0 references). - Every call site unconditionally calls the new AfdCreateConnection (sub_1C005AB94).

The new AfdCreateConnection in the patched build is functionally identical to the new AfdCreateConnection already present in the unpatched build (same body, relocated address). The delivered change is therefore purely the removal of the staged gate and the legacy fallback.

No spinlock is removed or added. Neither AfdCreateConnection_Old nor either copy of the new AfdCreateConnection acquires the endpoint spinlock (there is no KeAcquireInStackQueuedSpinLock in any of them). Both allocate a fresh connection object via AfdAllocateConnection (0x1C0009D6B in the old function), initialize it locally, and only publish it to the caller at the very end (mov [r12], rbx at 0x1C000A0AC in the old function; mov [rax], rbx at 0x1C005AFD7 in the new). Because the object is private to the creating thread until it is published, the field initializations, IoCreateFile, and ObReferenceObjectByHandle operate on memory no other thread can reach, so there is no TOCTOU window and no use-after-free.

Only real behavioral difference between old and new: the new function conditionally chooses the DesiredAccess and Options passed to IoCreateFile based on two endpoint flags, whereas the old function uses fixed values. In the old function DesiredAccess is the constant 0xC0100000 (0x1C0009E66) with Options = 0x100 (0x1C0009E55). In the new function, when [endpoint+0x10] & 0x10000 (0x1C005AD6F) or [endpoint+8] & 0x8000000 (0x1C005ADA6) is set, it instead uses DesiredAccess = 0x2000000 (MAXIMUM_ALLOWED) with Options = 0x101 (0x1C005ADB7), otherwise it uses the same 0xC0100000 / 0x100. This is a functionality change carried by the staged feature; it does not correspond to any demonstrable, reachable memory-safety or privilege-escalation impact.

3. Dispatch Diff

The unpatched build selects the path at runtime; the patched build hard-wires the new path. This is the only structural change at the call sites.

// --- UNPATCHED caller (repeated at all 10 sites) ---
if (Feature_1230705977__private_IsEnabledDeviceUsage()) {
    status = AfdCreateConnection(endpoint, ...);       // new implementation
} else {
    status = AfdCreateConnection_Old(endpoint, ...);   // legacy fallback
}

// --- PATCHED caller ---
status = AfdCreateConnection(endpoint, ...);           // gate and fallback removed

Both target functions allocate and initialize a private connection object, then publish it:

// Shared shape of AfdCreateConnection_Old and AfdCreateConnection (no lock held):
conn = AfdAllocateConnection(...);        // fresh, thread-private allocation
if (!conn) { PsReturnPoolQuota(...); return STATUS_INSUFFICIENT_RESOURCES; }
ObfReferenceObject(process);
conn->Process = process;                  // writes to a thread-private object
conn->Flags = ...;
IoCreateFile(&conn->FileHandle, DesiredAccess, ...);   // DesiredAccess differs old vs new
ObReferenceObjectByHandle(...);
conn->DeviceObject = IoGetRelatedDeviceObject(...);
// error paths: lock xadd [conn+0x30] refcount, AfdCloseConnection(conn) if it drops
*out = conn;                              // object first becomes reachable here

4. Assembly Analysis

The feature gate at 0x1C0009BBC (unpatched) is a standard WIL staging check, not a security gate:

; ---- Feature_1230705977__private_IsEnabledDeviceUsage @ 0x1C0009BBC ----
00000001C0009BC6  mov     eax, cs:Feature_1230705977__private_featureState
00000001C0009BD0  test    al, 10h
00000001C0009BD2  jz      short loc_1C0009BD9
00000001C0009BD4  and     eax, 1                ; return the staged enable bit
00000001C0009BD7  jmp     short loc_1C0009BE8
00000001C0009BD9  mov     rcx, [rsp+28h+arg_0]
00000001C0009BDE  mov     edx, 3
00000001C0009BE3  call    Feature_1230705977__private_IsEnabledFallback

Representative call site (unpatched) showing the runtime selection:

00000001C0036386  call    Feature_1230705977__private_IsEnabledDeviceUsage
; ... test result ...
00000001C0036567  call    AfdCreateConnection       ; feature enabled
; ... else ...
00000001C00365B2  call    AfdCreateConnection_Old   ; feature disabled (fallback)

AfdCreateConnection_Old allocates a thread-private connection object and never acquires a lock; the object is only published at the end:

; ---- AfdCreateConnection_Old @ 0x1C0009CD8 ----
00000001C0009D6B  call    AfdAllocateConnection       ; fresh, thread-private object -> rbx
00000001C0009D70  mov     rbx, rax
00000001C0009D9C  call    cs:__imp_ObfReferenceObject
00000001C0009DAA  mov     [rbx+20h], rsi              ; init private object (no lock needed)
00000001C0009E55  mov     [rsp+130h+Options], 100h    ; Options = 0x100 (fixed)
00000001C0009E66  mov     edx, 0C0100000h             ; DesiredAccess (fixed)
00000001C0009ED9  call    cs:__imp_IoCreateFile
00000001C0009F19  call    cs:__imp_ObReferenceObjectByHandle
00000001C0009F5C  mov     [rbx+18h], rax
; error handling uses a refcount, then AfdCloseConnection:
00000001C0009F9F  lock xadd [rbx+30h], ecx
00000001C0009FAC  call    AfdCloseConnection
00000001C000A0AC  mov     [r12], rbx                  ; object first published here

The new AfdCreateConnection (same in both builds, patched copy at 0x1C005AB94) differs only by conditionally selecting the access mask:

; ---- AfdCreateConnection @ 0x1C005AB94 (patched) ----
00000001C005AD1F  test    dword ptr [r15+10h], 10000h
00000001C005AD56  test    dword ptr [r15+8], 8000000h
00000001C005AD60  mov     edx, 0C0100000h             ; default DesiredAccess
00000001C005AD67  mov     esi, 101h                   ; Options when flag set
00000001C005AD6C  mov     edx, 2000000h               ; DesiredAccess = MAXIMUM_ALLOWED when flag set
00000001C005ADBE  call    cs:__imp_IoCreateFile

5. Trigger Conditions

Not applicable. No vulnerable state is introduced or removed. The behavioral difference is a staged change to the access mask requested by an internal IoCreateFile for the transport connection object; it is not a memory-safety condition and has no attacker-controllable primitive.

6. Exploit Primitive

None. Both connection-creation functions operate on a thread-private allocation and hold no shared state open across the blocking calls. There is no use-after-free, double-free, arbitrary write, or pool-corruption primitive delivered or removed by this change.

7. Reproduction Notes

There is nothing security-relevant to reproduce. To observe the code change with a kernel debugger on the unpatched build, break on 0x1C0009BBC (Feature_1230705977__private_IsEnabledDeviceUsage) to see the runtime selection, then step into either AfdCreateConnection (0x1C005ABE4) or AfdCreateConnection_Old (0x1C0009CD8). On the patched build the gate and AfdCreateConnection_Old are absent and callers go straight to AfdCreateConnection (0x1C005AB94). No crash or corruption results from exercising either build.

8. Changed Functions — Full Triage

Staged-feature rollout (no security impact): - AfdCreateConnection_Old (sub_1C0009CD8): legacy fallback present only in the unpatched build. Removed in the patched build as part of finalizing Feature_1230705977. Not vulnerable: it operates on a thread-private connection object and holds no lock because none is required. - sub_1C005CAD4, AfdAddFreeConnection (sub_1C0041694), sub_1C005A89C, sub_1C004F310, sub_1C0059A50, sub_1C0080590, AfdBind (sub_1C0035DE0): connection-establishment / dispatch callers. Change is the removal of the Feature_1230705977 gate and the AfdCreateConnection_Old fallback branch; the enabled path (new AfdCreateConnection) is retained. The list-head selection wording in earlier drafts is a refactor of the same enabled path, not a security fix. - sub_1C0080060 (AfdSanCancelAccept), sub_1C0083198 (AfdSanRedirectRequest), sub_1C0081E90 (AfdSanFastCompleteRequest): AFD SAN cancel/complete routines. Change is the removal of the Feature_623580472__private_IsEnabledDeviceUsage gate; the previously feature-enabled path is retained (e.g. the [rsi+0x91]/[rdi+0x91] state handling becomes unconditional). The IRP cancel spinlocks are present in both builds; no lock is added. - sub_1C0060E90 (AfdStartListen), sub_1C0061210 (AfdTLListenComplete): the endpoint spinlock is present in both builds. The patched build only adds an AfdDrainListeningIrps call gated by a WIL feature (Feature_3938434363 / Feature_2815687992); the feature-disabled path reproduces the unpatched behavior exactly. - sub_1C003A428 (AfdQueryCompartmentId), sub_1C0061EF0 (AfdSocketTransferEnd): AfdPreventUnbind/AfdReallowUnbind locking is already present in the unpatched build. The only patched delta is additional WIL feature gates (Feature_3081238842, and existing Feature_2413497656) used for IOCTL-code routing. No lock is added. - sub_1C003B1E0 (AfdQueryHandles): AfdPreventUnbind -> ObOpenObjectByPointer (DesiredAccess = 0x2000000, HandleAttributes = 0x40) -> AfdReallowUnbind is present identically in both builds. Only a Feature_3081238842 routing gate is added. No TOCTOU fix around the Ob* call. - sub_1C004449C (AfdPnpPower): for IOCTL 0xAFD1 the patched build wraps AfdPassQueryDeviceRelation in AfdPreventUnbind/AfdReallowUnbind, but this is gated by Feature_2083252539__private_IsEnabledDeviceUsage; the feature-disabled path calls the handler without a lock, identical to the unpatched build. Not an unconditional lock. - sub_1C000895C: string-matching helper. Change is the removal of the Feature_2480880955__private_IsEnabledDeviceUsage gate; the enabled path (always RtlFindUnicodeSubstring) is retained. Behavioral only.

Cosmetic / compiler-optimization changes: - sub_1C0081660, sub_1C0082E80, sub_1C0040C70, sub_1C007F750, sub_1C00489B0, sub_1C006EA18, sub_1C005BAB0, sub_1C0073058, sub_1C007F0CC: low similarity scores driven by inlining, function relocation, and re-matching artifacts. No security-relevant behavioral change.

9. Unmatched Functions

  • Removed: 0
  • Added: 0 Note: AfdCreateConnection_Old (sub_1C0009CD8) exists only in the unpatched build; in the patched build it is removed along with the Feature_1230705977 gate as part of finalizing the staged feature.

10. Confidence & Caveats

  • Confidence Level: High. The unpatched build contains both connection-creation functions and selects between them with a WIL feature gate at every call site; the patched build removes the gate and the legacy fallback. Neither function acquires the endpoint spinlock, so no lock was removed. Both operate on a thread-private allocation that is only published after all initialization, so there is no TOCTOU or use-after-free.
  • Assessment: No security-relevant change. The diff is staged-feature finalization (Feature_1230705977 and several related WIL features) plus compiler churn. The one genuine behavioral difference (a conditional IoCreateFile access mask in the new function) has no demonstrable, reachable security impact.