fix(discord,gateway): inbound WS buffer overflow and WebChat drop - #81
Conversation
6fa01ac to
08716db
Compare
discord_rx_append grew only when cap < rx_len+len, then wrote a NUL at rx_len+len. Two 64KiB LWS fragments filled a doubled buffer exactly, so the terminator was one byte past the heap block (typical READY payloads). Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
…split LWS delivers at most rx_buffer_size bytes per RECEIVE. The ws protocol used 256 with no fragment reassembly, so dashboard messages over ~220 characters were parsed as incomplete JSON and dropped. Size the buffer to fit a full WebChat frame (WS_TEXT_MAX plus JSON envelope). Distinct from outbound #62. Co-authored-by: Adrianno E. S. <adriannoes@users.noreply.github.com>
08716db to
d97452a
Compare
There was a problem hiding this comment.
Summary
This PR fixes two inbound WebSocket bugs that match the claimed intent. Discord Gateway RX now grows for the trailing NUL, so two 64 KiB libwebsockets fragments cannot write one byte past the heap block (typical READY). Dashboard WebChat raises ws rx_buffer_size from 256 to WS_TEXT_MAX + 64, which stops the deterministic drop of Chat JSON above ~220 characters. Helper tests for the exact-fill and dual-64 KiB cases pass locally under GCC ASan+UBSan. Required CI is red: make static fails cppcheck variableScope on the new helper.
Would be REQUEST_CHANGES if GitHub allowed it on this bot-authored PR. Do not merge until make static is green.
Must Fix
src/channels/discord_helpers.c:148— cppcheckvariableScopeonncapfailsmake static(--error-exitcode=1). GitHub Actionsstatic, test, releaseon 08716db stopped at this check (run 34793224931). Declarencapandpinsideif (*cap < need)so the next pass does not fail onp. Same class as #80.
Should Fix
src/gateway/http.c/ws_callbackRECEIVE still parses each callback as a complete JSON object. The buffer bump fixes the 256-byte LWS chunk split. It does not reassemble partial frames (lws_is_final_fragment). Discord already does. A split Chat frame can still be dropped.rx_skipinsrc/channels/discord.cis untested. Helper tests never walk overflow-on-non-final plus the following frame.WS_RX_BUFFER_SIZE = WS_TEXT_MAX + 64does not cover JSON escape expansion (quotes, backslashes). Typical<input>Chat text is fine.
Nice to Have
- Helper test for payload
max_cap - 1(NUL fits) vs payloadmax_cap(reject). - Keep
WS_RX_BUFFER_SIZEnext toWS_TEXT_MAXinws.hinstead of includingws.hfromhttp_lws.h. test_gateway_httpstill has no/wscase; GATEWAY=1 CI does not lock inbound Chat.ncap *= 2has no overflow guard. ProductionRX_MAX(512 KiB) is safe.
Positive Highlights
- Moving grow-for-NUL into
discord_helpers_rx_appendis the right split:discord.cis already over the 1000-line mark, and the 4095+1 plus two 64 KiB tests actually hit the old OOB. - CHANGELOG Unreleased lines are present for both fixes (unlike several earlier bug-hunt PRs).
rx_skipuntil the final LWS fragment is the correct fail-closed follow-through after overflow.- Because
tx_packet_sizedefaults to 0, the WebChat rx bump also sizes the send buffer, which is what lets a #62 32 KiBlws_writefit in one shot (ws_on_writabledoes not retry short writes).
Local check: helper tests passed when linked with -lm only (Makefile still passes -lcurl; this image has no curl import library). GCC ASan+UBSan detect_leaks=1 on test_discord_helpers passed. cppcheck is not installed here; the CI log is the oracle for variableScope.
Sent by Cursor Automation: Adrianno’s personal code review
There was a problem hiding this comment.
Could not approve: GitHub blocks self-approval on this cursor[bot] PR. Cursor Security Agent passed with no findings that need human review; Bugbot did not run on the current head, so that signal was skipped. adriannoes is already assigned; no additional reviewers were added.
Sent by Cursor Approval Agent: Pull Request Router and Approver
There was a problem hiding this comment.
Could not approve: GitHub blocks self-approval on this cursor[bot] PR. Cursor Security Agent passed with no findings that need human review; Bugbot did not run on the current head, so that signal was skipped. adriannoes is already assigned; no additional reviewers were added.
Sent by Cursor Approval Agent: Pull Request Router and Approver


Summary
Two inbound WebSocket buffer bugs: Discord Gateway RX wrote a NUL one byte past the heap block, and dashboard WebChat silently dropped messages larger than one 256-byte LWS callback.
Bug 1 — Discord Gateway RX heap overflow (CRITICAL)
src/channels/discord.c(discord_rx_append, nowdiscord_helpers_rx_append)rx_buffer_size = 65536. First 64KiB fragment doubles the buffer to 131072 withrx_len = 65536. Second 64KiB fragment:rx_cap < rx_len + lenis false (131072 < 131072), grow is skipped, thenrx_buf[rx_len] = '\0'writes at index 131072 (one past the allocation). Typical Discord READY (or any DISPATCH) payload >128KiB, well underRX_MAX(512KiB).parse_response_bodyrealloc-before-success. fix(webchat): align WebSocket payload limit with agent response buffer #62 is WebChat outbound 8KiB vs 32KiB (ws_send_to/MSG_MAX), not Discord client RX. Telegram/OpenAI curl writers already include+1for NUL.Bug 2 — WebChat inbound frames not reassembled (HIGH)
src/gateway/http.cprotocols"ws".rx_buffer_size = 256;src/gateway/http_lws.cws_callbackLWS_CALLBACK_RECEIVE{"type":"message","text":"..."}). LWS splits at 256 bytes; each chunk iscJSON_Parsed as a complete object and fails; the user message is dropped.rx_buffer_sizeor fragment reassembly.Fix
cap < rx_len + chunk + 1(payload plus trailing NUL). Discard remaining fragments after overflow untillws_is_final_fragment.rx_buffer_sizetoWS_RX_BUFFER_SIZE(32KiB) so a full frame atMSG_MAXplus JSON envelope arrives in one RECEIVE.Validation
CI=true make test_discord_helpers(includes exact-fill 4095+1 and two 64KiB fragments)test_discord_helpersGATEWAY=1compile ofdiscord.c,http.c,http_lws.cNote
Medium Risk
Touches live Discord Gateway and dashboard WebSocket receive paths; fixes memory safety and message delivery but changes buffer sizing on long-running gateway threads.
Overview
Fixes two inbound WebSocket buffer bugs: a one-byte heap overflow on Discord Gateway reassembly and silent loss of longer dashboard WebChat messages.
Discord Gateway RX now grows the reassembly buffer when
payload + trailing NULwould exceed capacity (e.g. two 64 KiB LWS chunks no longer skip a grow at exactly 128 KiB). Logic lives in testablediscord_helpers_rx_append; on overflow the client resets and skips fragments until the final LWS fragment viarx_skip.WebChat inbound raises the
"ws"protocolrx_buffer_sizefrom 256 bytes toWS_RX_BUFFER_SIZE(WS_TEXT_MAX+ envelope), with a compile-time assert, so typical{"type":"message","text":...}frames arrive in oneRECEIVEinstead of failing partial JSON parses.Unit tests cover NUL-after-fill and dual 64 KiB fragment cases plus max-cap rejection.
Reviewed by Cursor Bugbot for commit 08716db. Bugbot is set up for automated code reviews on this repo. Configure here.