net: bound the WebSocket handshake and reply sends on SocketServer - #832
Merged
Merged
Conversation
Yaraslaut
force-pushed
the
fix/534-socketserver-hardening
branch
2 times, most recently
from
September 26, 2026 10:42
cd259fa to
7dd7951
Compare
An unauthenticated peer that connects and sends one byte parks a clientLoop thread and a file descriptor forever: recvSome is a blocking ::recv with no SO_RCVTIMEO anywhere, and TcpSocket's fd-adopting constructor clears O_NONBLOCK on every accepted connection. SocketServerConfig gains handshakeTimeout, enforced as a single deadline across the whole handshake read via a poll-with-remaining-budget loop in readHttpHeaderBlock (not SO_RCVTIMEO, which restarts on every recv and would only bound each individual read). Separately, nothing ever set SO_SNDTIMEO on an accepted connection, so a server-side sendAll against a peer that stopped reading blocked forever instead of throwing -- leaving PR #558's ClientConnection::sendText retire-on- partial-write-failure catch unreachable and untestable. SocketServerConfig gains sendTimeout, applied via the existing setSendTimeout in acceptLoop, so that catch now fires and retires the connection instead of continuing to dispatch frames whose replies go nowhere. idleTimeout and maxConnections, both named in the original finding, are out of scope here per the issue's rescope: idleTimeout would disconnect a desktop client that is idle by design (no ping/pong keepalive exists to tell idle from dead), and maxConnections is real but deferred to a follow-up. Fixes #534 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fEUahMFF32wQLiWjbsfkc Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
Yaraslaut
force-pushed
the
fix/534-socketserver-hardening
branch
from
September 26, 2026 10:59
7dd7951 to
a085b76
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
handshakeTimeoutandsendTimeouttoSocketServerConfig, closing the two real defects identified in net: SocketServer has no connection cap, handshake timeout or idle timeout #534's rescope: an unauthenticated peer that connects and sends one byte parks aclientLoopthread and a file descriptor forever (noSO_RCVTIMEOanywhere on the accepted socket), and nothing ever bounded a server-side reply write, leaving PR net: reject illegal WebSocket frames, and bound the handshake read #558'sClientConnection::sendTextretire-on-partial-write-failure catch unreachable and untestable (noSO_SNDTIMEOon the accepted socket).handshakeTimeout(default 10s, 0 disables) is enforced as a single deadline across the whole handshake read via a poll-with-remaining-budget loop added toreadHttpHeaderBlock, deliberately notSO_RCVTIMEO(which only bounds each individualrecv, asSocketBackendConfig's own doc comment already documents as a known weakness on the client side).sendTimeout(default 30s, 0 disables) isSO_SNDTIMEO, applied once per accepted connection inacceptLoopvia the existingTcpSocket::setSendTimeout(previously used only bySocketBackend, morph#506).SocketServerConfig/SocketServerreference table indocs/spec/core/backend.mdwith the two new fields and the design rationale (that table already existed — the rescope comment's premise that nomorph::net/SocketServerspec exists turned out to be stale, so this extends it rather than adding a new file).Out of scope (per the rescope comment on #534)
idleTimeout— rejected: a desktop GUI client left open is idle by design, and with no ping/pong keepalive there's no way to distinguish "idle" from "dead" without disconnecting working clients.maxConnections— real, but deferred to a follow-up ticket; not blocking this change.Regression tests
Both added under the
[morph534]tag intests/net/test_socket_server.cpp, confirmed to fail before the fix (one on a timeout-bound assertion, one on a bounded wait) and pass after:clientLoopexits) instead of continuing to dispatch further frames whose replies would be silently dropped.Verification
ctest— 1800/1800 passing (netlabel: 193/193).WARN_AS_ERROR = FAIL_ON_WARNINGS) builds clean.Adjacent finding filed, not fixed here
Two independent review passes (reuse and altitude, from the
/simplifygate) flagged that the new poll-with-remaining-budget loop inreadHttpHeaderBlockis a third independent copy of a pattern already inline inTcpSocket::connect()and in a test helper (FakeWsServer::acceptWithin) — filed as #831 rather than fixed here, since extracting a shared primitive means changingTcpSocket::connect(), which this change never otherwise touches, and carries its own design decision (the helper's shape).Fixes #534
🤖 Generated with Claude Code
https://claude.ai/code/session_018fEUahMFF32wQLiWjbsfkc