1
0
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:
Sébastien Helleu
2026-05-30 16:38:42 +02:00
parent 4fdcbf8f93
commit 5dbb96b66a
4 changed files with 64 additions and 5 deletions
+1
View File
@@ -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)
+21 -5
View File
@@ -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;
+7
View File
@@ -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);
}
/*