Fix WebSocket 64-bit payload truncation and chunked status text#37
Merged
Conversation
…nked status text decode-frame silently ignored the high 4 bytes of 64-bit extended payload lengths (RFC 6455 §5.2), reading only the low 32 bits. A remote peer sending a frame header with non-zero high bytes would cause the decoder to compute a wrong payload length. The decoder now checks the high bytes and rejects frames whose payload length exceeds the 32-bit Int range. Response.chunked always set the reason phrase to "OK" regardless of the status code. Now uses Status.reason from the http library.
There was a problem hiding this comment.
Build & Tests
- Build: fails on ARM Linux (pre-existing
tm_zoneconst-correctness issue — identical failure onmain) - Tests: CI passes on macOS (the only CI platform for this repo)
- CI: pass (macOS)
Findings
No issues found. Both fixes are correct and well-tested.
WebSocket 64-bit payload length (RFC 6455 §5.2):
- Byte ordering is correct: offsets 2–5 are the high 32 bits, offsets 6–9 are the low 32 bits, read in big-endian order.
- The
bit-orrejection check correctly detects any non-zero high byte. Since Carp uses 32-bitInt, rejecting payloads > 2³² withNothingis the right approach. - Low-32-bit reconstruction (
byte6 * 2²⁴ + byte7 * 2¹⁶ + byte8 * 2⁸ + byte9) is correct big-endian. - Buffer bounds are checked:
(< avail 10)guard at the top ensures all offsets 2–9 are safe to read. - Test at websocket.carp crafts a header with byte 5 = 1 (high word = 2³²), verifying the old code would have read plen=0 but the fix now returns
Nothing. Round-trip test with 65536-byte payload confirms normal 64-bit frames still work.
Response.chunked status text:
Status.reasonexists in the http library (loaded ashttp@0.1.4) and maps status codes to standard reason phrases with an"Unknown"fallback.- Tests verify 200 → "OK", 404 → "Not Found", 500 → "Internal Server Error".
Formatting: the large re-indentation blocks in handle-writable-ws, handle-readable-ws, and handle-readable are pure whitespace changes — logic is identical.
CHANGELOG: entries accurately describe both fixes.
Verdict: merge
Both fixes address real bugs (RFC 6455 violation for >4 GiB frames, incorrect HTTP status lines for non-200 chunked responses). Well-tested and correctly implemented.
hellerve
approved these changes
Jun 21, 2026
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
Two bug fixes:
WebSocket
decode-frame64-bit payload length truncation (RFC 6455 §5.2)When the payload length indicator is 127 (64-bit extended length),
decode-frameonly read bytes 6–9 (the low 32 bits), ignoring bytes 2–5 (the high 32 bits). A remote peer sending a frame header with non-zero high bytes would cause the decoder to compute a wrong payload length, potentially desynchronizing the frame parser.The fix checks the high 4 bytes via
bit-or; if any are non-zero the payload exceeds the 32-bitIntrange and the frame is rejected (returnsNothing). When all high bytes are zero, the existing low-32-bit calculation is used unchanged.Response.chunkedhardcoded status textResponse.chunkedalways passed"OK"as the reason phrase regardless of thecodeparameter, so(Response.chunked 404 ...)would produceHTTP/1.1 404 OKon the wire. Now usesStatus.reasonfrom the http library to derive the correct phrase.Test plan
64-bit length high bytes are not truncated— crafts a raw frame header with byte 5 = 1 (plen = 2³²), verifies decoder returnsNothing64-bit frame round-trips with correct payload length— 65536-byte payload triggers the 64-bit path, verifies encode/decode round-tripencode-text uses 64-bit length for >= 65536 bytes— verifies the length indicator byte is 127chunked 200/404/500 has reason phrase ...— verifies correct reason phrase for different status codesOpened by the carpentry-org heartbeat agent (Claude). Veit Heller has not reviewed this yet.