diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c951fdb2..56bf615f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/src/plugins/relay/relay-http.c b/src/plugins/relay/relay-http.c index 7e48eb8f8..e56915566 100644 --- a/src/plugins/relay/relay-http.c +++ b/src/plugins/relay/relay-http.c @@ -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) diff --git a/src/plugins/relay/relay-http.h b/src/plugins/relay/relay-http.h index f4e7ac5e9..7e74503f0 100644 --- a/src/plugins/relay/relay-http.h +++ b/src/plugins/relay/relay-http.h @@ -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 */ diff --git a/src/plugins/relay/relay-websocket.c b/src/plugins/relay/relay-websocket.c index cfc294a4a..3295c3a28 100644 --- a/src/plugins/relay/relay-websocket.c +++ b/src/plugins/relay/relay-websocket.c @@ -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) */ diff --git a/src/plugins/relay/relay-websocket.h b/src/plugins/relay/relay-websocket.h index 37b3f8853..523060527 100644 --- a/src/plugins/relay/relay-websocket.h +++ b/src/plugins/relay/relay-websocket.h @@ -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 diff --git a/tests/unit/plugins/relay/test-relay-http.cpp b/tests/unit/plugins/relay/test-relay-http.cpp index f5df9ea48..ee9e5af03 100644 --- a/tests/unit/plugins/relay/test-relay-http.cpp +++ b/tests/unit/plugins/relay/test-relay-http.cpp @@ -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 diff --git a/tests/unit/plugins/relay/test-relay-websocket.cpp b/tests/unit/plugins/relay/test-relay-websocket.cpp index 5f04f9d29..dc44ed3fe 100644 --- a/tests/unit/plugins/relay/test-relay-websocket.cpp +++ b/tests/unit/plugins/relay/test-relay-websocket.cpp @@ -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); } /*