diff --git a/CHANGELOG.md b/CHANGELOG.md index 290ed7a51..84a04d819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ SPDX-License-Identifier: GPL-3.0-or-later - irc: limit size of data received from the server to prevent memory exhaustion - irc: fix out-of-bounds read on incoming DCC command with a quoted filename ending the message ([#2322](https://github.com/weechat/weechat/issues/2322)) - relay: limit size of received websocket frame and HTTP body to prevent memory exhaustion +- relay: limit size of partial message received while reading an HTTP request to prevent memory exhaustion - xfer: replace directory separator in remote nick by underscore in download filename to prevent writing the file outside the download directory ([#2321](https://github.com/weechat/weechat/issues/2321)) - xfer: fix out-of-bounds read when receiving empty line in DCC chat ([#2323](https://github.com/weechat/weechat/issues/2323)) diff --git a/src/plugins/relay/relay-http.c b/src/plugins/relay/relay-http.c index e56915566..5bcb88886 100644 --- a/src/plugins/relay/relay-http.c +++ b/src/plugins/relay/relay-http.c @@ -1006,6 +1006,14 @@ relay_http_recv (struct t_relay_client *client, const char *data, int size) if (client->partial_message) { + /* + * limit the size of the partial message: once the maximum is reached, + * ignore the extra data (protection against a client sending a huge + * amount of data without any end-of-line and dribbling it, which would + * consume all the memory) + */ + if (strlen (client->partial_message) >= RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH) + return; new_partial = realloc (client->partial_message, strlen (client->partial_message) + strlen (data) + 1); diff --git a/src/plugins/relay/relay-http.h b/src/plugins/relay/relay-http.h index 7e74503f0..9d24d4433 100644 --- a/src/plugins/relay/relay-http.h +++ b/src/plugins/relay/relay-http.h @@ -64,6 +64,15 @@ enum t_relay_client_http_status */ #define RELAY_HTTP_BODY_MAX_LENGTH (8 * 1024 * 1024) +/* + * maximum length of the partial message accumulated while reading an HTTP + * request: once this limit is reached, the extra data is ignored; this + * protects against a client sending a huge amount of data without any + * end-of-line (an unterminated method or header line), which would consume + * all the memory + */ +#define RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH (8 * 1024 * 1024) + struct t_relay_http_request { enum t_relay_client_http_status status; /* HTTP status */ diff --git a/tests/unit/plugins/relay/test-relay-http.cpp b/tests/unit/plugins/relay/test-relay-http.cpp index ee9e5af03..36735ab4a 100644 --- a/tests/unit/plugins/relay/test-relay-http.cpp +++ b/tests/unit/plugins/relay/test-relay-http.cpp @@ -41,6 +41,7 @@ extern "C" #include "src/plugins/relay/relay-client.h" #include "src/plugins/relay/relay-config.h" #include "src/plugins/relay/relay-http.h" +#include "src/plugins/relay/relay-server.h" #include "src/plugins/relay/relay-websocket.h" #include "src/plugins/weechat-plugin.h" @@ -1021,6 +1022,69 @@ TEST(RelayHttp, Recv) /* TODO: write tests */ } +/* + * Test functions: + * relay_http_recv (partial message accumulated is bounded) + * + * Check that data received without any end-of-line does not grow the partial + * message buffer without limit. + */ + +TEST(RelayHttp, RecvLimit) +{ + struct t_relay_server *server; + struct t_relay_client *client; + char *chunk; + int chunk_size, i; + size_t length1, length2; + + /* disable auto-open of relay buffer (it would pollute other tests) */ + config_file_option_set (relay_config_look_auto_open_buffer, "off", 1); + + server = relay_server_new ("weechat", RELAY_PROTOCOL_WEECHAT, NULL, + 9000, + NULL, /* path */ + 1, /* ipv4 */ + 0, /* ipv6 */ + 0, /* tls */ + 0); /* unix_socket */ + CHECK(server); + client = relay_client_new (-1, "test", server); + CHECK(client); + + chunk_size = 1024 * 1024; + chunk = (char *)malloc (chunk_size + 1); + CHECK(chunk); + memset (chunk, 'a', chunk_size); + chunk[chunk_size] = '\0'; + + /* feed more than the maximum, with no end-of-line (16 MB) */ + for (i = 0; i < 16; i++) + { + relay_http_recv (client, chunk, chunk_size); + } + CHECK(client->partial_message); + length1 = strlen (client->partial_message); + + /* the partial message must be bounded (not ~16 MB) */ + CHECK(length1 <= RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH + (size_t)chunk_size); + + /* feeding more data must not grow it any further */ + for (i = 0; i < 16; i++) + { + relay_http_recv (client, chunk, chunk_size); + } + length2 = strlen (client->partial_message); + LONGS_EQUAL(length1, length2); + + free (chunk); + relay_client_free (client); + relay_server_free (server); + + /* restore auto-open of relay buffer */ + config_file_option_reset (relay_config_look_auto_open_buffer, 1); +} + /* * Test functions: * relay_http_compress