1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-12 14:14:48 +02:00

relay: limit size of received websocket frame and HTTP body to prevent memory exhaustion

A relay client could announce a huge websocket frame (or HTTP body via
"Content-Length") and dribble its payload, making WeeChat accumulate it
in a buffer that grew without limit, until all memory was exhausted. The
websocket frame path is reachable before authentication with the
"weechat" and "irc" protocols.

The announced websocket frame length and HTTP "Content-Length" are now
bounded by WEBSOCKET_FRAME_MAX_LENGTH and RELAY_HTTP_BODY_MAX_LENGTH: an
oversized websocket frame closes the connection, and an oversized body is
rejected.
This commit is contained in:
Sébastien Helleu
2026-06-01 21:56:34 +02:00
parent 1211510ded
commit 3687ce0f0f
7 changed files with 104 additions and 1 deletions
+1
View File
@@ -24,6 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
- fset: remove error displayed in core buffer when clicking with the mouse below the last option displayed
- irc: limit size of data received from the server to prevent memory exhaustion
- relay: limit size of decompressed websocket frame with permessage-deflate to prevent memory exhaustion ([GHSA-v2v4-45wm-5cr3](https://github.com/weechat/weechat/security/advisories/GHSA-v2v4-45wm-5cr3))
- relay: limit size of received websocket frame and HTTP body to prevent memory exhaustion
- relay: fix timing attack on password authentication ([GHSA-vhv8-g2r9-cwcc](https://github.com/weechat/weechat/security/advisories/GHSA-vhv8-g2r9-cwcc))
- api, relay: fix timing attack on TOTP validation ([GHSA-vhv8-g2r9-cwcc](https://github.com/weechat/weechat/security/advisories/GHSA-vhv8-g2r9-cwcc))
+13
View File
@@ -513,6 +513,19 @@ relay_http_add_to_body (struct t_relay_http_request *request,
if (!partial_message || !*partial_message)
return;
/*
* reject the body if its announced length is too big: this prevents a
* client from forcing an unbounded allocation by announcing a huge
* "Content-Length"
*/
if (request->content_length > RELAY_HTTP_BODY_MAX_LENGTH)
{
free (*partial_message);
*partial_message = NULL;
request->status = RELAY_HTTP_END;
return;
}
num_bytes_missing = request->content_length
- request->body_size;
if (num_bytes_missing <= 0)
+7
View File
@@ -57,6 +57,13 @@ enum t_relay_client_http_status
#define RELAY_HTTP_ERROR_METHOD_NOT_ALLOWED "Method Not Allowed"
#define RELAY_HTTP_ERROR_OUT_OF_MEMORY "Out of memory"
/*
* maximum length of an HTTP request body: used as an upper bound on the
* "Content-Length" accepted from a client, to prevent a client from forcing
* an unbounded allocation by announcing a huge body
*/
#define RELAY_HTTP_BODY_MAX_LENGTH (8 * 1024 * 1024)
struct t_relay_http_request
{
enum t_relay_client_http_status status; /* HTTP status */
+8
View File
@@ -703,6 +703,14 @@ relay_websocket_decode_frame (const unsigned char *buffer,
index_buffer += length_frame_size;
}
/*
* reject the frame if its announced length is too big: this prevents
* a client from forcing an unbounded allocation (and unbounded
* accumulation of partial frames) by announcing a huge frame
*/
if (length_frame > WEBSOCKET_FRAME_MAX_LENGTH)
return 0;
if (masked_frame)
{
/* read mask (4 bytes) */
+8
View File
@@ -41,6 +41,14 @@
#define WEBSOCKET_SUB_PROTOCOL_API_WEECHAT "api.weechat"
/*
* maximum length of a websocket frame received from a client (or a remote
* WeeChat): used as an upper bound on the announced frame payload length, to
* prevent a client from forcing an unbounded allocation by announcing a huge
* frame and dribbling its payload
*/
#define WEBSOCKET_FRAME_MAX_LENGTH (8 * 1024 * 1024)
/*
* maximum size of a decompressed websocket frame (with "permessage-deflate"):
* used as an upper bound when inflating, to prevent a small compressed frame
@@ -161,6 +161,35 @@ TEST(RelayHttp, RequestAllocReinitFree)
relay_http_request_free (request);
}
/*
* Test functions:
* relay_http_add_to_body (body too large is rejected)
*/
TEST(RelayHttp, AddToBodyLimit)
{
struct t_relay_http_request *request;
char *partial;
request = relay_http_request_alloc ();
CHECK(request);
/* announce a body larger than the maximum allowed */
request->status = RELAY_HTTP_BODY;
request->content_length = RELAY_HTTP_BODY_MAX_LENGTH + 1;
partial = strdup ("some body data");
relay_http_add_to_body (request, &partial);
/* the body must be rejected: nothing allocated, request ended */
POINTERS_EQUAL(NULL, request->body);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, partial);
LONGS_EQUAL(RELAY_HTTP_END, request->status);
relay_http_request_free (request);
}
/*
* Test functions:
* relay_http_url_decode
@@ -510,7 +510,44 @@ TEST(RelayWebsocket, Inflate)
TEST(RelayWebsocket, DecodeFrame)
{
/* TODO: write tests */
struct t_relay_websocket_frame *frames;
char *partial_ws_frame;
int num_frames, partial_ws_frame_size;
/* small unmasked binary frame with payload "hello" */
unsigned char frame_ok[7] = { 0x82, 0x05, 'h', 'e', 'l', 'l', 'o' };
/* masked frame announcing a 1 GB payload (64-bit length field) */
unsigned char frame_too_big[10] = {
0x82, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
};
/* a valid small frame is decoded */
frames = NULL;
num_frames = 0;
partial_ws_frame = NULL;
partial_ws_frame_size = 0;
LONGS_EQUAL(1, relay_websocket_decode_frame (
frame_ok, sizeof (frame_ok), 0, NULL,
&frames, &num_frames, &partial_ws_frame,
&partial_ws_frame_size));
LONGS_EQUAL(1, num_frames);
CHECK(frames);
LONGS_EQUAL(5, frames[0].payload_size);
MEMCMP_EQUAL("hello", frames[0].payload, 5);
free (frames[0].payload);
free (frames);
free (partial_ws_frame);
/* a frame announcing an oversized payload is rejected (return 0) */
frames = NULL;
num_frames = 0;
partial_ws_frame = NULL;
partial_ws_frame_size = 0;
LONGS_EQUAL(0, relay_websocket_decode_frame (
frame_too_big, sizeof (frame_too_big), 1, NULL,
&frames, &num_frames, &partial_ws_frame,
&partial_ws_frame_size));
free (frames);
free (partial_ws_frame);
}
/*