1. Overview

  • Unpatched Binary: afd_unpatched.sys
  • Patched Binary: afd_patched.sys
  • Overall Similarity Score: 0.9742
  • Diff Statistics: 1093 matched functions, 21 changed functions, 1072 identical functions, 0 unmatched functions in either direction.
  • Verdict: The patch adds a feature-staged state check inside two AFD SAN (Winsock Sockets-Direct) request handlers. Under the endpoint spinlock that already guards the IRP-list insertion, when a staging feature flag is enabled the handler now refuses to queue an IRP onto an endpoint whose abort byte ([endpoint+0x91]) is set, completing the IRP with STATUS_CANCELLED instead. When the flag is disabled the prior queuing behavior is retained unchanged. This is a defense-in-depth hardening check, gated behind a Windows feature-staging flag with the old path kept in the else branch. It is not a demonstrable, reachable use-after-free.

2. Vulnerability Summary

Finding 1: AfdSanFastCompleteRequest abort-state hardening check

  • Severity: Informational (no demonstrable reachable impact)
  • Vulnerability Class: Defense-in-depth state check (hardening against a lifetime/teardown edge case). Not a demonstrated CWE-416/CWE-362.
  • Affected Function: AfdSanFastCompleteRequest (0x1C0077700 unpatched / 0x1C00787A0 patched) — AFD SAN (Sockets-Direct) completion handler.

What actually changed: In both builds the handler acquires the endpoint spinlock (KeAcquireInStackQueuedSpinLock on [endpoint+0x30]), sets the IRP cancel routine (AfdSanCancelRequest), checks IRP->Cancel ([IRP+0x44]), and, if the IRP is not cancelled, inserts the IRP into the endpoint pending list at [endpoint+0x70] — all while holding that spinlock. The insertion is therefore already serialized against other holders of the endpoint lock.

The patch inserts, still under the lock and only after the IRP->Cancel check, a call to EvaluateCurrentState(&g_Feature_757798200_60363861_FeatureDescriptorDetails). This is a Windows feature-staging (velocity) gate. If the feature returns disabled, control falls through to the exact same queuing code as the unpatched build. If the feature is enabled and [endpoint+0x91] is non-zero, the IRP is not queued; the lock is released and the IRP is completed with STATUS_CANCELLED (0xC0000120).

Why this is not a demonstrable UAF: - The IRP-list insertion is not unsynchronized: it runs under [endpoint+0x30], the same lock the added check runs under. - The added byte is an endpoint abort/teardown state indicator; the new code is a state check ("do not queue onto an endpoint flagged as aborting"), not the removal of a lock or a lifetime/refcount change. - The change is gated behind a feature-staging flag with the previous behavior retained in the else branch, i.e. a staged rollout rather than an unconditionally delivered fix. - No freed-endpoint dereference, controllable object, or exploit primitive is present in or reachable from these functions on the evidence in the two builds.

Call Chain (reachability): AfdSanFastCompleteRequest is an AFD SAN (Sockets-Direct) completion handler. It is not reached by ordinary Winsock receive/send IOCTLs on a normal socket; it belongs to the SAN switch/helper request path. Its sibling AfdSanRedirectRequest (Finding 2) is invoked from AfdDispatch, AfdReceive, and AfdSend, but only when the endpoint is a SAN/switch endpoint. Reachability is therefore limited to the Sockets-Direct provider path.

Finding 2: AfdSanRedirectRequest abort-state hardening check

  • Severity: Informational (no demonstrable reachable impact)
  • Vulnerability Class: Defense-in-depth state check (same as Finding 1). Not a demonstrated CWE-416/CWE-362.
  • Affected Function: AfdSanRedirectRequest (0x1C00785E4 unpatched / 0x1C0079734 patched) — AFD SAN (Sockets-Direct) redirect-request handler.

What actually changed: Same pattern as Finding 1. In both builds the handler runs entirely under the endpoint spinlock ([endpoint+0x30]), validates the endpoint (magic 0x1AFD at [endpoint+0], type byte 4 at [endpoint+2], connection state at [endpoint+0x8C]), sets AfdSanCancelRequest, checks IRP->Cancel, and links the IRP into the endpoint list at [endpoint+0x70]. The patch adds, under the lock and after the IRP->Cancel check, the same EvaluateCurrentState(&g_Feature_757798200_60363861_FeatureDescriptorDetails) gate followed by cmp byte [endpoint+0x91], 0. If the feature is enabled and the endpoint is aborting, the IRP is completed with STATUS_CANCELLED instead of being queued; otherwise the original queuing path runs.

Call Chain (reachability): 1. NtDeviceIoControlFile on an AFD socket handle bound to a SAN/switch endpoint. 2. afd!AfdDispatch / afd!AfdReceive / afd!AfdSend (the three call sites of AfdSanRedirectRequest). 3. afd!AfdSanRedirectRequest, which links the IRP into the endpoint list under [endpoint+0x30].

3. Pseudocode Diff

AfdSanFastCompleteRequest (0x1C0077700 unpatched / 0x1C00787A0 patched)

// --- UNPATCHED ---
// (endpoint spinlock [endpoint+0x30] already held here)
Irp->CancelRoutine = AfdSanCancelRequest;      // xchg [IRP+0x68]
if (Irp->Cancel == 0) {                          // [IRP+0x44]
    InsertTailList(&Endpoint->PendingIrpList,    // [endpoint+0x70], under lock
                   &Irp->ListEntry);             // [IRP+0xA8]
}
// release [endpoint+0x30]

// --- PATCHED ---
// (endpoint spinlock [endpoint+0x30] already held here)
Irp->CancelRoutine = AfdSanCancelRequest;
if (Irp->Cancel == 0) {
    // Added, still under the lock, after the Cancel check:
    if (EvaluateCurrentState(&g_Feature_757798200_60363861_FeatureDescriptorDetails)
        && Endpoint->AbortByte /* [endpoint+0x91] */ != 0) {
        // feature enabled AND endpoint aborting -> do not queue
        Irp->IoStatus.Status = STATUS_CANCELLED; // 0xC0000120
        // (release lock, complete IRP)
    } else {
        // feature disabled OR endpoint not aborting -> unchanged old path
        InsertTailList(&Endpoint->PendingIrpList, &Irp->ListEntry);
    }
}

4. Assembly Analysis

Function: AfdSanFastCompleteRequest — unpatched 0x1C0077700 vs patched 0x1C00787A0

Unpatched IRP queuing path (under the endpoint spinlock):

00000001C0077A15  lea     rcx, [r15+30h]                 ; endpoint spinlock
00000001C0077A19  lea     rdx, [rsp+...LockHandle]
00000001C0077A21  call    cs:__imp_KeAcquireInStackQueuedSpinLock
00000001C0077A27  lea     rax, AfdSanCancelRequest        ; cancel routine
00000001C0077A2E  xchg    rax, [rdi+68h]                  ; set IRP->CancelRoutine
00000001C0077A32  cmp     byte ptr [rdi+44h], 0           ; IRP->Cancel
00000001C0077A36  jnz     short loc_1C0077A75             ; if cancelled -> cancel path
00000001C0077A38  add     rdi, 0A8h                       ; &IRP list entry
00000001C0077A3F  lea     rax, [r15+70h]                  ; endpoint IRP list head
00000001C0077A53  mov     [rdi], rcx                      ; InsertTailList (under lock)
00000001C0077A69  call    cs:__imp_KeReleaseInStackQueuedSpinLock

Patched IRP queuing path (same lock, added feature-gated abort check):

00000001C0078AC8  lea     rcx, [r15+30h]                 ; endpoint spinlock
00000001C0078AD4  call    cs:__imp_KeAcquireInStackQueuedSpinLock
00000001C0078ADA  lea     rax, AfdSanCancelRequest
00000001C0078AE1  xchg    rax, [rsi+68h]                  ; set IRP->CancelRoutine
00000001C0078AE5  cmp     byte ptr [rsi+44h], 0           ; IRP->Cancel
00000001C0078AE9  jnz     short loc_1C0078B42             ; if cancelled -> cancel path
00000001C0078AEB  lea     rcx, g_Feature_757798200_60363861_FeatureDescriptorDetails
00000001C0078AF2  call    EvaluateCurrentState            ; WIL feature-staging gate
00000001C0078AF7  test    eax, eax
00000001C0078AF9  jz      short loc_1C0078B05             ; feature OFF -> old queuing path
00000001C0078AFB  cmp     byte ptr [r15+91h], 0           ; endpoint abort state
00000001C0078B03  jnz     short loc_1C0078B42             ; if aborting -> STATUS_CANCELLED
00000001C0078B05  add     rsi, 0A8h                       ; queue (under lock)
00000001C0078B36  call    cs:__imp_KeReleaseInStackQueuedSpinLock

Function: AfdSanRedirectRequest — unpatched 0x1C00785E4 vs patched 0x1C0079734

Unpatched (IRP linked into list under the lock, no abort-byte check):

00000001C00786A4  lea     rax, AfdSanCancelRequest
00000001C00786AB  xchg    rax, [rbx+68h]                  ; set IRP->CancelRoutine
00000001C00786AF  cmp     byte ptr [rbx+44h], 0           ; IRP->Cancel
00000001C00786B3  jz      short loc_1C00786F2             ; not cancelled -> queue
00000001C00786F2  ; ... link IRP into [rsi+70h] under spinlock, then release

Patched (adds the feature-gated abort-byte check before queuing):

00000001C00797F4  lea     rax, AfdSanCancelRequest
00000001C00797FB  xchg    rax, [rdi+68h]                  ; set IRP->CancelRoutine
00000001C00797FF  cmp     byte ptr [rdi+44h], 0           ; IRP->Cancel
00000001C0079803  jnz     loc_1C00798AA                   ; if cancelled -> cancel path
00000001C0079809  lea     rcx, g_Feature_757798200_60363861_FeatureDescriptorDetails
00000001C0079810  call    EvaluateCurrentState            ; WIL feature-staging gate
00000001C0079815  test    eax, eax
00000001C0079817  jz      short loc_1C0079826             ; feature OFF -> old queuing path
00000001C0079819  cmp     byte ptr [rsi+91h], 0           ; endpoint abort state
00000001C0079820  jnz     loc_1C00798AA                   ; if aborting -> STATUS_CANCELLED
00000001C0079826  ; ... link IRP into [rsi+70h] under spinlock, then release

5. Trigger Conditions

No exploitable condition is demonstrable from the two builds. The changed code path is the AFD SAN (Sockets-Direct) request path, reached only for SAN/switch endpoints. Both builds perform the IRP-list insertion under the endpoint spinlock ([endpoint+0x30]); the patch only adds, under that same lock and behind a feature-staging flag, a refusal to queue onto an endpoint whose abort byte ([endpoint+0x91]) is already set. There is no removed lock, no lifetime/refcount change, and no reachable freed-object dereference in these functions.

6. Exploit Primitive & Development Notes

No exploit primitive is present. The added instructions are a state check performed while the endpoint spinlock is held; they neither create nor expose a memory-corruption primitive. Any claim of a kernel read/write primitive, pool grooming, or protection bypass would be speculation unsupported by the binaries and is therefore not made.

7. Debugger PoC Playbook

There is no vulnerability to reproduce. For inspection of the changed behavior only: - bp afd!AfdSanFastCompleteRequest and bp afd!AfdSanRedirectRequest. - In the patched build, single-step to the EvaluateCurrentState(&g_Feature_757798200_60363861_FeatureDescriptorDetails) call and the following cmp byte [endpoint+0x91], 0. The endpoint pointer is in r15 (AfdSanFastCompleteRequest) / rsi (AfdSanRedirectRequest); the endpoint spinlock is at [endpoint+0x30] and is held across the check and the list insertion. - In the unpatched build the same insertion runs under the same lock without the feature gate or the +0x91 check.

8. Changed Functions — Full Triage

  • AfdSanFastCompleteRequest (0x1C0077700 / 0x1C00787A0) (Hardening, feature-staged): Adds, under the existing endpoint spinlock, a EvaluateCurrentState(&g_Feature_757798200_60363861_FeatureDescriptorDetails) gate plus cmp byte [endpoint+0x91], 0; when enabled and aborting, the IRP is completed STATUS_CANCELLED instead of queued. Old path retained when the feature is off. AfdSanCancelRequest is the same cancel routine in both builds (relocated 0x1C0076090 -> 0x1C0077190). The AccessMode && Length!=0 guard before the buffer poke is present identically in both builds (register renaming only).
  • AfdSanRedirectRequest (0x1C00785E4 / 0x1C0079734) (Hardening, feature-staged): Same feature-gated [endpoint+0x91] abort-state check under the endpoint spinlock.
  • sub_1C003B310 / sub_1C003C3F0 (Behavioral): Connection endpoint creation refactored into a feature-flagged dispatch between two IoCreateFile-based creation paths. Servicing refactor; no security-relevant logic change.
  • sub_1C0058000 / sub_1C00592B0 (Behavioral): Connection endpoint pool management (SLIST reuse). Refactoring of the connection-creation call and register/variable reassignments.
  • sub_1C0046CD4 / sub_1C007DA4 (Behavioral): IRP completion helper updated for the connection-creation refactoring.
  • sub_1C007508C / sub_1C007608C (Behavioral): Inline endpoint initialization extracted into helper sub_1c0079474; feature-flag address rebased.
  • sub_1C0076FF0 / sub_1C00780E0 (Behavioral): Same endpoint-initialization extraction; lock/reference helpers rebased.
  • sub_1C0046EC0 / sub_1C0047FD0 (Behavioral): Large AFD connect/bind IOCTL handler. Rebasing and helper extraction.
  • sub_1C0046010 / sub_1C0047000 (Behavioral): Large dispatch handler. Address rebasing.
  • Cosmetic/False-Positive Matches: sub_1C0052810, sub_1C0046CA0, sub_1C000CE50, sub_1C00379D0, sub_1C005988A, sub_1C00761A0, sub_1C0075770, sub_1C0040EF0, sub_1C0033AD0, sub_1C00156F4, and sub_1C0047F50 are either different functions matched by the diff due to heavy restructuring, or minor decompiler artifacts (__noreturn stubs).

9. Unmatched Functions

No functions were uniquely added or removed. The patch relies on injecting the feature-staged state check into the two existing SAN handlers and on refactoring connection-object initialization.

10. Confidence & Caveats

  • Confidence Level: High that the described diff is exactly what the two builds contain; the queuing already runs under the endpoint spinlock in both builds, and the added check is gated behind g_Feature_757798200_60363861 with the prior behavior retained when disabled.
  • [endpoint+0x91] is treated as an abort/teardown state byte on the AFD endpoint; the added code refuses to queue onto an endpoint with that byte set. This is inferred from the patch context and the surrounding state validation (magic 0x1AFD, connection state at [endpoint+0x8C]).
  • Severity rationale: Rated Informational because the surrounding IRP-list work is already lock-protected, the change is a staged (feature-gated) hardening state check with the old path retained, the affected code is the niche AFD SAN (Sockets-Direct) path, and no reachable, demonstrable use-after-free or exploit primitive is present in the binaries.

DONE