Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 222 additions & 0 deletions SPECS/libsoup/CVE-2026-1467.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
From 437f2dad08935d526038ee919424f71701afa590 Mon Sep 17 00:00:00 2001
From: AllSpark <allspark@microsoft.com>
Date: Mon, 9 Feb 2026 17:54:15 +0000
Subject: [PATCH] uri-utils: do host validation when checking if a GUri is
valid

Currently we only check if the host is not NULL and not empty, but it might contain invalid characters not allowed for a host name in a URL. This change replaces the SOUP_URI_IS_VALID internal macro by a function that in addition to the existing checks, also validates the host.\n\nCloses #488

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
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 | 6 ++--
libsoup/soup-uri-utils-private.h | 4 +--
libsoup/soup-uri-utils.c | 61 ++++++++++++++++++++++++++++++++
tests/uri-parsing-test.c | 47 ++++++++++++++++++++++++
5 files changed, 114 insertions(+), 6 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..3bbac1b 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -946,7 +946,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 +966,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,
@@ -2039,7 +2039,7 @@ soup_message_set_uri (SoupMessage *msg, GUri *uri)
GUri *normalized_uri;

g_return_if_fail (SOUP_IS_MESSAGE (msg));
- g_return_if_fail (SOUP_URI_IS_VALID (uri));
+ g_return_if_fail (soup_uri_is_valid (uri));

priv = soup_message_get_instance_private (msg);

diff --git a/libsoup/soup-uri-utils-private.h b/libsoup/soup-uri-utils-private.h
index 3dbdb85..a73e882 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,4 @@ 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])
-
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..27e6439 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<e>.com", "http://exampl<e>.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 {
@@ -192,6 +238,7 @@ 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 ("/uri/valid", do_valid_tests);
g_test_add_func ("/data", do_data_uri_tests);
g_test_add_func ("/path_and_query", do_path_and_query_tests);

--
2.45.4

100 changes: 100 additions & 0 deletions SPECS/libsoup/CVE-2026-1761.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
From 3ac5a0e9096db5ad74ebe7e5cdc63c7f11ae4e22 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

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
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

Loading
Loading