Skip to content

finish short writes in write_websocket_frame and open_stream_ - #2642

Merged
yhirose merged 2 commits into
yhirose:masterfrom
metsw24-max:ws-frame-short-write
Oct 26, 2026
Merged

yhirose merged 2 commits into
yhirose:masterfrom
metsw24-max:ws-frame-short-write

Conversation

@metsw24-max

Copy link
Copy Markdown
Contributor

Short writes truncate WebSocket frames and streamed request bodies

Problem: detail::write_websocket_frame and the request-body write in ClientImpl::open_stream_ call strm.write() once per buffer and only treat a negative return as failure, so a short write is taken as success and the rest of the data is dropped.

Cause: a short count is normal in two cases. Under the Mbed TLS backend, mbedtls_ssl_write() writes at most one record (16 KiB by default) per call, so any unmasked frame larger than that loses its tail. On a plain socket, send() returns a short count when SO_SNDTIMEO expires after part of the data has gone out to a slow reader. The frame header still announces the full length, so the receiver takes the next frame's header and payload as the rest of this one, then parses a frame header from the middle of a later payload. Whoever controls the content of a later message can therefore place a complete frame there (a Text or a Close), and it is delivered as a message of its own. With a stream capped at 16384 bytes per write, a 20000-byte binary message followed by a crafted text message arrives as three frames, the last two of them forged. On the client side, the streamed body falls short of its Content-Length and the server answers 400 once it gives up waiting.

Fix: both sites now go through detail::write_data, which the other body writers already use, so a short count is resumed and only a real error fails the send.

Tests: WebSocketPartialWriteTest.FrameIsWrittenWhole and OpenStreamPartialWriteTest.BodyIsWrittenWhole shrink the socket buffers, let the write timeout expire partway through a 4 MiB write while the peer sleeps, then check that everything arrives whole. Both fail on master (the server sees 294912 of 4194304 body bytes and answers 400, and the WebSocket client never reads the message). Both pass with the fix. They are skipped on Windows, where a timed-out blocking send() leaves the socket unusable rather than returning a short count.

Reviewer note: I could only run the suite without TLS here, so the Mbed TLS case rests on the documented mbedtls_ssl_write() behaviour rather than a run on that backend. It is probably worth a glance at that CI job.

metsw24-max and others added 2 commits October 24, 2026 21:15
write_websocket_frame and the request-body write in open_stream_ called
Stream::write once and only failed on a negative return, so a short
write (one record per mbedtls_ssl_write call, or SO_SNDTIMEO expiring
after part of the data went out) dropped the rest of the frame or body.
Route both through write_data, as the other body writers do.
@yhirose

yhirose commented Oct 26, 2026

Copy link
Copy Markdown
Owner

Thanks!

@yhirose
yhirose merged commit 391de32 into yhirose:master Oct 26, 2026
27 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants