tcpip.sys fse/port-tracker per-message length underflow (kernel oob read, seh-caught)
on this page
a short write-up of a confirmed memory-safety defect in the windows tcp/ip stack that turns out to be benign in impact, and the static analysis that bounds it. the value here is less the bug than the discipline: confirm the primitive, then chase the single escalation that would make it matter, and accept the refute when it comes.
summary
while forward-hunting the tcp/ip “Fse” / port-tracker WSK transport (the surface that produced CVE-2026-45657), a second, distinct defect surfaces in the same per-message processing path: an integer underflow in the per-message declared-length handling that produces a kernel out-of-bounds read in the MIDL/NDR deserializer. it is not CVE-2026-45657 (that is an out-of-bounds write in the reassembly tail) and it is not closed by the june 2026 fix — that patch added only upper-bound caps on a feature-gated path, while this lower-bound gap sits on a path the patch does not touch. confirmed present on multiple win11 25H2 builds (26100.8521 / 8655 and 26100.8457).
terminal effect, established by live test and static analysis: the out-of-bounds read executes but is caught by a structured-exception handler, and no over-read memory is reflected back to the peer. no bugcheck, no denial of service, no code execution, no information leak on this path. the transport is opt-in (off by default).
mechanism
the message dispatch reads a per-message declared length from the first dword of each message. the only validation before dispatch is an upper bound (declared vs remaining received bytes); there is no lower bound. the per-message handler then computes declared - 0x20 to skip a fixed 32-byte header. for a declared value below 0x20, that subtraction underflows in 32 bits (0x10 - 0x20 -> 0xFFFFFFF0), and the wrapped value is carried, unclamped, into the RPC/NDR decode buffer size:
FseProcessIncomingMessage:lea edi, [r8 - 0x20](REX.B only, 32-bit, wraps) — the underflow. (VA 0x1401bd216 on build 8655; 0x1401bd0f2 on 8457.)- the wrapped length becomes the 3rd argument to
FseMidlObjectDecode, which passes it verbatim as theBufferSizetomsrpc.sys!MesDecodeBufferHandleCreate(mov edx, [rsp+0x90]at the call).NdrMesTypeDecode3then walks a ~4 GB “stream” over the real<32-byte buffer atmsg + 0x20.
bug class: CWE-191 (integer underflow) -> CWE-125 (out-of-bounds read). attacker inputs: a message whose declared-length field is in {0..0x1f}, the version field set so the real decode stub is retained, and a received length above 0x20.
what was confirmed
- primitive, on real bytes (function emulation). the shipped
tcpip.sysmessage-processing bytes, run under a CPU emulator with a crafteddeclared = 0x10message, producedBufferSize = 0xFFFFFFF0at the real decode call; a benign control (declared = 0x40) produced a sane0x20. the buggy arithmetic executed as shipped, not re-implemented. - the OOB read executes on a live kernel. under a kernel debugger on a live windows 11 guest (real
tcpip.sys+ realmsrpc.sysNDR engine), the decode receivedBufferSize = 0xFFFFFFF0and the NDR walk faulted reading far above the buffer (#PF,CR2 = 0x00007FFFFFFF0000); thedeclared = 0x40control read a sane0x20and did not fault. - but it is caught.
FseMidlObjectDecodecarries anEHANDLERin its unwind data — anRpcTryExcept(__C_specific_handler). with natural unwind the access violation is caught and the handler returns an error status cleanly: no bugcheck. an early premise that the unbounded ~4 GB walk would self-fault into a crash was refuted by the live test.
the info-leak escalation, and why it is refuted
a caught over-read still matters if the partial NDR decode copies attacker-adjacent kernel memory into an output object that is then reflected back to the peer. that is the only path that would lift this above “a caught read.” it does not hold here, and the reason is visible statically:
- in
FseMidlObjectDecode, the decoded object is stored to the caller’s out-pointer only on full success (mov [r15], rax, reached only when the decode/exception status is zero). on the caught over-read, the exception handler resumes with a nonzero status, the function sets an error code (0xC0000001/0xC0000017), skips the store, and returns a negativeNTSTATUSwith the caller’s out-pointer leftNULL. - the caller,
FseProcessIncomingMessage, branches on that status (test eax, eax; js error). it forwards / consumes the decoded object only on the success branch; on the error branch it emits a trace and returns, never touching the out slot.
so on the underflow -> over-read -> caught path, the partially-decoded object is never stored to the caller and never forwarded to the peer; any transient object the NDR engine allocated dies with the exception. no over-read kernel memory is reflected. info-leak: refuted.
reachability
the port-tracker transport’s receive listener is not initialized by default. it is gated by the registry value Tcpip\Parameters\PortTrackerEnabledMode (absent by default on windows 11 client and server 2025), which corresponds to WSL2 mirrored networking. the realistic threat model is a WSL2-guest-to-windows-host-kernel interaction on a host that has opted into mirrored networking — opt-in, not default-on, so the practical population is small.
disclosure and disposition
honest grade: a real, live-verified, cross-build kernel out-of-bounds read that the RPC exception handler neutralizes, with no reflection of over-read memory to the peer, on an opt-in surface. not a denial of service, not code execution, not an information leak as analyzed.
this was not submitted to MSRC, because there is no security impact to coordinate: the read is caught, the leak path is refuted, and the surface is off by default. that judgement is consistent with Microsoft’s handling of the more-impactful neighbours found in the same campaign — e.g. an unprivileged, ~100%-reliable kernel NULL-deref bugcheck (NDKPing) and an RDMA-host OOB read (ndfltr) were both reported to MSRC and declined. a caught, leak-free over-read sits below that bar. it is recorded here for completeness and as a worked example of bounding a memory-safety primitive to its true terminal effect rather than stopping at “OOB read found.”
distinct from CVE-2026-45657 (CWE-787 OOB write in the same transport). if a future build removes the RpcTryExcept coverage, exposes an over-read path outside it, or reflects the partial decode on error, the disposition would change.