#5274·libuv

win: stack-cookie fast-fail (0xC0000409) in uv__tcp_connect on loopback connects; WSARecv/WSASend pass stack &bytes/&flags with a live OVERLAPPED

Author: AriaPrimeCreated Sep 14, 2026Updated Sep 14, 2026
  • Version: 1.51.0 (as vendored in Node.js v24.15.0, deps/uv at the tag)
  • Platform: Windows 11 Pro 10.0.26200, x64

Symptom

A long-running Node HTTP gateway dies with STATUS_STACK_BUFFER_OVERRUN (0xC0000409) roughly twice a day. The fast-fail parameter is 2 (FAST_FAIL_STACK_COOKIE_CHECK_FAILURE), so it is the /GS cookie check, not a generic overrun report. WER LocalDumps and Node's --report-on-fatalerror capture nothing, because fast-fail bypasses both; a procdump -e -x -mm wrapper captured two faults, on 2026-09-12 and 2026-09-13.

In both dumps the faulting frame is a loopback connect:

__report_gsfailure+0x1d
uv__tcp_connect+0x3af          (uv__tcp_try_connect inlined)
uv_tcp_connect+0x3b
TCPWrap::Connect<sockaddr_in6>  (first dump)  /  TCPWrap::Connect<sockaddr_in>  (second)
... V8 API call machinery ...
LibuvStreamWrap::OnUvRead -> http_parser Parser::OnStreamRead   (second dump)

The process connects periodically to a loopback service on a fixed port. In the first dump that connect was [::1] (the service binds IPv4 only, so with autoSelectFamily the ::1 attempt is refused on every poll); we removed those refused attempts, and 38 hours later the same fault reappeared on the plain 127.0.0.1 connect. The address family is therefore not the cause. The connect frame is the victim on both families.

What the cookie slot says

Both dumps show the same corruption, not a general smash:

cookie slot in the faulting frame intact cookies in live frames
dump 1 0x00000008d6f73084 0x0000d8edd6f7xxxx
dump 2 0x0000000865300ade 0x0000c4a46530158e

The low dword is intact and in-family; the high dword has been replaced by 0x00000008. That is one aligned 4-byte store of the value 8 at cookie+4. Neighbouring locals are untouched. In both dumps only the faulting thread was executing (the rest parked in ntdll), the Winsock catalog contains only Microsoft mswsock.dll providers with chain length 1 (no LSPs), and the only non-Microsoft module loaded is a synchronous SQLite addon that holds no Winsock pointers.

Source observation

src/win/tcp.c hands Winsock the address of stack locals while an overlapped operation is in flight:

  • uv__tcp_queue_read: WSARecv(..., &bytes, &flags, &req->u.io.overlapped, NULL)
  • uv__tcp_write: WSASend(..., &bytes, ..., &req->u.io.overlapped, NULL)

where bytes and flags are DWORD locals. Microsoft's documentation for lpNumberOfBytesRecvd / lpNumberOfBytesSent says:

Use NULL for this parameter if the lpOverlapped parameter is not NULL to avoid potentially erroneous results.

A late write through such a pointer, after the frame that owned the local has returned and a later frame at the same depth has reused the slot for its cookie, produces exactly the observed shape: a single small DWORD store into a cookie slot with intact neighbours. I cannot demonstrate the store happening inside the provider, and I am not claiming to have witnessed it; what the dumps establish is the corruption shape, and what the source establishes is that a stack address is handed out where the documentation says it should not be.

Reproduction

It does not reproduce on demand, and I would rather say so than dress up a guess:

  • 20,000 DNS-then-IPv6-loopback connects in a throwaway process: no fault.
  • 3,000 rounds of 24 concurrent 8-byte echo writes interleaved with 8 refused [::1] connects per round (24,000 refused connects, 72,000 writes, 3.4 s): no fault.

The evidence is the two dumps, ~38 hours apart, with byte-identical corruption signatures on different address families.

Suggested change

Point lpNumberOfBytes* at a field in the request (req->u.io) rather than a stack local, or pass NULL while lpOverlapped is non-NULL, in tcp.c and the stream/pipe equivalents; keep lpFlags in the request as well.

Five minidumps (stack + thread state only, ~4.3 MB each), the symbolized candidate lists and the raw frame words are available on request if that would help.