tcpip.sys — UdpConnectRedirect Stack Use-After-Return via Asynchronous WFP ALE Connect-Redirect
KB5095091
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_c8at[rbp+0x40]— the local/source sockaddr buffer,memset(...,0,0x80)var_148at[rbp-0x40]— the redirect-destination sockaddr buffer,memset(...,0,0x80)rbp = rsp-0xe8at the prologue; total framesub rsp,0x1e8- The attacker-influenced redirect destination (from
arg1[4]/arg1[5]) is copied intovar_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,AleInspectConnectRequestreturnsSTATUS_PENDINGand registersUdpConnectRedirectCallback(0x140082aa0), which retains&var_c8/&var_148. OnceUdpConnectRedirectreturns, 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 byCreateUdpConnectRedirectCallbackContext. The sockaddrs now live at&P_1[5]and&P_1[0x15]in that heap object; an interlocked refcount at*P_1andExFreePoolWithTagcleanup keep the object alive across the async completion. If the flag is off, execution is routed toUdpConnectRedirectObsolete, 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).UdpConnectRedirecthas 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) — returnsSTATUS_PENDING(0x103), registersUdpConnectRedirectCallback(sub_140082aa0).- Async:
UdpConnectRedirectCallbackdereferences&var_c8/&var_148afterUdpConnectRedirectalready 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
- Reach the target. From a local process, create a UDP socket (
AF_INET6dual-stack/redirect path orAF_INET) and initiate aconnect()/WSAConnect()so the WFPALE_CONNECT_REDIRECT_V4/V6layer classifies the operation, driving execution intoUdpConnectRedirectvia its registered classify function pointer. - Ensure redirect data is present:
arg1[5]([rcx+0x28], redirect data length) must be non-zero so the branch at0x1400710bdfalls through into the redirect/heap path instead of takingUdpSetDestinationEndpointat0x140071427. - Choose the address family/flags: for the IPv6 branch, the family word at
[rdx]must be0x17(AF_INET6) with endpoint flag bit0x20set (test al,0x20at0x1400710e6); the attacker redirect address ([rcx+0x20]) is copied —0x1cbytes for IPv6,0x10for IPv4 — into stack buffervar_148at0x1400710fd. - 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/V6so classification completes asynchronously. This makesAleInspectConnectRequestreturnSTATUS_PENDING(0x103) and registerUdpConnectRedirectCallback(0x140082aa0) holding&var_c8/&var_148. - Fire it: with deferral in place, the
connect()causesUdpConnectRedirectto hitcmp esi,0x103at0x14007125a, take the branch to0x1400712cc, and return0x103— releasing the stack frame while the callback remains queued. When the callback later runs it dereferences the now-stale stack addresses. - Observable effect. The queued
UdpConnectRedirectCallbackreads/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 asDRIVER_OVERRAN_STACK_BUFFERorKERNEL_SECURITY_CHECK_FAILUREinside 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_148on 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
UdpConnectRedirectreturnsSTATUS_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 at0x140071200). Record the two escaping stack addresses&var_c8 = @rbp+0x40and&var_148 = @rbp-0x40(withrbp = rsp-0xe8at 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. Ifesi == 0x103(STATUS_PENDING) the branch to0x1400712ccreturns 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_148values 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 onFeature_TCPIP_Stack_Var_To_Heap_Fix, moving the state into a ref-counted heap context viaCreateUdpConnectRedirectCallbackContextand falling back toUdpConnectRedirectObsoletewhen the flag is off.AleInspectConnectRequest(sub_140071fb0) (similarity 0.6514, security_relevant): The inspection routine that can defer (returnSTATUS_PENDING) and registerUdpConnectRedirectCallback. 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 newFeature_TCPIP_Stack_Var_To_Heap_Fixflag — 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.
CreateUdpConnectRedirectCallbackContextandUdpConnectRedirectObsoleteare referenced by the patchedUdpConnectRedirectbut were paired/inlined within the existing function set rather than appearing as newly added symbols in the diff. The fix is controlled byFeature_TCPIP_Stack_Var_To_Heap_Fix; the vulnerable behavior persists viaUdpConnectRedirectObsoletewhen 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 registersUdpConnectRedirectCallbackand can returnSTATUS_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 ofCreateUdpConnectRedirectCallbackContextwith a refcount andExFreePoolWithTagcleanup, exactly matches the "move stack variable to heap" remediation pattern. - The
0x103discriminator at0x14007125aand the early return at0x1400712ccclearly identify the pending path that abandons the frame. - Assumptions made:
- That
AleInspectConnectRequestactually retains and later uses&var_c8/&var_148on the pending path (itsassembly_diffwas 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
AleInspectConnectRequestto return0x103on a local UDPconnect(). - 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.