CNA-BUG-163: HttpNotificationChannel writes its reply with ::send flags 0, so a peer that aborts its connection mid-request can end the process with SIGPIPE

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; inferred from the source (the behaviour was not run); tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

The listener's 200 or 400 reply is written with no MSG_NOSIGNAL or SO_NOSIGPIPE, unlike CNA's other socket code. After a peer's abortive close has been reported to the worker as a receive error, Linux delivers SIGPIPE on that write, and the default action terminates the game. The push sender omits the flag too, but its single first write did not raise SIGPIPE in the modelled cases.

Identifier
CNA-BUG-163
Category
Bug
Subsystem
Networking & gamer services
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Medium (a triage suggestion, not a project priority)
Evidence basis
Inferred (strong): follows from the code, but the behaviour was not run
Tests touching this area
Yes: see Current tests
Affected contract
HttpNotificationChannel's listener thread (Listen) and the internal Post behind both SendAsync methods

Expected behaviour

CNA's other socket and pipe code treats SIGPIPE as something a library must not expose: InternalSocket.cpp sends with MSG_NOSIGNAL and sets SO_NOSIGPIPE where it exists; X11ModeGuardian.cpp uses send(MSG_NOSIGNAL) so that a peer that went away becomes EPIPE; WaylandDataDevice.cpp blocks SIGPIPE per write because “a library must not change the process's disposition”. A failed push should fail that push, not end the game.

Actual behaviour at TARGET

Listen replies with ::send(connection, response, size, 0) after ReadRequestBody, and Post writes its request with ::send(connection, request, length, 0); neither sets MSG_NOSIGNAL or SO_NOSIGPIPE, and CNA changes the SIGPIPE disposition nowhere. When a client aborts its connection (RST) part-way through a request, the worker's recv reports the reset, ReadRequestBody returns false or a truncated body, and the reply send then fails with EPIPE, so Linux delivers SIGPIPE, whose default action terminates the process. A client that closes normally, or that resets only after sending a complete request, does not trigger it: the write then succeeds or returns ECONNRESET without a signal. The sender's request is its first and only write on a fresh connection, and a reset seen before that write is reported as ECONNRESET without a signal, so the sender half is a latent inconsistency with the Inspector's convention rather than a demonstrated crash path.

Source locations

Evidence

Checked by reading both phone sources at 009d40f5 and searching CNA for any SIGPIPE disposition change (none; only per-call avoidance in the Inspector, X11 and Wayland code). Executed: a standalone probe, not CNA code, compiled with g++ 14.2 in the site repository's shared probe directory and removed afterwards, performed the listener's call sequence (accept, recv until error or end of stream, one send with flags 0) in a child process with the default SIGPIPE disposition, against a local client that ended its connection abortively part-way through a request. The child was terminated by SIGPIPE in 3 of 3 runs on Linux 6.12. The phone module itself was not compiled or run; macOS (where SO_NOSIGPIPE exists) and Windows (no SIGPIPE) were not examined.

Independent re-verification: Checked by reading both phone sources at 009d40f5 and searching CNA for any SIGPIPE disposition change (none; only per-call avoidance in the Inspector, X11 and Wayland code). Executed outside CNA: the entry records a g++ probe of the listener's call sequence that was killed by SIGPIPE in 3 of 3 runs, and this review re-ran the sequence as a Python loopback model on Linux 6.12 (SIGPIPE 3 of 3 after an abortive mid-request close, 0 of 3 after a normal close, ECONNRESET without a signal after a complete request followed by a reset, and ECONNRESET without a signal for the sender's first write after the listener reset the connection). The phone module itself was not compiled or run; macOS (SO_NOSIGPIPE) and Windows (no SIGPIPE) were not examined.

Focused reproduction

No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.

Current tests

The notification tests' clients always read the reply before closing normally; no test ends a connection abortively, and none checks that the process survives a failed reply.

Regression test

A test that runs an open channel in a child process with the default SIGPIPE disposition, has a client end its connection abortively mid-request, and requires the child to survive and keep accepting. The fix is MSG_NOSIGNAL on Linux and SO_NOSIGPIPE on Apple platforms for both the reply and the sender, as InternalSocket.cpp does.

Blast radius

POSIX processes that opened a channel or send pushes to an endpoint that may reset. Windows has no SIGPIPE; hosts that already ignore SIGPIPE see only a failed write.

Workaround

Ignore SIGPIPE process-wide (signal(SIGPIPE, SIG_IGN)) before opening a channel or sending, if the host can make that decision.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Maintainer workflow
Thread and callback map
Known issues
Bug index