mirror of
https://github.com/weechat/weechat.git
synced 2026-06-12 14:14:48 +02:00
relay: limit size of decompressed websocket frame to prevent memory exhaustion (GHSA-v2v4-45wm-5cr3)
An authenticated relay client using the permessage-deflate websocket extension could send a small compressed frame that decompresses to an unbounded amount of data, exhausting all memory and crashing WeeChat. The output buffer in relay_websocket_inflate is now capped to WEBSOCKET_INFLATE_MAX_SIZE: frames decompressing beyond this limit are rejected and the connection is closed.
This commit is contained in:
@@ -22,6 +22,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
- core: fix option weechat.look.color_real_white not applied when color is "white" on 16+ colors terminals ([#1742](https://github.com/weechat/weechat/issues/1742))
|
||||
- irc: fix tag in message with list of names when joining a channel
|
||||
- fset: remove error displayed in core buffer when clicking with the mouse below the last option displayed
|
||||
- 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))
|
||||
|
||||
## Version 4.9.0 (2026-03-29)
|
||||
|
||||
|
||||
@@ -532,7 +532,7 @@ relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
|
||||
int rc;
|
||||
unsigned char append_bytes[4] = { 0x00, 0x00, 0xFF, 0xFF };
|
||||
Bytef *data2, *dest, *dest2;
|
||||
uLongf size2, dest_size;
|
||||
uLongf size2, dest_size, new_size;
|
||||
|
||||
if (!data || (size == 0) || !strm || !size_decompressed)
|
||||
return NULL;
|
||||
@@ -549,8 +549,13 @@ relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
|
||||
memcpy (data2, data, size);
|
||||
memcpy (data2 + size, append_bytes, sizeof (append_bytes));
|
||||
|
||||
/* estimate the decompressed size, by default 10 * size */
|
||||
dest_size = 10 * size2;
|
||||
/*
|
||||
* estimate the decompressed size, by default 10 * size, capped to the
|
||||
* maximum allowed size (also prevents an integer overflow on the
|
||||
* multiplication)
|
||||
*/
|
||||
dest_size = (size2 > WEBSOCKET_INFLATE_MAX_SIZE / 10) ?
|
||||
WEBSOCKET_INFLATE_MAX_SIZE : 10 * size2;
|
||||
dest = malloc (dest_size);
|
||||
if (!dest)
|
||||
goto error;
|
||||
@@ -579,8 +584,19 @@ relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
|
||||
&& (strm->avail_in > 0)))
|
||||
{
|
||||
/* output buffer is not large enough */
|
||||
strm->avail_out += dest_size;
|
||||
dest_size *= 2;
|
||||
if (dest_size >= WEBSOCKET_INFLATE_MAX_SIZE)
|
||||
{
|
||||
/*
|
||||
* decompressed data is too large: reject the frame
|
||||
* (protection against a "deflate bomb")
|
||||
*/
|
||||
goto error;
|
||||
}
|
||||
/* double the buffer, capped to the maximum allowed size */
|
||||
new_size = (dest_size > WEBSOCKET_INFLATE_MAX_SIZE / 2) ?
|
||||
WEBSOCKET_INFLATE_MAX_SIZE : dest_size * 2;
|
||||
strm->avail_out += (uInt)(new_size - dest_size);
|
||||
dest_size = new_size;
|
||||
dest2 = realloc (dest, dest_size);
|
||||
if (!dest2)
|
||||
goto error;
|
||||
|
||||
@@ -41,6 +41,13 @@
|
||||
|
||||
#define WEBSOCKET_SUB_PROTOCOL_API_WEECHAT "api.weechat"
|
||||
|
||||
/*
|
||||
* maximum size of a decompressed websocket frame (with "permessage-deflate"):
|
||||
* used as an upper bound when inflating, to prevent a small compressed frame
|
||||
* from decompressing to an unbounded amount of data ("deflate bomb")
|
||||
*/
|
||||
#define WEBSOCKET_INFLATE_MAX_SIZE (8 * 1024 * 1024)
|
||||
|
||||
struct t_relay_client;
|
||||
struct t_relay_http_request;
|
||||
|
||||
|
||||
@@ -466,6 +466,41 @@ TEST(RelayWebsocket, Inflate)
|
||||
free (payload_comp);
|
||||
|
||||
relay_websocket_deflate_free (ws_deflate);
|
||||
|
||||
/*
|
||||
* protection against "deflate bomb": a small compressed frame that
|
||||
* decompresses to more than WEBSOCKET_INFLATE_MAX_SIZE must be rejected
|
||||
* (relay_websocket_inflate returns NULL)
|
||||
*/
|
||||
ws_deflate = relay_websocket_deflate_alloc ();
|
||||
CHECK(ws_deflate);
|
||||
ws_deflate->window_bits_deflate = 15;
|
||||
ws_deflate->window_bits_inflate = 15;
|
||||
ws_deflate->strm_deflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_deflate));
|
||||
CHECK(ws_deflate->strm_deflate);
|
||||
LONGS_EQUAL(1, relay_websocket_deflate_init_stream_deflate (ws_deflate));
|
||||
ws_deflate->strm_inflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_inflate));
|
||||
CHECK(ws_deflate->strm_inflate);
|
||||
LONGS_EQUAL(1, relay_websocket_deflate_init_stream_inflate (ws_deflate));
|
||||
|
||||
/* highly compressible payload that decompresses past the maximum size */
|
||||
size_t bomb_size = WEBSOCKET_INFLATE_MAX_SIZE + (1024 * 1024);
|
||||
char *bomb = (char *)calloc (1, bomb_size);
|
||||
CHECK(bomb);
|
||||
|
||||
payload_comp = (char *)relay_websocket_deflate (bomb, bomb_size,
|
||||
ws_deflate->strm_deflate, &size_comp);
|
||||
CHECK(payload_comp);
|
||||
CHECK(size_comp < bomb_size);
|
||||
|
||||
payload_decomp = (char *)relay_websocket_inflate (payload_comp, size_comp,
|
||||
ws_deflate->strm_inflate, &size_decomp);
|
||||
POINTERS_EQUAL(NULL, payload_decomp);
|
||||
|
||||
free (payload_comp);
|
||||
free (bomb);
|
||||
|
||||
relay_websocket_deflate_free (ws_deflate);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user