1. Overview

Item Value
Unpatched binary tcpip_unpatched.sys
Patched binary tcpip_patched.sys
Overall similarity 0.9915
Matched functions 6063
Changed functions 18
Identical functions 6045
Unmatched (unpatched / patched) 0 / 0

Verdict: The patch fixes a kernel stack use-after-return in UdpConnectRedirect (sub_140071050), the WFP ALE connect-redirect handler for UDP. Two sockaddr work buffers that were kept on the caller's stack are handed to an asynchronous inspection routine that can defer; when it does, the function returns STATUS_PENDING and unwinds its stack frame while a queued callback still holds pointers into it.

Of the 18 changed functions, only a tight cluster is security-relevant: UdpConnectRedirect, AleInspectConnectRequest, and the completion callback UdpConnectRedirectCallback. The remaining changes are either supporting plumbing for the fix (UdpConnectRedirectCallbackContinue, the two WIL feature-cache/telemetry routines) or unrelated framing-layer / address-mapping / interface-management refactors (FlpMulticastListSet, KfdDispatchDevCtl, Fl4tMapAddress, Fl6tMapAddress, Fl68MapAddress, IppProcessInternetConnectivityFlags, etc.). The actual fix is concentrated in UdpConnectRedirect and is gated behind a new WIL feature flag literally named Feature_TCPIP_Stack_Var_To_Heap_Fix. When the flag is disabled, the patched code intentionally falls back to UdpConnectRedirectObsolete — the original vulnerable path — so the fix is feature-flag gated rather than unconditional.


2. Vulnerability Summary

2.1 HIGH — Stack use-after-return / dangling pointer across async callback (CWE-825 -> CWE-562)

  • Affected function: UdpConnectRedirect (sub_140071050) (unpatched), UdpConnectRedirect (sub_140086ce0) (patched) — the WFP ALE connect-redirect classify handler for UDP endpoints.
  • Root cause: The function allocates two sockaddr work buffers on its own stack and then escapes their addresses into an asynchronous inspection call.
  • var_c8 at [rbp+0x40] — the local/source sockaddr buffer, memset(...,0,0x80)
  • var_148 at [rbp-0x40] — the redirect-destination sockaddr buffer, memset(...,0,0x80)
  • rbp = rsp-0xe8 at the prologue; total frame sub rsp,0x1e8
  • The attacker-influenced redirect destination (from arg1[4]/arg1[5]) is copied into var_148:
0x1400710fd call 0x1401c2400   ; memcpy(&var_148, rdx, 0x1c/0x10) -> STACK buffer

Pointers to both stack buffers are then marshalled into the inspection call:

int32_t result_1 = AleInspectConnectRequest(r14_1, nullptr, 0, 0x40082aa0,
                                             var_1e8, 0x11, nullptr,
                                             &var_c8, &var_148, &var_1a0);
if (result_1 == 0x103)      // STATUS_PENDING
    result = result_1;      // return immediately -> stack frame abandoned
  • Why exploitable: The attacker controls the redirect data pointer ([rcx+0x20]) and length ([rcx+0x28]), and drives the address family / flags that decide how much data is copied into the stack sockaddr. If the ALE inspection defers, AleInspectConnectRequest returns STATUS_PENDING and registers UdpConnectRedirectCallback (0x140082aa0), which retains &var_c8/&var_148. Once UdpConnectRedirect returns, that kernel-stack region is reclaimed and reused by subsequent frames; the callback later reads and writes it. The result is a stale/out-of-bounds read of adjacent stack contents (potential info leak into the redirect result) and a write into memory that no longer belongs to the buffers (corruption of whatever frame reused the region).
  • What the patch does: Under Feature_TCPIP_Stack_Var_To_Heap_Fix, the redirect state is relocated into a reference-counted heap context created by CreateUdpConnectRedirectCallbackContext. The sockaddrs now live at &P_1[5] and &P_1[0x15] in that heap object; an interlocked refcount at *P_1 and ExFreePoolWithTag cleanup keep the object alive across the async completion. If the flag is off, execution is routed to UdpConnectRedirectObsolete, which retains the vulnerable stack-based behavior.
  • Attacker-reachable entry point: Local UDP connect()/WSAConnect() that traverses the WFP ALE connect-redirect layer (FWPM_LAYER_ALE_CONNECT_REDIRECT_V4/V6). UdpConnectRedirect has no static callers — it is invoked via a registered classify function pointer.
  • Call chain:
  • User: socket(AF_INET6/AF_INET, SOCK_DGRAM) + connect()/WSAConnect() to an attacker-chosen remote endpoint.
  • afd.sys -> tcpip UDP endpoint connect path.
  • WFP ALE connect-redirect classify dispatch (function pointer).
  • UdpConnectRedirect (sub_140071050).
  • AleInspectConnectRequest (sub_140071fb0) — returns STATUS_PENDING (0x103), registers UdpConnectRedirectCallback (sub_140082aa0).
  • Async: UdpConnectRedirectCallback dereferences &var_c8/&var_148 after UdpConnectRedirect already returned (0x14007123d captured them; use-after-return).

3. Pseudocode Diff

3.1 UdpConnectRedirect (sub_140071050 unpatched / sub_140086ce0 patched)

// UNPATCHED — UdpConnectRedirect (sub_140071050)
int32_t UdpConnectRedirect(void *arg1 /*rcx*/, void *arg2 /*rdx*/)
{
    int16_t var_c8;                 // [rbp+0x40] LOCAL/SOURCE sockaddr — STACK
    int16_t var_148;                // [rbp-0x40] REDIRECT DEST sockaddr — STACK
    memset(&var_c8, 0, 0x80);
    memset(&var_148, 0, 0x80);

    void  *rdx = arg1[4];           // [rcx+0x20] attacker redirect data ptr
    if (arg1[5] == 0)               // [rcx+0x28] redirect length == 0
        return UdpSetDestinationEndpoint(...);   // non-redirect path

    // ... AF_INET6 / flag 0x20 checks ...
    size_t n = (*(uint16_t*)rdx == 0x17) ? 0x1c : 0x10;   // AF_INET6 ? 28 : 16
    memcpy(&var_148, rdx, n);       // copy attacker addr into STACK buffer
    // (IPv6) IN6_IS_ADDR_V4MAPPED(rdx+8) validation, else 0xc0000207

    P    = ExAllocatePool2(0x40, 0x20, 'Curt');      // small ctrl object
    P[2] = ExAllocatePool2(0x42, arg1[5], 'Curt');   // heap copy of redirect data
    memcpy(P[2], arg1[4], arg1[5]);

    // register async callback (0x140082aa0) with pointers to STACK buffers
    int32_t r = AleInspectConnectRequest(..., 0x40082aa0, ...,
                                         &var_c8, &var_148, &var_1a0);
    if (r == 0x103)                 // STATUS_PENDING
        return r;                   // <-- BUG: return, unwind frame, but callback
                                    //     still holds &var_c8 / &var_148

    UdpConnectRedirectCallbackContinue(...);  // synchronous completion path
    return r;
}
// PATCHED — UdpConnectRedirect (sub_140086ce0)
int32_t UdpConnectRedirect(void *arg1, void *arg2)
{
    if (Feature_TCPIP_Stack_Var_To_Heap_Fix is DISABLED)   // WIL gate
        return UdpConnectRedirectObsolete(arg1, arg2);     // old vulnerable path

    // allocate a ref-counted HEAP context; sockaddrs live inside it
    void *P_1 = CreateUdpConnectRedirectCallbackContext(...);   // ExAllocatePool2
    // &P_1[5]  = local/source sockaddr  (heap, was var_c8)
    // &P_1[0x15] = redirect dest sockaddr (heap, was var_148)
    memcpy(&P_1[0x15], rdx, n);
    // ... inline IN6_IS_ADDR_V4MAPPED (rsi_1[4..8]==0 && rsi_1[9]==0xffff) ...

    int32_t r = AleInspectConnectRequest(..., 0x40082aa0, ...,
                                         &P_1[5], &P_1[0x15], ...);
    if (r == 0x103)                 // STATUS_PENDING
        return r;                   // safe: heap context outlives the callback
                                    //       (refcount at *P_1, ExFreePoolWithTag on release)
    // ...
}

Key changes: - Unpatched dangerous operation: &var_c8 ([rbp+0x40]) and &var_148 ([rbp-0x40]) — pointers into the stack frame — are passed to AleInspectConnectRequest, which can register the async callback and defer. - Patched path allocates a reference-counted heap context (CreateUdpConnectRedirectCallbackContext) and stores the sockaddrs at &P_1[5]/&P_1[0x15]; the interlocked refcount at *P_1 and ExFreePoolWithTag('Curt') cleanup guarantee the buffers survive the pending async completion. - Fallback that remains vulnerable: when Feature_TCPIP_Stack_Var_To_Heap_Fix is disabled, the patched build calls UdpConnectRedirectObsolete, which is the original stack-based (vulnerable) logic.


4. Assembly Analysis

4.1 UdpConnectRedirect (sub_140071050)

Unpatched — stack buffers escape into the async inspection:

0x140071050 push rbp
0x140071052 push rbx
0x140071053 push rsi
0x140071054 push r13
0x140071056 lea rbp,[rsp-0xe8]
0x14007105e sub rsp,0x1e8
0x140071065 mov rax,[rel 0x14022db00]    ; __security_cookie
0x14007106c xor rax,rsp
0x14007106f mov [rbp+0xc0],rax
0x140071076 mov rbx,rdx
0x140071079 mov rsi,rcx
0x14007107e lea rcx,[rbp+0x40]           ; &var_c8 (STACK)
0x140071082 mov r8d,0x80
0x140071088 call 0x1401c2700             ; memset(&var_c8,0,0x80)
0x140071090 lea rcx,[rbp-0x40]           ; &var_148 (STACK)
0x14007109b mov r8d,0x80
0x1400710a6 call 0x1401c2700             ; memset(&var_148,0,0x80)
0x1400710ab mov rdx,[rsi+0x20]           ; arg1[4] redirect data ptr
0x1400710bd cmp [rsi+0x28],rax           ; arg1[5] length == 0 ?
0x1400710c1 je  0x140071427              ; -> UdpSetDestinationEndpoint (non-redirect)
0x1400710c7 mov eax,[rbx+0x18]           ; endpoint flags
0x1400710e6 test al,0x20
0x1400710e8 jne 0x1400712d0              ; IPv6 V4MAPPED branch
0x1400712d0 cmp word [rdx],0x17          ; AF_INET6 ?
0x1400710f6 mov r8,r15                    ; 0x1c
0x1400710f9 cmovne r8,r12                 ; else 0x10
0x1400710fd call 0x1401c2400             ; memcpy(&var_148, rdx, 0x1c/0x10) -> STACK
0x140071172 call [rel 0x1402529b0]       ; ExAllocatePool2(0x40,0x20,'Curt')
0x14007119e call [rel 0x1402529b0]       ; ExAllocatePool2(0x42,arg1[5],'Curt')
0x1400711c2 call 0x1401c2400             ; memcpy(P[2], arg1[4], arg1[5])
0x140071200 lea r9,[rel 0x140082aa0]     ; UdpConnectRedirectCallback (async completion)
0x140071216 lea rax,[rbp-0x40]           ; &var_148 (STACK) marshalled into args
0x140071222 lea rax,[rbp+0x40]           ; &var_c8  (STACK) marshalled into args
0x14007123d call 0x140071fb0             ; AleInspectConnectRequest(..., &var_c8, &var_148, &var_1a0)
0x140071249 mov esi,eax
0x14007125a cmp esi,0x103                ; STATUS_PENDING ?
0x140071260 je  0x1400712cc              ; yes -> return, ABANDON frame
0x1400712cc mov eax,esi
0x1400712ce jmp 0x14007128f              ; epilogue

Annotations: - 0x1400710bd: the only gate that decides the redirect path — if arg1[5] (redirect length) is non-zero the code proceeds to copy attacker data into the stack buffer and register the async callback. - 0x1400710fd: the copy of attacker-controlled redirect address data into the stack buffer var_148. - 0x140071216 / 0x140071222: the two stack pointers &var_148/&var_c8 are loaded as arguments — this is where they escape the frame. - 0x14007123d: the deferring inspection call; r9 = 0x140082aa0 (the callback) was loaded at 0x140071200. - 0x14007125a/0x140071260: the discriminator — on STATUS_PENDING the function returns without running the continuation, unwinding the frame while the callback is still queued against it.

Patched — safe heap-context path (structure):

; UdpConnectRedirect @ 0x140086ce0 (patched)
;   top-of-function feature test against data_140237670
;   (Feature_TCPIP_Stack_Var_To_Heap_Fix)
;   flag OFF  -> tail-call UdpConnectRedirectObsolete (original stack logic)
;   flag ON   -> CreateUdpConnectRedirectCallbackContext (ExAllocatePool2)
;                sockaddrs at &P_1[5] / &P_1[0x15] (HEAP)
;                interlocked refcount at *P_1
;                ExFreePoolWithTag('Curt') on release
;                AleInspectConnectRequest given HEAP pointers, not stack

Annotations: - Feature check at the top of sub_140086ce0 (test against data_140237670) selects between the safe heap path and the obsolete stack path. - The heap context created by CreateUdpConnectRedirectCallbackContext carries a refcount at *P_1; the callback and the caller each hold a reference, so the buffers survive the STATUS_PENDING deferral and are freed with ExFreePoolWithTag only when the last reference drops.


5. Trigger Conditions

  1. Reach the target. From a local process, create a UDP socket (AF_INET6 dual-stack/redirect path or AF_INET) and initiate a connect()/WSAConnect() so the WFP ALE_CONNECT_REDIRECT_V4/V6 layer classifies the operation, driving execution into UdpConnectRedirect via its registered classify function pointer.
  2. Ensure redirect data is present: arg1[5] ([rcx+0x28], redirect data length) must be non-zero so the branch at 0x1400710bd falls through into the redirect/heap path instead of taking UdpSetDestinationEndpoint at 0x140071427.
  3. Choose the address family/flags: for the IPv6 branch, the family word at [rdx] must be 0x17 (AF_INET6) with endpoint flag bit 0x20 set (test al,0x20 at 0x1400710e6); the attacker redirect address ([rcx+0x20]) is copied — 0x1c bytes for IPv6, 0x10 for IPv4 — into stack buffer var_148 at 0x1400710fd.
  4. Force the inspection to defer: install/register a WFP connect-redirect callout or connection-redirect provider (or otherwise induce out-of-band arbitration) at FWPM_LAYER_ALE_CONNECT_REDIRECT_V4/V6 so classification completes asynchronously. This makes AleInspectConnectRequest return STATUS_PENDING (0x103) and register UdpConnectRedirectCallback (0x140082aa0) holding &var_c8/&var_148.
  5. Fire it: with deferral in place, the connect() causes UdpConnectRedirect to hit cmp esi,0x103 at 0x14007125a, take the branch to 0x1400712cc, and return 0x103 — releasing the stack frame while the callback remains queued. When the callback later runs it dereferences the now-stale stack addresses.
  6. Observable effect. The queued UdpConnectRedirectCallback reads/writes reclaimed stack memory: corrupted sockaddr contents in the redirect result, an access to an address matching the earlier [rbp+0x40]/[rbp-0x40], and — with a groomed stack or kernel verifier active — a bugcheck such as DRIVER_OVERRAN_STACK_BUFFER or KERNEL_SECURITY_CHECK_FAILURE inside the tcpip async completion path.

Notes: The vulnerable async path is distinguished from the safe synchronous path purely by the 0x103 return at 0x14007125a; the synchronous path instead falls through to UdpConnectRedirectCallbackContinue (call 0x14008330c at 0x140071285). The bug requires the ability to make classification defer — practically, control over (or the ability to register) a connect-redirect callout/provider. Timing between the return of UdpConnectRedirect and the callback firing determines how much the stack region has been reused, which affects both the info-leak and corruption windows. On patched builds the bug is only reachable if Feature_TCPIP_Stack_Var_To_Heap_Fix is disabled (falling into UdpConnectRedirectObsolete).


6. Exploit Primitive & Development Notes

  • Primitive: Kernel stack use-after-return. The async callback both reads and writes memory that used to be var_c8/var_148 on a returned frame. This yields (a) a stale read of adjacent kernel-stack contents that can be surfaced into the redirect result (info leak), and (b) a write of callback-controlled sockaddr data into whatever frame subsequently reuses that stack region.
  • Turning it into a full exploit:
  • Stack grooming: After UdpConnectRedirect returns STATUS_PENDING, drive controlled activity on the same kernel thread so a chosen frame reoccupies the [rbp-0x40]/[rbp+0x40] region before the callback fires — placing an attacker-influenced object (e.g., a saved return address, a function pointer, or a structure with a pointer field) where the callback will overwrite it.
  • What to corrupt: target the reused frame's saved return address or a pointer field the reused frame will later dereference, steering it to controlled data or code.
  • Info leak: use the read side (stale sockaddr bytes delivered into the redirect result observable to the initiating socket) to leak adjacent stack contents and defeat KASLR / recover cookies before attempting the write.
  • Path to code execution / data-only: convert the controlled stack write into control-flow hijack, or, if CFI blocks that, a data-only escalation by corrupting a security-relevant field in the reused frame.
  • Mitigations to consider:
  • kASLR: must be defeated; the info-leak side of this same primitive (leaked stack bytes) is a candidate source of pointers/cookies.
  • SMEP / SMAP: the write lands in kernel stack memory, so no user-mode page is dereferenced directly; a ROP/JOP payload in kernel memory is still needed for control flow.
  • CFG / CET (kCET shadow stack): because the corruption is on the kernel stack, a naive saved-return-address overwrite is likely caught by kCET; favor a data-only field-corruption target instead.
  • HVCI / VBS: code injection is blocked; a data-only variant (corrupting a token/privilege field or a pointer used data-only) is the survivable route.
  • Pool hardening: not directly applicable — the corruption is stack-based, not pool-based — though the associated 'Curt' heap allocations (ExAllocatePool2) are governed by pool hardening.
  • Realistic outcome: Local kernel crash (DoS) is straightforward; local privilege escalation to SYSTEM is plausible but requires precise stack grooming and a KASLR leak, and is constrained by kCET on modern builds — a data-only variant is the most realistic path to LPE.

7. Debugger PoC Playbook

Assume WinDbg/KD attached to the UNPATCHED tcpip.sys.

7.1 UdpConnectRedirect (sub_140071050)

Breakpoints:

bp tcpip+0x71050    ; UdpConnectRedirect entry — inspect rcx (arg1) / [rcx+0x28] length
bp tcpip+0x7123d    ; call AleInspectConnectRequest — record &var_c8 / &var_148 stack ptrs
bp tcpip+0x7125a    ; cmp esi,0x103 — the STATUS_PENDING discriminator (arms the bug)
bp tcpip+0x82aa0    ; UdpConnectRedirectCallback — compare its ptr args to the captured stack addrs

Rebase note: the RVAs above are analysis addresses minus the image base 0x140000000 (entry 0x140071050 -> +0x71050, call site 0x14007123d -> +0x7123d, discriminator 0x14007125a -> +0x7125a, callback 0x140082aa0 -> +0x82aa0). If the loaded base differs, subtract that base from the absolute addresses shown here.

Inspect at each breakpoint: - At +0x71050 (entry): rcx = arg1 (redirect request). [rcx+0x20] = attacker redirect data pointer, [rcx+0x28] = redirect data length (must be non-zero to take the vulnerable path). rdx = arg2 (endpoint object); [rdx+0x18] flags, bit 0x20 selects the IPv6 V4MAPPED branch.

dq @rcx+0x20 L2      ; redirect data ptr + length
dt tcpip!_ADDRINFO ... (or dq @rdx+0x18 L1 for flags)
  • At +0x7123d (inspection call): r9 = 0x140082aa0 (the registered callback, loaded at 0x140071200). Record the two escaping stack addresses &var_c8 = @rbp+0x40 and &var_148 = @rbp-0x40 (with rbp = rsp-0xe8 at prologue). These are the pointers that will dangle.
? @rbp+0x40          ; &var_c8
? @rbp-0x40          ; &var_148
dq @r9 L1            ; confirm callback == tcpip!UdpConnectRedirectCallback
  • At +0x7125a (discriminator): watch esi/eax. If esi == 0x103 (STATUS_PENDING) the branch to 0x1400712cc returns and abandons the frame — the smoking gun that the async use-after-return is armed.
  • At +0x82aa0 (callback): compare the callback's context/sockaddr argument pointers to the &var_c8/&var_148 values captured earlier. If they match a frame that has already returned, you are observing the dangling access.

Trigger setup (from user mode): 1. Ensure Feature_TCPIP_Stack_Var_To_Heap_Fix is not applied (any pre-fix build, or the obsolete path). 2. Register a WFP connect-redirect callout/provider at FWPM_LAYER_ALE_CONNECT_REDIRECT_V4/V6 that arbitrates the connect so classification defers (returns pending), causing AleInspectConnectRequest to return STATUS_PENDING. 3. From a local process, create a UDP socket and issue connect()/WSAConnect() with redirect data present ([rcx+0x28] != 0):

s = socket(AF_INET6, SOCK_DGRAM, 0);   // dual-stack / redirect flag path
connect(s, &attacker_remote_endpoint, len);   // classification defers

Expected observation: After UdpConnectRedirect returns 0x103, the queued UdpConnectRedirectCallback dereferences the old stack addresses. With kernel verifier or a groomed stack you observe a read/write to reclaimed stack memory — corrupted sockaddr content delivered to the redirect result, an access to an address matching [rbp+0x40]/[rbp-0x40], or a bugcheck (DRIVER_OVERRAN_STACK_BUFFER / KERNEL_SECURITY_CHECK_FAILURE) inside the tcpip async completion path. On a patched build, the callback instead consumes a heap context pointer from ExAllocatePool2 (CreateUdpConnectRedirectCallbackContext) with a refcount at *P_1.

Struct / offset notes:

arg1 (redirect request, rcx):
  +0x20  redirect data pointer   (attacker-controlled)     ; arg1[4]
  +0x28  redirect data length    (must be non-zero)        ; arg1[5]
arg2 (endpoint object, rdx):
  +0x18  flags (bit 0x20 => IPv6 V4MAPPED branch)

UdpConnectRedirect stack frame (rbp = rsp-0xe8):
  [rbp+0x40]  var_c8   local/source sockaddr  (0x80, memset 0) — escapes to callback
  [rbp-0x40]  var_148  redirect dest sockaddr (0x80, memset 0) — escapes to callback
  [rbp-0x90]   LockHandle KeAcquireInStackQueuedSpinLock handle (KLOCK_QUEUE_HANDLE)
  [rbp+0xc0]  security cookie
  P[2]        heap copy of redirect data (ExAllocatePool2 0x42, arg1[5], 'Curt')

Pool tag: 'Curt' (0x74727543)
Callback: tcpip!UdpConnectRedirectCallback @ 0x140082aa0
Discriminator: STATUS_PENDING == 0x103 at 0x14007125a
Sync path: UdpConnectRedirectCallbackContinue @ 0x14008330c (call at 0x140071285)
Patched equivalent: UdpConnectRedirect @ 0x140086ce0 (feature test vs data_140237670)

8. Changed Functions — Full Triage

  • UdpConnectRedirect (sub_140071050) -> UdpConnectRedirect (sub_140086ce0) (similarity 0.5157, security_relevant): Root of the fix. Unpatched stores redirect/local sockaddrs on the stack (var_c8/var_148) and hands their addresses to the deferring inspection; patched gates on Feature_TCPIP_Stack_Var_To_Heap_Fix, moving the state into a ref-counted heap context via CreateUdpConnectRedirectCallbackContext and falling back to UdpConnectRedirectObsolete when the flag is off.
  • AleInspectConnectRequest (sub_140071fb0) (similarity 0.6514, security_relevant): The inspection routine that can defer (return STATUS_PENDING) and register UdpConnectRedirectCallback. Its argument marshaling was adjusted to accept the redirect state from the new heap context rather than caller stack pointers. Co-participant in the bug, not the root allocation site.
  • UdpConnectRedirectCallback (sub_140082aa0) (similarity 0.7903, security_relevant): The async completion callback. Unpatched dereferences pointers that alias the returned stack frame; patched consumes the heap context (&P_1[...]) with a proper refcount, closing the dangling-pointer window.
  • UdpConnectRedirectCallbackContinue (sub_14008330c) (similarity 0.7546, behavioral): Adjusted to the new heap-context object model versus consuming caller state — supporting plumbing for the fix, not itself the flaw.
  • wil_details_FeatureStateCache_ReevaluateCachedFeatureEnabledState (similarity 0.975, behavioral): WIL feature-state cache updated to include the new Feature_TCPIP_Stack_Var_To_Heap_Fix flag — confirming the connect-redirect change is a gated security fix.
  • wil_details_RegisterFeatureUsageProvider (similarity 0.9853, cosmetic): Telemetry plumbing for the new feature flag.
  • FlpMulticastListSet (similarity 0.9312, behavioral): Framing-layer refactor, unrelated.
  • KfdDispatchDevCtl (similarity 0.9367, behavioral): IOCTL dispatcher changes; large function, no security boundary tied to this patch.
  • Fl4tMapAddress (0.8406), Fl6tMapAddress (0.9122), Fl68MapAddress (0.8174) (cosmetic): Address-mapping helper refactors, unrelated.
  • IppProcessInternetConnectivityFlags (0.8432, behavioral): Connectivity-flag logic change, unrelated.
  • FlpAddInterfaceComplete (0.8813), FlUnbindAdapter (0.9296), FlRdmaUnbindAdapter (0.7611), FlIfCreateVirtualInterface (0.9368), FlpSetIpAddress (0.9453), IppCommitSetAllInterfaceParameters (0.9827) (cosmetic/behavioral): Interface/adapter management refactors, unrelated to the connect-redirect fix.

Collapsed note on non-security changes: Everything outside the UdpConnectRedirect/AleInspectConnectRequest/UdpConnectRedirectCallback cluster (plus the two WIL feature-flag support routines) is framing-layer, IOCTL, address-mapping, or interface-management refactoring with register-allocation shifts and no security boundary change — the only meaningful diff is the stack-to-heap relocation flagged in section 2.1.


9. Unmatched Functions

  • Removed (unpatched only): None
  • Added (patched only): None
  • Implication: The patch is additive-in-place and feature-flag gated rather than introducing net-new exported functions. CreateUdpConnectRedirectCallbackContext and UdpConnectRedirectObsolete are referenced by the patched UdpConnectRedirect but were paired/inlined within the existing function set rather than appearing as newly added symbols in the diff. The fix is controlled by Feature_TCPIP_Stack_Var_To_Heap_Fix; the vulnerable behavior persists via UdpConnectRedirectObsolete when that flag is disabled.

10. Confidence & Caveats

  • Confidence: high
  • Rationale:
  • The stack-to-heap relocation is a textbook async-lifetime fix: the unpatched assembly unambiguously passes &var_c8/&var_148 (stack addresses [rbp+0x40]/[rbp-0x40]) into a routine that registers UdpConnectRedirectCallback and can return STATUS_PENDING, after which the function returns and unwinds the frame.
  • The feature flag name itself — Feature_TCPIP_Stack_Var_To_Heap_Fix — plus the addition of CreateUdpConnectRedirectCallbackContext with a refcount and ExFreePoolWithTag cleanup, exactly matches the "move stack variable to heap" remediation pattern.
  • The 0x103 discriminator at 0x14007125a and the early return at 0x1400712cc clearly identify the pending path that abandons the frame.
  • Assumptions made:
  • That AleInspectConnectRequest actually retains and later uses &var_c8/&var_148 on the pending path (its assembly_diff was not captured); this is strongly implied by the callback registration and the fix, but the exact field capture inside the inspection routine was not fully traced.
  • That an attacker can reliably force the deferral (STATUS_PENDING) — assumed reachable by registering a WFP connect-redirect callout/provider, but the precise provider configuration that guarantees async arbitration was not verified.
  • That the write side of the callback is attacker-influenceable enough for corruption (versus only an info leak); the callback's exact write targets were not disassembled here.
  • To verify before a PoC:
  • Confirm the exact ALE connect-redirect callout/provider registration that makes classification defer and drives AleInspectConnectRequest to return 0x103 on a local UDP connect().
  • Trace UdpConnectRedirectCallback (0x140082aa0) to confirm which offsets of the captured sockaddr pointers it reads and writes, and reproduce the reclaimed-stack access under kernel verifier to observe the bugcheck / stale data.