Skip to content

WebSocket: inbound frames >= 528 bytes are rejected (decoder given a BUFSIZE buffer instead of WS_MAX_PAYLOAD) #98

Description

@rubinlinux

Branch: ircv3.2-upgrade @ 3868b34.

Summary

Any inbound WebSocket frame whose payload is ≥ 528 bytes disconnects the client with WebSocket frame error. The decoder advertises a 16 KB limit, but the buffer it's handed is 528 bytes. Since browsers don't let a page control fragmentation, a web client can't work around this except by refusing to send lines over ~500 bytes — which rules out draft/multiline (the server advertises max-bytes=16384) and larger client-tag payloads.

Reproduction

Over wss:// (see #97 for why not ws://), register a client and send a single frame:

PRIVMSG #seance :xxxx…   (600 bytes total)

Server response:

ERROR :Closing Link: bigframe by irc.seance.test (WebSocket frame error)

followed by TCP close (WS close code 1006 — no Close frame is sent). A 400-byte line in one frame is delivered fine.

Script used (Node 22, ws package):

const ws = new WebSocket("wss://localhost:8443/", ["text.ircv3.net"], {rejectUnauthorized: false});
ws.on("open", () => { ws.send("CAP END"); ws.send("NICK bigframe"); ws.send("USER b 0 * :b"); });
ws.on("message", (d) => {
  const line = d.toString();
  if (line.startsWith("PING")) ws.send("PONG" + line.slice(4));
  if (/ 001 /.test(line)) { ws.send("JOIN #seance"); ws.send("PRIVMSG #seance :" + "x".repeat(583)); }
  if (/ERROR/.test(line)) console.log(line);
});

Cause

  • ircd/websocket.c:67WS_MAX_PAYLOAD is 16384 and websocket_decode_frame() accepts payload lengths up to that.
  • ircd/s_bsd.c:1126 — the caller passes a stack buffer char ws_payload[BUFSIZE + 16] (= 528 bytes).
  • ircd/websocket.c:551-554 — the decoder rejects any frame whose payload length ≥ payload_size, returning the error that becomes WebSocket frame error at s_bsd.c (exit_client(... "WebSocket frame error")).

So the effective inbound frame cap is 527 bytes, not 16 KB.

Suggested fix

Size the decode buffer to WS_MAX_PAYLOAD (heap or a static per-call buffer, same as the ws_frame/concat_buf statics on the send side at s_bsd.c:332-333), then hand the decoded payload to the existing line splitter so an oversized IRC line still gets the normal ERR_INPUTTOOLONG / truncation treatment rather than a transport-level disconnect. Also worth sending a WS Close frame (1009 Message Too Big) before dropping the TCP connection so clients can distinguish this from a network failure.

Context

Found while bringing up a browser IRCv3 client (Seance, a TheLounge fork) against a docker build of this branch. Happy to test a fix.

Metadata

Metadata

Assignees

Labels

bugircv3-upgradeIssues against the ircv3.2-upgrade / ircv3.2-hardening branches

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions