Update to 3.6.6

Also, add fixes for CVE-2026-4271 and CVE-2026-5119.

Resolves: RHEL-151634
Resolves: RHEL-159788
Resolves: RHEL-167775
This commit is contained in:
Michael Catanzaro 2026-05-01 10:56:52 -05:00
parent 2eb78ac576
commit 0080cb6c4b
18 changed files with 503 additions and 1827 deletions

1
.gitignore vendored
View File

@ -19,3 +19,4 @@
/libsoup-3.6.1.tar.xz
/libsoup-3.6.3.tar.xz
/libsoup-3.6.5.tar.xz
/libsoup-3.6.6.tar.xz

View File

@ -1,30 +0,0 @@
From 9ba1243a24e442fa5ec44684617a4480027da960 Mon Sep 17 00:00:00 2001
From: Eugene Mutavchi <Ievgen_Mutavchi@comcast.com>
Date: Fri, 10 Oct 2025 16:24:27 +0000
Subject: [PATCH] fix 'heap-use-after-free' caused by 'finishing' queue item
twice
---
libsoup/soup-session.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index c5694d2c..4e6b478b 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -2894,8 +2894,10 @@ run_until_read_done (SoupMessage *msg,
if (soup_message_io_in_progress (msg))
soup_message_io_finished (msg);
item->paused = FALSE;
- item->state = SOUP_MESSAGE_FINISHING;
- soup_session_process_queue_item (item->session, item, FALSE);
+ if (item->state != SOUP_MESSAGE_FINISHED) {
+ item->state = SOUP_MESSAGE_FINISHING;
+ soup_session_process_queue_item (item->session, item, FALSE);
+ }
}
async_send_request_return_result (item, NULL, error);
soup_message_queue_item_unref (item);
--
GitLab

View File

@ -1,695 +0,0 @@
From 3c975f8d53df061538f865edfe35f9dd4b3e2f80 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Wed, 7 Jan 2026 14:50:33 -0600
Subject: [PATCH] Reject duplicate Host headers
RFC 9112 section 3.2 says:
A server MUST respond with a 400 (Bad Request) status code to any
HTTP/1.1 request message that lacks a Host header field and to any
request message that contains more than one Host header field line or a
Host header field with an invalid field value.
In addition to rejecting a duplicate header when parsing headers, also
reject attempts to add the duplicate header using the
soup_message_headers_append() API, and add tests for both cases.
These checks will also apply to HTTP/2. I'm not sure whether this is
actually desired or not, but the header processing code is not aware of
which HTTP version is in use.
(Note that while SoupMessageHeaders does not require the Host header to
be present in an HTTP/1.1 request, SoupServer itself does. So we can't
test the case of missing Host header via the header parsing test, but it
really is enforced.)
Fixes #472
---
libsoup/soup-headers.c | 3 +-
libsoup/soup-message-headers-private.h | 4 +-
libsoup/soup-message-headers.c | 78 +++++++++------
tests/header-parsing-test.c | 133 ++++++++++++++++---------
4 files changed, 137 insertions(+), 81 deletions(-)
diff --git a/libsoup/soup-headers.c b/libsoup/soup-headers.c
index 52ef2ece..9dcffbf2 100644
--- a/libsoup/soup-headers.c
+++ b/libsoup/soup-headers.c
@@ -139,7 +139,8 @@ soup_headers_parse (const char *str, int len, SoupMessageHeaders *dest)
for (p = strchr (value, '\r'); p; p = strchr (p, '\r'))
*p = ' ';
- soup_message_headers_append_untrusted_data (dest, name, value);
+ if (!soup_message_headers_append_untrusted_data (dest, name, value))
+ goto done;
}
success = TRUE;
diff --git a/libsoup/soup-message-headers-private.h b/libsoup/soup-message-headers-private.h
index 98154645..770f3ef1 100644
--- a/libsoup/soup-message-headers-private.h
+++ b/libsoup/soup-message-headers-private.h
@@ -10,10 +10,10 @@
G_BEGIN_DECLS
-void soup_message_headers_append_untrusted_data (SoupMessageHeaders *hdrs,
+gboolean soup_message_headers_append_untrusted_data (SoupMessageHeaders *hdrs,
const char *name,
const char *value);
-void soup_message_headers_append_common (SoupMessageHeaders *hdrs,
+gboolean soup_message_headers_append_common (SoupMessageHeaders *hdrs,
SoupHeaderName name,
const char *value);
const char *soup_message_headers_get_one_common (SoupMessageHeaders *hdrs,
diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c
index f101d4b4..b0d5828f 100644
--- a/libsoup/soup-message-headers.c
+++ b/libsoup/soup-message-headers.c
@@ -273,12 +273,16 @@ soup_message_headers_clean_connection_headers (SoupMessageHeaders *hdrs)
soup_header_free_list (tokens);
}
-void
+gboolean
soup_message_headers_append_common (SoupMessageHeaders *hdrs,
SoupHeaderName name,
const char *value)
{
SoupCommonHeader header;
+ if (name == SOUP_HEADER_HOST && soup_message_headers_get_one (hdrs, "Host")) {
+ g_warning ("Attempted to add duplicate Host header to a SoupMessageHeaders that already contains a Host header");
+ return FALSE;
+ }
if (!hdrs->common_headers)
hdrs->common_headers = g_array_sized_new (FALSE, FALSE, sizeof (SoupCommonHeader), 6);
@@ -290,33 +294,19 @@ soup_message_headers_append_common (SoupMessageHeaders *hdrs,
g_hash_table_remove (hdrs->common_concat, GUINT_TO_POINTER (header.name));
soup_message_headers_set (hdrs, name, value);
+ return TRUE;
}
-/**
- * soup_message_headers_append:
- * @hdrs: a #SoupMessageHeaders
- * @name: the header name to add
- * @value: the new value of @name
- *
- * Appends a new header with name @name and value @value to @hdrs.
- *
- * (If there is an existing header with name @name, then this creates a second
- * one, which is only allowed for list-valued headers; see also
- * [method@MessageHeaders.replace].)
- *
- * The caller is expected to make sure that @name and @value are
- * syntactically correct.
- **/
-void
-soup_message_headers_append (SoupMessageHeaders *hdrs,
- const char *name, const char *value)
+static gboolean
+soup_message_headers_append_internal (SoupMessageHeaders *hdrs,
+ const char *name, const char *value)
{
SoupUncommonHeader header;
SoupHeaderName header_name;
- g_return_if_fail (hdrs);
- g_return_if_fail (name != NULL);
- g_return_if_fail (value != NULL);
+ g_return_val_if_fail (hdrs, FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+ g_return_val_if_fail (value != NULL, FALSE);
/* Setting a syntactically invalid header name or value is
* considered to be a programming error. However, it can also
@@ -324,23 +314,22 @@ soup_message_headers_append (SoupMessageHeaders *hdrs,
* compiled with G_DISABLE_CHECKS.
*/
#ifndef G_DISABLE_CHECKS
- g_return_if_fail (*name && strpbrk (name, " \t\r\n:") == NULL);
- g_return_if_fail (strpbrk (value, "\r\n") == NULL);
+ g_return_val_if_fail (*name && strpbrk (name, " \t\r\n:") == NULL, FALSE);
+ g_return_val_if_fail (strpbrk (value, "\r\n") == NULL, FALSE);
#else
if (*name && strpbrk (name, " \t\r\n:")) {
g_warning ("soup_message_headers_append: Ignoring bad name '%s'", name);
- return;
+ return FALSE;
}
if (strpbrk (value, "\r\n")) {
g_warning ("soup_message_headers_append: Ignoring bad value '%s'", value);
- return;
+ return FALSE;
}
#endif
header_name = soup_header_name_from_string (name);
if (header_name != SOUP_HEADER_UNKNOWN) {
- soup_message_headers_append_common (hdrs, header_name, value);
- return;
+ return soup_message_headers_append_common (hdrs, header_name, value);
}
if (!hdrs->uncommon_headers)
@@ -351,21 +340,48 @@ soup_message_headers_append (SoupMessageHeaders *hdrs,
g_array_append_val (hdrs->uncommon_headers, header);
if (hdrs->uncommon_concat)
g_hash_table_remove (hdrs->uncommon_concat, header.name);
+ return TRUE;
+}
+
+/**
+ * soup_message_headers_append:
+ * @hdrs: a #SoupMessageHeaders
+ * @name: the header name to add
+ * @value: the new value of @name
+ *
+ * Appends a new header with name @name and value @value to @hdrs.
+ *
+ * (If there is an existing header with name @name, then this creates a second
+ * one, which is only allowed for list-valued headers; see also
+ * [method@MessageHeaders.replace].)
+ *
+ * The caller is expected to make sure that @name and @value are
+ * syntactically correct.
+ **/
+void
+soup_message_headers_append (SoupMessageHeaders *hdrs,
+ const char *name, const char *value)
+{
+ soup_message_headers_append_internal (hdrs, name, value);
}
/*
- * Appends a header value ensuring that it is valid UTF8.
+ * Appends a header value ensuring that it is valid UTF-8, and also checking the
+ * return value of soup_message_headers_append_internal() to report whether the
+ * headers are invalid for various other reasons.
*/
-void
+gboolean
soup_message_headers_append_untrusted_data (SoupMessageHeaders *hdrs,
const char *name,
const char *value)
{
char *safe_value = g_utf8_make_valid (value, -1);
char *safe_name = g_utf8_make_valid (name, -1);
- soup_message_headers_append (hdrs, safe_name, safe_value);
+ gboolean result = soup_message_headers_append_internal (hdrs, safe_name, safe_value);
+
g_free (safe_value);
g_free (safe_name);
+ return result;
}
void
diff --git a/tests/header-parsing-test.c b/tests/header-parsing-test.c
index 4faafbd6..ea42d5e6 100644
--- a/tests/header-parsing-test.c
+++ b/tests/header-parsing-test.c
@@ -24,6 +24,7 @@ static struct RequestTest {
const char *method, *path;
SoupHTTPVersion version;
Header headers[10];
+ GLogLevelFlags log_flags;
} reqtests[] = {
/**********************/
/*** VALID REQUESTS ***/
@@ -33,7 +34,7 @@ static struct RequestTest {
"GET / HTTP/1.0\r\n", -1,
SOUP_STATUS_OK,
"GET", "/", SOUP_HTTP_1_0,
- { { NULL } }
+ { { NULL } }, 0
},
{ "Req w/ 1 header", NULL,
@@ -42,7 +43,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Host", "example.com" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 1 header, no leading whitespace", NULL,
@@ -51,7 +52,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Host", "example.com" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 1 header including trailing whitespace", NULL,
@@ -60,7 +61,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Host", "example.com" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 1 header, wrapped", NULL,
@@ -69,7 +70,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Foo", "bar baz" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 1 header, wrapped with additional whitespace", NULL,
@@ -78,7 +79,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Foo", "bar baz" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 1 header, wrapped with tab", NULL,
@@ -87,7 +88,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Foo", "bar baz" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 1 header, wrapped before value", NULL,
@@ -96,7 +97,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Foo", "bar baz" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 1 header with empty value", NULL,
@@ -105,7 +106,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Host", "" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 2 headers", NULL,
@@ -115,7 +116,7 @@ static struct RequestTest {
{ { "Host", "example.com" },
{ "Connection", "close" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 3 headers", NULL,
@@ -126,7 +127,7 @@ static struct RequestTest {
{ "Connection", "close" },
{ "Blah", "blah" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 3 headers, 1st wrapped", NULL,
@@ -137,7 +138,7 @@ static struct RequestTest {
{ "Foo", "bar baz" },
{ "Blah", "blah" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 3 headers, 2nd wrapped", NULL,
@@ -148,7 +149,7 @@ static struct RequestTest {
{ "Blah", "blah" },
{ "Foo", "bar baz" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ 3 headers, 3rd wrapped", NULL,
@@ -159,7 +160,7 @@ static struct RequestTest {
{ "Blah", "blah" },
{ "Foo", "bar baz" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ same header multiple times", NULL,
@@ -168,7 +169,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Foo", "bar, baz, quux" },
{ NULL }
- }
+ }, 0
},
{ "Connection header on HTTP/1.0 message", NULL,
@@ -178,21 +179,21 @@ static struct RequestTest {
{ { "Connection", "Bar, Quux" },
{ "Foo", "bar" },
{ NULL }
- }
+ }, 0
},
{ "GET with full URI", "667637",
"GET http://example.com HTTP/1.1\r\n", -1,
SOUP_STATUS_OK,
"GET", "http://example.com", SOUP_HTTP_1_1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "GET with full URI in upper-case", "667637",
"GET HTTP://example.com HTTP/1.1\r\n", -1,
SOUP_STATUS_OK,
"GET", "HTTP://example.com", SOUP_HTTP_1_1,
- { { NULL } }
+ { { NULL } }, 0
},
/* It's better for this to be passed through: this means a SoupServer
@@ -202,7 +203,7 @@ static struct RequestTest {
"GET AbOuT: HTTP/1.1\r\n", -1,
SOUP_STATUS_OK,
"GET", "AbOuT:", SOUP_HTTP_1_1,
- { { NULL } }
+ { { NULL } }, 0
},
/****************************/
@@ -217,7 +218,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Host", "example.com" },
{ NULL }
- }
+ }, 0
},
/* RFC 2616 section 3.1 says we MUST accept this */
@@ -228,7 +229,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Host", "example.com" },
{ NULL }
- }
+ }, 0
},
/* RFC 2616 section 19.3 says we SHOULD accept these */
@@ -240,7 +241,7 @@ static struct RequestTest {
{ { "Host", "example.com" },
{ "Connection", "close" },
{ NULL }
- }
+ }, 0
},
{ "LF instead of CRLF after Request-Line", NULL,
@@ -249,7 +250,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Host", "example.com" },
{ NULL }
- }
+ }, 0
},
{ "Mixed CRLF/LF", "666316",
@@ -261,7 +262,7 @@ static struct RequestTest {
{ "e", "f" },
{ "g", "h" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ incorrect whitespace in Request-Line", NULL,
@@ -270,7 +271,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Host", "example.com" },
{ NULL }
- }
+ }, 0
},
{ "Req w/ incorrect whitespace after Request-Line", "475169",
@@ -279,7 +280,7 @@ static struct RequestTest {
"GET", "/", SOUP_HTTP_1_1,
{ { "Host", "example.com" },
{ NULL }
- }
+ }, 0
},
/* If the request/status line is parseable, then we
@@ -293,7 +294,7 @@ static struct RequestTest {
{ { "Host", "example.com" },
{ "Bar", "two" },
{ NULL }
- }
+ }, 0
},
{ "First header line is continuation", "666316",
@@ -303,7 +304,7 @@ static struct RequestTest {
{ { "Host", "example.com" },
{ "c", "d" },
{ NULL }
- }
+ }, 0
},
{ "Zero-length header name", "666316",
@@ -313,7 +314,7 @@ static struct RequestTest {
{ { "a", "b" },
{ "c", "d" },
{ NULL }
- }
+ }, 0
},
{ "CR in header name", "666316",
@@ -323,7 +324,7 @@ static struct RequestTest {
{ { "a", "b" },
{ "c", "d" },
{ NULL }
- }
+ }, 0
},
{ "CR in header value", "666316",
@@ -336,7 +337,7 @@ static struct RequestTest {
{ "s", "t" }, /* CR at end is ignored */
{ "c", "d" },
{ NULL }
- }
+ }, 0
},
{ "Tab in header name", "666316",
@@ -351,7 +352,7 @@ static struct RequestTest {
{ "p", "q z: w" },
{ "c", "d" },
{ NULL }
- }
+ }, 0
},
{ "Tab in header value", "666316",
@@ -364,7 +365,7 @@ static struct RequestTest {
{ "z", "w" }, /* trailing tab ignored */
{ "c", "d" },
{ NULL }
- }
+ }, 0
},
/************************/
@@ -375,77 +376,77 @@ static struct RequestTest {
"GET /\r\n", -1,
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "HTTP 1.2 request (no such thing)", NULL,
"GET / HTTP/1.2\r\n", -1,
SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "HTTP 2000 request (no such thing)", NULL,
"GET / HTTP/2000.0\r\n", -1,
SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "Long HTTP version terminating at missing minor version", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/404",
unterminated_http_version, sizeof (unterminated_http_version),
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "Non-HTTP request", NULL,
"GET / SOUP/1.1\r\nHost: example.com\r\n", -1,
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "Junk after Request-Line", NULL,
"GET / HTTP/1.1 blah\r\nHost: example.com\r\n", -1,
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "NUL in Method", NULL,
"G\x00T / HTTP/1.1\r\nHost: example.com\r\n", 37,
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "NUL at beginning of Method", "666316",
"\x00 / HTTP/1.1\r\nHost: example.com\r\n", 35,
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "NUL in Path", NULL,
"GET /\x00 HTTP/1.1\r\nHost: example.com\r\n", 38,
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "No terminating CRLF", NULL,
"GET / HTTP/1.1\r\nHost: example.com", -1,
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "Unrecognized expectation", NULL,
"GET / HTTP/1.1\r\nHost: example.com\r\nExpect: the-impossible\r\n", -1,
SOUP_STATUS_EXPECTATION_FAILED,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
// https://gitlab.gnome.org/GNOME/libsoup/-/issues/377
@@ -453,21 +454,31 @@ static struct RequestTest {
"GET / HTTP/1.1\r\nHost\x00: example.com\r\n", 36,
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "NUL in header value", NULL,
"HTTP/1.1 200 OK\r\nFoo: b\x00" "ar\r\n", 28,
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
},
{ "Only newlines", NULL,
only_newlines, sizeof (only_newlines),
SOUP_STATUS_BAD_REQUEST,
NULL, NULL, -1,
- { { NULL } }
+ { { NULL } }, 0
+ },
+
+ { "Duplicate Host headers",
+ "https://gitlab.gnome.org/GNOME/libsoup/-/issues/472",
+ "GET / HTTP/1.1\r\nHost: example.com\r\nHost: example.org\r\n",
+ -1,
+ SOUP_STATUS_BAD_REQUEST,
+ NULL, NULL, -1,
+ { { NULL } },
+ G_LOG_LEVEL_WARNING
}
};
static const int num_reqtests = G_N_ELEMENTS (reqtests);
@@ -915,10 +926,17 @@ do_request_tests (void)
len = strlen (reqtests[i].request);
else
len = reqtests[i].length;
+
+ if (reqtests[i].log_flags)
+ g_test_expect_message ("libsoup", reqtests[i].log_flags, "*");
+
status = soup_headers_parse_request (reqtests[i].request, len,
headers, &method, &path,
&version);
g_assert_cmpint (status, ==, reqtests[i].status);
+ if (reqtests[i].log_flags)
+ g_test_assert_expected_messages ();
+
if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
g_assert_cmpstr (method, ==, reqtests[i].method);
g_assert_cmpstr (path, ==, reqtests[i].path);
@@ -1314,6 +1332,25 @@ do_bad_header_tests (void)
soup_message_headers_unref (hdrs);
}
+static void
+do_append_duplicate_host_test (void)
+{
+ SoupMessageHeaders *hdrs;
+ const char *list_value;
+
+ hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST);
+ soup_message_headers_append (hdrs, "Host", "a");
+ g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING,
+ "Attempted to add duplicate Host header to a SoupMessageHeaders that already contains a Host header");
+ soup_message_headers_append (hdrs, "Host", "b");
+ g_test_assert_expected_messages ();
+
+ list_value = soup_message_headers_get_list (hdrs, "Host");
+ g_assert_cmpstr (list_value, ==, "a");
+
+ soup_message_headers_unref (hdrs);
+}
+
int
main (int argc, char **argv)
{
@@ -1330,6 +1367,8 @@ main (int argc, char **argv)
g_test_add_func ("/header-parsing/append-param", do_append_param_tests);
g_test_add_func ("/header-parsing/bad", do_bad_header_tests);
+ g_test_add_func ("/header-parsing/append-duplicate-host", do_append_duplicate_host_test);
+
ret = g_test_run ();
test_cleanup ();
--
2.52.0

View File

@ -1,30 +0,0 @@
From 84e601252a9ae5eafaba9cb9cb5e4bd77ca41bdb Mon Sep 17 00:00:00 2001
From: Milan Crha <mcrha@redhat.com>
Date: Tue, 15 Apr 2025 12:17:39 +0200
Subject: [PATCH] soup-message-headers: Correct merge of ranges
It had been skipping every second range, which generated an array
of a lot of insane ranges, causing large memory usage by the server.
Closes #428
---
libsoup/soup-message-headers.c | 1 +
tests/meson.build | 1 +
tests/server-mem-limit-test.c | 144 +++++++++++++++++++++++++++++++++
3 files changed, 146 insertions(+)
create mode 100644 tests/server-mem-limit-test.c
diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c
index 64847e30..f612bff1 100644
--- a/libsoup/soup-message-headers.c
+++ b/libsoup/soup-message-headers.c
@@ -1024,6 +1024,7 @@ soup_message_headers_get_ranges_internal (SoupMessageHeaders *hdrs,
if (cur->start <= prev->end) {
prev->end = MAX (prev->end, cur->end);
g_array_remove_index (array, i);
+ i--;
}
}
}
--
2.49.0

View File

@ -1,130 +0,0 @@
From a792b23ab87cacbf4dd9462bf7b675fa678efbae Mon Sep 17 00:00:00 2001
From: Milan Crha <mcrha@redhat.com>
Date: Tue, 15 Apr 2025 09:59:05 +0200
Subject: [PATCH] soup-server-http2: Check validity of the constructed
connection URI
The HTTP/2 pseudo-headers can contain invalid values, which the GUri rejects
and returns NULL, but the soup-server did not check the validity and could
abort the server itself later in the code.
Closes #429
---
.../http2/soup-server-message-io-http2.c | 4 +++
tests/http2-test.c | 28 +++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/libsoup/server/http2/soup-server-message-io-http2.c b/libsoup/server/http2/soup-server-message-io-http2.c
index 943ecfd3..f1fe2d5c 100644
--- a/libsoup/server/http2/soup-server-message-io-http2.c
+++ b/libsoup/server/http2/soup-server-message-io-http2.c
@@ -771,9 +771,13 @@ on_frame_recv_callback (nghttp2_session *session,
char *uri_string;
GUri *uri;
+ if (msg_io->scheme == NULL || msg_io->authority == NULL || msg_io->path == NULL)
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
uri_string = g_strdup_printf ("%s://%s%s", msg_io->scheme, msg_io->authority, msg_io->path);
uri = g_uri_parse (uri_string, SOUP_HTTP_URI_FLAGS, NULL);
g_free (uri_string);
+ if (uri == NULL)
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
soup_server_message_set_uri (msg_io->msg, uri);
g_uri_unref (uri);
diff --git a/tests/http2-test.c b/tests/http2-test.c
index 5b6da5e4..ec7972fe 100644
--- a/tests/http2-test.c
+++ b/tests/http2-test.c
@@ -1341,6 +1341,30 @@ do_connection_closed_test (Test *test, gconstpointer data)
g_uri_unref (uri);
}
+static void
+do_broken_pseudo_header_test (Test *test, gconstpointer data)
+{
+ char *path;
+ SoupMessage *msg;
+ GUri *uri;
+ GBytes *body = NULL;
+ GError *error = NULL;
+
+ uri = g_uri_parse_relative (base_uri, "/ag", SOUP_HTTP_URI_FLAGS, NULL);
+
+ /* an ugly cheat to construct a broken URI, which can be sent from other libs */
+ path = (char *) g_uri_get_path (uri);
+ path[1] = '%';
+
+ msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+ body = soup_test_session_async_send (test->session, msg, NULL, &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT);
+ g_assert_null (body);
+ g_clear_error (&error);
+ g_object_unref (msg);
+ g_uri_unref (uri);
+}
+
static gboolean
unpause_message (SoupServerMessage *msg)
{
@@ -1662,6 +1686,10 @@ main (int argc, char **argv)
setup_session,
do_connection_closed_test,
teardown_session);
+ g_test_add ("/http2/broken-pseudo-header", Test, NULL,
+ setup_session,
+ do_broken_pseudo_header_test,
+ teardown_session);
ret = g_test_run ();
--
GitLab
From 527428a033df573ef4558ce1106e080fd9ec5c71 Mon Sep 17 00:00:00 2001
From: Milan Crha <mcrha@redhat.com>
Date: Mon, 28 Apr 2025 10:55:42 +0200
Subject: [PATCH] soup-server-http2: Correct check of the validity of the
constructed connection URI
RFC 5740: the CONNECT has unset the "scheme" and "path", thus allow them unset.
The commit a792b23ab87cacbf4dd9462bf7b675fa678efbae also missed to decrement
the `io->in_callback` in the early returns.
Related to #429
---
.../server/http2/soup-server-message-io-http2.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/libsoup/server/http2/soup-server-message-io-http2.c b/libsoup/server/http2/soup-server-message-io-http2.c
index f1fe2d5c..913afb46 100644
--- a/libsoup/server/http2/soup-server-message-io-http2.c
+++ b/libsoup/server/http2/soup-server-message-io-http2.c
@@ -771,13 +771,18 @@ on_frame_recv_callback (nghttp2_session *session,
char *uri_string;
GUri *uri;
- if (msg_io->scheme == NULL || msg_io->authority == NULL || msg_io->path == NULL)
- return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
- uri_string = g_strdup_printf ("%s://%s%s", msg_io->scheme, msg_io->authority, msg_io->path);
+ if (msg_io->authority == NULL) {
+ io->in_callback--;
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
+ /* RFC 5740: the CONNECT has unset the "scheme" and "path", but the GUri requires the scheme, thus let it be "(null)" */
+ uri_string = g_strdup_printf ("%s://%s%s", msg_io->scheme, msg_io->authority, msg_io->path == NULL ? "" : msg_io->path);
uri = g_uri_parse (uri_string, SOUP_HTTP_URI_FLAGS, NULL);
g_free (uri_string);
- if (uri == NULL)
- return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ if (uri == NULL) {
+ io->in_callback--;
+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+ }
soup_server_message_set_uri (msg_io->msg, uri);
g_uri_unref (uri);
--
GitLab

View File

@ -1,107 +0,0 @@
From 5bfcf8157597f2d327050114fb37ff600004dbcf Mon Sep 17 00:00:00 2001
From: Milan Crha <mcrha@redhat.com>
Date: Tue, 15 Apr 2025 09:03:00 +0200
Subject: [PATCH] multipart: Fix read out of buffer bounds under
soup_multipart_new_from_message()
This is CVE-2025-32914, special crafted input can cause read out of buffer bounds
of the body argument.
Closes #436
---
libsoup/soup-multipart.c | 2 +-
tests/multipart-test.c | 58 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/libsoup/soup-multipart.c b/libsoup/soup-multipart.c
index 2421c91f8..102ce3722 100644
--- a/libsoup/soup-multipart.c
+++ b/libsoup/soup-multipart.c
@@ -173,7 +173,7 @@ soup_multipart_new_from_message (SoupMessageHeaders *headers,
return NULL;
}
- split = strstr (start, "\r\n\r\n");
+ split = g_strstr_len (start, body_end - start, "\r\n\r\n");
if (!split || split > end) {
soup_multipart_free (multipart);
return NULL;
diff --git a/tests/multipart-test.c b/tests/multipart-test.c
index 2c0e7e969..f5b986889 100644
--- a/tests/multipart-test.c
+++ b/tests/multipart-test.c
@@ -471,6 +471,62 @@ test_multipart (gconstpointer data)
loop = NULL;
}
+static void
+test_multipart_bounds_good (void)
+{
+ #define TEXT "line1\r\nline2"
+ SoupMultipart *multipart;
+ SoupMessageHeaders *headers, *set_headers = NULL;
+ GBytes *bytes, *set_bytes = NULL;
+ const char *raw_data = "--123\r\nContent-Type: text/plain;\r\n\r\n" TEXT "\r\n--123--\r\n";
+ gboolean success;
+
+ headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+ soup_message_headers_append (headers, "Content-Type", "multipart/mixed; boundary=\"123\"");
+
+ bytes = g_bytes_new (raw_data, strlen (raw_data));
+
+ multipart = soup_multipart_new_from_message (headers, bytes);
+
+ g_assert_nonnull (multipart);
+ g_assert_cmpint (soup_multipart_get_length (multipart), ==, 1);
+ success = soup_multipart_get_part (multipart, 0, &set_headers, &set_bytes);
+ g_assert_true (success);
+ g_assert_nonnull (set_headers);
+ g_assert_nonnull (set_bytes);
+ g_assert_cmpint (strlen (TEXT), ==, g_bytes_get_size (set_bytes));
+ g_assert_cmpstr ("text/plain", ==, soup_message_headers_get_content_type (set_headers, NULL));
+ g_assert_cmpmem (TEXT, strlen (TEXT), g_bytes_get_data (set_bytes, NULL), g_bytes_get_size (set_bytes));
+
+ soup_message_headers_unref (headers);
+ g_bytes_unref (bytes);
+
+ soup_multipart_free (multipart);
+
+ #undef TEXT
+}
+
+static void
+test_multipart_bounds_bad (void)
+{
+ SoupMultipart *multipart;
+ SoupMessageHeaders *headers;
+ GBytes *bytes;
+ const char *raw_data = "--123\r\nContent-Type: text/plain;\r\nline1\r\nline2\r\n--123--\r\n";
+
+ headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+ soup_message_headers_append (headers, "Content-Type", "multipart/mixed; boundary=\"123\"");
+
+ bytes = g_bytes_new (raw_data, strlen (raw_data));
+
+ /* it did read out of raw_data/bytes bounds */
+ multipart = soup_multipart_new_from_message (headers, bytes);
+ g_assert_null (multipart);
+
+ soup_message_headers_unref (headers);
+ g_bytes_unref (bytes);
+}
+
int
main (int argc, char **argv)
{
@@ -498,6 +554,8 @@ 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-good", test_multipart_bounds_good);
+ g_test_add_func ("/multipart/bounds-bad", test_multipart_bounds_bad);
ret = g_test_run ();
--
GitLab

View File

@ -1,7 +1,7 @@
From 845e198402377f8644e9ae5200f1715df1ddfc08 Mon Sep 17 00:00:00 2001
From 3b0a3a4a9c751e2874d8cfb7b61349881ba2114a Mon Sep 17 00:00:00 2001
From: Patrick Griffis <pgriffis@igalia.com>
Date: Thu, 27 Mar 2025 14:43:26 -0500
Subject: [PATCH 1/3] cookie: Always normalize domain value
Subject: [PATCH] cookie: Always normalize domain value
In order for libpsl to give accurate results the domain must be lowercased.
@ -16,7 +16,7 @@ To make it easiest we normalize it at construction time of the cookie.
6 files changed, 76 insertions(+), 7 deletions(-)
diff --git a/libsoup/cookies/soup-cookie-jar.c b/libsoup/cookies/soup-cookie-jar.c
index fac53a5f9..7f92ace1f 100644
index 20805c20..51c51d3c 100644
--- a/libsoup/cookies/soup-cookie-jar.c
+++ b/libsoup/cookies/soup-cookie-jar.c
@@ -519,6 +519,7 @@ incoming_cookie_is_third_party (SoupCookieJar *jar,
@ -48,7 +48,7 @@ index fac53a5f9..7f92ace1f 100644
if (policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
return TRUE;
diff --git a/libsoup/cookies/soup-cookie.c b/libsoup/cookies/soup-cookie.c
index cc80d001f..b6d771646 100644
index d4a9f4c0..e0dbbdda 100644
--- a/libsoup/cookies/soup-cookie.c
+++ b/libsoup/cookies/soup-cookie.c
@@ -168,6 +168,16 @@ parse_date (const char **val_p)
@ -109,7 +109,7 @@ index cc80d001f..b6d771646 100644
/**
diff --git a/libsoup/soup-tld.c b/libsoup/soup-tld.c
index 2d8151662..71fac5749 100644
index 02e54059..ee98c459 100644
--- a/libsoup/soup-tld.c
+++ b/libsoup/soup-tld.c
@@ -15,6 +15,7 @@
@ -152,23 +152,21 @@ index 2d8151662..71fac5749 100644
/**
diff --git a/libsoup/soup-uri-utils-private.h b/libsoup/soup-uri-utils-private.h
index 0119f0814..c2f984f86 100644
index a73e8821..83c4fb36 100644
--- a/libsoup/soup-uri-utils-private.h
+++ b/libsoup/soup-uri-utils-private.h
@@ -28,6 +28,8 @@ GUri *soup_uri_copy_with_normalized_flags (GUri *uri);
@@ -30,4 +30,6 @@ GUri *soup_uri_copy_with_normalized_flags (GUri *uri);
char *soup_uri_get_host_for_headers (GUri *uri);
+char *soup_uri_normalize_domain (const char *domain);
+
#define SOUP_URI_IS_VALID(x) ((x) && g_uri_get_host(x) && g_uri_get_host(x)[0])
G_END_DECLS
diff --git a/libsoup/soup-uri-utils.c b/libsoup/soup-uri-utils.c
index 0963a1143..1f65faede 100644
index 44cc59b1..63262018 100644
--- a/libsoup/soup-uri-utils.c
+++ b/libsoup/soup-uri-utils.c
@@ -506,3 +506,21 @@ soup_uri_get_host_for_headers (GUri *uri)
@@ -566,3 +566,21 @@ soup_uri_get_host_for_headers (GUri *uri)
return g_strdup (host);
}
@ -191,10 +189,10 @@ index 0963a1143..1f65faede 100644
+ return normalized;
+}
diff --git a/tests/cookies-test.c b/tests/cookies-test.c
index 1d2d45630..7007aaf59 100644
index 27cf6d9b..f3dfd6df 100644
--- a/tests/cookies-test.c
+++ b/tests/cookies-test.c
@@ -695,6 +695,24 @@ do_cookies_threads_test (void)
@@ -715,6 +715,24 @@ do_cookies_threads_test (void)
soup_test_session_abort_unref (session);
}
@ -219,7 +217,7 @@ index 1d2d45630..7007aaf59 100644
int
main (int argc, char **argv)
{
@@ -726,6 +744,7 @@ main (int argc, char **argv)
@@ -748,6 +766,7 @@ main (int argc, char **argv)
g_test_add_func ("/cookies/secure-cookies", do_cookies_strict_secure_test);
g_test_add_func ("/cookies/prefix", do_cookies_prefix_test);
g_test_add_func ("/cookies/threads", do_cookies_threads_test);
@ -228,4 +226,5 @@ index 1d2d45630..7007aaf59 100644
ret = g_test_run ();
--
GitLab
2.54.0

View File

@ -1,245 +0,0 @@
From 8988379984e33dcc7d3aa58551db13e48755959f Mon Sep 17 00:00:00 2001
From: Milan Crha <mcrha@redhat.com>
Date: Thu, 15 May 2025 07:59:14 +0200
Subject: [PATCH 1/2] soup-date-utils: Add value checks for date/time parsing
Reject date/time when it does not represent a valid value.
Closes https://gitlab.gnome.org/GNOME/libsoup/-/issues/448
---
libsoup/soup-date-utils.c | 23 +++++++++++++++--------
tests/cookies-test.c | 10 ++++++++++
2 files changed, 25 insertions(+), 8 deletions(-)
diff --git a/libsoup/soup-date-utils.c b/libsoup/soup-date-utils.c
index fd785f509..34ca99503 100644
--- a/libsoup/soup-date-utils.c
+++ b/libsoup/soup-date-utils.c
@@ -129,7 +129,7 @@ parse_day (int *day, const char **date_string)
while (*end == ' ' || *end == '-')
end++;
*date_string = end;
- return TRUE;
+ return *day >= 1 && *day <= 31;
}
static inline gboolean
@@ -169,7 +169,7 @@ parse_year (int *year, const char **date_string)
while (*end == ' ' || *end == '-')
end++;
*date_string = end;
- return TRUE;
+ return *year > 0 && *year < 9999;
}
static inline gboolean
@@ -193,7 +193,7 @@ parse_time (int *hour, int *minute, int *second, const char **date_string)
while (*p == ' ')
p++;
*date_string = p;
- return TRUE;
+ return *hour >= 0 && *hour < 24 && *minute >= 0 && *minute < 60 && *second >= 0 && *second < 60;
}
static inline gboolean
@@ -209,9 +209,14 @@ parse_timezone (GTimeZone **timezone, const char **date_string)
gulong val;
int sign = (**date_string == '+') ? 1 : -1;
val = strtoul (*date_string + 1, (char **)date_string, 10);
- if (**date_string == ':')
- val = 60 * val + strtoul (*date_string + 1, (char **)date_string, 10);
- else
+ if (val > 9999)
+ return FALSE;
+ if (**date_string == ':') {
+ gulong val2 = strtoul (*date_string + 1, (char **)date_string, 10);
+ if (val > 99 || val2 > 99)
+ return FALSE;
+ val = 60 * val + val2;
+ } else
val = 60 * (val / 100) + (val % 100);
offset_minutes = sign * val;
utc = (sign == -1) && !val;
@@ -264,7 +269,8 @@ parse_textual_date (const char *date_string)
if (!parse_month (&month, &date_string) ||
!parse_day (&day, &date_string) ||
!parse_time (&hour, &minute, &second, &date_string) ||
- !parse_year (&year, &date_string))
+ !parse_year (&year, &date_string) ||
+ !g_date_valid_dmy (day, month, year))
return NULL;
/* There shouldn't be a timezone, but check anyway */
@@ -276,7 +282,8 @@ parse_textual_date (const char *date_string)
if (!parse_day (&day, &date_string) ||
!parse_month (&month, &date_string) ||
!parse_year (&year, &date_string) ||
- !parse_time (&hour, &minute, &second, &date_string))
+ !parse_time (&hour, &minute, &second, &date_string) ||
+ !g_date_valid_dmy (day, month, year))
return NULL;
/* This time there *should* be a timezone, but we
diff --git a/tests/cookies-test.c b/tests/cookies-test.c
index 1d2d45630..ff809a400 100644
--- a/tests/cookies-test.c
+++ b/tests/cookies-test.c
@@ -460,6 +460,15 @@ do_cookies_parsing_max_age_long_overflow (void)
soup_cookie_free (cookie);
}
+static void
+do_cookies_parsing_int32_overflow (void)
+{
+ SoupCookie *cookie = soup_cookie_parse ("Age=1;expires=3Mar9 999:9:9+ 999999999-age=main=gne=", NULL);
+ g_assert_nonnull (cookie);
+ g_assert_null (soup_cookie_get_expires (cookie));
+ soup_cookie_free (cookie);
+}
+
static void
do_cookies_equal_nullpath (void)
{
@@ -718,6 +727,7 @@ main (int argc, char **argv)
g_test_add_func ("/cookies/parsing/no-path-null-origin", do_cookies_parsing_nopath_nullorigin);
g_test_add_func ("/cookies/parsing/max-age-int32-overflow", do_cookies_parsing_max_age_int32_overflow);
g_test_add_func ("/cookies/parsing/max-age-long-overflow", do_cookies_parsing_max_age_long_overflow);
+ g_test_add_func ("/cookies/parsing/int32-overflow", do_cookies_parsing_int32_overflow);
g_test_add_func ("/cookies/parsing/equal-nullpath", do_cookies_equal_nullpath);
g_test_add_func ("/cookies/parsing/control-characters", do_cookies_parsing_control_characters);
g_test_add_func ("/cookies/parsing/name-value-max-size", do_cookies_parsing_name_value_max_size);
--
GitLab
From 0d6bfa52da7313de848fb13fcfdbc561c04afef8 Mon Sep 17 00:00:00 2001
From: Brian Yurko <155515-byurko@users.noreply.gitlab.gnome.org>
Date: Wed, 11 Jun 2025 11:00:56 -0400
Subject: [PATCH 2/2] tests: Add tests for date-time including timezone
validation work
These tests are built on top of earlier work in a related pull request.
Closes #448
---
libsoup/soup-date-utils.c | 8 ++++----
tests/cookies-test.c | 1 +
tests/date-test.c | 37 ++++++++++++++++++++++++++++++-------
3 files changed, 35 insertions(+), 11 deletions(-)
diff --git a/libsoup/soup-date-utils.c b/libsoup/soup-date-utils.c
index 34ca99503..73f80ab60 100644
--- a/libsoup/soup-date-utils.c
+++ b/libsoup/soup-date-utils.c
@@ -40,7 +40,7 @@ soup_date_time_is_past (GDateTime *date)
g_return_val_if_fail (date != NULL, TRUE);
/* optimization */
- if (g_date_time_get_year (date) < 2020)
+ if (g_date_time_get_year (date) < 2025)
return TRUE;
return g_date_time_to_unix (date) < time (NULL);
@@ -219,15 +219,15 @@ parse_timezone (GTimeZone **timezone, const char **date_string)
} else
val = 60 * (val / 100) + (val % 100);
offset_minutes = sign * val;
- utc = (sign == -1) && !val;
+ utc = (sign == -1) && !val;
} else if (**date_string == 'Z') {
offset_minutes = 0;
- utc = TRUE;
+ utc = TRUE;
(*date_string)++;
} else if (!strcmp (*date_string, "GMT") ||
!strcmp (*date_string, "UTC")) {
offset_minutes = 0;
- utc = TRUE;
+ utc = TRUE;
(*date_string) += 3;
} else if (strchr ("ECMP", **date_string) &&
((*date_string)[1] == 'D' || (*date_string)[1] == 'S') &&
diff --git a/tests/cookies-test.c b/tests/cookies-test.c
index ff809a400..18c9b60d8 100644
--- a/tests/cookies-test.c
+++ b/tests/cookies-test.c
@@ -464,6 +464,7 @@ static void
do_cookies_parsing_int32_overflow (void)
{
SoupCookie *cookie = soup_cookie_parse ("Age=1;expires=3Mar9 999:9:9+ 999999999-age=main=gne=", NULL);
+ g_test_bug ("https://gitlab.gnome.org/GNOME/libsoup/-/issues/448");
g_assert_nonnull (cookie);
g_assert_null (soup_cookie_get_expires (cookie));
soup_cookie_free (cookie);
diff --git a/tests/date-test.c b/tests/date-test.c
index 7eefd7c00..abf89ddd1 100644
--- a/tests/date-test.c
+++ b/tests/date-test.c
@@ -92,6 +92,11 @@ static const OkDate ok_dates[] = {
{ "Sat, 06 Nov 2004 08:09:07", NULL },
{ "06 Nov 2004 08:09:07 GMT", NULL },
{ "SAT, 06 NOV 2004 08:09:07 +1000", "644048" },
+ { "Sat, 06-Nov-2004 08:09:07 -10000", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+ { "Sat, 06-Nov-2004 08:09:07 +01:30", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+ { "Sat, 06-Nov-2004 08:09:07 +0:180", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+ { "Sat, 06-Nov-2004 08:09:07 +100:100", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+ { "Sat, 06-Nov-2004 08:09:07 Z", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
/* rfc850-date, and broken variants */
{ "Saturday, 06-Nov-04 08:09:07 GMT", NULL },
@@ -109,6 +114,8 @@ static const OkDate ok_dates[] = {
{ "Sat Nov 06 08:09:07 2004", NULL },
{ "Sat Nov 6 08:09:07 2004", NULL },
{ "Sat Nov 6 08:09:07 2004 GMT", NULL },
+ { "Sat Nov 6 08:09:07 2004 NoZone", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
+ { "Sat Nov 6 08:09:07 2004 UTC", "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" },
/* Netscape cookie spec date, and broken variants */
{ "Sat, 06-Nov-2004 08:09:07 GMT", NULL },
@@ -182,7 +189,23 @@ static const BadDate bad_dates[] = {
{ "Sat Nov 6 08::07 2004", NULL },
{ "Sat Nov 6 08:09: 2004", NULL },
{ "Sat Nov 6 08:09:07", NULL },
- { "Sat Nov 6 08:09:07 GMT 2004", NULL }
+ { "Sat Nov 6 08:09:07 GMT 2004", NULL },
+
+ /* range constraints added "https://gitlab.gnome.org/GNOME/libsoup/-/issues/448" */
+ { "Sat, 00-Nov-2004 08:09:07 GMT", NULL },
+ { "Sat, 32-Nov-2004 08:09:07 GMT", NULL },
+ { "Sat, 06-Nov-0 08:09:07 GMT", NULL },
+ { "Sat, 06-Nov-9999 08:09:07 GMT", NULL },
+ { "Sat, 06-Nov-2004 0-1:09:07 GMT", NULL },
+ { "(Sat), Nov 6 -1:09:07 2004", NULL },
+ { "Sat, 06-Nov-2004 24:09:07 GMT", NULL },
+ { "Sat, 06-Nov-2004 08:-1:07 GMT", NULL },
+ { "Sat, 06-Nov-2004 08:60:07 GMT", NULL },
+ { "Sat, 06-Nov-2004 08:09:-10 GMT", NULL },
+ { "Sat, 06-Nov-2004 08:09:60 GMT", NULL },
+ { "Sat, 06-Nov-71 08:09:99 UTC", NULL },
+ { "Sat, 31-Feb-2004 08:09:07 UTC", NULL },
+ { "2004-11-06T08:09:07Z", NULL }
};
static void
@@ -198,12 +221,12 @@ check_bad (gconstpointer data)
soup_test_assert (date == NULL,
"date parsing succeeded for '%s': %d %d %d - %d %d %d",
bad->date,
- g_date_time_get_year (date),
- g_date_time_get_month (date),
- g_date_time_get_day_of_month (date),
- g_date_time_get_hour (date),
- g_date_time_get_minute (date),
- g_date_time_get_second (date));
+ g_date_time_get_year (date),
+ g_date_time_get_month (date),
+ g_date_time_get_day_of_month (date),
+ g_date_time_get_hour (date),
+ g_date_time_get_minute (date),
+ g_date_time_get_second (date));
g_clear_pointer (&date, g_date_time_unref);
}
--
GitLab

View File

@ -1,89 +0,0 @@
From 66521f00e9f87f709d8ad9138f19052db933cf06 Mon Sep 17 00:00:00 2001
From: Milan Crha <mcrha@redhat.com>
Date: Thu, 15 May 2025 17:49:11 +0200
Subject: [PATCH] soup-multipart: Verify boundary limits for multipart body
It could happen that the boundary started at a place which resulted into
a negative number, which in an unsigned integer is a very large value.
Check the body size is not a negative value before setting it.
Closes https://gitlab.gnome.org/GNOME/libsoup/-/issues/449
---
libsoup/soup-multipart.c | 2 +-
tests/multipart-test.c | 40 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/libsoup/soup-multipart.c b/libsoup/soup-multipart.c
index 102ce372..a587fe7c 100644
--- a/libsoup/soup-multipart.c
+++ b/libsoup/soup-multipart.c
@@ -204,7 +204,7 @@ soup_multipart_new_from_message (SoupMessageHeaders *headers,
*/
part_body = g_bytes_new_from_bytes (body, // FIXME
split - body_data,
- end - 2 - split);
+ end - 2 >= split ? end - 2 - split : 0);
g_ptr_array_add (multipart->bodies, part_body);
start = end;
diff --git a/tests/multipart-test.c b/tests/multipart-test.c
index f5b98688..92b673eb 100644
--- a/tests/multipart-test.c
+++ b/tests/multipart-test.c
@@ -527,6 +527,45 @@ test_multipart_bounds_bad (void)
g_bytes_unref (bytes);
}
+static void
+test_multipart_too_large (void)
+{
+ const char *raw_body =
+ "-------------------\r\n"
+ "-\n"
+ "Cont\"\r\n"
+ "Content-Tynt----e:n\x8erQK\r\n"
+ "Content-Disposition: name= form-; name=\"file\"; filename=\"ype:i/ -d; ----\xae\r\n"
+ "Content-Typimag\x01/png--\\\n"
+ "\r\n"
+ "---:\n\r\n"
+ "\r\n"
+ "-------------------------------------\r\n"
+ "---------\r\n"
+ "----------------------";
+ GBytes *body;
+ GHashTable *params;
+ SoupMessageHeaders *headers;
+ SoupMultipart *multipart;
+
+ params = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (params, (gpointer) "boundary", (gpointer) "-----------------");
+ headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+ soup_message_headers_set_content_type (headers, "multipart/form-data", params);
+ g_hash_table_unref (params);
+
+ body = g_bytes_new_static (raw_body, strlen (raw_body));
+ multipart = soup_multipart_new_from_message (headers, body);
+ soup_message_headers_unref (headers);
+ g_bytes_unref (body);
+
+ g_assert_nonnull (multipart);
+ g_assert_cmpint (soup_multipart_get_length (multipart), ==, 1);
+ g_assert_true (soup_multipart_get_part (multipart, 0, &headers, &body));
+ g_assert_cmpint (g_bytes_get_size (body), ==, 0);
+ soup_multipart_free (multipart);
+}
+
int
main (int argc, char **argv)
{
@@ -556,6 +595,7 @@ main (int argc, char **argv)
g_test_add_data_func ("/multipart/async-small-reads", GINT_TO_POINTER (ASYNC_MULTIPART_SMALL_READS), test_multipart);
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/too-large", test_multipart_too_large);
ret = g_test_run ();
--
GitLab

View File

@ -1,185 +0,0 @@
From 427a5ed7048dda4d22f13c164a3a439e68604406 Mon Sep 17 00:00:00 2001
From: Mike Gorse <mgorse@suse.com>
Date: Thu, 8 Jan 2026 16:19:37 -0600
Subject: [PATCH] soup-auth-ntlm: Reject excessively long passwords
According to
https://learn.microsoft.com/en-us/troubleshoot/windows-server/windows-security/ntlm-user-authentication,
the practical limit for a NTLM password is 128 Unicode characters, so it
should be safe to reject passwords longer than 256 bytes. Previously,
md4sum could overflow and cause an out-of-bounds memory access if an
extremely long password was provided. Also update md4sum to use unsigned
variables for size-related calculations, as a precaution.
This is CVE-2026-0719.
Closes #477.
---
libsoup/auth/soup-auth-ntlm.c | 27 +++++++++++----
tests/ntlm-test.c | 64 +++++++++++++++++++++++++++++++++++
2 files changed, 84 insertions(+), 7 deletions(-)
diff --git a/libsoup/auth/soup-auth-ntlm.c b/libsoup/auth/soup-auth-ntlm.c
index dc440ad1..a338389b 100644
--- a/libsoup/auth/soup-auth-ntlm.c
+++ b/libsoup/auth/soup-auth-ntlm.c
@@ -355,6 +355,14 @@ soup_auth_ntlm_update_connection (SoupConnectionAuth *auth, SoupMessage *msg,
return FALSE;
}
+ if (priv->password_state == SOUP_NTLM_PASSWORD_PROVIDED && !priv->nt_hash[0]) {
+ /* This can happen if an excessively long password was
+ * provided, in which case we don't try to hash */
+ conn->state = SOUP_NTLM_FAILED;
+ priv->password_state = SOUP_NTLM_PASSWORD_REJECTED;
+ return TRUE;
+ }
+
if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce,
priv->domain ? NULL : &priv->domain,
&conn->ntlmv2_session, &conn->negotiate_target,
@@ -449,8 +457,10 @@ soup_auth_ntlm_authenticate (SoupAuth *auth, const char *username,
priv->username = g_strdup (username);
}
- soup_ntlm_nt_hash (password, priv->nt_hash);
- soup_ntlm_lanmanager_hash (password, priv->lm_hash);
+ if (strlen (password) < 256) {
+ soup_ntlm_nt_hash (password, priv->nt_hash);
+ soup_ntlm_lanmanager_hash (password, priv->lm_hash);
+ }
priv->password_state = SOUP_NTLM_PASSWORD_PROVIDED;
}
@@ -616,7 +626,7 @@ soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class)
}
static void md4sum (const unsigned char *in,
- int nbytes,
+ size_t nbytes,
unsigned char digest[16]);
typedef guint32 DES_KS[16][2]; /* Single-key DES key schedule */
@@ -662,7 +672,7 @@ soup_ntlm_nt_hash (const char *password, guchar hash[21])
{
unsigned char *buf, *p;
- p = buf = g_malloc (strlen (password) * 2);
+ p = buf = g_malloc_n (strlen (password), 2);
while (*password) {
*p++ = *password++;
@@ -1104,15 +1114,16 @@ calc_response (const guchar *key, const guchar *plaintext, guchar *results)
#define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) )
static void
-md4sum (const unsigned char *in, int nbytes, unsigned char digest[16])
+md4sum (const unsigned char *in, size_t nbytes, unsigned char digest[16])
{
unsigned char *M;
guint32 A, B, C, D, AA, BB, CC, DD, X[16];
- int pbytes, nbits = nbytes * 8, i, j;
+ size_t pbytes, nbits = nbytes * 8;
+ int i, j;
/* There is *always* padding of at least one bit. */
pbytes = ((119 - (nbytes % 64)) % 64) + 1;
- M = alloca (nbytes + pbytes + 8);
+ M = g_malloc (nbytes + pbytes + 8);
memcpy (M, in, nbytes);
memset (M + nbytes, 0, pbytes + 8);
M[nbytes] = 0x80;
@@ -1212,6 +1223,8 @@ md4sum (const unsigned char *in, int nbytes, unsigned char digest[16])
digest[13] = (D >> 8) & 0xFF;
digest[14] = (D >> 16) & 0xFF;
digest[15] = (D >> 24) & 0xFF;
+
+ g_free (M);
}
diff --git a/tests/ntlm-test.c b/tests/ntlm-test.c
index e19f5663..c95fcd50 100644
--- a/tests/ntlm-test.c
+++ b/tests/ntlm-test.c
@@ -740,6 +740,67 @@ do_retrying_test (TestServer *ts,
soup_test_session_abort_unref (session);
}
+static gboolean
+long_password_test_authenticate (SoupMessage *msg,
+ SoupAuth *auth,
+ gboolean retrying,
+ gpointer user)
+{
+ size_t l = 65536;
+ char *password;
+ char tmp[10000];
+ size_t i;
+
+ password = (char *)g_malloc (l);
+
+ for (i = 0; i < 10000; i++) {
+ tmp[i] = 'A';
+ }
+ for (i = 0; i < l/10000; i++) {
+ memcpy (password + i * 10000, tmp, 10000);
+ }
+ memcpy (password + l - 1 - 10000, tmp, 10000);
+
+ soup_auth_authenticate (auth, "alice", password);
+
+ g_free (password);
+ return TRUE;
+}
+
+static void
+do_long_password_test (TestServer *ts,
+ gconstpointer data)
+{
+ SoupSession *session;
+ SoupMessage *msg;
+ GUri *uri;
+ GBytes *body;
+
+ if (!can_do_ntlm_test ()) {
+ g_test_skip ("NTLM authentication not available (likely due to FIPS mode)");
+ return;
+ }
+
+ session = soup_test_session_new (NULL);
+ soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM);
+ soup_session_set_proxy_resolver(session, NULL);
+
+ uri = g_uri_parse_relative (ts->uri, "/alice", SOUP_HTTP_URI_FLAGS, NULL);
+ msg = soup_message_new_from_uri ("GET", uri);
+ g_signal_connect (msg, "authenticate",
+ G_CALLBACK (long_password_test_authenticate), NULL);
+ g_uri_unref (uri);
+
+ body = soup_session_send_and_read (session, msg, NULL, NULL);
+
+ soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED);
+
+ g_bytes_unref (body);
+ g_object_unref (msg);
+
+ soup_test_session_abort_unref (session);
+}
+
int
main (int argc, char **argv)
{
@@ -763,6 +824,9 @@ main (int argc, char **argv)
g_test_add ("/ntlm/retry", TestServer, NULL,
setup_server, do_retrying_test, teardown_server);
+ g_test_add ("/ntlm/long-password", TestServer, NULL,
+ setup_server, do_long_password_test, teardown_server);
+
ret = g_test_run ();
test_cleanup ();
--
2.52.0

View File

@ -1,97 +0,0 @@
From 0a55f5ebde48cc3e488bff70215b22f103cc6077 Mon Sep 17 00:00:00 2001
From: Carlos Garcia Campos <cgarcia@igalia.com>
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
---
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 b1e616c7..22541aa0 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 92b673eb..0496a4c6 100644
--- a/tests/multipart-test.c
+++ b/tests/multipart-test.c
@@ -527,6 +527,51 @@ test_multipart_bounds_bad (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)
{
@@ -595,6 +640,7 @@ main (int argc, char **argv)
g_test_add_data_func ("/multipart/async-small-reads", GINT_TO_POINTER (ASYNC_MULTIPART_SMALL_READS), test_multipart);
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-3", test_multipart_bounds_bad_3);
g_test_add_func ("/multipart/too-large", test_multipart_too_large);
ret = g_test_run ();
--
2.52.0

361
CVE-2026-4271.patch Normal file
View File

@ -0,0 +1,361 @@
From f99a814cadc4c19c5b70a42476f05e62ac2d5bab Mon Sep 17 00:00:00 2001
From: Carlos Garcia Campos <cgarcia@igalia.com>
Date: Mon, 16 Feb 2026 12:09:08 +0100
Subject: [PATCH] server: protect message io while reading and writing
Ensure the nghttp2 session is not destroyed while being used.
Closes #496
---
.../http2/soup-server-message-io-http2.c | 117 +++++++++++++-----
tests/http2-test.c | 54 ++++++++
2 files changed, 141 insertions(+), 30 deletions(-)
diff --git a/libsoup/server/http2/soup-server-message-io-http2.c b/libsoup/server/http2/soup-server-message-io-http2.c
index 913afb46..6f8d1bb6 100644
--- a/libsoup/server/http2/soup-server-message-io-http2.c
+++ b/libsoup/server/http2/soup-server-message-io-http2.c
@@ -69,6 +69,8 @@ typedef struct {
GHashTable *messages;
guint in_callback;
+ guint protected;
+ gboolean destroyed;
} SoupServerMessageIOHTTP2;
static void soup_server_message_io_http2_send_response (SoupServerMessageIOHTTP2 *io,
@@ -146,6 +148,8 @@ soup_server_message_io_http2_destroy (SoupServerMessageIO *iface)
{
SoupServerMessageIOHTTP2 *io = (SoupServerMessageIOHTTP2 *)iface;
+ io->destroyed = TRUE;
+
if (io->read_source) {
g_source_destroy (io->read_source);
g_source_unref (io->read_source);
@@ -160,10 +164,14 @@ soup_server_message_io_http2_destroy (SoupServerMessageIO *iface)
}
g_clear_object (&io->iostream);
- g_clear_pointer (&io->session, nghttp2_session_del);
- g_clear_pointer (&io->messages, g_hash_table_unref);
+ io->istream = NULL;
+ io->ostream = NULL;
- g_free (io);
+ if (io->protected == 0) {
+ g_clear_pointer (&io->session, nghttp2_session_del);
+ g_clear_pointer (&io->messages, g_hash_table_unref);
+ g_free (io);
+ }
}
static void
@@ -321,7 +329,33 @@ static const SoupServerMessageIOFuncs io_funcs = {
soup_server_message_io_http2_is_paused
};
+static void
+soup_server_message_io_http2_protect (SoupServerMessageIOHTTP2 *io)
+{
+ io->protected++;
+ g_object_ref (io->conn);
+}
+
static gboolean
+soup_server_message_io_http2_unprotect (SoupServerMessageIOHTTP2 *io)
+{
+ g_object_unref (io->conn);
+
+ if (--io->protected > 0)
+ return FALSE;
+
+ if (io->destroyed) {
+ g_clear_pointer (&io->session, nghttp2_session_del);
+ g_clear_pointer (&io->messages, g_hash_table_unref);
+ g_free (io);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
io_write (SoupServerMessageIOHTTP2 *io,
GError **error)
{
@@ -336,51 +370,57 @@ io_write (SoupServerMessageIOHTTP2 *io,
if (io->write_buffer_size == 0) {
/* Done */
io->write_buffer = NULL;
- return TRUE;
+ return;
}
}
+ if (!io->ostream)
+ return;
+
gssize ret = g_pollable_stream_write (io->ostream,
io->write_buffer + io->written_bytes,
io->write_buffer_size - io->written_bytes,
FALSE, NULL, error);
- if (ret < 0)
- return FALSE;
-
- io->written_bytes += ret;
- return TRUE;
+ if (ret > 0)
+ io->written_bytes += ret;
}
static gboolean
io_write_ready (GObject *stream,
SoupServerMessageIOHTTP2 *io)
{
- SoupServerConnection *conn = io->conn;
GError *error = NULL;
- g_object_ref (conn);
+ soup_server_message_io_http2_protect (io);
+
+ while (!error) {
+ if (io->destroyed)
+ break;
+
+ if (!nghttp2_session_want_write (io->session))
+ break;
- while (!error && soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io && nghttp2_session_want_write (io->session))
io_write (io, &error);
+ }
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
g_error_free (error);
- g_object_unref (conn);
+ soup_server_message_io_http2_unprotect (io);
return G_SOURCE_CONTINUE;
}
- if (soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io) {
+ if (!io->destroyed) {
if (error)
h2_debug (io, NULL, "[SESSION] IO error: %s", error->message);
g_clear_pointer (&io->write_source, g_source_unref);
if (error || (!nghttp2_session_want_read (io->session) && !nghttp2_session_want_write (io->session)))
- soup_server_connection_disconnect (conn);
+ soup_server_connection_disconnect (io->conn);
}
g_clear_error (&error);
- g_object_unref (conn);
+ soup_server_message_io_http2_unprotect (io);
return G_SOURCE_REMOVE;
}
@@ -390,13 +430,12 @@ static gboolean io_write_idle_cb (SoupServerMessageIOHTTP2* io);
static void
io_try_write (SoupServerMessageIOHTTP2 *io)
{
- SoupServerConnection *conn = io->conn;
GError *error = NULL;
if (io->write_source)
return;
- if (io->in_callback && soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io) {
+ if (io->in_callback && !io->destroyed) {
if (!nghttp2_session_want_write (io->session))
return;
@@ -416,12 +455,19 @@ io_try_write (SoupServerMessageIOHTTP2 *io)
g_clear_pointer (&io->write_idle_source, g_source_unref);
}
- g_object_ref (conn);
+ soup_server_message_io_http2_protect (io);
+
+ while (!error) {
+ if (io->destroyed)
+ break;
+
+ if (!nghttp2_session_want_write (io->session))
+ break;
- while (!error && soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io && !io->in_callback && nghttp2_session_want_write (io->session))
io_write (io, &error);
+ }
- if (soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io) {
+ if (!io->destroyed) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
g_clear_error (&error);
io->write_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (io->ostream), NULL);
@@ -434,11 +480,11 @@ io_try_write (SoupServerMessageIOHTTP2 *io)
h2_debug (io, NULL, "[SESSION] IO error: %s", error->message);
if (error || (!nghttp2_session_want_read (io->session) && !nghttp2_session_want_write (io->session)))
- soup_server_connection_disconnect (conn);
+ soup_server_connection_disconnect (io->conn);
}
g_clear_error (&error);
- g_object_unref (conn);
+ soup_server_message_io_http2_unprotect (io);
}
static gboolean
@@ -481,31 +527,37 @@ static gboolean
io_read_ready (GObject *stream,
SoupServerMessageIOHTTP2 *io)
{
- SoupServerConnection *conn = io->conn;
gboolean progress = TRUE;
GError *error = NULL;
- g_object_ref (conn);
+ soup_server_message_io_http2_protect (io);
+
+ while (progress) {
+ if (io->destroyed)
+ break;
+
+ if (!nghttp2_session_want_read (io->session))
+ break;
- while (progress && soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io && nghttp2_session_want_read (io->session))
progress = io_read (io, &error);
+ }
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
g_error_free (error);
- g_object_unref (conn);
+ soup_server_message_io_http2_unprotect (io);
return G_SOURCE_CONTINUE;
}
- if (soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io) {
+ if (!io->destroyed) {
if (error)
h2_debug (io, NULL, "[SESSION] IO error: %s", error->message);
if (error || (!nghttp2_session_want_read (io->session) && !nghttp2_session_want_write (io->session)))
- soup_server_connection_disconnect (conn);
+ soup_server_connection_disconnect (io->conn);
}
g_clear_error (&error);
- g_object_unref (conn);
+ soup_server_message_io_http2_unprotect (io);
return G_SOURCE_REMOVE;
}
@@ -931,5 +983,10 @@ soup_server_message_io_http2_new (SoupServerConnection *conn,
nghttp2_submit_settings (io->session, NGHTTP2_FLAG_NONE, settings, G_N_ELEMENTS (settings));
io_try_write (io);
+#ifdef __clang_analyzer__
+ // Suppress false positive about io being destroyed here, since at this point we have only
+ // send the initial settings and not callback is called.
+ [[clang::suppress]]
+#endif
return (SoupServerMessageIO *)io;
}
diff --git a/tests/http2-test.c b/tests/http2-test.c
index dafcb95a..ee15e072 100644
--- a/tests/http2-test.c
+++ b/tests/http2-test.c
@@ -1368,6 +1368,40 @@ do_broken_pseudo_header_test (Test *test, gconstpointer data)
g_uri_unref (uri);
}
+static void
+disconnect_on_got_headers (SoupServerMessage *msg, gpointer user_data)
+{
+ GUri *uri;
+ SoupServerConnection *conn;
+
+ uri = soup_server_message_get_uri (msg);
+ if (!g_str_equal (g_uri_get_path (uri), "/close-on-got-headers"))
+ return;
+
+ conn = soup_server_message_get_connection (msg);
+ soup_server_connection_disconnect (conn);
+}
+
+static void
+do_server_disconnect_on_got_headers_test (Test *test, gconstpointer data)
+{
+ SoupMessage *msg;
+ GUri *uri;
+ GBytes *response;
+ GError *error = NULL;
+
+ uri = g_uri_parse_relative (base_uri, "/close-on-got-headers", SOUP_HTTP_URI_FLAGS, NULL);
+ msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+
+ response = soup_test_session_async_send (test->session, msg, NULL, &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT);
+
+ g_clear_error (&error);
+ g_bytes_unref (response);
+ g_object_unref (msg);
+ g_uri_unref (uri);
+}
+
static gboolean
unpause_message (SoupServerMessage *msg)
{
@@ -1505,12 +1539,26 @@ server_handler (SoupServer *server,
shutdown (fd, SHUT_WR);
#endif
+ soup_server_message_set_response (msg, "text/plain",
+ SOUP_MEMORY_STATIC,
+ "Success!", 8);
+ } else if (strcmp (path, "/close-on-got-headers") == 0) {
soup_server_message_set_response (msg, "text/plain",
SOUP_MEMORY_STATIC,
"Success!", 8);
}
}
+static void
+server_request_started (SoupServer *server,
+ SoupServerMessage *msg,
+ SoupServerConnection *conn,
+ gpointer user_data)
+{
+ g_signal_connect (msg, "got-headers",
+ G_CALLBACK (disconnect_on_got_headers), NULL);
+}
+
static gboolean
server_basic_auth_callback (SoupAuthDomain *auth_domain,
SoupServerMessage *msg,
@@ -1537,6 +1585,8 @@ main (int argc, char **argv)
return 0;
server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD | SOUP_TEST_SERVER_HTTP2);
+ g_signal_connect (server, "request-started",
+ G_CALLBACK (server_request_started), NULL);
auth = soup_auth_domain_basic_new ("realm", "http2-test",
"auth-callback", server_basic_auth_callback,
NULL);
@@ -1697,6 +1747,10 @@ main (int argc, char **argv)
setup_session,
do_broken_pseudo_header_test,
teardown_session);
+ g_test_add ("/http2/server-disconnect-on-got-headers", Test, NULL,
+ setup_session,
+ do_server_disconnect_on_got_headers_test,
+ teardown_session);
ret = g_test_run ();
--
GitLab

120
CVE-2026-5119.patch Normal file
View File

@ -0,0 +1,120 @@
From 1922f08e4cbae4d13430421b1377aad21ad54025 Mon Sep 17 00:00:00 2001
From: Carlos Garcia Campos <cgarcia@igalia.com>
Date: Fri, 27 Feb 2026 12:03:25 +0100
Subject: [PATCH] cookies: do not send cookies to a HTTP proxy for a HTTPS
request
Closes #502
---
libsoup/cookies/soup-cookie-jar.c | 24 +++++++++++-----
tests/proxy-test.c | 47 +++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+), 7 deletions(-)
diff --git a/libsoup/cookies/soup-cookie-jar.c b/libsoup/cookies/soup-cookie-jar.c
index 51c51d3c..1be56d89 100644
--- a/libsoup/cookies/soup-cookie-jar.c
+++ b/libsoup/cookies/soup-cookie-jar.c
@@ -890,18 +890,28 @@ process_set_cookie_header (SoupMessage *msg, gpointer user_data)
g_slist_free (new_cookies);
}
+static gboolean
+allow_cookies_for_request (SoupMessage *msg)
+{
+ /* Do not send cookies to a HTTP proxy for a HTTPS request */
+ return soup_message_get_method (msg) != SOUP_METHOD_CONNECT || !soup_connection_is_tunnelled (soup_message_get_connection (msg));
+}
+
static void
msg_starting_cb (SoupMessage *msg, gpointer feature)
{
SoupCookieJar *jar = SOUP_COOKIE_JAR (feature);
- GSList *cookies;
+ GSList *cookies = NULL;
+
+ if (allow_cookies_for_request (msg)) {
+ cookies = soup_cookie_jar_get_cookie_list_with_same_site_info (jar, soup_message_get_uri (msg),
+ soup_message_get_first_party (msg),
+ soup_message_get_site_for_cookies (msg),
+ TRUE,
+ SOUP_METHOD_IS_SAFE (soup_message_get_method (msg)),
+ soup_message_get_is_top_level_navigation (msg));
+ }
- cookies = soup_cookie_jar_get_cookie_list_with_same_site_info (jar, soup_message_get_uri (msg),
- soup_message_get_first_party (msg),
- soup_message_get_site_for_cookies (msg),
- TRUE,
- SOUP_METHOD_IS_SAFE (soup_message_get_method (msg)),
- soup_message_get_is_top_level_navigation (msg));
if (cookies != NULL) {
char *cookie_header = soup_cookies_to_cookie_header (cookies);
soup_message_headers_replace_common (soup_message_get_request_headers (msg), SOUP_HEADER_COOKIE, cookie_header, SOUP_HEADER_VALUE_TRUSTED);
diff --git a/tests/proxy-test.c b/tests/proxy-test.c
index d730c8a7..7d90c053 100644
--- a/tests/proxy-test.c
+++ b/tests/proxy-test.c
@@ -373,6 +373,52 @@ do_proxy_connect_error_test (gconstpointer data)
soup_test_session_abort_unref (session);
}
+static void
+connect_message_wrote_headers_cb (SoupMessage *msg, guint *counter)
+{
+ SoupMessageHeaders *hdrs;
+
+ *counter += 1;
+
+ hdrs = soup_message_get_request_headers (msg);
+ if (soup_message_get_method (msg) == SOUP_METHOD_CONNECT)
+ g_assert_null (soup_message_headers_get_one (hdrs, "Cookie"));
+ else
+ g_assert_nonnull (soup_message_headers_get_one (hdrs, "Cookie"));
+}
+
+static void
+request_queued_cb (SoupSession *session, SoupMessage *msg, guint *counter)
+{
+ g_signal_connect (msg, "wrote-headers", G_CALLBACK (connect_message_wrote_headers_cb), counter);
+}
+
+static void
+do_proxy_secure_cookies_test (void)
+{
+ SoupSession *session;
+ SoupMessage *msg;
+ SoupCookieJar *jar;
+ guint counter = 0;
+
+ SOUP_TEST_SKIP_IF_NO_APACHE;
+ SOUP_TEST_SKIP_IF_NO_TLS;
+
+ session = soup_test_session_new ("proxy-resolver", proxy_resolvers[SIMPLE_PROXY], NULL);
+ g_signal_connect (session, "request-queued", G_CALLBACK (request_queued_cb), &counter);
+
+ soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR);
+ jar = SOUP_COOKIE_JAR (soup_session_get_feature (session, SOUP_TYPE_COOKIE_JAR));
+
+ msg = soup_message_new (SOUP_METHOD_GET, HTTPS_SERVER);
+ soup_cookie_jar_set_cookie (jar, soup_message_get_uri (msg), "user=password; secure");
+ soup_test_session_send_message (session, msg);
+ soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+ g_assert_cmpuint (counter, ==, 2);
+
+ soup_test_session_abort_unref (session);
+}
+
int
main (int argc, char **argv)
{
@@ -404,6 +450,7 @@ main (int argc, char **argv)
g_test_add_func ("/proxy/redirect", do_proxy_redirect_test);
g_test_add_func ("/proxy/auth-cache", do_proxy_auth_cache_test);
g_test_add_data_func ("/proxy/connect-error", base_https_uri, do_proxy_connect_error_test);
+ g_test_add_func ("/proxy/secure-cookies", do_proxy_secure_cookies_test);
ret = g_test_run ();
--
2.54.0

View File

@ -1,30 +0,0 @@
From a2eaa4aab2b62976118a4a62f5041eead5c90a02 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Thu, 1 May 2025 08:57:46 -0500
Subject: [PATCH] Reduce runtime of http2-body-size test
This test is *really* slow and I think it would be excessive to increase
the test timeout any further, so let's test less data.
Fixes #444
Obsoletes: !309
---
tests/http2-body-stream-test.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/http2-body-stream-test.c b/tests/http2-body-stream-test.c
index 540beb3b..c53f22bc 100644
--- a/tests/http2-body-stream-test.c
+++ b/tests/http2-body-stream-test.c
@@ -24,7 +24,7 @@ static void
do_large_data_test (void)
{
#define CHUNK_SIZE ((gsize)1024 * 1024 * 512) // 512 MiB
-#define TEST_SIZE (CHUNK_SIZE * 20) // 10 GiB
+#define TEST_SIZE (CHUNK_SIZE * 4) // 2 GiB
GInputStream *stream = soup_body_input_stream_http2_new ();
SoupBodyInputStreamHttp2 *mem_stream = SOUP_BODY_INPUT_STREAM_HTTP2 (stream);
--
GitLab

View File

@ -1,8 +1,8 @@
%global glib2_version 2.69.1
Name: libsoup3
Version: 3.6.5
Release: 3%{?dist}.%{autorelease -n}
Version: 3.6.6
Release: %autorelease
Summary: Soup, an HTTP library implementation
License: LGPL-2.0-or-later
@ -12,35 +12,14 @@ Source0: https://download.gnome.org/sources/libsoup/3.6/libsoup-%{version}.tar.x
# Downstream patch, needed due to glib2 gnutls-hmac.patch
Patch: no-ntlm-in-fips-mode.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/426
Patch: test-timeouts.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/454
Patch: server-test-timeout.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/455
Patch: http2-body-size-test-timeout.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/450
Patch: CVE-2025-32914.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/451
Patch: CVE-2025-32908.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/448 (simplified and corrected)
Patch: CVE-2025-4035.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/463
Patch: CVE-2025-4948.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/408 (simplified)
Patch: CVE-2025-32049.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/452
Patch: CVE-2025-32907.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/473
Patch: CVE-2025-4945-CVE-2025-11021.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/481
Patch: CVE-2025-12105.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/491
Patch: CVE-2025-14523.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/494
Patch: CVE-2026-0719.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/496
Patch: CVE-2026-1761.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/516
Patch: CVE-2026-5119.patch
# https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/511
Patch: CVE-2026-4271.patch
BuildRequires: ca-certificates
BuildRequires: gcc

View File

@ -1,43 +0,0 @@
From 9ff306aa714efd06ceeafacee03298a3665055b1 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Wed, 30 Apr 2025 14:13:41 -0500
Subject: [PATCH] test-utils: fix deadlock in add_listener_in_thread()
The mutex is locked in the wrong place here.
Hopefully fixes #379
---
tests/test-utils.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/tests/test-utils.c b/tests/test-utils.c
index df4cee44..5c1e316c 100644
--- a/tests/test-utils.c
+++ b/tests/test-utils.c
@@ -607,9 +607,11 @@ static gboolean
add_listener_in_thread (gpointer user_data)
{
AddListenerData *data = user_data;
+ GUri *uri;
- data->uri = add_listener (data->server, data->scheme, data->host);
+ uri = add_listener (data->server, data->scheme, data->host);
g_mutex_lock (&data->mutex);
+ data->uri = uri;
g_cond_signal (&data->cond);
g_mutex_unlock (&data->mutex);
@@ -641,9 +643,9 @@ soup_test_server_get_uri (SoupServer *server,
data.host = host;
data.uri = NULL;
- g_mutex_lock (&data.mutex);
soup_add_completion (context, add_listener_in_thread, &data);
+ g_mutex_lock (&data.mutex);
while (!data.uri)
g_cond_wait (&data.cond, &data.mutex);
--
GitLab

View File

@ -1 +1 @@
SHA512 (libsoup-3.6.5.tar.xz) = cb44d93b16048d31ae04a8c2416bbe233e0e9bdaf2d9bfe2879260fd3da27e90a0bb05cddbd82cdf81a4a778bd451ad172a14dd31e2fd113c3bbbe13c0029b03
SHA512 (libsoup-3.6.6.tar.xz) = 4018dc6f9823fd82cde0fecbb50cd1b5dd0ff4963f92f7ea465e67faf81e71580709eec59914ddbdff317963a88e4a8024e60e44087041175bc21e04022857d2

View File

@ -1,103 +0,0 @@
From e308ed1a277c6b9c35c3f5c0e8e9deb06b2bcf89 Mon Sep 17 00:00:00 2001
From: Michael Catanzaro <mcatanzaro@redhat.com>
Date: Mon, 13 Jan 2025 11:50:35 -0600
Subject: [PATCH] Mark several tests as slow
Let's allow 30 seconds to run most tests, or 5 minutes for tests marked
slow. Most of them are fast on my computer:
1/34 cache-test OK 0.08s 7 subtests passed
2/34 chunk-io-test OK 0.08s 1 subtests passed
3/34 coding-test OK 0.08s 11 subtests passed
4/34 continue-test OK 0.08s 12 subtests passed
5/34 cookies-test OK 0.07s 13 subtests passed
6/34 date-test OK 0.07s 66 subtests passed
7/34 header-parsing-test OK 0.07s 8 subtests passed
8/34 logger-test OK 0.05s 6 subtests passed
9/34 multipart-test OK 0.05s 4 subtests passed
10/34 multithread-test OK 0.05s 8 subtests passed
11/34 no-ssl-test OK 0.04s 1 subtests passed
12/34 redirect-test OK 0.04s 29 subtests passed
13/34 request-body-test OK 0.04s 18 subtests passed
14/34 samesite-test OK 0.04s 16 subtests passed
15/34 streaming-test OK 0.02s 4 subtests passed
16/34 tld-test OK 0.02s 2 subtests passed
17/34 uri-parsing-test OK 0.02s 4 subtests passed
18/34 sniffing-test OK 0.03s 37 subtests passed
19/34 brotli-decompressor-test OK 0.01s 3 subtests passed
20/34 unix-socket-test OK 0.01s 1 subtests passed
21/34 hsts-db-test OK 0.08s 3 subtests passed
22/34 forms-test OK 0.11s 5 subtests passed
23/34 server-test OK 0.08s 18 subtests passed
24/34 ntlm-test OK 0.10s 21 subtests passed
25/34 ssl-test OK 0.12s 7 subtests passed
26/34 session-test OK 0.16s 6 subtests passed
27/34 misc-test OK 0.22s 18 subtests passed
28/34 context-test OK 0.41s 1 subtests passed
29/34 server-auth-test OK 0.37s 12 subtests passed
30/34 http2-test OK 2.73s 35 subtests passed
31/34 websocket-test OK 3.97s 55 subtests passed
32/34 timeout-test OK 4.08s 4 subtests passed
33/34 http2-body-stream-test OK 8.05s 3 subtests passed
34/34 hsts-test OK 12.12s 25 subtests passed
A 2 minute timeout is not good enough, so let's use 5 minutes.
I'm not marking hsts-test as slow because it is fast with the exception
of some hardcoded 2-3 second timeouts that should never cause the total
time to exceed 30s.
---
.gitlab-ci.yml | 4 ++--
tests/meson.build | 14 +++++++-------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/tests/meson.build b/tests/meson.build
index ee118a01..b4dc8064 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -87,8 +87,8 @@ tests = [
{'name': 'date'},
{'name': 'forms'},
{'name': 'header-parsing'},
- {'name': 'http2'},
- {'name': 'http2-body-stream'},
+ {'name': 'http2', 'slow': true},
+ {'name': 'http2-body-stream', 'slow': true},
{'name': 'hsts'},
{'name': 'hsts-db'},
{'name': 'logger'},
@@ -115,11 +115,12 @@ tests = [
]
},
{'name': 'streaming'},
- {'name': 'timeout'},
+ {'name': 'timeout', 'slow': true},
{'name': 'tld'},
{'name': 'uri-parsing'},
{'name': 'websocket',
- 'dependencies': [libz_dep]},
+ 'dependencies': [libz_dep],
+ 'slow': true},
]
if brotlidec_dep.found()
@@ -219,14 +220,13 @@ foreach test: tests
install_dir : installed_tests_execdir,
install_rpath : abs_installed_tests_execdir,
)
- # Increase the timeout as on some architectures the tests could be slower
- # than the default 30 seconds.
+
test(test_name, test_target,
args : ['--debug'],
env : env,
is_parallel : test.get('parallel', true),
depends : test.get('depends', []),
- timeout : 60,
+ timeout : test.get('slow', false) ? 300 : 30,
protocol : 'tap',
)
endforeach
--
2.49.0