diff --git a/CHANGELOG.md b/CHANGELOG.md index 914c905b4..2a9523b3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- 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/weechat: fix empty buffers in client when WeeChat is running on Solaris/illumos - build: fix build on Solaris/illumos (issue #2251) diff --git a/src/plugins/relay/relay-websocket.c b/src/plugins/relay/relay-websocket.c index a6cebfdcc..4e6b9fc1e 100644 --- a/src/plugins/relay/relay-websocket.c +++ b/src/plugins/relay/relay-websocket.c @@ -530,7 +530,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; @@ -547,8 +547,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; @@ -577,8 +582,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; diff --git a/src/plugins/relay/relay-websocket.h b/src/plugins/relay/relay-websocket.h index 4b1a36f44..42cf4b822 100644 --- a/src/plugins/relay/relay-websocket.h +++ b/src/plugins/relay/relay-websocket.h @@ -39,6 +39,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; diff --git a/tests/unit/plugins/relay/test-relay-websocket.cpp b/tests/unit/plugins/relay/test-relay-websocket.cpp index 4d27d0470..f99e40bcb 100644 --- a/tests/unit/plugins/relay/test-relay-websocket.cpp +++ b/tests/unit/plugins/relay/test-relay-websocket.cpp @@ -464,6 +464,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); } /*