diff --git a/SPECS/libsoup/CVE-2025-32049.patch b/SPECS/libsoup/CVE-2025-32049.patch new file mode 100644 index 00000000000..0dace146ad2 --- /dev/null +++ b/SPECS/libsoup/CVE-2025-32049.patch @@ -0,0 +1,247 @@ +From c0ef6fa6df2561d9bcdf8667869fa7e84ce0eefd Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Mon, 9 Feb 2026 16:06:10 +0000 +Subject: [PATCH] websocket: add max-total-message-size safety valve and + property; split payload/message too-big errors; fix tests to use + g_assert_true + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/408.patch +--- + libsoup/websocket/soup-websocket-connection.c | 107 +++++++++++++++++- + libsoup/websocket/soup-websocket-connection.h | 8 ++ + tests/websocket-test.c | 6 +- + 3 files changed, 116 insertions(+), 5 deletions(-) + +diff --git a/libsoup/websocket/soup-websocket-connection.c b/libsoup/websocket/soup-websocket-connection.c +index 5eb8150..50dcc9e 100644 +--- a/libsoup/websocket/soup-websocket-connection.c ++++ b/libsoup/websocket/soup-websocket-connection.c +@@ -84,6 +84,7 @@ enum { + PROP_MAX_INCOMING_PAYLOAD_SIZE, + PROP_KEEPALIVE_INTERVAL, + PROP_EXTENSIONS, ++ PROP_MAX_TOTAL_MESSAGE_SIZE, + + LAST_PROPERTY + }; +@@ -126,6 +127,7 @@ typedef struct { + char *origin; + char *protocol; + guint64 max_incoming_payload_size; ++ guint64 max_total_message_size; + guint keepalive_interval; + + gushort peer_close_code; +@@ -156,6 +158,7 @@ typedef struct { + } SoupWebsocketConnectionPrivate; + + #define MAX_INCOMING_PAYLOAD_SIZE_DEFAULT 128 * 1024 ++#define MAX_TOTAL_MESSAGE_SIZE_DEFAULT 128 * 1024 + #define READ_BUFFER_SIZE 1024 + #define MASK_LENGTH 4 + +@@ -670,7 +673,7 @@ bad_data_error_and_close (SoupWebsocketConnection *self) + } + + static void +-too_big_error_and_close (SoupWebsocketConnection *self, ++too_big_incoming_payload_error_and_close (SoupWebsocketConnection *self, + guint64 payload_len) + { + SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self); +@@ -687,6 +690,24 @@ too_big_error_and_close (SoupWebsocketConnection *self, + emit_error_and_close (self, error, TRUE); + } + ++static void ++too_big_message_error_and_close (SoupWebsocketConnection *self, ++ guint64 len) ++{ ++ SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self); ++ GError *error; ++ ++ error = g_error_new_literal (SOUP_WEBSOCKET_ERROR, ++ SOUP_WEBSOCKET_CLOSE_TOO_BIG, ++ priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ? ++ "Received WebSocket payload from the client larger than configured max-total-message-size" : ++ "Received WebSocket payload from the server larger than configured max-total-message-size"); ++ g_debug ("%s received message of size %" G_GUINT64_FORMAT " or greater, but max supported size is %" G_GUINT64_FORMAT, ++ priv->connection_type == SOUP_WEBSOCKET_CONNECTION_SERVER ? "server" : "client", ++ len, priv->max_total_message_size); ++ emit_error_and_close (self, error, TRUE); ++} ++ + static void + close_connection (SoupWebsocketConnection *self, + gushort code, +@@ -918,6 +939,12 @@ process_contents (SoupWebsocketConnection *self, + switch (priv->message_opcode) { + case 0x01: + case 0x02: ++ /* Safety valve */ ++ if (priv->max_total_message_size > 0 && ++ (priv->message_data->len + payload_len) > priv->max_total_message_size) { ++ too_big_message_error_and_close (self, (priv->message_data->len + payload_len)); ++ return; ++ } + g_byte_array_append (priv->message_data, payload, payload_len); + break; + default: +@@ -1056,7 +1083,7 @@ process_frame (SoupWebsocketConnection *self) + /* Safety valve */ + if (priv->max_incoming_payload_size > 0 && + payload_len > priv->max_incoming_payload_size) { +- too_big_error_and_close (self, payload_len); ++ too_big_incoming_payload_error_and_close (self, payload_len); + return FALSE; + } + +@@ -1359,6 +1386,10 @@ soup_websocket_connection_get_property (GObject *object, + g_value_set_uint (value, priv->keepalive_interval); + break; + ++ case PROP_MAX_TOTAL_MESSAGE_SIZE: ++ g_value_set_uint64 (value, priv->max_total_message_size); ++ break; ++ + case PROP_EXTENSIONS: + g_value_set_pointer (value, priv->extensions); + break; +@@ -1384,6 +1415,10 @@ soup_websocket_connection_set_property (GObject *object, + priv->io_stream = g_value_dup_object (value); + break; + ++ case PROP_MAX_TOTAL_MESSAGE_SIZE: ++ priv->max_total_message_size = g_value_get_uint64 (value); ++ break; ++ + case PROP_CONNECTION_TYPE: + priv->connection_type = g_value_get_enum (value); + break; +@@ -1471,6 +1506,26 @@ soup_websocket_connection_finalize (GObject *object) + static void + soup_websocket_connection_class_init (SoupWebsocketConnectionClass *klass) + { ++ /** ++ * SoupWebsocketConnection:max-total-message-size: ++ * ++ * The total message size for incoming packets. ++ * ++ * The protocol expects or 0 to not limit it. ++ * ++ * Since: 3.8 ++ */ ++ properties[PROP_MAX_TOTAL_MESSAGE_SIZE] = ++ g_param_spec_uint64 ("max-total-message-size", ++ "Max total message size", ++ "Max total message size ", ++ 0, ++ G_MAXUINT64, ++ MAX_TOTAL_MESSAGE_SIZE_DEFAULT, ++ G_PARAM_READWRITE | ++ G_PARAM_CONSTRUCT | ++ G_PARAM_STATIC_STRINGS); ++ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructed = soup_websocket_connection_constructed; +@@ -2080,6 +2135,54 @@ soup_websocket_connection_close (SoupWebsocketConnection *self, + */ + guint64 + soup_websocket_connection_get_max_incoming_payload_size (SoupWebsocketConnection *self) ++ ++/** ++ * soup_websocket_connection_get_max_total_message_size: ++ * @self: the WebSocket ++ * ++ * Gets the maximum total message size allowed for packets. ++ * ++ * Returns: the maximum total message size. ++ * ++ * Since: 3.8 ++ */ ++SOUP_AVAILABLE_IN_ALL ++guint64 ++soup_websocket_connection_get_max_total_message_size (SoupWebsocketConnection *self) ++{ ++ SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self); ++ ++ g_return_val_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self), MAX_TOTAL_MESSAGE_SIZE_DEFAULT); ++ ++ return priv->max_total_message_size; ++} ++ ++/** ++ * soup_websocket_connection_set_max_total_message_size: ++ * @self: the WebSocket ++ * @max_total_message_size: the maximum total message size ++ * ++ * Sets the maximum total message size allowed for packets. ++ * ++ * It does not limit the outgoing packet size. ++ * ++ * Since: 3.8 ++ */ ++SOUP_AVAILABLE_IN_ALL ++void ++soup_websocket_connection_set_max_total_message_size (SoupWebsocketConnection *self, ++ guint64 max_total_message_size) ++{ ++ SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self); ++ ++ g_return_if_fail (SOUP_IS_WEBSOCKET_CONNECTION (self)); ++ ++ if (priv->max_total_message_size != max_total_message_size) { ++ priv->max_total_message_size = max_total_message_size; ++ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_TOTAL_MESSAGE_SIZE]); ++ } ++} ++ + { + SoupWebsocketConnectionPrivate *priv = soup_websocket_connection_get_instance_private (self); + +diff --git a/libsoup/websocket/soup-websocket-connection.h b/libsoup/websocket/soup-websocket-connection.h +index eeb093d..666756f 100644 +--- a/libsoup/websocket/soup-websocket-connection.h ++++ b/libsoup/websocket/soup-websocket-connection.h +@@ -86,6 +86,14 @@ guint64 soup_websocket_connection_get_max_incoming_payload_size (Sou + + SOUP_AVAILABLE_IN_ALL + void soup_websocket_connection_set_max_incoming_payload_size (SoupWebsocketConnection *self, ++ ++SOUP_AVAILABLE_IN_ALL ++guint64 soup_websocket_connection_get_max_total_message_size (SoupWebsocketConnection *self); ++ ++SOUP_AVAILABLE_IN_ALL ++void soup_websocket_connection_set_max_total_message_size (SoupWebsocketConnection *self, ++ guint64 max_total_message_size); ++ + guint64 max_incoming_payload_size); + + SOUP_AVAILABLE_IN_ALL +diff --git a/tests/websocket-test.c b/tests/websocket-test.c +index a0b8334..7d057f0 100644 +--- a/tests/websocket-test.c ++++ b/tests/websocket-test.c +@@ -563,14 +563,14 @@ 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_true (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_true (soup_websocket_connection_get_max_incoming_payload_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_assert_true (g_bytes_equal (sent, received)); + g_bytes_unref (sent); + g_bytes_unref (received); + } +-- +2.45.4 + diff --git a/SPECS/libsoup/CVE-2026-1467.patch b/SPECS/libsoup/CVE-2026-1467.patch new file mode 100644 index 00000000000..395a7ba3106 --- /dev/null +++ b/SPECS/libsoup/CVE-2026-1467.patch @@ -0,0 +1,224 @@ +From 0dc65c431b0682f3de8dfcbb26949a4b019add9c Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Mon, 9 Feb 2026 15:45:49 +0000 +Subject: [PATCH] uri-utils: do host validation when checking if a GUri is + valid; replace SOUP_URI_IS_VALID macro with soup_uri_is_valid() function, + update message and auth uses, and add tests for host validity + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/498.patch +--- + libsoup/auth/soup-auth.c | 2 +- + libsoup/soup-message.c | 7 ++-- + libsoup/soup-uri-utils-private.h | 4 ++- + libsoup/soup-uri-utils.c | 61 ++++++++++++++++++++++++++++++++ + tests/uri-parsing-test.c | 48 +++++++++++++++++++++++++ + 5 files changed, 117 insertions(+), 5 deletions(-) + +diff --git a/libsoup/auth/soup-auth.c b/libsoup/auth/soup-auth.c +index d9bf4af..278baa1 100644 +--- a/libsoup/auth/soup-auth.c ++++ b/libsoup/auth/soup-auth.c +@@ -643,7 +643,7 @@ GSList * + soup_auth_get_protection_space (SoupAuth *auth, GUri *source_uri) + { + g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL); +- g_return_val_if_fail (SOUP_URI_IS_VALID (source_uri), NULL); ++ g_return_val_if_fail (soup_uri_is_valid (source_uri), NULL); + + GUri *source_uri_normalized = soup_uri_copy_with_normalized_flags (source_uri); + GSList *ret = SOUP_AUTH_GET_CLASS (auth)->get_protection_space (auth, source_uri_normalized); +diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c +index 2f5b267..b286874 100644 +--- a/libsoup/soup-message.c ++++ b/libsoup/soup-message.c +@@ -923,7 +923,8 @@ soup_message_new (const char *method, const char *uri_string) + uri = g_uri_parse (uri_string, SOUP_HTTP_URI_FLAGS, NULL); + if (!uri) + return NULL; +- if (!g_uri_get_host (uri)) { ++ ++ if (!soup_uri_is_valid (uri)) { + g_uri_unref (uri); + return NULL; + } +@@ -946,7 +947,7 @@ SoupMessage * + soup_message_new_from_uri (const char *method, GUri *uri) + { + g_return_val_if_fail (method != NULL, NULL); +- g_return_val_if_fail (SOUP_URI_IS_VALID (uri), NULL); ++ g_return_val_if_fail (soup_uri_is_valid (uri), NULL); + + return g_object_new (SOUP_TYPE_MESSAGE, + "method", method, +@@ -966,7 +967,7 @@ soup_message_new_from_uri (const char *method, GUri *uri) + SoupMessage * + soup_message_new_options_ping (GUri *base_uri) + { +- g_return_val_if_fail (SOUP_URI_IS_VALID (base_uri), NULL); ++ g_return_val_if_fail (soup_uri_is_valid (base_uri), NULL); + + return g_object_new (SOUP_TYPE_MESSAGE, + "method", SOUP_METHOD_OPTIONS, +diff --git a/libsoup/soup-uri-utils-private.h b/libsoup/soup-uri-utils-private.h +index 3dbdb85..e3dd454 100644 +--- a/libsoup/soup-uri-utils-private.h ++++ b/libsoup/soup-uri-utils-private.h +@@ -10,6 +10,8 @@ + + G_BEGIN_DECLS + ++gboolean soup_uri_is_valid (GUri *uri); ++ + gboolean soup_uri_is_http (GUri *uri); + + gboolean soup_uri_is_https (GUri *uri); +@@ -28,6 +30,6 @@ GUri *soup_uri_copy_with_normalized_flags (GUri *uri); + + char *soup_uri_get_host_for_headers (GUri *uri); + +-#define SOUP_URI_IS_VALID(x) (x && g_uri_get_host(x) && g_uri_get_host(x)[0]) ++/* replaced by soup_uri_is_valid() */ + + G_END_DECLS +diff --git a/libsoup/soup-uri-utils.c b/libsoup/soup-uri-utils.c +index ce9b2a1..3289798 100644 +--- a/libsoup/soup-uri-utils.c ++++ b/libsoup/soup-uri-utils.c +@@ -244,6 +244,67 @@ soup_uri_host_equal (gconstpointer v1, gconstpointer v2) + return g_ascii_strcasecmp (one_host, two_host) == 0; + } + ++static gboolean ++is_valid_character_for_host (char c) ++{ ++ static const char forbidden_chars[] = { ' ', '\n', '\r', ' ', '#', '/', ':', '<', '>', '?', '@', '[', '\\', ']', '^', '|' }; ++ int i; ++ ++ for (i = 0; i < G_N_ELEMENTS (forbidden_chars); ++i) { ++ if (c == forbidden_chars[i]) ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++is_host_valid (const char* host) ++{ ++ int i; ++ gboolean is_valid; ++ char *ascii_host = NULL; ++ ++ if (!host || !host[0]) ++ return FALSE; ++ ++ if (g_hostname_is_non_ascii (host)) { ++ ascii_host = g_hostname_to_ascii (host); ++ if (!ascii_host) ++ return FALSE; ++ ++ host = ascii_host; ++ } ++ ++ if ((g_ascii_isdigit (host[0]) || strchr (host, ':')) && g_hostname_is_ip_address (host)) { ++ g_free (ascii_host); ++ return TRUE; ++ } ++ ++ is_valid = TRUE; ++ for (i = 0; host[i] && is_valid; i++) ++ is_valid = is_valid_character_for_host (host[i]); ++ ++ g_free (ascii_host); ++ ++ return is_valid; ++} ++ ++gboolean ++soup_uri_is_valid (GUri *uri) ++{ ++ if (!uri) ++ return FALSE; ++ ++ if (!is_host_valid (g_uri_get_host (uri))) ++ return FALSE; ++ ++ /* FIXME: validate other URI components? */ ++ ++ return TRUE; ++} ++ ++ + gboolean + soup_uri_is_https (GUri *uri) + { +diff --git a/tests/uri-parsing-test.c b/tests/uri-parsing-test.c +index 4c16d7e..34320eb 100644 +--- a/tests/uri-parsing-test.c ++++ b/tests/uri-parsing-test.c +@@ -116,6 +116,52 @@ do_copy_tests (void) + g_uri_unref (uri); + } + ++ ++static struct { ++ const char *scheme; ++ const char *host; ++ const char *as_string; ++ gboolean valid; ++} valid_tests[] = { ++ { "http", "example.com", "http://example.com/", TRUE }, ++ { "http", "localhost", "http://localhost/", TRUE }, ++ { "http", "127.0.0.1", "http://127.0.0.1/", TRUE }, ++ { "http", "::1", "http://[::1]/", TRUE }, ++ { "http", "::192.168.0.10", "http://[::192.168.0.10]/", TRUE }, ++ { "http", "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/", TRUE }, ++ { "http", "\xe4\xbe\x8b\xe5\xad\x90.\xe6\xb5\x8b\xe8\xaf\x95", "http://\xe4\xbe\x8b\xe5\xad\x90.\xe6\xb5\x8b\xe8\xaf\x95/", TRUE }, ++ { "http", "012x:4567:89AB:cdef:3210:7654:ba98:FeDc", "http://012x:4567:89AB:cdef:3210:7654:ba98:FeDc/", FALSE }, ++ { "http", " example.com", "http:// example.com/", FALSE }, ++ { "http", "example.com\n", "http://example.com\n/", FALSE }, ++ { "http", "\r\nexample.com", "http://\r\nexample.com/", FALSE }, ++ { "http", "example .com", "http://example .com/", FALSE }, ++ { "http", "example:com", "http://example:com/", FALSE }, ++ { "http", "exampl.com", "http://exampl.com/", FALSE }, ++ { "http", "exampl[e].com", "http://exampl[e].com/", FALSE }, ++ { "http", "exampl^e.com", "http://exampl^e.com/", FALSE }, ++ { "http", "examp|e.com", "http://examp|e.com/", FALSE }, ++}; ++ ++static void ++do_valid_tests (void) ++{ ++ int i; ++ ++ for (i = 0; i < G_N_ELEMENTS (valid_tests); ++i) { ++ GUri *uri; ++ char *uri_str; ++ ++ uri = g_uri_build (SOUP_HTTP_URI_FLAGS | G_URI_FLAGS_ENCODED, valid_tests[i].scheme, NULL, valid_tests[i].host, -1, "", NULL, NULL); ++ uri_str = g_uri_to_string (uri); ++ ++ g_assert_cmpstr (uri_str, ==, valid_tests[i].as_string); ++ g_assert_true (soup_uri_is_valid (uri) == valid_tests[i].valid); ++ ++ g_free (uri_str); ++ g_uri_unref (uri); ++ } ++} ++ + #define CONTENT_TYPE_DEFAULT "text/plain;charset=US-ASCII" + + static struct { +@@ -193,6 +239,8 @@ main (int argc, char **argv) + g_test_add_func ("/uri/equality", do_equality_tests); + g_test_add_func ("/uri/copy", do_copy_tests); + g_test_add_func ("/data", do_data_uri_tests); ++ g_test_add_func ("/uri/valid", do_valid_tests); ++ + g_test_add_func ("/path_and_query", do_path_and_query_tests); + + ret = g_test_run (); +-- +2.45.4 + diff --git a/SPECS/libsoup/CVE-2026-1536.patch b/SPECS/libsoup/CVE-2026-1536.patch new file mode 100644 index 00000000000..30c8fa558ee --- /dev/null +++ b/SPECS/libsoup/CVE-2026-1536.patch @@ -0,0 +1,113 @@ +From 5f7763b83fe6f0ff86a9d6662a02307367ef2bff Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Mon, 9 Feb 2026 15:53:36 +0000 +Subject: [PATCH] Backport: Validate headers value for untrusted sources; add + trusted_value param to append/replace_common and update callers + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/500.patch +--- + libsoup/soup-message-headers-private.h | 6 ++++-- + libsoup/soup-message-headers.c | 27 ++++++++++++++++++++++---- + 2 files changed, 27 insertions(+), 6 deletions(-) + +diff --git a/libsoup/soup-message-headers-private.h b/libsoup/soup-message-headers-private.h +index 9815464..86c95c0 100644 +--- a/libsoup/soup-message-headers-private.h ++++ b/libsoup/soup-message-headers-private.h +@@ -15,7 +15,8 @@ void soup_message_headers_append_untrusted_data (SoupMessageHeaders *hdr + const char *value); + void soup_message_headers_append_common (SoupMessageHeaders *hdrs, + SoupHeaderName name, +- const char *value); ++ const char *value, ++ gboolean trusted_value); + const char *soup_message_headers_get_one_common (SoupMessageHeaders *hdrs, + SoupHeaderName name); + const char *soup_message_headers_get_list_common (SoupMessageHeaders *hdrs, +@@ -24,7 +25,8 @@ void soup_message_headers_remove_common (SoupMessageHeaders *hdr + SoupHeaderName name); + void soup_message_headers_replace_common (SoupMessageHeaders *hdrs, + SoupHeaderName name, +- const char *value); ++ const char *value, ++ gboolean trusted_value); + gboolean soup_message_headers_header_contains_common (SoupMessageHeaders *hdrs, + SoupHeaderName name, + const char *token); +diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c +index 9123dc1..213fd59 100644 +--- a/libsoup/soup-message-headers.c ++++ b/libsoup/soup-message-headers.c +@@ -247,6 +247,7 @@ soup_message_headers_clear (SoupMessageHeaders *hdrs) + g_array_set_size (hdrs->uncommon_headers, 0); + } + ++ + if (hdrs->uncommon_concat) + g_hash_table_remove_all (hdrs->uncommon_concat); + } +@@ -273,12 +274,24 @@ soup_message_headers_clean_connection_headers (SoupMessageHeaders *hdrs) + for (t = tokens; t; t = t->next) + soup_message_headers_remove (hdrs, t->data); + soup_header_free_list (tokens); ++ ++static inline gboolean is_valid_header_name (const char *name) ++{ ++ return name && *name && strpbrk (name, " \r\n:") == NULL; ++} ++ ++static inline gboolean is_valid_header_value (const char *value) ++{ ++ return value && strpbrk (value, "\r\n") == NULL; ++} ++ + } + + void + soup_message_headers_append_common (SoupMessageHeaders *hdrs, + SoupHeaderName name, +- const char *value) ++ const char *value, ++ gboolean trusted_value) + { + SoupCommonHeader header; + +@@ -287,6 +300,11 @@ soup_message_headers_append_common (SoupMessageHeaders *hdrs, + + header.name = name; + header.value = g_strdup (value); ++ if (!trusted_value && !is_valid_header_value (header.value)) { ++ g_warning ("soup_message_headers_append: Rejecting bad value '%s'", header.value); ++ g_free (header.value); ++ return; ++ } + g_array_append_val (hdrs->common_headers, header); + if (hdrs->common_concat) + g_hash_table_remove (hdrs->common_concat, GUINT_TO_POINTER (header.name)); +@@ -339,7 +357,7 @@ soup_message_headers_append (SoupMessageHeaders *hdrs, + + header_name = soup_header_name_from_string (name); + if (header_name != SOUP_HEADER_UNKNOWN) { +- soup_message_headers_append_common (hdrs, header_name, value); ++ soup_message_headers_append_common (hdrs, header_name, value, FALSE); + return; + } + +@@ -371,10 +389,11 @@ soup_message_headers_append_untrusted_data (SoupMessageHeaders *hdrs, + void + soup_message_headers_replace_common (SoupMessageHeaders *hdrs, + SoupHeaderName name, +- const char *value) ++ const char *value, ++ gboolean trusted_value) + { + soup_message_headers_remove_common (hdrs, name); +- soup_message_headers_append_common (hdrs, name, value); ++ soup_message_headers_append_common (hdrs, name, value, trusted_value); + } + + /** +-- +2.45.4 + diff --git a/SPECS/libsoup/CVE-2026-1761.patch b/SPECS/libsoup/CVE-2026-1761.patch new file mode 100644 index 00000000000..59cfdb7051d --- /dev/null +++ b/SPECS/libsoup/CVE-2026-1761.patch @@ -0,0 +1,100 @@ +From ad94dd1ef96b81d0a642bf29fb5701bb4b5dda07 Mon Sep 17 00:00:00 2001 +From: Carlos Garcia Campos +Date: Mon, 19 Jan 2026 15:14:58 +0100 +Subject: [PATCH] multipart: check length of bytes read + soup_filter_input_stream_read_until() + +We do make sure the read length is smaller than the buffer length when +the boundary is not found, but we should do the same when the boundary +is found. + +Spotted in #YWH-PGM9867-149 +Closes #493 + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/496.patch +--- + libsoup/soup-filter-input-stream.c | 3 +- + tests/multipart-test.c | 46 ++++++++++++++++++++++++++++++ + 2 files changed, 48 insertions(+), 1 deletion(-) + +diff --git a/libsoup/soup-filter-input-stream.c b/libsoup/soup-filter-input-stream.c +index b1e616c..22541aa 100644 +--- a/libsoup/soup-filter-input-stream.c ++++ b/libsoup/soup-filter-input-stream.c +@@ -337,6 +337,7 @@ soup_filter_input_stream_read_until (SoupFilterInputStream *fstream, + if (eof && !*got_boundary) + read_length = MIN (priv->buf->len, length); + else +- read_length = p - buf; ++ read_length = MIN ((gsize)(p - buf), length); ++ + return read_from_buf (fstream, buffer, read_length); + } +diff --git a/tests/multipart-test.c b/tests/multipart-test.c +index d05000f..a83fc64 100644 +--- a/tests/multipart-test.c ++++ b/tests/multipart-test.c +@@ -550,6 +550,51 @@ test_multipart_bounds_bad_2 (void) + g_bytes_unref (bytes); + } + ++static void ++test_multipart_bounds_bad_3 (void) ++{ ++ SoupMessage *msg; ++ SoupMessageHeaders *headers; ++ GInputStream *in; ++ SoupMultipartInputStream *multipart; ++ GError *error = NULL; ++ const char raw_data[] = "\0$--A\r\nContent-Disposition: form-data; name=\"f\"\r\n\r\nXXXXXXXXX\r\n--A--\r\n"; ++ ++ msg = soup_message_new(SOUP_METHOD_POST, "http://foo/upload"); ++ headers = soup_message_get_response_headers (msg); ++ soup_message_headers_replace (headers, "Content-Type", "multipart/form-data; boundary=\"A\""); ++ ++ in = g_memory_input_stream_new_from_data (raw_data + 2, sizeof(raw_data) - 2, NULL); ++ multipart = soup_multipart_input_stream_new (msg, in); ++ g_object_unref (in); ++ ++ while (TRUE) { ++ in = soup_multipart_input_stream_next_part (multipart, NULL, &error); ++ g_assert_no_error (error); ++ if (!in) { ++ g_clear_error (&error); ++ break; ++ } ++ ++ char buffer[10]; ++ while (TRUE) { ++ gssize bytes_read; ++ ++ bytes_read = g_input_stream_read (in, buffer, sizeof(buffer), NULL, &error); ++ g_assert_no_error (error); ++ if (bytes_read <= 0) { ++ g_clear_error (&error); ++ break; ++ } ++ } ++ ++ g_object_unref (in); ++ } ++ ++ g_object_unref (multipart); ++ g_object_unref (msg); ++} ++ + static void + test_multipart_too_large (void) + { +@@ -619,6 +664,7 @@ main (int argc, char **argv) + g_test_add_func ("/multipart/bounds-good", test_multipart_bounds_good); + g_test_add_func ("/multipart/bounds-bad", test_multipart_bounds_bad); + g_test_add_func ("/multipart/bounds-bad-2", test_multipart_bounds_bad_2); ++ g_test_add_func ("/multipart/bounds-bad-3", test_multipart_bounds_bad_3); + g_test_add_func ("/multipart/too-large", test_multipart_too_large); + + ret = g_test_run (); +-- +2.45.4 + diff --git a/SPECS/libsoup/CVE-2026-1801.patch b/SPECS/libsoup/CVE-2026-1801.patch new file mode 100644 index 00000000000..06bc0ef5ed7 --- /dev/null +++ b/SPECS/libsoup/CVE-2026-1801.patch @@ -0,0 +1,74 @@ +From 5ced360f6b2afe84b2a69cd8a6eb6cb6747bd0ce Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Mon, 9 Feb 2026 15:52:11 +0000 +Subject: [PATCH] http1 chunked parsing: use CRLF boundary via + soup_filter_input_stream_read_until; adjust trailers end check. Add server + chunked tests. Also adjust headers encoding precedence and connection close + for requests with both Content-Length and Transfer-Encoding (partial + backport). + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/506.patch +--- + libsoup/http1/soup-body-input-stream.c | 27 +++++++++++++++----------- + 1 file changed, 16 insertions(+), 11 deletions(-) + +diff --git a/libsoup/http1/soup-body-input-stream.c b/libsoup/http1/soup-body-input-stream.c +index 28acae8..0c517c7 100644 +--- a/libsoup/http1/soup-body-input-stream.c ++++ b/libsoup/http1/soup-body-input-stream.c +@@ -176,15 +176,19 @@ soup_body_input_stream_read_chunked (SoupBodyInputStream *bistream, + again: + switch (priv->chunked_state) { + case SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE: +- nread = soup_filter_input_stream_read_line ( +- fstream, metabuf, sizeof (metabuf), blocking, ++ nread = soup_filter_input_stream_read_until ( ++ fstream, metabuf, sizeof (metabuf), ++ "\r\n", 2, blocking, TRUE, + &got_line, cancellable, error); +- if (nread <= 0) ++ if (nread < 0) + return nread; +- if (!got_line) { +- g_set_error_literal (error, G_IO_ERROR, +- G_IO_ERROR_PARTIAL_INPUT, +- _("Connection terminated unexpectedly")); ++ ++ if (nread == 0 || !got_line) { ++ if (error && *error == NULL) { ++ g_set_error_literal (error, G_IO_ERROR, ++ G_IO_ERROR_PARTIAL_INPUT, ++ _("Connection terminated unexpectedly")); ++ } + return -1; + } + +@@ -208,11 +212,12 @@ again: + return nread; + + case SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END: +- nread = soup_filter_input_stream_read_line ( ++ nread = soup_filter_input_stream_read_until ( + SOUP_FILTER_INPUT_STREAM (priv->base_stream), +- metabuf, sizeof (metabuf), blocking, ++ metabuf, sizeof (metabuf), ++ "\r\n", 2, blocking, TRUE, + &got_line, cancellable, error); +- if (nread <= 0) ++ if (nread < 0) + return nread; + if (!got_line) { + g_set_error_literal (error, G_IO_ERROR, +@@ -231,7 +236,7 @@ again: + if (nread <= 0) + return nread; + +- if (strncmp (buffer, "\r\n", nread) || strncmp (buffer, "\n", nread)) { ++ if (nread == 2 && strncmp (buffer, "\r\n", nread) == 0) { + priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_DONE; + priv->eof = TRUE; + } +-- +2.45.4 + diff --git a/SPECS/libsoup/libsoup.spec b/SPECS/libsoup/libsoup.spec index 7a4b61361fc..596da0f6449 100644 --- a/SPECS/libsoup/libsoup.spec +++ b/SPECS/libsoup/libsoup.spec @@ -2,7 +2,7 @@ Summary: libsoup HTTP client/server library Name: libsoup Version: %{BaseVersion}.4 -Release: 10%{?dist} +Release: 11%{?dist} License: GPLv2 Vendor: Microsoft Corporation Distribution: Mariner @@ -36,6 +36,11 @@ Patch17: CVE-2025-4476.patch Patch18: CVE-2025-4948.patch Patch19: CVE-2025-4969.patch Patch20: CVE-2025-11021.patch +Patch21: CVE-2025-32049.patch +Patch22: CVE-2026-1467.patch +Patch23: CVE-2026-1536.patch +Patch24: CVE-2026-1761.patch +Patch25: CVE-2026-1801.patch BuildRequires: meson @@ -148,6 +153,9 @@ find %{buildroot} -type f -name "*.la" -delete -print %defattr(-,root,root) %changelog +* Mon Feb 09 2026 Azure Linux Security Servicing Account - 3.0.4-11 +- Patch for CVE-2026-1801, CVE-2026-1761, CVE-2026-1536, CVE-2025-32049, CVE-2026-1467 + * Wed Oct 29 2025 Azure Linux Security Servicing Account - 3.0.4-10 - Patch for CVE-2025-11021