1. Overview

  • Unpatched Binary: afd_unpatched.sys
  • Patched Binary: afd_patched.sys
  • Overall Similarity: 0.9888
  • Diff Statistics: 1073 matched functions, 10 changed functions, 1063 identical functions, and 0 unmatched functions in either direction.
  • Verdict: The patch adds bounds clamping to the SAN (System Area Network) accept path in AfdSanAcceptCore, where the remote peer's transport-address length is used as a copy size without an upper-bound check against the destination buffer capacity. The clamps address a real out-of-bounds write, but they are gated behind a Windows feature flag (EvaluateCurrentState on g_Feature_232206649_59779869); when the flag is disabled the patched code follows the same unbounded path as the unpatched build. The path is only reachable through SAN sockets (endpoint type 0x1AFD), a specialized direct-access networking path, so severity is assessed as Medium rather than High.

2. Vulnerability Summary

  • Severity: Medium
  • Vulnerability Class: Out-of-bounds write (CWE-787)
  • Affected Function: AfdSanAcceptCore

Root Cause: AfdSanAcceptCore handles SAN socket accept operations. When a connection is accepted, the function copies the remote peer's transport-address data (captured at connect time) into the accepting socket's address buffer, which is described by an MDL and reached through MmMapLockedPages. The copy size is taken directly from a 16-bit length field in the connection data (*(word)(ConnData+0x14), plus a small constant of 8, 2, or 4 depending on the address format). In the unpatched build, the destination capacity (*(dword)(AcceptInfo+0x10), AddressBufferLength) is only tested for being non-zero (cmp [rbx+0x10], edi; jbe); it is never compared against the copy size. A peer whose transport-address length exceeds the accepting socket's address-buffer capacity therefore causes a write past the end of the destination buffer.

The patched build adds, before each copy, a minimum-capacity requirement (>= 0xc) and a clamp of the copy size to the destination capacity (min(copy_size, capacity)). Each of these new checks is guarded by EvaluateCurrentState(&g_Feature_232206649_59779869_FeatureDescriptorDetails); when the feature evaluates to disabled, the copies run without the clamp exactly as in the unpatched build.

Attacker-Reachable Entry Point & Call Chain: Reachable locally from a process that creates an AFD SAN socket and issues an accept IOCTL. 1. NtDeviceIoControlFile (User-mode API targeting \Device\Afd) 2. AfdDispatchDeviceControl (Kernel dispatch) 3. AfdAccept / AfdServiceSuperAccept (IOCTL_AFD_ACCEPT / IOCTL_AFD_SUPER_ACCEPT) 4. AfdServiceWaitForListen 5. AfdSanAcceptCore (address copy)

Reachability is constrained to SAN endpoints (type 0x1AFD); creating a functional SAN socket requires an active SAN provider and is not part of the ordinary TCP/UDP accept path.


3. Decompilation Diff

Real decompiled excerpts from both builds. v15 is the accept address descriptor (*(endpoint+0xB8)): v15[2]=AddressBuffer offset (+0x8), v15[4]=AddressBufferLength capacity (+0x10), v15[6]=AddressOffset (+0x18). v36/v59 is the stored remote transport address; *(word)(v36+0x14) is the peer-supplied length.

// --- UNPATCHED AfdSanAcceptCore ---
if ( v15[4] != 0 )                     // ONLY checks capacity != 0
{
    v21 = *(unsigned __int16 *)(v36 + 20);   // peer address length (+0x14)
    LODWORD(Size) = v21 + 8;                 // copy size, no upper bound
    v22 = *(_QWORD *)(a1 + 8);               // MDL
    if ( *(_BYTE *)(v8 + 172) != 0 )         // TDI-format flag
    {
        v23 = v21 + 2;
        ...
        memmove(&v26[v15[6] + v15[2]], (const void *)(v36 + 22), v23);   // OOB write
    }
    else
    {
        ...
        memmove(&v28[v15[6] + v15[2]], (const void *)(v36 + 16), (unsigned int)Size); // OOB write
    }
    ...
}

// --- PATCHED AfdSanAcceptCore ---
if ( v15[4] == 0
  || EvaluateCurrentState(&g_Feature_232206649_59779869_...) && v15[4] < 0xCu )  // NEW min capacity 12 (feature-gated)
{
    goto LABEL_59;
}
LODWORD(Irql) = v59[10];
v22 = (_DWORD)Irql + 8;
if ( *(_BYTE *)(v7 + 172) != 0 )
{
    v23 = (_WORD)Irql + 2;
    if ( EvaluateCurrentState(&g_Feature_232206649_59779869_...)      // NEW clamp (feature-gated)
      && (unsigned __int64)(unsigned int)v15[4] - 2 < v23 )
    {
        v23 = *((_WORD *)v15 + 8) - 2;                                 // clamp to capacity - 2
    }
    ...
    memmove(&v25[v15[2]], v59 + 11, v23);
}
else
{
    if ( EvaluateCurrentState(&g_Feature_232206649_59779869_...) && v15[4] < v22 )  // NEW clamp (feature-gated)
        v22 = v15[4];                                                 // clamp to capacity
    ...
    memmove(&v32[v15[2] + v15[6]], v59 + 8, v33);
}

When EvaluateCurrentState returns 0, none of the v15[4] comparisons run and the copies use the unclamped sizes, matching the unpatched behavior.


4. Assembly Analysis

Instructions below are copied from the disassembly of AfdSanAcceptCore @ 0x1C007508C in each build.

Unpatched (missing upper-bound check and unclamped copies):

; Capacity check (only != 0):
 0x1c0075305  cmp     [rbx+0x10], edi         ; edi=0; AddressBufferLength
 0x1c0075308  jbe     0x1c0075485             ; skip copies only if capacity == 0

; Load peer length and compute copy size, no bounds:
 0x1c007530e  movzx   r12d, word [r9+0x14]    ; r12 = peer transport addr len
 0x1c0075313  lea     eax, [r12+0x8]          ; size = len + 8
 0x1c0075318  mov     [rsp+Size], eax

; Primary copy (non-TDI path):
 0x1c00753d2  mov     r8d, [rsp+Size]         ; r8 = len + 8 (unclamped)
 0x1c00753df  lea     rdx, [r12+0x10]         ; src = ConnData+0x10
 0x1c00753e4  mov     eax, [rbx+0x18]         ; AddressOffset
 0x1c00753e7  mov     ecx, [rbx+0x8]          ; AddressBuffer
 0x1c00753ea  add     rax, r9
 0x1c00753ed  add     rcx, rax                ; dest = AddressBuffer + AddressOffset + MDL VA
 0x1c00753f0  call    memmove

; Primary copy (TDI path, size = len + 2):
 0x1c007539c  call    memmove

; Secondary copies (also unclamped):
 0x1c0075432  movzx   r8d, word [r12+0x18]    ; secondary len
 0x1c007543e  add     r8, r13                 ; + 2 (TDI secondary)
 0x1c0075449  call    memmove
 0x1c007546b  movzx   eax, word [r12+0x18]
 0x1c0075471  add     eax, 0x4                ; + 4 (non-TDI secondary)
 0x1c0075480  call    memmove

Patched (feature-gated minimum-capacity and clamp-to-capacity):

 0x1c0075317  cmp     [rbx+0x10], edi         ; same capacity != 0 check
 0x1c007531a  jbe     0x1c0075610
 0x1c0075320  lea     rcx, g_Feature_232206649_59779869_FeatureDescriptorDetails
 0x1c0075327  call    EvaluateCurrentState    ; feature gate
 0x1c007532c  test    eax, eax
 0x1c007532e  jz      0x1c007533a             ; feature disabled -> skip min-size check
 0x1c0075330  cmp     dword [rbx+0x10], 0xc   ; NEW: require capacity >= 12
 0x1c0075334  jb      0x1c0075610
; ...
 0x1c0075433  cmp     [rbx+0x10], r14d        ; NEW: clamp size to capacity
 0x1c0075437  cmovb   r14d, [rbx+0x10]        ; r14 = min(size, capacity)

5. Trigger Conditions

  1. Obtain a handle: Create an AFD SAN socket (endpoint type 0x1AFD) or open \Device\Afd and drive it to the SAN endpoint state.
  2. Bind and Listen: Issue an AfdBind IOCTL with a SAN transport address. The accepting socket's address buffer is set up with a specific capacity (AddressBufferLength at AcceptInfo+0x10).
  3. Issue Accept: Post an accept on the listening SAN socket via IOCTL_AFD_ACCEPT or IOCTL_AFD_SUPER_ACCEPT through NtDeviceIoControlFile.
  4. Connect with an oversized address: Have a peer connect with a transport address whose length field (*(word)(ConnData+0x14) or the secondary length at +0x18) plus the fixed offset (8, 2, or 4) exceeds the accepting socket's AddressBufferLength.
  5. Observable Effect: AfdSanAcceptCore writes past the end of the mapped destination address buffer. Depending on layout this results in corruption of adjacent memory in the mapped region or a fault when the write reaches unmapped pages.

6. Exploit Primitive & Notes

  • Primitive: An out-of-bounds write whose size is derived from a peer-controlled 16-bit transport-address length (up to 0xFFFF plus a small constant), against the SAN accept address buffer reached through MmMapLockedPages. The write extends past the intended destination within the mapped address region.
  • Reachability: Only through the SAN accept path (endpoint type 0x1AFD), which requires an active SAN provider. This is not the ordinary TCP/UDP accept path and is not reachable by a default unprivileged process without SAN support present.
  • Delivered fix status: The patched clamps are gated behind EvaluateCurrentState(g_Feature_232206649_59779869). When the feature is disabled the patched binary executes the same unbounded copies as the unpatched binary, so this is a staged (feature-flagged) rollout of the bounds check rather than an unconditionally active fix.
  • Detection: Driver Verifier Special Pool placed after the destination buffer will fault on the overwrite at the copy instruction, making the boundary violation directly observable.

No working privilege-escalation chain is demonstrated from these binaries; any claim of controlled kernel-object corruption, information disclosure, or code execution would require artifacts not present in this diff.


7. Debugger Verification Playbook

Attach a kernel debugger to the unpatched build to confirm the boundary condition.

  • Breakpoints: text bp afd_unpatched!AfdSanAcceptCore+0x282 ; 0x1c007530e, peer length load bp afd_unpatched!AfdSanAcceptCore+0x364 ; 0x1c00753f0, primary memmove
  • What to inspect:
  • At 0x1c007530e: wo(@r9+0x14) is the peer transport-address length; dwo(@rbx+0x10) is the destination AddressBufferLength. If length + 8 > capacity, the copy will overflow.
  • At 0x1c00753f0: r8d is the copy size (length + 8), rcx is the destination, rdx is the source (ConnData+0x10). r8d strictly greater than *(rbx+0x10) confirms the out-of-bounds write.
  • Key instructions/offsets:
  • 0x1c0075305/0x1c0075308: the only capacity gate (> 0, no upper bound).
  • 0x1c0075313: unclamped size computation (len + 8).
  • 0x1c00753f0, 0x1c007539c, 0x1c0075449, 0x1c0075480: the four copies.
  • Patched equivalents: 0x1c0075330 (cmp [rbx+0x10], 0xc) and 0x1c0075433/0x1c0075437 (cmp + cmovb clamp), each reached only when the feature flag is enabled.

8. Changed Functions — Full Triage

  • AfdSanAcceptCore (Sim: 0.8109, Security Relevant): Adds minimum-capacity (>= 0xc) and clamp-to-capacity checks before each memmove that copies SAN transport addresses. All new checks are gated on EvaluateCurrentState(g_Feature_232206649_59779869); with the flag disabled the unbounded copies of the unpatched build remain.
  • AfdSanConnectHandler (Sim: 0.9407, Behavioral): Retains the connection-data copy (sub_1c000c900) unchanged; only register allocations differ. The copy is bounded by its own allocation, so no overflow.
  • AfdSocketTransferBegin (Sim: 0.8896, Behavioral): Removed feature-gating around socket-state validation; state checks now applied unconditionally. Defense-in-depth, not an exploitable fix.
  • AfdSocketTransferEnd (Sim: 0.8896, Behavioral): Same pattern as AfdSocketTransferBegin; state checks now unconditional.
  • AfdBind (Sim: 0.9682, Behavioral): Feature-flag stabilization; unbind-prevention calls during bind are now always made.
  • AfdGetInformation (Sim: 0.9768, Cosmetic): Register reallocation and stack-offset changes. No logic change.
  • EvaluateCurrentState (Sim: 0.1602, Cosmetic): Logic unchanged; offsets shifted by global data relocation.
  • EvaluateCurrentStateFromRegistry (Sim: 0.255, Cosmetic): Logic unchanged; offset shifts.
  • QueryFeatureOverride (Sim: 0.1061, Cosmetic): Logic unchanged; offset shifts.
  • AfdCopyMdlChainToBufferAvoidMapping$filt$0 (Sim: 0.5905, Cosmetic): Exception filter stack-layout adjustment; no behavioral change.

9. Unmatched Functions

No functions were added or removed in the patched binary.


10. Confidence & Caveats

  • Confidence: High that the pattern is real and the direction is correct. The unpatched build copies peer-controlled transport-address lengths into the accept address buffer with only a capacity != 0 gate, and the patched build adds minimum-capacity and clamp-to-capacity checks at the same copies.
  • Feature-gated: The delivered clamps are conditional on EvaluateCurrentState(g_Feature_232206649_59779869). In this build the fix is a staged rollout; the unbounded path is retained for the disabled state.
  • Reachability: SAN sockets are a specialized direct-access networking path (endpoint type 0x1AFD) requiring a SAN provider. This constrains practical reachability and is the main reason severity is Medium rather than High.
  • Not demonstrated: No exploit primitive beyond the out-of-bounds write itself is provable from these binaries; escalation to privilege gain is not established here.