From 446b2e0a97ac1c241e9c58545f1b0f5f962d98e2 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Tue, 15 Apr 2025 12:17:39 +0200 Subject: [PATCH] soup-message-headers: Correct merge of ranges It had been skipping every second range, which generated an array of a lot of insane ranges, causing large memory usage by the server. Closes #428 --- libsoup/soup-message-headers.c | 1 + tests/meson.build | 1 + tests/server-mem-limit-test.c | 172 +++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 tests/server-mem-limit-test.c diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c index ee7a3cb1..f101d4b4 100644 --- a/libsoup/soup-message-headers.c +++ b/libsoup/soup-message-headers.c @@ -1244,6 +1244,7 @@ soup_message_headers_get_ranges_internal (SoupMessageHeaders *hdrs, if (cur->start <= prev->end) { prev->end = MAX (prev->end, cur->end); g_array_remove_index (array, i); + i--; } } } diff --git a/tests/meson.build b/tests/meson.build index ee118a01..8e7b51d1 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -102,6 +102,7 @@ tests = [ {'name': 'samesite'}, {'name': 'session'}, {'name': 'server-auth'}, + {'name': 'server-mem-limit'}, {'name': 'server'}, {'name': 'sniffing', 'depends': [test_resources], diff --git a/tests/server-mem-limit-test.c b/tests/server-mem-limit-test.c new file mode 100644 index 00000000..929f1a01 --- /dev/null +++ b/tests/server-mem-limit-test.c @@ -0,0 +1,172 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright 2025 Red Hat + */ + +#include "test-utils.h" + +#include + +typedef struct { + SoupServer *server; + GUri *base_uri, *ssl_base_uri; + GSList *handlers; +} ServerData; + +static void +server_callback (SoupServer *server, + SoupServerMessage *msg, + const char *path, + GHashTable *query, + gpointer data) +{ + const char *method; + + soup_message_headers_append (soup_server_message_get_response_headers (msg), + "X-Handled-By", "server_callback"); + + if (!strcmp (path, "*")) { + soup_test_assert (FALSE, "default server_callback got request for '*'"); + soup_server_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR, NULL); + return; + } + + method = soup_server_message_get_method (msg); + if (method != SOUP_METHOD_GET && method != SOUP_METHOD_POST) { + soup_server_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED, NULL); + return; + } + + soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); + soup_server_message_set_response (msg, "text/plain", + SOUP_MEMORY_STATIC, "index", 5); +} + +static void +server_setup_nohandler (ServerData *sd, gconstpointer test_data) +{ + sd->server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD); + sd->base_uri = soup_test_server_get_uri (sd->server, "http", NULL); + if (tls_available) + sd->ssl_base_uri = soup_test_server_get_uri (sd->server, "https", NULL); +} + +static void +server_add_handler (ServerData *sd, + const char *path, + SoupServerCallback callback, + gpointer user_data, + GDestroyNotify destroy) +{ + soup_server_add_handler (sd->server, path, callback, user_data, destroy); + sd->handlers = g_slist_prepend (sd->handlers, g_strdup (path)); +} + +static void +server_setup (ServerData *sd, gconstpointer test_data) +{ + server_setup_nohandler (sd, test_data); + server_add_handler (sd, NULL, server_callback, NULL, NULL); +} + +static void +server_teardown (ServerData *sd, gconstpointer test_data) +{ + GSList *iter; + + for (iter = sd->handlers; iter; iter = iter->next) + soup_server_remove_handler (sd->server, iter->data); + g_slist_free_full (sd->handlers, g_free); + + g_clear_pointer (&sd->server, soup_test_server_quit_unref); + g_clear_pointer (&sd->base_uri, g_uri_unref); + g_clear_pointer (&sd->ssl_base_uri, g_uri_unref); +} + +static void +server_file_callback (SoupServer *server, + SoupServerMessage *msg, + const char *path, + GHashTable *query, + gpointer data) +{ + void *mem; + + if (strcmp (path, "/file") != 0) { + soup_test_assert (FALSE, "server_file_callback got request for '%s'", path); + soup_server_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR, NULL); + return; + } + + if (soup_server_message_get_method (msg) != SOUP_METHOD_GET) { + soup_server_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED, NULL); + return; + } + + mem = g_malloc0 (sizeof (char) * 1024 *1024); + soup_server_message_set_response (msg, "application/octet-stream", SOUP_MEMORY_TAKE, mem, sizeof (char) * 1024 *1024); + soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); +} + +static void +do_ranges_overlaps_test (ServerData *sd, gconstpointer test_data) +{ + SoupSession *session; + SoupMessage *msg; + GString *range; + GUri *uri; + const char *chunk = ",0,0,0,0,0,0,0,0,0,0,0"; + + g_test_bug ("428"); + + #ifdef G_OS_WIN32 + g_test_skip ("Cannot run under windows"); + return; + #endif + + range = g_string_sized_new (99 * 1024); + g_string_append (range, "bytes=1024"); + while (range->len < 99 * 1024) + g_string_append (range, chunk); + + session = soup_test_session_new (NULL); + server_add_handler (sd, "/file", server_file_callback, NULL, NULL); + + uri = g_uri_parse_relative (sd->base_uri, "/file", SOUP_HTTP_URI_FLAGS, NULL); + + msg = soup_message_new_from_uri ("GET", uri); + soup_message_headers_append (soup_message_get_request_headers (msg), "Range", range->str); + + soup_test_session_send_message (session, msg); + + soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT); + + g_object_unref (msg); + + g_string_free (range, TRUE); + g_uri_unref (uri); + + soup_test_session_abort_unref (session); +} + +int +main (int argc, char **argv) +{ + int ret; + + test_init (argc, argv, NULL); + + #ifndef G_OS_WIN32 + struct rlimit new_rlimit = { 1024 * 1024 * 64, 1024 * 1024 * 64 }; + /* limit memory usage, to trigger too large memory allocation abort */ + g_assert_cmpint (setrlimit (RLIMIT_DATA, &new_rlimit), ==, 0); + #endif + + g_test_add ("/server-mem/range-overlaps", ServerData, NULL, + server_setup, do_ranges_overlaps_test, server_teardown); + + ret = g_test_run (); + + test_cleanup (); + return ret; +} -- GitLab