1. Overview

Item Value
Unpatched binary afd_unpatched.sys
Patched binary afd_patched.sys
Overall similarity 0.9835 (16 changed of 1179 matched)
Identical functions 1163
Changed functions 16
Unmatched (added/removed) 0 / 0

Verdict. The patch adds a feature-staged correctness fix to the AFD "San" connection cancel/teardown path. In the unpatched build, AfdSanCancelAccept cancels one IRP and then resets the connection object without draining the other IRPs still queued on the connection's pending-IRP list at conn+0x70. Those siblings are left linked on a list whose head is about to be zeroed, on a connection object that is reset and recycled — a use-after-free / dangling-pointer condition (CWE-416) with a teardown race (CWE-362). The patch adds a drain loop, a teardown flag at conn+0x91, and matching gates in the send/receive and connection-creation paths. Every one of these changes is gated behind the Windows feature g_Feature_556471608_60363862; when that feature is disabled the patched binary still executes the original (unsafe) path, so this is a staged rollout rather than an unconditional fix. The connection is reset/recycled in place, not freed through ExFreePool, so the demonstrable impact is a list-integrity bugcheck / operation on a recycled object, not a proven arbitrary read/write primitive.


2. Vulnerability Summary

Finding 1 — Medium: pending IRPs not drained on teardown (AfdSanCancelAccept)

Field Value
Severity Medium
Class Use-After-Free of a recycled object (CWE-416) + teardown race (CWE-362)
Function (unpatched) AfdSanCancelAccept @ 0x1C007E110 (cancel routine for AFD San accept IRPs)
Function (patched) AfdSanCancelAccept @ 0x1C007F120 (relocated)
Feature gate g_Feature_556471608_60363862 via EvaluateCurrentState (old path retained when disabled)
Entry point NtDeviceIoControlFile on \Device\Afd, AFD San accept/redirect IOCTL path

Root cause. When an AFD San accept IRP is cancelled, AfdSanCancelAccept runs as the IRP's cancel routine. It: 1. Acquires the connection spinlock at conn+0x30. 2. Unlinks the current IRP from the connection's pending-IRP list (the IRP's own list entry is at irp+0xA8). 3. If the connection is not already marked closed (conn+0x08 & 0x800), it drops the endpoint reference held at conn+0x50 (AfdDereferenceEndpointInline), then memsets 0x48 bytes starting at conn+0x50, clears the state word at conn+0x08, and rewrites the type tag at conn+0x00 to 0xAFD0 (the recycled/free marker) with the byte at conn+0x02 set to 1. 4. Completes only the current IRP with STATUS_CANCELLED (0xC0000120).

The problem: the pending-IRP list head lives at conn+0x70/+0x78, which is inside the memset range (conn+0x50 .. conn+0x98). Any other IRPs that were queued on that list (their entries at irp+0xA8) are never unlinked or completed. After the memset zeroes the list head and the type tag marks the connection recycled, those sibling IRPs still point at the connection. When one of them is later cancelled or completed, its cancel routine (AfdSanCancelRequest @ 0x1C007E3A0) re-acquires the connection lock and manipulates list linkage on the zeroed head / recycled object → list-integrity __fastfail (int 29h) bugcheck, or operation on a connection object that has been reset and may have been re-handed-out by the AFD SLIST allocator.

What the patch does. The patched AfdSanCancelAccept @ 0x1C007F120, when g_Feature_556471608_60363862 is enabled: 1. Sets a teardown flag at conn+0x91 = 1 before touching the list. 2. Walks the entire pending-IRP list at conn+0x70, unlinking every entry into a local drain chain (linked through the IRP list-entry fields); the connection is not reset in this first locked pass. 3. Releases the spinlock, then completes each drained IRP with STATUS_CANCELLED (0xC0000120) outside the lock (clearing each IRP's cancel routine and synchronising with the cancel spinlock first). 4. Re-acquires the connection spinlock a second time and only then runs the endpoint-deref + memset reset sequence.

When the feature is disabled, the patched routine falls through the else branch to the exact original sequence (endpoint deref, memset at conn+0x50, type reset) with no drain — identical to the unpatched behaviour.

Entry point & call chain. 1. A Winsock operation issues NtDeviceIoControlFile on \Device\Afd reaching the AFD San accept path. 2. AfdSanAcceptCore @ 0x1C007D0B8 installs AfdSanCancelAccept as the accept IRP's CancelRoutine (lea rax, AfdSanCancelAccept at 0x1C007D2B5). 3. When the IRP is cancelled (endpoint closed/aborted), AfdSanCancelAccept runs and performs the teardown described above.

Finding 2 — Low: missing teardown-flag gate in send/receive insert paths

Field Value
Severity Low (race-window amplifier for Finding 1; same feature gate)
Class Missing state check / TOCTOU (CWE-362)
Functions (unpatched) AfdSanFastCompleteRequest @ 0x1C007FE70, AfdSanRedirectRequest @ 0x1C0081074
Functions (patched) AfdSanFastCompleteRequest @ 0x1C0080F80, AfdSanRedirectRequest @ 0x1C0082238 (both relocated)
Patched behavior Before inserting an IRP into the pending list at conn+0x70, they now additionally require !EvaluateCurrentState(g_Feature_556471608_60363862) || conn+0x91 == 0.

The unpatched insert paths queue an IRP into conn+0x70 after only checking the IRP-cancelled flag (irp+0x68). They have no notion of a teardown flag (none exists in the unpatched build). The patched paths add the conn+0x91 teardown-flag check so that, once teardown has begun, new IRPs are not queued onto a connection that is being drained. Both the relocated cancel routine reference (AfdSanCancelRequest, relocated 0x1C007E3A0 → 0x1C007F4D0) and this gate belong to the same staged feature.

Finding 3 — Low: device-attachment validation in connection creation (AfdCreateConnection)

Field Value
Severity Low (defense-in-depth; same feature gate, old path retained)
Class Improper input validation (CWE-20)
Function (unpatched) AfdCreateConnection @ 0x1C0055AB0
Function (patched) AfdCreateConnection @ 0x1C0055BF0 (new); original logic retained as AfdCreateConnection_Old @ 0x1C0007DE4
Patched behavior The new routine adds IoGetDeviceAttachmentBaseRef-based device-attachment validation (the count of IoGetDeviceAttachmentBaseRef call sites rises from 3 to 5 across the binary) plus DesiredAccess/Options checks. On mismatch it fails the request.

This is a hardening of connection creation, not the primary teardown issue. It is gated behind the same feature; when the feature is disabled the retained AfdCreateConnection_Old runs instead. Note that address 0x1C0007DE4 holds a different function in the unpatched build (rbc_InitializeFeatureStaging); AfdCreateConnection_Old is the relocated pre-patch connection-creation logic and must be matched by content, not by that reused address.


3. Pseudocode Diff

AfdSanCancelAccept (0x1C007E1100x1C007F120)

// ====== UNPATCHED AfdSanCancelAccept @ 0x1C007E110 ======
void AfdSanCancelAccept(void* a1, IRP* a2) {
    void* ctx  = a2->Tail.Overlay.CurrentStackLocation->...NamedPipeType; // context
    conn = ctx[3];                                     // *(ctx+0x18)
    KeAcquireInStackQueuedSpinLockAtDpcLevel(conn+0x30, &LockHandle);

    // Unlink ONLY the current IRP from the pending list (entry at irp+0xA8)
    Flink = a2->Tail.Overlay.ListEntry.Flink;
    if (Flink) {
        // integrity-checked RemoveEntryList of the current IRP
        Blink->Flink = Flink; Flink->Blink = Blink;

        if ((*(uint*)(conn+0x08) & 0x800) == 0) {      // not already closed
            // *** NO DRAIN OF THE OTHER PENDING IRPs ***
            AfdDereferenceEndpointInline(*(void**)(conn+0x50)); // drop endpoint ref
            memset(conn+0x50, 0, 0x48);                // zeroes conn+0x50..0x98,
                                                       // INCLUDING the list head at +0x70/+0x78
            *(uint*)(conn+0x08) = 0;
            *(ushort*)conn      = 0xAFD0;              // recycled/free marker
            *(byte*)(conn+0x02) = 1;
        }
        KeReleaseInStackQueuedSpinLockFromDpcLevel(&LockHandle);
        IoReleaseCancelSpinLock(a2->CancelIrql);
        _InterlockedExchange(conn+0x158, 0);
        ObfDereferenceObject(ctx);                     // deref the context object
        a2->IoStatus.Status = STATUS_CANCELLED;        // 0xC0000120
        IofCompleteRequest(a2, AfdPriorityBoost);      // completes ONLY this IRP
        // *** any other IRPs still on conn+0x70 now dangle on a zeroed head ***
    } else {
        KeReleaseInStackQueuedSpinLockFromDpcLevel(&LockHandle);
        IoReleaseCancelSpinLock(a2->CancelIrql);
    }
}

// ====== PATCHED AfdSanCancelAccept @ 0x1C007F120 ======
void AfdSanCancelAccept(void* a1, IRP* a2) {
    conn = ctx[3];
    KeAcquireInStackQueuedSpinLockAtDpcLevel(conn+0x30, &LockHandle);
    // ... identical integrity-checked RemoveEntryList of the current IRP ...
    void* drain = NULL;
    if ((*(uint*)(conn+0x08) & 0x800) == 0) {
        if (EvaluateCurrentState(&g_Feature_556471608_60363862)) {
            *(byte*)(conn+0x91) = 1;                    // << NEW teardown flag
            // << NEW: drain EVERY IRP off the list at conn+0x70 into 'drain'
            for (head = conn+0x70; *head != head; ) {
                entry = *head;
                // integrity-checked unlink of 'entry'
                *head = entry->Flink; entry->Flink->Blink = head;
                entry->Flink = NULL; entry->Blink = drain; drain = entry;
            }
        } else {
            // OLD path retained (feature disabled): reset immediately, no drain
            AfdDereferenceEndpointInline(*(void**)(conn+0x50));
            memset(conn+0x50, 0, 0x48);
            *(uint*)(conn+0x08) = 0; *(ushort*)conn = 0xAFD0; *(byte*)(conn+0x02) = 1;
        }
    }
    KeReleaseInStackQueuedSpinLockFromDpcLevel(&LockHandle);
    IoReleaseCancelSpinLock(a2->CancelIrql);

    if (EvaluateCurrentState(&g_Feature_556471608_60363862)) {
        // << NEW: complete every drained IRP outside the lock, clearing its
        //         cancel routine and syncing with the cancel spinlock first.
        while (drain) {
            IRP* p = (IRP*)((char*)drain - 0xA8);
            drain = drain->Blink;
            if (_InterlockedExchange64(&p->CancelRoutine, 0) == 0) {
                IoAcquireCancelSpinLock(&Irql); IoReleaseCancelSpinLock(Irql);
            }
            p->IoStatus.Information = 0;
            p->IoStatus.Status = STATUS_CANCELLED;      // 0xC0000120
            IofCompleteRequest(p, AfdPriorityBoost);
        }
        // << NEW: re-acquire the lock and only NOW reset the connection.
        KeAcquireInStackQueuedSpinLock(conn+0x30, &LockHandle);
        if ((*(uint*)(conn+0x08) & 0x800) == 0) {
            AfdDereferenceEndpointInline(*(void**)(conn+0x50));
            memset(conn+0x50, 0, 0x48);
            *(uint*)(conn+0x08) = 0; *(ushort*)conn = 0xAFD0; *(byte*)(conn+0x02) = 1;
        }
        KeReleaseInStackQueuedSpinLock(&LockHandle);
    }
    _InterlockedExchange(conn+0x158, 0);
    ObfDereferenceObject(ctx);
    a2->IoStatus.Status = STATUS_CANCELLED;
    IofCompleteRequest(a2, AfdPriorityBoost);
}

AfdSanFastCompleteRequest / AfdSanRedirectRequest (teardown-flag gate)

// UNPATCHED (insert into pending list at conn+0x70)
KeAcquireInStackQueuedSpinLock(conn+0x30, &LockHandle);
_InterlockedExchange64(&irp->CancelRoutine, AfdSanCancelRequest);
if (*(byte*)(irp+0x44) == 0) {                 // IRP not already cancelled
    InsertTailList(conn+0x70, irp+0xA8);       // unconditional insert
}

// PATCHED (adds the teardown-flag gate under the same feature)
if (*(byte*)(irp+0x44) == 0
    && (!EvaluateCurrentState(&g_Feature_556471608_60363862)
        || *(byte*)(conn+0x91) == 0)) {        // reject if teardown in progress
    InsertTailList(conn+0x70, irp+0xA8);
}

4. Assembly Analysis

Unpatched AfdSanCancelAccept @ 0x1C007E110 — full listing with inline annotations

; Prologue
00000001C007E110  mov     r11, rsp
00000001C007E113  mov     [r11+8], rbx
00000001C007E117  mov     [r11+10h], rbp
00000001C007E11B  mov     [r11+18h], rsi
00000001C007E11F  push    rdi
00000001C007E120  sub     rsp, 40h

; rdx = IRP. Resolve context and connection object.
00000001C007E124  mov     rax, [rdx+0B8h]                 ; IRP->Tail.Overlay
00000001C007E12B  mov     rsi, rdx                        ; rsi = IRP
00000001C007E12E  lea     rdx, [r11-28h]                  ; LockHandle (stack)
00000001C007E132  mov     rbp, [rax+20h]                  ; rbp = context object
00000001C007E136  mov     rdi, [rbp+18h]                  ; rdi = connection object
00000001C007E13A  lea     rcx, [rdi+30h]                  ; &conn->SpinLock (conn+0x30)
00000001C007E13E  call    cs:__imp_KeAcquireInStackQueuedSpinLockAtDpcLevel

; Integrity-checked RemoveEntryList of the CURRENT IRP (entry at irp+0xA8)
00000001C007E14A  lea     rax, [rsi+0A8h]                 ; &irp->list_entry
00000001C007E151  mov     rdx, [rax]                      ; Flink
00000001C007E154  test    rdx, rdx
00000001C007E157  jz      loc_1C007E230                   ; null → early release
00000001C007E15D  cmp     [rdx+8], rax                    ; Flink->Blink == us?
00000001C007E161  jnz     loc_1C007E229                   ; corrupt → __fastfail
00000001C007E167  mov     rcx, [rax+8]                    ; Blink
00000001C007E16B  cmp     [rcx], rax                      ; Blink->Flink == us?
00000001C007E16E  jnz     loc_1C007E229
00000001C007E174  mov     [rcx], rdx                      ; Blink->Flink = Flink
00000001C007E177  mov     [rdx+8], rcx                    ; Flink->Blink = Blink

00000001C007E17B  test    dword ptr cs:WPP_MAIN_CB.Queue, 40000h  ; WPP trace flag
00000001C007E185  jz      short loc_1C007E19E

; Is the connection already closed?
00000001C007E19E  test    dword ptr [rdi+8], 800h
00000001C007E1A5  jnz     short loc_1C007E1CF             ; if closed, skip reset

; ===== NO DRAIN OF SIBLING IRPs — straight to connection reset =====
00000001C007E1A7  mov     rcx, [rdi+50h]                  ; endpoint pointer at conn+0x50
00000001C007E1AB  call    AfdDereferenceEndpointInline    ; drop endpoint reference
00000001C007E1B0  xor     edx, edx
00000001C007E1B2  lea     rcx, [rdi+50h]                  ; dst = conn+0x50
00000001C007E1B6  lea     r8d, [rdx+48h]                  ; size = 0x48 (covers +0x50..+0x98)
00000001C007E1BA  call    memset                          ; zeroes list head at +0x70/+0x78 too
00000001C007E1BF  and     dword ptr [rdi+8], 0            ; state = 0
00000001C007E1C3  mov     eax, 0AFD0h
00000001C007E1C8  mov     [rdi], ax                       ; type = 0xAFD0 (recycled marker)
00000001C007E1CB  mov     byte ptr [rdi+2], 1
; ===== other IRPs on the conn+0x70 list now dangle on a zeroed head =====

00000001C007E1CF  lea     rcx, [rsp+48h+LockHandle]
00000001C007E1D4  call    cs:__imp_KeReleaseInStackQueuedSpinLockFromDpcLevel
00000001C007E1E0  mov     cl, [rsi+45h]                   ; IRP cancel IRQL
00000001C007E1E3  call    cs:__imp_IoReleaseCancelSpinLock
00000001C007E1EF  xor     eax, eax
00000001C007E1F1  mov     rcx, rbp                        ; rcx = context object
00000001C007E1F4  xchg    eax, [rdi+158h]                 ; clear field at conn+0x158
00000001C007E1FA  call    cs:__imp_ObfDereferenceObject   ; deref context (not conn)

00000001C007E206  mov     dl, cs:AfdPriorityBoost
00000001C007E20C  mov     rcx, rsi                        ; rcx = current IRP
00000001C007E20F  and     qword ptr [rsi+38h], 0          ; Information = 0
00000001C007E214  mov     dword ptr [rsi+30h], 0C0000120h ; STATUS_CANCELLED
00000001C007E21B  call    cs:__imp_IofCompleteRequest     ; completes ONLY this IRP
00000001C007E227  jmp     short loc_1C007E250

; List-corruption fail-fast:
00000001C007E229  mov     ecx, 3
00000001C007E22E  int     29h

; Null-Flink early release:
00000001C007E230  lea     rcx, [rsp+48h+LockHandle]
00000001C007E235  call    cs:__imp_KeReleaseInStackQueuedSpinLockFromDpcLevel
00000001C007E241  mov     cl, [rsi+45h]
00000001C007E244  call    cs:__imp_IoReleaseCancelSpinLock

00000001C007E250  mov     rbx, [rsp+48h+arg_0]
00000001C007E255  mov     rbp, [rsp+48h+arg_8]
00000001C007E25A  mov     rsi, [rsp+48h+arg_10]
00000001C007E25F  add     rsp, 40h
00000001C007E263  pop     rdi
00000001C007E264  retn

What the patch changes (patched AfdSanCancelAccept @ 0x1C007F120). After the same "already closed" check, the patched routine evaluates g_Feature_556471608_60363862 (call EvaluateCurrentState at 0x1C007F1C7). If the feature is enabled it sets conn+0x91 = 1 (0x1C007F1D0) and runs a drain loop over the list head at conn+0x70 (0x1C007F1D70x1C007F20C), unlinking every entry into a local chain without resetting the connection in that pass. If the feature is disabled it branches to loc_1C007F20E, which is the original endpoint-deref + memset(conn+0x50, 0, 0x48) + type-reset sequence — identical to the unpatched code. After releasing the first spinlock it re-evaluates the feature (0x1C007F251); when enabled it completes each drained IRP with STATUS_CANCELLED outside the lock (0x1C007F26D0x1C007F2C5), then re-acquires the connection spinlock via KeAcquireInStackQueuedSpinLock (0x1C007F2D0) and only then runs the reset sequence (0x1C007F2E50x1C007F308). Ordering when enabled: drain → release lock → complete siblings → re-acquire lock → reset.


5. Trigger Conditions

  1. Open an AFD endpoint and reach the AFD San accept path (AfdSanAcceptCore @ 0x1C007D0B8), which installs AfdSanCancelAccept as the accept IRP's cancel routine.
  2. Queue more than one IRP on the connection's pending list at conn+0x70. The insert paths AfdSanFastCompleteRequest / AfdSanRedirectRequest add IRPs (entry at irp+0xA8) to this list.
  3. Cancel one queued IRP (endpoint close/abort) so AfdSanCancelAccept runs while at least one sibling IRP is still on the list. In the unpatched build (and in the patched build with g_Feature_556471608_60363862 disabled) the connection is reset without draining the siblings.
  4. Observable effect. When a surviving sibling IRP is subsequently cancelled/completed, AfdSanCancelRequest @ 0x1C007E3A0 re-acquires the connection lock and operates on list linkage whose head (conn+0x70/+0x78) was zeroed by the reset memset, on a connection whose type tag is now 0xAFD0. The integrity checks in that path (cmp [Flink+8], entry etc.) fail, driving a __fastfail(3) / int 29h bugcheck. If the reset connection has already been re-handed-out by the AFD SLIST allocator, the sibling operates on a different live object.

Because this is a cancel/teardown race across two IRPs on the same connection, reliable triggering requires racing a cancel against at least one still-queued sibling IRP.


6. Impact Assessment

Primitive. The unpatched teardown leaves sibling IRPs linked on a connection object that has been reset in place: the endpoint reference at conn+0x50 is dropped, the 0x48-byte region at conn+0x50 (including the pending-list head at conn+0x70/+0x78) is zeroed, and the type tag is set to the recycled marker 0xAFD0. The object is not freed to the pool in this routine — there is no ExFreePool here; it is recycled in place and returned to the AFD connection allocator for reuse.

Demonstrable impact. - A subsequent cancel/completion of a stale sibling IRP hits the list-integrity checks in AfdSanCancelRequest on a zeroed list head, producing a __fastfail/int 29h bugcheck — a reliable local denial of service. - Because the connection is recycled rather than freed, a stale IRP can also end up operating on a re-issued connection object under lock (use of a recycled object, CWE-416). Turning that into a controlled read/write would require winning the reallocation race and controlling the reused object's contents; that is not demonstrable from these binaries and is not claimed here.

Why Medium, not Critical. - The fix is feature-staged: the drain, the teardown flag, and the device-attachment check are all gated behind g_Feature_556471608_60363862. With the feature disabled the patched binary still runs the original unsafe path, so the vulnerable behaviour is not unconditionally removed. - The object is reset/recycled, not pool-freed, so there is no clean pool-allocation UAF primitive and no demonstrable arbitrary read/write. - Reaching the bug requires the AFD San accept/redirect path plus a cancel-versus-sibling race.

No exploit chain (pool spray, information leak, KASLR defeat, SMEP/CFG/HVCI bypass, or token theft) is asserted, because none is provable from the binaries.


7. Debugger Notes

Target: afd_unpatched.sys (or afd_patched.sys with g_Feature_556471608_60363862 disabled), kernel debugger attached. Symbols resolve afd!AfdSanCancelAccept.

Breakpoints

; (1) Entry to the teardown/cancel routine
bp afd_unpatched+0x7e110
; rdx = IRP being cancelled. context = *(*(rdx+0xB8)+0x20); connection = *(context+0x18) (rdi).

; (2) Start of the reset sequence (no drain performed first)
bp afd_unpatched+0x7e1a7
; rdi = connection. Inspect the pending-list head at rdi+0x70 / rdi+0x78:
; if Flink (rdi+0x70) != &rdi+0x70, sibling IRPs remain and will be left dangling.

; (3) The memset that resets the connection (and zeroes the list head)
bp afd_unpatched+0x7e1ba
; rcx = rdi+0x50, r8d = 0x48 — confirm the range covers rdi+0x70/+0x78.

; (4) Cancel routine for the SURVIVING sibling IRPs
bp afd_unpatched+0x7e3a0   ; AfdSanCancelRequest
; Runs later on a stale sibling IRP; it re-acquires the connection lock and
; manipulates list linkage on the (now zeroed) head — the fault/failfast site.

What to inspect

BP Register / memory Why
+0x7e110 rdx = IRP; *(*(rdx+0xB8)+0x20) → context; *(context+0x18) → connection Resolves the connection being torn down.
+0x7e1a7 rdi+0x70 (Flink), rdi+0x78 (Blink) If Flink ≠ &rdi+0x70, siblings remain undrained.
+0x7e1ba rcx = rdi+0x50, r8d = 0x48 Confirms the reset range covers the list head at +0x70.
+0x7e1f4 [rdi+0x158] after xchg Field at +0x158 cleared during teardown.
+0x7e3a0 connection as seen by a surviving sibling IRP Where the stale reference is used after reset.

Connection-structure layout

Offset Field
+0x00 Type tag (0xAFD0 written on reset/recycle)
+0x02 Byte set to 1 on reset
+0x08 State flags (bit 0x800 = closed)
+0x30 KSPIN_LOCK (connection lock)
+0x50 Endpoint back-pointer (AfdDereferenceEndpointInline); start of the 0x48-byte reset memset
+0x70 Pending-IRP list head Flink (initialised to self; inside the reset range)
+0x78 Pending-IRP list head Blink
+0x91 Teardown flag (patched only, feature-gated)
+0x158 Field cleared by xchg during teardown

8. Changed Functions — Full Triage

Security-relevant (behavioural)

Function Sim. Change Note
AfdSanCancelAccept @ 0x1C007E110 → 0x1C007F120 0.60 security Adds feature-gated pending-IRP drain + teardown flag conn+0x91; old (undrained) path retained under the disabled branch. Primary finding.
AfdSanAcceptCore @ 0x1C007D0B8 0.98 security Installs the (relocated) AfdSanCancelAccept as the accept IRP cancel routine; connection init refactor.
AfdSanFastCompleteRequest @ 0x1C007FE70 → 0x1C0080F80 0.97 security Adds conn+0x91 teardown-flag gate before list insert; cancel routine relocated to AfdSanCancelRequest @ 0x1C007F4D0.
AfdSanRedirectRequest @ 0x1C0081074 → 0x1C0082238 0.95 security Same teardown-flag gate; cancel routine relocated.
AfdCreateConnection @ 0x1C0055AB0 → 0x1C0055BF0 0.10 security New routine adds IoGetDeviceAttachmentBaseRef device-attachment validation + access/option checks; original logic retained as AfdCreateConnection_Old @ 0x1C0007DE4. Feature-gated.

Behavioural but not directly exploitable

Function Sim. Change Note
sub_1C00589F0 0.91 behavioural Feature-gated dispatch between old and new connection-creation paths.
sub_1C003F0CC 0.76 behavioural Feature-gated dispatch between AfdCreateConnection (0x1C0055AB0) and new 0x1C0055BF0.
sub_1C005591C 0.93 behavioural Connection/endpoint SLIST allocator; feature-gated alloc path + refactor.
sub_1C0057ED0 0.92 behavioural IOCTL dispatch; connection-alloc path gated; stack layout shift.
sub_1C005A210 0.92 behavioural IOCTL dispatch; connection-alloc paths gated.
AfdSanConnectHandler @ 0x1C007E500 0.96 behavioural Cancel-routine address + WPP trace changes; feature-gated paths.
AfdCreateConnection_Old @ 0x1C0007DE4 0.36 behavioural Relocated pre-patch AfdCreateConnection logic kept for the feature-disabled fallback. Address 0x1C0007DE4 is a different function (rbc_InitializeFeatureStaging) in the unpatched build — match by content.

Cosmetic / register-allocation

  • sub_1C0046230 (0.98) and sub_1C00379D0 (0.94) — stack-variable renames and WPP/telemetry address changes; no semantic effect.
  • EvaluateCurrentState @ 0x1C0007AF8 — the Windows feature-staging evaluator used to gate the patched paths. It is present at the same address in both builds (not a new function); the diff attributes low similarity to relocation/inlining churn, not new logic.

9. Unmatched Functions

None added or removed. All changes are in-place edits or relocations of existing routines. The "new" AfdCreateConnection @ 0x1C0055BF0 and AfdCreateConnection_Old @ 0x1C0007DE4 are relocated/feature-staged variants of the original AfdCreateConnection @ 0x1C0055AB0, not net additions.

Implication: No mitigation was removed. The added surface is the feature-gated teardown drain, the conn+0x91 teardown flag, and the device-attachment validation — all controlled by g_Feature_556471608_60363862.


10. Confidence & Caveats

Confidence: High on mechanism, deliberately conservative on impact. The unpatched teardown without a sibling-IRP drain, and the patched feature-gated drain + teardown flag, are unambiguous in both the disassembly and the decompilation. The connection is reset/recycled in place (type 0xAFD0, memset at conn+0x50), not freed to the pool; there is no ExFreePool on this object in the cancel path.

Facts anchored to the binaries. - AfdSanCancelAccept reset sequence: endpoint deref at conn+0x50 (AfdDereferenceEndpointInline), memset(conn+0x50, 0, 0x48), type 0xAFD0, int 29h fail-fast on list corruption (0x1C007E1A70x1C007E22E). - Patched drain over conn+0x70 and second locked reset (0x1C007F1C70x1C007F318). - Teardown-flag gate in the insert paths keyed on conn+0x91 and EvaluateCurrentState(g_Feature_556471608_60363862). - AfdSanAcceptCore @ 0x1C007D0B8 installs the cancel routine (lea rax, AfdSanCancelAccept at 0x1C007D2B5).

Not claimed (no supporting evidence in the binaries). Pool-spray reclamation, information leaks, KASLR defeat, SMEP/CFG/HVCI bypass, arbitrary kernel read/write, and privilege escalation to SYSTEM. The demonstrable impact is a list-integrity bugcheck (local DoS) and use of a recycled connection object; anything beyond that would require winning a reallocation race and controlling the reused object, which is not shown here.

Staging. Because the drain, the teardown flag, and the device check are all gated behind g_Feature_556471608_60363862 with the original paths retained, the patched binary only exhibits the corrected behaviour when that feature is enabled at runtime.