Files
Stefan-Sanger a4f17e2506 fix: cap WebSocket fragment buffer at configurable max message size
The mod_websocket continuation-frame patch accumulated fragments in an
unbounded dataBuffer with no size limit, fragment count limit, or
timeout. A buggy or malicious client sending endless continuation
frames without FIN could exhaust memory and degrade the entire server.

Add a configurable websocket_max_message_size option (default 2 MB)
that closes the connection with code 1009 ("Message too big") and
resets the buffer when exceeded. Also add wss-perf-analysis.md
documenting the WebSocket performance and drop investigation.

Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/8>
2026-07-16 06:10:01 +00:00

70 lines
2.1 KiB
Diff

--- a/plugins/mod_websocket.lua 2026-07-15 09:48:22.424559760 +0200
+++ b/plugins/mod_websocket.lua 2026-07-16 12:00:00.000000000 +0200
@@ -199,9 +199,6 @@
end
-- Other (XMPP-specific) validity checks
- if not frame.FIN then
- return false, 1003, "Continuation frames are not supported, RFC 7395 3.3.3";
- end
if opcode == 0x01 and frame.data and frame.data:byte(1, 1) ~= 60 then
return false, 1007, "Invalid payload start character, RFC 7395 3.3.3";
end
@@ -249,6 +246,9 @@
end
end
+ local max_message_size = module:get_option_number("websocket_max_message_size", 2 * 1024 * 1024);
+ local dataBufferLen = 0;
+ local dataBuffer;
local function handle_frame(frame)
module:log("debug", "Websocket received frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
@@ -266,12 +266,44 @@
return "";
elseif opcode == 0xA then -- Pong frame, MAY be sent unsolicited, eg as keepalive
return "";
- elseif opcode ~= 0x1 then -- Not text frame (which is all we support)
+ end
+
+ -- Text (0x1) and continuation (0x0) frames: accumulate fragments
+ if opcode == 0x0 and not dataBuffer then
+ return false, 1002, "Unexpected continuation frame";
+ end
+ if opcode == 0x1 and dataBuffer then
+ return false, 1002, "Continuation frame expected";
+ end
+
+ if opcode == 0x0 then -- Continuation frame
+ dataBufferLen = dataBufferLen + #frame.data;
+ if dataBufferLen > max_message_size then
+ dataBuffer = nil;
+ dataBufferLen = 0;
+ return false, 1009, "Message too big";
+ end
+ dataBuffer[#dataBuffer+1] = frame.data;
+ elseif opcode == 0x1 then -- Text frame
+ dataBufferLen = #frame.data;
+ if dataBufferLen > max_message_size then
+ dataBuffer = nil;
+ dataBufferLen = 0;
+ return false, 1009, "Message too big";
+ end
+ dataBuffer = { frame.data };
+ else
log("warn", "Received frame with unsupported opcode %i", opcode);
return "";
end
- return frame.data;
+ if frame.FIN then
+ local data = t_concat(dataBuffer, "");
+ dataBuffer = nil;
+ dataBufferLen = 0;
+ return data;
+ end
+ return "";
end
conn:setlistener(c2s_listener);