From 3c87790a4ba141125e6ba165c478f0440e8e693e Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Fri, 16 May 2025 16:55:40 -0500 Subject: [PATCH 4/4] Add tests for max-incoming-packet-size and max-total-message-size An even better test would verify that it's possible to send big messages containing small packets, but libsoup doesn't offer control over packet size, and I don't want to take the time to learn how WebSockets work to figure out how to do that manually. Instead, I just check that both limits work, for both client and server. I didn't add deflate variants of these tests because I doubt that would add valuable coverage. Upstream-Status: Backport [https://gitlab.gnome.org/GNOME/libsoup/-/commit/4d00b45b7eebdcfa0706b58e34c40b8a0a16015b] CVE: CVE-2025-32049 Signed-off-by: Changqing Li --- tests/websocket-test.c | 214 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 197 insertions(+), 17 deletions(-) diff --git a/tests/websocket-test.c b/tests/websocket-test.c index 827b041..ec1324c 100644 --- a/tests/websocket-test.c +++ b/tests/websocket-test.c @@ -543,16 +543,9 @@ test_send_big_packets (Test *test, { GBytes *sent = NULL; GBytes *received = NULL; + gulong signal_id; - g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received); - - sent = g_bytes_new_take (g_strnfill (400, '!'), 400); - soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL)); - WAIT_UNTIL (received != NULL); - g_assert (g_bytes_equal (sent, received)); - g_bytes_unref (sent); - g_bytes_unref (received); - received = NULL; + signal_id = g_signal_connect (test->client, "message", G_CALLBACK (on_text_message), &received); sent = g_bytes_new_take (g_strnfill (100 * 1000, '?'), 100 * 1000); soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL)); @@ -563,23 +556,174 @@ test_send_big_packets (Test *test, received = NULL; soup_websocket_connection_set_max_incoming_payload_size (test->client, 1000 * 1000 + 1); - g_assert (soup_websocket_connection_get_max_incoming_payload_size (test->client) == (1000 * 1000 + 1)); + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->client), ==, 1000 * 1000 + 1); soup_websocket_connection_set_max_incoming_payload_size (test->server, 1000 * 1000 + 1); - g_assert (soup_websocket_connection_get_max_incoming_payload_size (test->server) == (1000 * 1000 + 1)); + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->server), ==, 1000 * 1000 + 1); soup_websocket_connection_set_max_total_message_size (test->client, 1000 * 1000 + 1); - g_assert (soup_websocket_connection_get_max_total_message_size (test->client) == (1000 * 1000 + 1)); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->client), ==, 1000 * 1000 + 1); soup_websocket_connection_set_max_total_message_size (test->server, 1000 * 1000 + 1); - g_assert (soup_websocket_connection_get_max_total_message_size (test->server) == (1000 * 1000 + 1)); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->server), ==, 1000 * 1000 + 1); sent = g_bytes_new_take (g_strnfill (1000 * 1000, '?'), 1000 * 1000); soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL)); WAIT_UNTIL (received != NULL); g_assert (g_bytes_equal (sent, received)); + g_bytes_unref (received); + received = NULL; + + /* Reverse the test and send the big message to the server. */ + g_signal_handler_disconnect (test->client, signal_id); + g_signal_connect (test->server, "message", G_CALLBACK (on_text_message), &received); + + soup_websocket_connection_send_text (test->client, g_bytes_get_data (sent, NULL)); + WAIT_UNTIL (received != NULL); + g_assert_true (g_bytes_equal (sent, received)); g_bytes_unref (sent); g_bytes_unref (received); } +static void +test_send_big_packets_direct (Test *test, + gconstpointer data) +{ + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->client), ==, 128 * 1024); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->client), ==, 0); + + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->server), ==, 128 * 1024); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->server), ==, 0); + + test_send_big_packets (test, data); +} + +static void +test_send_big_packets_soup (Test *test, + gconstpointer data) +{ + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->client), ==, 128 * 1024); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->client), ==, 0); + + /* Max total message size defaults to 0 (unlimited), but SoupServer applies its own limit by default. */ + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->server), ==, 128 * 1024); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->server), ==, 128 * 1024); + + test_send_big_packets (test, data); +} + +static void +test_send_exceeding_client_max_payload_size (Test *test, + gconstpointer data) +{ + GBytes *sent = NULL; + GBytes *received = NULL; + gboolean close_event = FALSE; + GError *error = NULL; + + g_signal_connect (test->server, "error", G_CALLBACK (on_error_copy), &error); + g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event); + + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->client), ==, 128 * 1024); + + soup_websocket_connection_set_max_incoming_payload_size (test->server, 0); + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->server), ==, 0); + + /* The message to the client is dropped due to the client's limit. */ + sent = g_bytes_new_take (g_strnfill (1000 * 1000, '?'), 1000 * 1000); + soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL)); + g_bytes_unref (sent); + WAIT_UNTIL (close_event); + g_assert_null (received); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED); + g_assert_no_error (test->client_error); +} + +static void +test_send_exceeding_server_max_payload_size (Test *test, + gconstpointer data) +{ + GBytes *sent = NULL; + GBytes *received = NULL; + gboolean close_event = FALSE; + GError *error = NULL; + + g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error); + g_signal_connect (test->server, "closed", G_CALLBACK (on_close_set_flag), &close_event); + + soup_websocket_connection_set_max_incoming_payload_size (test->client, 0); + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->client), ==, 0); + + g_assert_cmpuint (soup_websocket_connection_get_max_incoming_payload_size (test->server), ==, 128 * 1024); + + /* The message to the server is dropped due to the server's limit. */ + sent = g_bytes_new_take (g_strnfill (1000 * 1000, '?'), 1000 * 1000); + soup_websocket_connection_send_text (test->client, g_bytes_get_data (sent, NULL)); + g_bytes_unref (sent); + WAIT_UNTIL (close_event); + g_assert_null (received); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED); + g_assert_no_error (test->client_error); +} + +static void +test_send_exceeding_client_max_message_size (Test *test, + gconstpointer data) +{ + GBytes *sent = NULL; + GBytes *received = NULL; + gboolean close_event = FALSE; + GError *error = NULL; + + g_signal_connect (test->server, "error", G_CALLBACK (on_error_copy), &error); + g_signal_connect (test->client, "closed", G_CALLBACK (on_close_set_flag), &close_event); + + soup_websocket_connection_set_max_total_message_size (test->client, 128 * 1024); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->client), ==, 128 * 1024); + + soup_websocket_connection_set_max_total_message_size (test->server, 0); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->server), ==, 0); + + /* The message to the client is dropped due to the client's limit. */ + sent = g_bytes_new_take (g_strnfill (1000 * 1000, '?'), 1000 * 1000); + soup_websocket_connection_send_text (test->server, g_bytes_get_data (sent, NULL)); + g_bytes_unref (sent); + WAIT_UNTIL (close_event); + g_assert_null (received); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED); + g_assert_no_error (test->client_error); +} + +static void +test_send_exceeding_server_max_message_size (Test *test, + gconstpointer data) +{ + GBytes *sent = NULL; + GBytes *received = NULL; + gboolean close_event = FALSE; + GError *error = NULL; + + g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error); + g_signal_connect (test->server, "closed", G_CALLBACK (on_close_set_flag), &close_event); + + soup_websocket_connection_set_max_total_message_size (test->client, 0); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->client), ==, 0); + + /* Set the server message total message size manually, because its + * default is different for direct connection vs. soup connection. + */ + soup_websocket_connection_set_max_total_message_size (test->server, 128 * 1024); + g_assert_cmpuint (soup_websocket_connection_get_max_total_message_size (test->server), ==, 128 * 1024); + + /* The message to the server is dropped due to the server's limit. */ + sent = g_bytes_new_take (g_strnfill (1000 * 1000, '?'), 1000 * 1000); + soup_websocket_connection_send_text (test->client, g_bytes_get_data (sent, NULL)); + g_bytes_unref (sent); + WAIT_UNTIL (close_event); + g_assert_null (received); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED); + g_assert_no_error (test->client_error); +} + + static void test_send_empty_packets (Test *test, gconstpointer data) @@ -2064,11 +2208,47 @@ main (int argc, g_test_add ("/websocket/direct/send-big-packets", Test, NULL, setup_direct_connection, - test_send_big_packets, + test_send_big_packets_direct, teardown_direct_connection); g_test_add ("/websocket/soup/send-big-packets", Test, NULL, setup_soup_connection, - test_send_big_packets, + test_send_big_packets_soup, + teardown_soup_connection); + + g_test_add ("/websocket/direct/send-exceeding-client-max-payload-size", Test, NULL, + setup_direct_connection, + test_send_exceeding_client_max_payload_size, + teardown_direct_connection); + g_test_add ("/websocket/soup/send-exceeding-client-max-payload-size", Test, NULL, + setup_soup_connection, + test_send_exceeding_client_max_payload_size, + teardown_soup_connection); + + g_test_add ("/websocket/direct/send-exceeding-server-max-payload-size", Test, NULL, + setup_direct_connection, + test_send_exceeding_server_max_payload_size, + teardown_direct_connection); + g_test_add ("/websocket/soup/send-exceeding-server-max-payload-size", Test, NULL, + setup_soup_connection, + test_send_exceeding_server_max_payload_size, + teardown_soup_connection); + + g_test_add ("/websocket/direct/send-exceeding-client-max-message-size", Test, NULL, + setup_direct_connection, + test_send_exceeding_client_max_message_size, + teardown_direct_connection); + g_test_add ("/websocket/soup/send-exceeding-client-max-message-size", Test, NULL, + setup_soup_connection, + test_send_exceeding_client_max_message_size, + teardown_soup_connection); + + g_test_add ("/websocket/direct/send-exceeding-server-max-message-size", Test, NULL, + setup_direct_connection, + test_send_exceeding_server_max_message_size, + teardown_direct_connection); + g_test_add ("/websocket/soup/send-exceeding-server-max-message-size", Test, NULL, + setup_soup_connection, + test_send_exceeding_server_max_message_size, teardown_soup_connection); g_test_add ("/websocket/direct/send-empty-packets", Test, NULL, @@ -2217,11 +2397,11 @@ main (int argc, g_test_add ("/websocket/direct/deflate-send-big-packets", Test, NULL, setup_direct_connection_with_extensions, - test_send_big_packets, + test_send_big_packets_direct, teardown_direct_connection); g_test_add ("/websocket/soup/deflate-send-big-packets", Test, NULL, setup_soup_connection_with_extensions, - test_send_big_packets, + test_send_big_packets_soup, teardown_soup_connection); g_test_add ("/websocket/direct/deflate-send-empty-packets", Test, NULL, -- 2.34.1