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:
@@ -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))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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) */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user