File libsoup-CVE-2026-0716.patch of Package libsoup
From f15b82082e5c885bdf315921a3925bcbf1905190 Mon Sep 17 00:00:00 2001
From: Mike Gorse <mgorse@suse.com>
Date: Tue, 3 Mar 2026 21:39:35 -0600
Subject: [PATCH] websocket: Fix out-of-bounds read when reading unmasked frame
The original fix for CVE-2026-0716 was incomplete; the same out-of-bounds
read can occur if a server sends a malicious unmasked frame to the client.
Closes #476
---
libsoup/websocket/soup-websocket-connection.c | 12 ++--
tests/websocket-test.c | 61 +++++++++++++++++--
2 files changed, 62 insertions(+), 11 deletions(-)
diff --git a/libsoup/websocket/soup-websocket-connection.c b/libsoup/websocket/soup-websocket-connection.c
index 04837646..2f031f1d 100644
--- a/libsoup/websocket/soup-websocket-connection.c
+++ b/libsoup/websocket/soup-websocket-connection.c
@@ -1146,17 +1146,17 @@ process_frame (SoupWebsocketConnection *self)
payload = header + at;
+ /* at has a maximum value of 10 + 4 = 14 */
+ if (payload_len > G_MAXSIZE - 14) {
+ bad_data_error_and_close (self);
+ return FALSE;
+ }
+
if (masked) {
mask = header + at;
payload += 4;
at += 4;
- /* at has a maximum value of 10 + 4 = 14 */
- if (payload_len > G_MAXSIZE - 14) {
- bad_data_error_and_close (self);
- return FALSE;
- }
-
if (len < at + payload_len)
return FALSE; /* need more data */
diff --git a/tests/websocket-test.c b/tests/websocket-test.c
index 67b9e49d..d48a9694 100644
--- a/tests/websocket-test.c
+++ b/tests/websocket-test.c
@@ -2392,7 +2392,7 @@ test_fragment_assembly_corruption (Test *test, gconstpointer data)
}
static void
-test_cve_2026_0716 (Test *test,
+test_bad_length_masked (Test *test,
gconstpointer unused)
{
GError *error = NULL;
@@ -2426,6 +2426,53 @@ test_cve_2026_0716 (Test *test,
g_assert_cmpuint (soup_websocket_connection_get_close_code (test->client), ==, SOUP_WEBSOCKET_CLOSE_BAD_DATA);
}
+static gpointer
+send_bad_length_frame_server_thread (gpointer user_data)
+{
+ Test *test = user_data;
+ const char frame[] = "\x82\x7f\xff\xff\xff\xff\xff\xff\xff\xf6";
+ gsize written;
+ GError *error = NULL;
+
+ g_output_stream_write_all (g_io_stream_get_output_stream (test->raw_server),
+ frame, sizeof (frame), &written, NULL, &error);
+ g_assert_no_error (error);
+ g_assert_cmpuint (written, ==, sizeof (frame));
+
+ g_io_stream_close (test->raw_server, NULL, &error);
+ g_assert_no_error (error);
+
+ return NULL;
+}
+
+static void
+test_bad_length_unmasked (Test *test,
+ gconstpointer unused)
+{
+ GThread *thread;
+ GBytes *received = NULL;
+ GError *error = NULL;
+
+ g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error);
+ g_signal_connect (test->client, "message", G_CALLBACK (on_binary_message), &received);
+
+ soup_websocket_connection_set_max_incoming_payload_size (test->client, 0);
+
+ thread = g_thread_new ("send-bad-length-frame-thread", send_bad_length_frame_server_thread, test);
+
+ WAIT_UNTIL (error != NULL || received != NULL);
+ g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_BAD_DATA);
+ g_clear_error (&error);
+ g_assert_null (received);
+
+ /* it can emit more errors while joining the thread, thus disconnect, to avoid memory leak */
+ g_signal_handlers_disconnect_by_func (test->client, G_CALLBACK (on_error_copy), &error);
+
+ g_thread_join (thread);
+
+ WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED);
+}
+
int
main (int argc,
char *argv[])
@@ -2717,14 +2764,18 @@ main (int argc,
test_fragment_assembly_corruption,
teardown_direct_connection);
- g_test_add ("/websocket/direct/cve-2026-0716", Test, NULL,
+ g_test_add ("/websocket/direct/bad-length-masked", Test, NULL,
setup_direct_connection,
- test_cve_2026_0716,
+ test_bad_length_masked,
teardown_direct_connection);
- g_test_add ("/websocket/soup/cve-2026-0716", Test, NULL,
+ g_test_add ("/websocket/soup/bad-length-masked", Test, NULL,
setup_soup_connection,
- test_cve_2026_0716,
+ test_bad_length_masked,
teardown_soup_connection);
+ g_test_add ("/websocket/direct/bad-length-unmasked", Test, NULL,
+ setup_half_direct_connection,
+ test_bad_length_unmasked,
+ teardown_direct_connection);
ret = g_test_run ();
--
GitLab