Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libsoup2 for openSUSE:Factory checked in at 2026-10-08 15:12:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libsoup2 (Old) and /work/SRC/openSUSE:Factory/.libsoup2.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libsoup2" Thu Oct 8 15:12:47 2026 rev:28 rq:1382958 version:2.74.3 Changes: -------- --- /work/SRC/openSUSE:Factory/libsoup2/libsoup2.changes 2026-09-23 14:33:06.452714103 +0200 +++ /work/SRC/openSUSE:Factory/.libsoup2.new.3698736/libsoup2.changes 2026-10-08 15:12:54.985568735 +0200 @@ -1,0 +2,34 @@ +Tue Oct 6 19:55:28 UTC 2026 - Michael Gorse <[email protected]> + +- CVE-2026-15711: WebSocket denial of service via oversized control + frame protocol violation (bsc#1271446) + * libsoup2-CVE-2026-15711.patch +- CVE-2026-15714: Out-of-bounds read in + soup_multipart_input_stream_read_headers via an oversized + multipart boundary string (bsc#1271449) + * libsoup2-CVE-2026-15714.patch +- CVE-2026-66339: proxy credentials leak to destination server via + `Proxy-Authorization` header in CONNECT tunnels (bsc#1273156) + * libsoup2-CVE-2026-66339.patch +- CVE-2026-12547: Header "Proxy-Authorization" leads to credentials + being send to different proxy after proxy switch (bsc#1272195) + * libsoup2-CVE-2026-12547.patch +- CVE-2026-102558: Heap buffer overflow during WebSocket + receive-buffer growth (bsc#1283302) + * libsoup2-CVE-2026-102558.patch +- CVE-2026-102556: Heap buffer overflow from WebSocket Pong signal + type confusion (bsc#1283300) + * libsoup2-CVE-2026-102556.patch +- CVE-2026-102560: Heap buffer overflow during outgoing + permessage-deflate buffer growth (bsc#1283304) + * libsoup2-CVE-2026-102560.patch +- CVE-2026-102557: Heap buffer overflow during WebSocket message + reassembly (bsc#1283301) +- CVE-2026-102559: Heap buffer overflow during WebSocket + client-frame masking (bsc#1283303) + * libsoup2-CVE-2026-102557.patch +- Tweak libsoup2-CVE-2026-12548.patch and + libsoup2-CVE-2026-2708.patch to remove superfluous initializer + values in the tests. + +------------------------------------------------------------------- @@ -41 +75 @@ - (bsc#1256418 CVE-2026-0716 glgo#GNOME/libsoup#476). + (bsc#1271401 CVE-2026-12478 glgo#GNOME/libsoup#476). New: ---- libsoup2-CVE-2026-102556.patch libsoup2-CVE-2026-102557.patch libsoup2-CVE-2026-102558.patch libsoup2-CVE-2026-102560.patch libsoup2-CVE-2026-12547.patch libsoup2-CVE-2026-15711.patch libsoup2-CVE-2026-15714.patch libsoup2-CVE-2026-66339.patch ----------(New B)---------- New: type confusion (bsc#1283300) * libsoup2-CVE-2026-102556.patch - CVE-2026-102560: Heap buffer overflow during outgoing New: client-frame masking (bsc#1283303) * libsoup2-CVE-2026-102557.patch - Tweak libsoup2-CVE-2026-12548.patch and New: receive-buffer growth (bsc#1283302) * libsoup2-CVE-2026-102558.patch - CVE-2026-102556: Heap buffer overflow from WebSocket Pong signal New: permessage-deflate buffer growth (bsc#1283304) * libsoup2-CVE-2026-102560.patch - CVE-2026-102557: Heap buffer overflow during WebSocket message New: being send to different proxy after proxy switch (bsc#1272195) * libsoup2-CVE-2026-12547.patch - CVE-2026-102558: Heap buffer overflow during WebSocket New: frame protocol violation (bsc#1271446) * libsoup2-CVE-2026-15711.patch - CVE-2026-15714: Out-of-bounds read in New: multipart boundary string (bsc#1271449) * libsoup2-CVE-2026-15714.patch - CVE-2026-66339: proxy credentials leak to destination server via New: `Proxy-Authorization` header in CONNECT tunnels (bsc#1273156) * libsoup2-CVE-2026-66339.patch - CVE-2026-12547: Header "Proxy-Authorization" leads to credentials ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libsoup2.spec ++++++ --- /var/tmp/diff_new_pack.pFOzxP/_old 2026-10-08 15:12:57.610678741 +0200 +++ /var/tmp/diff_new_pack.pFOzxP/_new 2026-10-08 15:12:57.612678825 +0200 @@ -110,8 +110,24 @@ Patch41: libsoup2-CVE-2026-1801.patch # PATCH-FIX-UPSTREAM libsoup2-CVE-2026-12548.patch bsc#1272196, glgo#GNOME/libsoup!524 [email protected] -- Fix heap out-of-bounds read flaw when parsing multipart HTTP messages Patch42: libsoup2-CVE-2026-12548.patch -# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-77680.patch bsc#1276759, bsc#1275988, glgo#GNOME/libsoup!550 [email protected] -- Fix flaw in HTTP Range heaader processing +# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-77680.patch bsc#1276759, bsc#1275988, glgo#GNOME/libsoup!550 [email protected] -- Fix flaw in HTTP Range header processing Patch43: libsoup2-CVE-2026-77680.patch +# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-15711.patch bsc#1271446 [email protected] -- websocket: Reject oversized control frames. +Patch44: libsoup2-CVE-2026-15711.patch +# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-15714.patch bsc#1271449 [email protected] -- multipart: Fix out-of-bounds read in case of an excessively. +Patch45: libsoup2-CVE-2026-15714.patch +# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-66339.patch bsc#1273156 [email protected] -- auth: Don't send Proxy-Authorization through an established. +Patch46: libsoup2-CVE-2026-66339.patch +# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-12547.patch bsc#1272195 [email protected] -- auth: Scope cached proxy credentials to the proxy they came. +Patch47: libsoup2-CVE-2026-12547.patch +# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-102558.patch bsc#1283302 [email protected] -- websocket: Cap incoming frame payloads at what the buffer can hold. +Patch48: libsoup2-CVE-2026-102558.patch +# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-102556.patch bsc#1283300 [email protected] -- websocket: Emit the pong signal with a GBytes as declared. +Patch49: libsoup2-CVE-2026-102556.patch +# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-102560.patch bsc#1283304 [email protected] -- websocket: Bound outgoing permessage-deflate buffer growth. +Patch50: libsoup2-CVE-2026-102560.patch +# PATCH-FIX-UPSTREAM libsoup2-CVE-2026-102557.patch bsc#1283301 bsc#1283303 [email protected] -- websocket: Cap message reassembly and outgoing frames at the buffer limit. +Patch51: libsoup2-CVE-2026-102557.patch BuildRequires: glib-networking BuildRequires: meson >= 0.50 ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.pFOzxP/_old 2026-10-08 15:12:57.678681591 +0200 +++ /var/tmp/diff_new_pack.pFOzxP/_new 2026-10-08 15:12:57.681681717 +0200 @@ -1,7 +1,7 @@ -mtime: 1789997740 -commit: 987683895276916fd7ff1ac2f6f74d00e739b13313153be3eaff48c5dd08508e +mtime: 1791317579 +commit: a5c48ee5831de2c24c1bb6b38d0f95fdac1261e3f937831a16628a969b3d8149 url: https://src.opensuse.org/GNOME/libsoup2 -revision: 987683895276916fd7ff1ac2f6f74d00e739b13313153be3eaff48c5dd08508e +revision: a5c48ee5831de2c24c1bb6b38d0f95fdac1261e3f937831a16628a969b3d8149 trackingbranch: factory projectscmsync: https://src.opensuse.org/GNOME/_ObsPrj ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-10-06 22:12:59.000000000 +0200 @@ -0,0 +1,4 @@ +*.obscpio +*.osc +_build.* +.pbuild ++++++ libsoup2-CVE-2026-102556.patch ++++++ >From d2cbaf25f81a9ce041714cf842c366c67b0b873e Mon Sep 17 00:00:00 2001 From: Patrick Griffis <[email protected]> Date: Fri, 4 Sep 2026 14:28:14 -0500 Subject: [PATCH] websocket: Emit the pong signal with a GBytes as declared Fixes #554 --- libsoup/websocket/soup-websocket-connection.c | 7 ++++--- tests/websocket-test.c | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff -urp libsoup-2.74.3.orig/libsoup/soup-websocket-connection.c libsoup-2.74.3/libsoup/soup-websocket-connection.c --- libsoup-2.74.3.orig/libsoup/soup-websocket-connection.c 2026-10-05 13:35:09.601629971 -0500 +++ libsoup-2.74.3/libsoup/soup-websocket-connection.c 2026-10-05 13:35:46.680248564 -0500 @@ -830,6 +830,7 @@ receive_pong (SoupWebsocketConnection *s gsize len) { GByteArray *bytes; + GBytes *payload; g_debug ("received pong message"); @@ -840,9 +841,9 @@ receive_pong (SoupWebsocketConnection *s /* But don't include the null terminator in the byte count */ bytes->len--; - g_signal_emit (self, signals[PONG], 0, bytes); - g_byte_array_unref (bytes); - + payload = g_byte_array_free_to_bytes (bytes); + g_signal_emit (self, signals[PONG], 0, payload); + g_bytes_unref (payload); } static void ++++++ libsoup2-CVE-2026-102557.patch ++++++ >From d7f074f8c8c90216f2476522836287c6f80fc53e Mon Sep 17 00:00:00 2001 From: Patrick Griffis <[email protected]> Date: Fri, 4 Sep 2026 14:34:38 -0500 Subject: [PATCH] websocket: Cap message reassembly and outgoing frames at the buffer limit Both a reassembled message and an outgoing frame are built in GByteArrays, whose length is a guint. A message grown past G_MAXINT by a run of fragments (or by an extension that expands its input) and a send payload at or above 2^32 both truncated the size passed to g_byte_array_set_size() / g_byte_array_sized_new() while the surrounding code kept using the full length, corrupting the heap. Reject a reassembled message that would exceed what the array can hold before appending, independently of max-total-message-size, and reject an outgoing payload above the same ceiling both before and after extensions have processed it. Fixes #554 --- libsoup/websocket/soup-websocket-connection.c | 54 +++++++ po/POTFILES.in | 1 + tests/websocket-test.c | 141 ++++++++++++++++++ 3 files changed, 196 insertions(+) Index: libsoup-2.74.3/libsoup/soup-websocket-connection.c =================================================================== --- libsoup-2.74.3.orig/libsoup/soup-websocket-connection.c +++ libsoup-2.74.3/libsoup/soup-websocket-connection.c @@ -22,6 +22,8 @@ #include <string.h> +#include <glib/gi18n-lib.h> + #include "soup-websocket-connection.h" #include "soup-enum-types.h" #include "soup-io-stream.h" @@ -162,6 +164,16 @@ struct _SoupWebsocketConnectionPrivate { */ #define MAX_INCOMING_PAYLOAD_SIZE ((guint64)G_MAXINT - MAX_FRAME_HEADER_SIZE - READ_BUFFER_SIZE) +/* Likewise for a reassembled message and for an outgoing frame, which are + * also built in GByteArrays. Extensions may expand a frame's payload, so the + * incoming cap above does not bound these on its own. + */ +#define MAX_MESSAGE_SIZE ((guint64)G_MAXINT - 1) +#define MAX_OUTGOING_PAYLOAD_SIZE ((gsize)G_MAXINT - MAX_FRAME_HEADER_SIZE) + +static void too_big_outgoing_payload_error_and_close (SoupWebsocketConnection *self, + gsize len); + G_DEFINE_TYPE_WITH_PRIVATE (SoupWebsocketConnection, soup_websocket_connection, G_TYPE_OBJECT) static void queue_frame (SoupWebsocketConnection *self, SoupWebsocketQueueFlags flags, @@ -503,6 +515,11 @@ send_message (SoupWebsocketConnection *s return; } + if (length > MAX_OUTGOING_PAYLOAD_SIZE) { + too_big_outgoing_payload_error_and_close (self, length); + return; + } + bytes = g_byte_array_sized_new (14 + length); outer = bytes->data; outer[0] = 0x80 | opcode; @@ -523,6 +540,14 @@ send_message (SoupWebsocketConnection *s data = g_bytes_get_data (filtered_bytes, &length); buffered_amount = length; + /* Extensions may have grown the payload */ + if (length > MAX_OUTGOING_PAYLOAD_SIZE) { + g_byte_array_free (bytes, TRUE); + g_bytes_unref (filtered_bytes); + too_big_outgoing_payload_error_and_close (self, length); + return; + } + /* If control message, check payload size */ if (opcode & 0x08) { if (length > 125) { @@ -703,6 +728,20 @@ too_big_error_and_close (SoupWebsocketCo } static void +too_big_outgoing_payload_error_and_close (SoupWebsocketConnection *self, + gsize len) +{ + GError *error; + + error = g_error_new_literal (SOUP_WEBSOCKET_ERROR, + SOUP_WEBSOCKET_CLOSE_TOO_BIG, + _("WebSocket message is too large to send")); + g_debug ("attempted to send a frame of size %" G_GSIZE_FORMAT ", but max supported size is %" G_GSIZE_FORMAT, + len, MAX_OUTGOING_PAYLOAD_SIZE); + emit_error_and_close (self, error, FALSE); +} + +static void close_connection (SoupWebsocketConnection *self, gushort code, const char *data) @@ -928,6 +967,21 @@ process_contents (SoupWebsocketConnectio g_debug ("received frame %d with %d payload", (int)opcode, (int)payload_len); } + /* Regardless of max-total-message-size, the reassembled message + * must fit in a GByteArray. Check before allocating, since + * g_byte_array_sized_new() takes a guint too. + */ + if (payload_len > MAX_MESSAGE_SIZE - (pv->message_data ? pv->message_data->len : 0)) { + guint64 message_size = pv->message_data ? pv->message_data->len : 0; + + if (payload_len > G_MAXUINT64 - message_size) + message_size = G_MAXUINT64; + else + message_size += payload_len; + too_big_error_and_close (self, message_size); + return; + } + if (opcode) { pv->message_opcode = opcode; pv->message_data = g_byte_array_sized_new (payload_len + 1); Index: libsoup-2.74.3/po/POTFILES.in =================================================================== --- libsoup-2.74.3.orig/po/POTFILES.in +++ libsoup-2.74.3/po/POTFILES.in @@ -10,4 +10,5 @@ libsoup/soup-server.c libsoup/soup-session.c libsoup/soup-socket.c libsoup/soup-websocket.c +libsoup/soup-websocket-connection.c libsoup/soup-tld.c Index: libsoup-2.74.3/tests/websocket-test.c =================================================================== --- libsoup-2.74.3.orig/tests/websocket-test.c +++ libsoup-2.74.3/tests/websocket-test.c @@ -21,6 +21,9 @@ #include "test-utils.h" #include <zlib.h> +#ifdef G_OS_UNIX +#include <sys/mman.h> +#endif typedef struct { GSocket *listener; @@ -38,6 +41,7 @@ typedef struct { GIOStream *raw_server; gboolean enable_extensions; + GType server_extension_type; gboolean disable_deflate_in_message; GList *initial_cookies; @@ -163,6 +167,8 @@ got_connection (GSocket *listener, NULL, NULL)); extensions = g_list_prepend (extensions, extension); } + if (test->server_extension_type) + extensions = g_list_prepend (extensions, g_object_new (test->server_extension_type, NULL)); test->server = soup_websocket_connection_new_with_extensions (G_IO_STREAM (conn), uri, SOUP_WEBSOCKET_CONNECTION_SERVER, NULL, NULL, @@ -2080,6 +2086,50 @@ test_bad_length_unmasked (Test *test, WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); } +/* Sending a payload larger than a GByteArray can hold. The frame is built in + * a GByteArray, so g_byte_array_sized_new() and g_byte_array_append() used to + * truncate the length while xor_with_mask() still iterated the full length, + * writing past the small allocation. The mapping is never read when the size + * is rejected up front, so MAP_NORESERVE keeps this cheap. + */ +static void +test_send_over_buffer_limit (Test *test, + gconstpointer unused) +{ +#ifdef G_OS_UNIX + GError *error = NULL; + gsize length = (gsize)G_MAXINT + 1; + void *payload; + + if (sizeof (gsize) < 8) { + g_test_skip ("needs a 64-bit gsize"); + return; + } + + payload = mmap (NULL, length, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + if (payload == MAP_FAILED) { + g_test_skip ("could not map a 2 GiB region"); + return; + } + + g_signal_handlers_disconnect_by_func (test->client, on_error_not_reached, NULL); + g_signal_connect (test->client, "error", G_CALLBACK (on_error_copy), &error); + + soup_websocket_connection_send_binary (test->client, payload, length); + + wait_for_websocket_error (&error); + g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_TOO_BIG); + g_clear_error (&error); + + WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); + g_assert_cmpuint (soup_websocket_connection_get_close_code (test->client), ==, SOUP_WEBSOCKET_CLOSE_TOO_BIG); + + munmap (payload, length); +#else + g_test_skip ("needs mmap"); +#endif +} + /* A declared payload length that fits in a gsize, * but that the GByteArray-backed incoming buffer can never * hold. With no configured max-incoming-payload-size the receiver used to @@ -2383,6 +2433,10 @@ main (int argc, setup_direct_connection, test_payload_over_buffer_limit, teardown_direct_connection); + g_test_add ("/websocket/direct/send-over-buffer-limit", Test, NULL, + setup_direct_connection, + test_send_over_buffer_limit, + teardown_direct_connection); g_test_add ("/websocket/soup/bad-length-masked", Test, NULL, setup_soup_connection, test_bad_length_masked, ++++++ libsoup2-CVE-2026-102558.patch ++++++ >From 381a474ff437d0f7573d2bb3e8d3027260ab584f Mon Sep 17 00:00:00 2001 From: Patrick Griffis <[email protected]> Date: Fri, 4 Sep 2026 14:26:44 -0500 Subject: [PATCH] websocket: Cap incoming frame payloads at what the buffer can hold Incoming frames are accumulated in a GByteArray, whose length is a guint and whose growth is not safe beyond G_MAXINT on all supported GLib versions. With max-incoming-payload-size set to 0 (unlimited) a peer could declare a payload of a few GiB and stream it, and the buffer length would eventually wrap in g_byte_array_set_size() while the read still targeted the original offset, writing past the allocation. Reject any payload length above what the buffer can represent as soon as the frame header is parsed, before waiting for the payload. Doing this check first also means the at + payload_len arithmetic can no longer overflow, so the separate G_MAXSIZE guard that previously ran after that addition is no longer needed. Such frames are now reported as SOUP_WEBSOCKET_CLOSE_TOO_BIG rather than SOUP_WEBSOCKET_CLOSE_BAD_DATA, and, as for exceeding the configured limit, the stream is closed without a close handshake so that no further data is buffered. Fixes #553 Fixes #554 (bug 6) --- Index: libsoup-2.74.3/libsoup/soup-websocket-connection.c =================================================================== --- libsoup-2.74.3.orig/libsoup/soup-websocket-connection.c +++ libsoup-2.74.3/libsoup/soup-websocket-connection.c @@ -154,6 +154,13 @@ struct _SoupWebsocketConnectionPrivate { #define MAX_INCOMING_PAYLOAD_SIZE_DEFAULT 128 * 1024 #define READ_BUFFER_SIZE 1024 #define MASK_LENGTH 4 +#define MAX_FRAME_HEADER_SIZE (10 + MASK_LENGTH) + +/* Incoming frames are buffered in a GByteArray, which uses guint lengths and + * whose growth is not safe beyond G_MAXINT on all supported GLib versions. + * Leave room for the largest possible frame header and one extra read. + */ +#define MAX_INCOMING_PAYLOAD_SIZE ((guint64)G_MAXINT - MAX_FRAME_HEADER_SIZE - READ_BUFFER_SIZE) G_DEFINE_TYPE_WITH_PRIVATE (SoupWebsocketConnection, soup_websocket_connection, G_TYPE_OBJECT) @@ -1074,9 +1081,14 @@ process_frame (SoupWebsocketConnection * break; } - /* Safety valve */ - if (self->pv->max_incoming_payload_size > 0 && - payload_len >= self->pv->max_incoming_payload_size) { + /* Safety valve. Even with no configured limit, the frame can never + * exceed what the incoming buffer can hold. Checking this before any + * arithmetic on payload_len also guarantees at + payload_len cannot + * overflow below. + */ + if (payload_len > MAX_INCOMING_PAYLOAD_SIZE || + (self->pv->max_incoming_payload_size > 0 && + payload_len > self->pv->max_incoming_payload_size)) { too_big_error_and_close (self, payload_len); return FALSE; } @@ -1086,12 +1098,6 @@ process_frame (SoupWebsocketConnection * 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; Index: libsoup-2.74.3/tests/websocket-test.c =================================================================== --- libsoup-2.74.3.orig/tests/websocket-test.c +++ libsoup-2.74.3/tests/websocket-test.c @@ -1983,13 +1983,12 @@ test_bad_length_masked (Test *test, g_assert_cmpuint (written, ==, 14); WAIT_UNTIL (error != NULL); - g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_BAD_DATA); + g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_TOO_BIG); g_clear_error (&error); + /* The receiver closes the stream immediately rather than negotiating a close */ WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); g_assert_true (close_event); - - g_assert_cmpuint (soup_websocket_connection_get_close_code (test->client), ==, SOUP_WEBSOCKET_CLOSE_BAD_DATA); } static gpointer @@ -2027,7 +2026,7 @@ test_bad_length_unmasked (Test *test, 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_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_TOO_BIG); g_clear_error (&error); g_assert_null (received); @@ -2039,6 +2038,44 @@ test_bad_length_unmasked (Test *test, WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); } +/* A declared payload length that fits in a gsize, + * but that the GByteArray-backed incoming buffer can never + * hold. With no configured max-incoming-payload-size the receiver used to + * keep buffering until the array length wrapped. + */ +static void +test_payload_over_buffer_limit (Test *test, + gconstpointer unused) +{ + GError *error = NULL; + GIOStream *io; + gsize written; + /* Masked binary frame declaring a 2 GiB payload (10-byte header + 4-byte mask) */ + const char frame[] = "\x82\xff\x00\x00\x00\x00\x80\x00\x00\x00\xaa\xbb\xcc\xdd"; + gboolean close_event = FALSE; + + g_signal_handlers_disconnect_by_func (test->server, on_error_not_reached, 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_incoming_payload_size (test->server, 0); + + io = soup_websocket_connection_get_io_stream (test->client); + g_output_stream_write_all (g_io_stream_get_output_stream (io), + frame, sizeof (frame) - 1, &written, NULL, &error); + g_assert_no_error (error); + g_assert_cmpuint (written, ==, sizeof (frame) - 1); + + /* The error must be reported from the header alone, without waiting for payload */ + wait_for_websocket_error (&error); + g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_TOO_BIG); + g_clear_error (&error); + + /* The receiver closes the stream immediately rather than negotiating a close */ + WAIT_UNTIL (soup_websocket_connection_get_state (test->client) == SOUP_WEBSOCKET_STATE_CLOSED); + g_assert_true (close_event); +} + int main (int argc, char *argv[]) @@ -2297,6 +2334,10 @@ main (int argc, setup_direct_connection, test_bad_length_masked, teardown_direct_connection); + g_test_add ("/websocket/direct/payload-over-buffer-limit", Test, NULL, + setup_direct_connection, + test_payload_over_buffer_limit, + teardown_direct_connection); g_test_add ("/websocket/soup/bad-length-masked", Test, NULL, setup_soup_connection, test_bad_length_masked, ++++++ libsoup2-CVE-2026-102560.patch ++++++ >From dbd0417ea3a569135e9a9344b7812076b7b178f1 Mon Sep 17 00:00:00 2001 From: Patrick Griffis <[email protected]> Date: Fri, 4 Sep 2026 14:30:58 -0500 Subject: [PATCH] websocket: Bound outgoing permessage-deflate buffer growth The outgoing deflater stored deflateBound()'s uLong result in a guint and grew its GByteArray by that amount. For a payload approaching 4 GiB the bound exceeds G_MAXUINT, the truncated value let buffer->len + max_length wrap on a later growth, and zlib then wrote through next_out past the allocation. Refuse payloads whose compressed bound cannot fit in a GByteArray before compressing, and check each incremental growth as well. Fixes #554 --- .../soup-websocket-extension-deflate.c | 24 +++++++++- tests/websocket-test.c | 44 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff -urp libsoup-2.74.3.orig/libsoup/soup-websocket-extension-deflate.c libsoup-2.74.3/libsoup/soup-websocket-extension-deflate.c --- libsoup-2.74.3.orig/libsoup/soup-websocket-extension-deflate.c 2022-10-11 13:27:22.000000000 -0500 +++ libsoup-2.74.3/libsoup/soup-websocket-extension-deflate.c 2026-10-05 13:37:27.434343591 -0500 @@ -292,6 +292,7 @@ soup_websocket_extension_deflate_process { const guint8 *payload_data; gsize payload_length; + unsigned long bound; guint max_length; gboolean control; GByteArray *buffer; @@ -315,11 +316,21 @@ soup_websocket_extension_deflate_process if (payload_length == 0) return payload; + bound = deflateBound (&priv->deflater.zstream, payload_length); + if (bound > G_MAXUINT) { + g_set_error_literal (error, + SOUP_WEBSOCKET_ERROR, + SOUP_WEBSOCKET_CLOSE_TOO_BIG, + "Outgoing WebSocket message is too large to compress"); + g_bytes_unref (payload); + return NULL; + } + /* Mark the frame as compressed using reserved bit 1 (0x40) */ header[0] |= 0x40; buffer = g_byte_array_new (); - max_length = deflateBound(&priv->deflater.zstream, payload_length); + max_length = (guint)bound; priv->deflater.zstream.next_in = (void *)payload_data; priv->deflater.zstream.avail_in = payload_length; @@ -333,6 +344,17 @@ soup_websocket_extension_deflate_process if (priv->deflater.zstream.avail_out == 0) { guint write_position; + if (buffer->len > G_MAXUINT - max_length) { + deflateReset (&priv->deflater.zstream); + g_byte_array_unref (buffer); + g_bytes_unref (payload); + g_set_error_literal (error, + SOUP_WEBSOCKET_ERROR, + SOUP_WEBSOCKET_CLOSE_TOO_BIG, + "Compressed WebSocket message is too large"); + return NULL; + } + priv->deflater.zstream.avail_out = max_length; write_position = buffer->len; g_byte_array_set_size (buffer, buffer->len + max_length); diff -urp libsoup-2.74.3.orig/tests/websocket-test.c libsoup-2.74.3/tests/websocket-test.c --- libsoup-2.74.3.orig/tests/websocket-test.c 2026-10-05 13:35:09.602907626 -0500 +++ libsoup-2.74.3/tests/websocket-test.c 2026-10-05 13:37:27.434870030 -0500 @@ -1770,6 +1770,48 @@ test_deflate_negotiate_direct (Test *tes } static void +test_deflate_outgoing_too_large (void) +{ + SoupWebsocketExtension *extension; + GBytes *payload; + GBytes *output; + GError *error = NULL; + guint8 header = 0x82; + static const guint8 dummy[1] = { 0 }; + + if (sizeof (gsize) < 8) { + g_test_skip ("needs a 64-bit gsize"); + return; + } + + extension = g_object_new (SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE, NULL); + g_assert_true (soup_websocket_extension_configure (extension, + SOUP_WEBSOCKET_CONNECTION_CLIENT, + NULL, &error)); + g_assert_no_error (error); + + /* The payload data is never read, so a bogus pointer is enough */ + payload = g_bytes_new_static (dummy, (gsize)G_MAXUINT - 100); + output = soup_websocket_extension_process_outgoing_message (extension, &header, + payload, &error); + g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_TOO_BIG); + g_assert_null (output); + /* The frame must not have been marked as compressed */ + g_assert_cmpuint (header & 0x40, ==, 0); + g_clear_error (&error); + + /* The extension must still be usable afterwards */ + payload = g_bytes_new_static ("hello", 5); + output = soup_websocket_extension_process_outgoing_message (extension, &header, + payload, &error); + g_assert_no_error (error); + g_assert_nonnull (output); + g_bytes_unref (output); + + g_object_unref (extension); +} + +static void test_deflate_disabled_in_message_direct (Test *test, gconstpointer unused) { @@ -2260,6 +2302,9 @@ main (int argc, test_deflate_negotiate_direct, NULL); + g_test_add_func ("/websocket/deflate/outgoing-too-large", + test_deflate_outgoing_too_large); + g_test_add ("/websocket/direct/deflate-disabled-in-message", Test, NULL, NULL, test_deflate_disabled_in_message_direct, NULL); ++++++ libsoup2-CVE-2026-12547.patch ++++++ >From c683326d3b05b7b263a62f9fea62a801134755ba Mon Sep 17 00:00:00 2001 From: Patrick Griffis <[email protected]> Date: Fri, 4 Sep 2026 15:18:26 -0500 Subject: [PATCH] auth: Scope cached proxy credentials to the proxy they came from priv->proxy_auth cached a single SoupAuth with no record of which proxy it belonged to. After the session switched to a different proxy, the cached credentials were sent to the new proxy, leaking them across proxies (and, when the proxy configuration was removed entirely, to a direct connection). Remember the authority the cached proxy auth was obtained from and only reuse it for a message whose proxy authority matches. Adds a regression test that uses two SoupServers as proxy endpoints and fails without the fix. Fixes #506 (CVE-2026-12547) --- libsoup/auth/soup-auth-manager.c | 54 +++++++++- tests/meson.build | 1 + tests/proxy-auth-cache-test.c | 166 +++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 tests/proxy-auth-cache-test.c Index: libsoup-2.74.3/libsoup/soup-auth-manager.c =================================================================== --- libsoup-2.74.3.orig/libsoup/soup-auth-manager.c +++ libsoup-2.74.3/libsoup/soup-auth-manager.c @@ -69,6 +69,7 @@ struct SoupAuthManagerPrivate { GMutex lock; SoupAuth *proxy_auth; + char *proxy_auth_authority; GHashTable *auth_hosts; }; @@ -113,6 +114,7 @@ soup_auth_manager_finalize (GObject *obj g_hash_table_destroy (priv->auth_hosts); g_clear_object (&priv->proxy_auth); + g_clear_pointer (&priv->proxy_auth_authority, g_free); g_mutex_clear (&priv->lock); @@ -498,6 +500,36 @@ lookup_auth (SoupAuthManagerPrivate *pri return NULL; } +static char * +get_proxy_authority (SoupMessage *msg) +{ + SoupConnection *conn = soup_message_get_connection (msg); + SoupURI *proxy_uri; + char *authority = NULL; + + if (!conn) + return NULL; + + proxy_uri = soup_connection_get_proxy_uri (conn); + if (proxy_uri) + authority = g_strdup_printf ("%s:%d", soup_uri_get_host (proxy_uri), soup_uri_get_port (proxy_uri)); + + return authority; +} + +static gboolean +proxy_auth_matches_msg (SoupAuthManagerPrivate *priv, SoupMessage *msg) +{ + char *authority = get_proxy_authority (msg); + gboolean matches; + + matches = authority && priv->proxy_auth_authority && + g_strcmp0 (authority, priv->proxy_auth_authority) == 0; + g_free (authority); + + return matches; +} + static SoupAuth * lookup_proxy_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) { @@ -511,7 +543,13 @@ lookup_proxy_auth (SoupAuthManagerPrivat if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE) return NULL; - return priv->proxy_auth; + /* Only reuse the cached proxy credentials for the same proxy they were + * obtained from, otherwise switching proxies would leak them. + */ + if (priv->proxy_auth && proxy_auth_matches_msg (priv, msg)) + return priv->proxy_auth; + + return NULL; } static void @@ -688,8 +726,9 @@ proxy_auth_got_headers (SoupMessage *msg prior_auth_failed = TRUE; } - if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) - auth = priv->proxy_auth ? g_object_ref (priv->proxy_auth) : NULL; + if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE) && + priv->proxy_auth && proxy_auth_matches_msg (priv, msg)) + auth = g_object_ref (priv->proxy_auth); if (!auth) { auth = create_auth (priv, msg); @@ -697,8 +736,11 @@ proxy_auth_got_headers (SoupMessage *msg g_mutex_unlock (&priv->lock); return; } - if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) - priv->proxy_auth = g_object_ref (auth); + if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE)) { + g_set_object (&priv->proxy_auth, auth); + g_free (priv->proxy_auth_authority); + priv->proxy_auth_authority = get_proxy_authority (msg); + } } /* If we need to authenticate, try to do it. */ Index: libsoup-2.74.3/tests/meson.build =================================================================== --- libsoup-2.74.3.orig/tests/meson.build +++ libsoup-2.74.3/tests/meson.build @@ -48,6 +48,7 @@ tests = [ ['hsts-db', true, []], ['misc', true, []], ['multipart', true, []], + ['proxy-auth-cache', false, []], ['no-ssl', true, []], ['ntlm', true, []], ['redirect', true, []], Index: libsoup-2.74.3/tests/proxy-auth-cache-test.c =================================================================== --- /dev/null +++ libsoup-2.74.3/tests/proxy-auth-cache-test.c @@ -0,0 +1,164 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright 2026 Igalia S.L. + * + * This file is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "test-utils.h" + +/* Two SoupServers stand in as HTTP proxies. When a session uses an HTTP proxy + * for an http:// URL it sends the full request, including Proxy-Authorization, + * straight to the proxy, so a SoupServer can observe it without forwarding. + * After authenticating to the first proxy, switching the session to a second + * proxy must not send the first proxy's credentials to it. See issue #506. + */ + +static gboolean second_proxy_saw_authorization; +static gboolean second_proxy_hit; + +static void +first_proxy_cb (SoupServer *server, + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *context, + gpointer user_data) +{ + /* Challenge until the client authenticates */ + if (!soup_message_headers_get_one (msg->request_headers, "Proxy-Authorization")) { + soup_message_set_status (msg, SOUP_STATUS_PROXY_UNAUTHORIZED); + soup_message_headers_append (msg->response_headers, + "Proxy-Authenticate", "Basic realm=\"first\""); + return; + } + + soup_message_set_status (msg, SOUP_STATUS_OK); + soup_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "ok", 2); +} + +static void +second_proxy_cb (SoupServer *server, + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *context, + gpointer user_data) +{ + second_proxy_hit = TRUE; + if (soup_message_headers_get_one (msg->request_headers, "Proxy-Authorization")) + second_proxy_saw_authorization = TRUE; + + soup_message_set_status (msg, SOUP_STATUS_OK); + soup_message_set_response (msg, "text/plain", SOUP_MEMORY_STATIC, "ok", 2); +} + +static gboolean +authenticate_cb (SoupSession *session, + SoupMessage *msg, + SoupAuth *auth, + gboolean retrying, + gpointer user_data) +{ + if (soup_auth_is_for_proxy (auth) && !retrying) { + soup_auth_authenticate (auth, "user", "password"); + return TRUE; + } + return FALSE; +} + +static void +send_via_proxy (SoupSession *session, gboolean new_connection) +{ + SoupMessage *msg; + + msg = soup_message_new (SOUP_METHOD_GET, "http://origin.test/"); + + if (new_connection) { + SoupMessageFlags flags = soup_message_get_flags (msg); + soup_message_set_flags (msg, flags | SOUP_MESSAGE_NEW_CONNECTION); + } + g_signal_connect (session, "authenticate", G_CALLBACK (authenticate_cb), NULL); + soup_session_send_message (session, msg); + + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + + g_object_unref (msg); +} + +static void +do_proxy_switch_no_credential_leak (void) +{ + SoupServer *first, *second; + SoupURI *first_uri, *second_uri; + char *first_str, *second_str; + GProxyResolver *resolver; + SoupSession *session; + + first = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD); + soup_server_add_handler (first, NULL, first_proxy_cb, NULL, NULL); + first_uri = soup_test_server_get_uri (first, "http", "127.0.0.1"); + first_str = soup_uri_to_string (first_uri, FALSE); + + second = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD); + soup_server_add_handler (second, NULL, second_proxy_cb, NULL, NULL); + second_uri = soup_test_server_get_uri (second, "http", "127.0.0.1"); + second_str = soup_uri_to_string (second_uri, FALSE); + + resolver = g_simple_proxy_resolver_new (first_str, NULL); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, "proxy-resolver", resolver, NULL); + + /* Authenticate to the first proxy */ + send_via_proxy (session, FALSE); + + /* Switch the session to the second proxy and send again on a fresh + * connection so the request actually goes to the second proxy. + */ + g_simple_proxy_resolver_set_default_proxy (G_SIMPLE_PROXY_RESOLVER (resolver), second_str); + second_proxy_hit = FALSE; + second_proxy_saw_authorization = FALSE; + send_via_proxy (session, TRUE); + + /* Make sure the second request really reached the second proxy... */ + g_assert_true (second_proxy_hit); + /* ...and that the first proxy's credentials did not leak to it */ + g_assert_false (second_proxy_saw_authorization); + + soup_test_session_abort_unref (session); + g_object_unref (resolver); + g_free (first_str); + g_free (second_str); + soup_uri_free (first_uri); + soup_uri_free (second_uri); + soup_test_server_quit_unref (first); + soup_test_server_quit_unref (second); +} + +int +main (int argc, char **argv) +{ + int ret; + + test_init (argc, argv, NULL); + + g_test_add_func ("/proxy-auth-cache/switch-no-credential-leak", + do_proxy_switch_no_credential_leak); + + ret = g_test_run (); + + test_cleanup (); + return ret; +} ++++++ libsoup2-CVE-2026-12548.patch ++++++ --- /var/tmp/diff_new_pack.pFOzxP/_old 2026-10-08 15:12:58.203703592 +0200 +++ /var/tmp/diff_new_pack.pFOzxP/_new 2026-10-08 15:12:58.207703760 +0200 @@ -29,7 +29,7 @@ @@ -493,6 +493,14 @@ SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, - { { NULL } }, 0 + { { NULL } } + }, + + { "Header fileds too large", NULL, @@ -37,7 +37,7 @@ + (18 + 256) * 1024, + SOUP_STATUS_BAD_REQUEST, + NULL, NULL, -1, -+ { { NULL } }, 0 ++ { { NULL } } } }; static const int num_reqtests = G_N_ELEMENTS (reqtests); ++++++ libsoup2-CVE-2026-15711.patch ++++++ >From 60aa1ce2bdc7bb5da33be9062f50bcec7db67fca Mon Sep 17 00:00:00 2001 From: Zayd Rajab <[email protected]> Date: Thu, 16 Jul 2026 02:32:19 +0000 Subject: [PATCH] websocket: Reject oversized control frames RFC 6455 section 5.5 limits control frame payloads to 125 bytes. Reject extended payload length indicators as soon as the base header is available, before waiting for additional length or payload bytes. Add regression coverage for both extended length indicators and both receive directions. This is CVE-2026-15711. Closes #515 --- libsoup/websocket/soup-websocket-connection.c | 7 ++ tests/websocket-test.c | 113 ++++++++++++++++++ 2 files changed, 120 insertions(+) diff -urp libsoup-2.74.3.orig/libsoup/soup-websocket-connection.c libsoup-2.74.3/libsoup/soup-websocket-connection.c --- libsoup-2.74.3.orig/libsoup/soup-websocket-connection.c 2026-09-28 19:21:10.207636435 -0500 +++ libsoup-2.74.3/libsoup/soup-websocket-connection.c 2026-09-28 19:34:48.026514214 -0500 @@ -1021,6 +1021,13 @@ process_frame (SoupWebsocketConnection * return FALSE; } + /* RFC 6455 section 5.5 limits control frame payloads to 125 bytes. */ + if (control && (header[1] & 0x7f) > 125) { + g_debug ("received oversized control frame"); + protocol_error_and_close (self); + return FALSE; + } + switch (header[1] & 0x7f) { case 126: /* If 126, the following 2 bytes interpreted as a 16-bit diff -urp libsoup-2.74.3.orig/tests/websocket-test.c libsoup-2.74.3/tests/websocket-test.c --- libsoup-2.74.3.orig/tests/websocket-test.c 2026-09-28 19:21:10.192045502 -0500 +++ libsoup-2.74.3/tests/websocket-test.c 2026-09-28 19:34:48.027514241 -0500 @@ -630,6 +630,98 @@ test_send_bad_data (Test *test, g_assert_cmpuint (soup_websocket_connection_get_close_code (test->client), ==, SOUP_WEBSOCKET_CLOSE_BAD_DATA); } +static gboolean +on_timeout_set_flag (gpointer user_data) +{ + gboolean *timed_out = user_data; + + *timed_out = TRUE; + + return G_SOURCE_REMOVE; +} + +static void +wait_for_websocket_error (GError **error) +{ + gboolean timed_out = FALSE; + guint timeout_id; + + timeout_id = g_timeout_add_seconds (1, on_timeout_set_flag, &timed_out); + WAIT_UNTIL (*error != NULL || timed_out); + + if (!timed_out) + g_source_remove (timeout_id); + g_assert_false (timed_out); +} + +static void +write_oversized_control_frame_header (GIOStream *io, + guint8 length_indicator, + gboolean masked) +{ + guint8 frame[] = { 0x8a, length_indicator }; + GError *error = NULL; + gsize written; + + g_assert_true (length_indicator == 126 || length_indicator == 127); + + if (masked) + frame[1] |= 0x80; + + g_output_stream_write_all (g_io_stream_get_output_stream (io), + frame, sizeof (frame), &written, NULL, &error); + g_assert_no_error (error); + g_assert_cmpuint (written, ==, sizeof (frame)); +} + +typedef struct { + guint8 length_indicator; + gboolean server_receives; +} OversizedControlFrameTest; + +static void +test_receive_oversized_control_frame (Test *test, + gconstpointer data) +{ + const OversizedControlFrameTest *config = data; + SoupWebsocketConnection *receiver; + SoupWebsocketConnection *sender; + GError *error = NULL; + GIOStream *io; + gulong error_id; + gboolean close_event = FALSE; + + if (config->server_receives) { + receiver = test->server; + sender = test->client; + } else { + receiver = test->client; + sender = test->server; + } + + g_signal_handlers_disconnect_by_func (receiver, on_error_not_reached, NULL); + error_id = g_signal_connect (receiver, "error", G_CALLBACK (on_error_copy), &error); + g_signal_connect (sender, "closed", G_CALLBACK (on_close_set_flag), &close_event); + + io = soup_websocket_connection_get_io_stream (sender); + write_oversized_control_frame_header (io, config->length_indicator, + config->server_receives); + wait_for_websocket_error (&error); + g_assert_error (error, SOUP_WEBSOCKET_ERROR, SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR); + g_clear_error (&error); + g_signal_handler_disconnect (receiver, error_id); + + WAIT_UNTIL (soup_websocket_connection_get_state (sender) == SOUP_WEBSOCKET_STATE_CLOSED); + g_assert_true (close_event); + g_assert_cmpuint (soup_websocket_connection_get_close_code (sender), ==, + SOUP_WEBSOCKET_CLOSE_PROTOCOL_ERROR); +} + +static const OversizedControlFrameTest oversized_control_frame_16_server = { 126, TRUE }; +static const OversizedControlFrameTest oversized_control_frame_64_server = { 127, TRUE }; +static const OversizedControlFrameTest oversized_control_frame_16_client = { 126, FALSE }; +static const OversizedControlFrameTest oversized_control_frame_64_client = { 127, FALSE }; + static const char *negotiate_client_protocols[] = { "bbb", "ccc", NULL }; static const char *negotiate_server_protocols[] = { "aaa", "bbb", "ccc", NULL }; static const char *negotiated_protocol = "bbb"; @@ -2009,6 +2101,27 @@ main (int argc, test_send_bad_data, teardown_soup_connection); + g_test_add ("/websocket/direct/server-receive-oversized-control-frame/16-bit", + Test, &oversized_control_frame_16_server, + setup_direct_connection, + test_receive_oversized_control_frame, + teardown_direct_connection); + g_test_add ("/websocket/direct/server-receive-oversized-control-frame/64-bit", + Test, &oversized_control_frame_64_server, + setup_direct_connection, + test_receive_oversized_control_frame, + teardown_direct_connection); + g_test_add ("/websocket/direct/client-receive-oversized-control-frame/16-bit", + Test, &oversized_control_frame_16_client, + setup_direct_connection, + test_receive_oversized_control_frame, + teardown_direct_connection); + g_test_add ("/websocket/direct/client-receive-oversized-control-frame/64-bit", + Test, &oversized_control_frame_64_client, + setup_direct_connection, + test_receive_oversized_control_frame, + teardown_direct_connection); + g_test_add ("/websocket/direct/close-clean-client", Test, NULL, NULL, test_close_clean_client_direct, NULL); ++++++ libsoup2-CVE-2026-15714.patch ++++++ >From 79a52cadc490360e249cc2b23038d532b44dbf23 Mon Sep 17 00:00:00 2001 From: Mike Gorse <[email protected]> Date: Fri, 4 Sep 2026 14:10:46 -0500 Subject: [PATCH] multipart: Fix out-of-bounds read in case of an excessively long boundary soup_multipart_input_stream_read_headers() compared meta_buf against the boundary using the boundary length without checking that meta_buf held that many bytes, so a server sending a boundary longer than the read buffer could make strncmp() read past the end of the allocation. It also computed read_buf + nread - 4 before verifying nread was large enough, and asserted rather than failing when a line did not end in a newline. Only compare once enough bytes have been read, index read_buf after the length checks, and treat an unterminated line as end of parsing. Closes !546 --- libsoup/soup-multipart-input-stream.c | 12 ++++----- tests/multipart-test.c | 37 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) Index: libsoup-2.74.3/libsoup/soup-multipart-input-stream.c =================================================================== --- libsoup-2.74.3.orig/libsoup/soup-multipart-input-stream.c +++ libsoup-2.74.3/libsoup/soup-multipart-input-stream.c @@ -348,7 +348,6 @@ soup_multipart_input_stream_read_headers { SoupMultipartInputStreamPrivate *priv = multipart->priv; guchar read_buf[RESPONSE_BLOCK_SIZE]; - guchar *buf; gboolean got_boundary = FALSE; gboolean got_lf = FALSE; gssize nread = 0; @@ -370,22 +369,23 @@ soup_multipart_input_stream_read_headers * may get the multipart end indicator without getting a new line. */ if (!got_boundary && + priv->meta_buf->len >= priv->boundary_size && !strncmp ((char *)priv->meta_buf->data, priv->boundary, priv->boundary_size)) { got_boundary = TRUE; /* Now check for possible multipart termination. */ - buf = &read_buf[nread - 4]; - if ((nread >= 4 && !memcmp (buf, "--\r\n", 4)) || - (nread >= 3 && !memcmp (buf + 1, "--\n", 3)) || - (nread >= 3 && !memcmp (buf + 2, "--", 2))) { + if ((nread >= 4 && !memcmp (read_buf + nread - 4, "--\r\n", 4)) || + (nread >= 3 && !memcmp (read_buf + nread - 3, "--\n", 3)) || + (nread >= 3 && !memcmp (read_buf + nread - 2, "--", 2))) { g_byte_array_set_size (priv->meta_buf, 0); return FALSE; } } - g_return_val_if_fail (got_lf, FALSE); + if (!got_lf) + return FALSE; /* Discard pre-boundary lines. */ if (!got_boundary) { Index: libsoup-2.74.3/tests/multipart-test.c =================================================================== --- libsoup-2.74.3.orig/tests/multipart-test.c +++ libsoup-2.74.3/tests/multipart-test.c @@ -413,6 +413,40 @@ sync_multipart_handling_cb (GObject *sou } static void +test_multipart_bounds_bad_4 (void) +{ + SoupMessage *msg; + GInputStream *in; + GInputStream *next_part; + SoupMultipartInputStream *multipart; + GError *error = NULL; + gchar *boundary; + gchar *content_type; + gchar *raw_data; + + boundary = g_new (gchar, 20001); + memset (boundary, 'a', 20000); + boundary[20000] = '\0'; + + msg = soup_message_new(SOUP_METHOD_POST, "http://foo/upload"); + content_type = g_strconcat ("multipart/form-data; boundary=\"", boundary, "\"", NULL); + soup_message_headers_replace (msg->response_headers, "Content-Type", content_type); + + raw_data = g_strconcat ("--", boundary, "\r\nContent-Type: text-plain\r\n\r\nX\r\n", NULL); + in = g_memory_input_stream_new_from_data (raw_data, strlen (raw_data), NULL); + multipart = soup_multipart_input_stream_new (msg, in); + g_object_unref (in); + next_part = soup_multipart_input_stream_next_part (multipart, NULL, &error); + g_assert_no_error (error); + g_assert_null (next_part); + g_object_unref (multipart); + g_object_unref (msg); + g_free (raw_data); + g_free (content_type); + g_free (boundary); +} + +static void test_multipart (gconstpointer data) { int headers_expected = 1, sniffed_expected = 1; @@ -508,6 +542,7 @@ main (int argc, char **argv) g_test_add_data_func ("/multipart/sync", GINT_TO_POINTER (SYNC_MULTIPART), test_multipart); g_test_add_data_func ("/multipart/async", GINT_TO_POINTER (ASYNC_MULTIPART), test_multipart); g_test_add_data_func ("/multipart/async-small-reads", GINT_TO_POINTER (ASYNC_MULTIPART_SMALL_READS), test_multipart); + g_test_add_func ("/multipart/bounds-bad-4", test_multipart_bounds_bad_4); ret = g_test_run (); ++++++ libsoup2-CVE-2026-2708.patch ++++++ --- /var/tmp/diff_new_pack.pFOzxP/_old 2026-10-08 15:12:58.337709208 +0200 +++ /var/tmp/diff_new_pack.pFOzxP/_new 2026-10-08 15:12:58.342709418 +0200 @@ -88,7 +88,7 @@ + -1, + SOUP_STATUS_OK, + "POST", "/", SOUP_HTTP_1_1, -+ { { "Content-Length", "4" } }, 0 ++ { { "Content-Length", "4" } } + }, + + { "Duplicate Content-Length with the same decimal value", NULL, @@ -96,7 +96,7 @@ + -1, + SOUP_STATUS_OK, + "POST", "/", SOUP_HTTP_1_1, -+ { { "Content-Length", "04" } }, 0 ++ { { "Content-Length", "04" } } + }, + /************************/ @@ -114,7 +114,7 @@ + -1, + SOUP_STATUS_BAD_REQUEST, + NULL, NULL, -1, -+ { { NULL } }, 0 ++ { { NULL } } } }; static const int num_reqtests = G_N_ELEMENTS (reqtests); ++++++ libsoup2-CVE-2026-66339.patch ++++++ >From 6ed1711ae498f91c78fc12d960f1298c32c5d01d Mon Sep 17 00:00:00 2001 From: Patrick Griffis <[email protected]> Date: Fri, 4 Sep 2026 15:18:26 -0500 Subject: [PATCH] auth: Don't send Proxy-Authorization through an established tunnel After a CONNECT tunnel to an HTTPS origin is set up, the actual request travels inside the tunnel directly to the origin server. auth_msg_starting() attached Proxy-Authorization to every message including these, leaking the proxy credentials to the origin. Skip the proxy-auth handling entirely when the message's connection is a tunnel, and clear any Proxy-Authorization header from it. The proxy test suite requires Apache and a CONNECT-tunnel-with-TLS setup that the in-tree harness does not provide, so this has no automated regression test here. Fixes #534 (CVE-2026-66339) --- libsoup/auth/soup-auth-manager.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) Index: libsoup-2.74.3/libsoup/soup-auth-manager.c =================================================================== --- libsoup-2.74.3.orig/libsoup/soup-auth-manager.c +++ libsoup-2.74.3/libsoup/soup-auth-manager.c @@ -15,6 +15,7 @@ #include "soup.h" #include "soup-connection-auth.h" #include "soup-message-private.h" +#include "soup-connection.h" #include "soup-message-queue.h" #include "soup-path-map.h" #include "soup-session-private.h" @@ -734,6 +735,8 @@ auth_msg_starting (SoupMessage *msg, gpo { SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv; SoupAuth *auth; + SoupConnection *conn; + gboolean tunnelled; if (soup_message_get_flags (msg) & SOUP_MESSAGE_DO_NOT_USE_AUTH_CACHE) return; @@ -751,6 +754,22 @@ auth_msg_starting (SoupMessage *msg, gpo update_authorization_header (msg, auth, FALSE); } + /* Proxy-Authorization must only be sent to the proxy itself, never on + * a request travelling through an established CONNECT tunnel to the + * origin server, which would leak the proxy credentials to the origin. + * The CONNECT request that sets the tunnel up is addressed to the + * proxy, so it still needs the header. + */ + conn = soup_message_get_connection (msg); + tunnelled = conn && soup_connection_is_tunnelled (conn) && + msg->method != SOUP_METHOD_CONNECT; + if (tunnelled) { + soup_message_set_proxy_auth (msg, NULL); + update_authorization_header (msg, NULL, TRUE); + g_mutex_unlock (&priv->lock); + return; + } + auth = lookup_proxy_auth (priv, msg); if (auth) { authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE);
