Skip to content

Commit 7041ea4

Browse files
CBL-Mariner-Botazurelinux-securityjykanaserealsdxjslobodzian
authored
Merge PR "[AUTO-CHERRYPICK] [AutoPR- Security] Patch libsoup for CVE-2026-1801, CVE-2026-1761, CVE-2026-1467 [HIGH] - branch main" #16155
Co-authored-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> Co-authored-by: jykanase <v-jykanase@microsoft.com> Co-authored-by: Sudipta Pandit <sudpandit@microsoft.com> Co-authored-by: jslobodzian <joslobo@microsoft.com> Co-authored-by: Kanishk Bansal <103916909+Kanishk-Bansal@users.noreply.github.com>
1 parent c516535 commit 7041ea4

4 files changed

Lines changed: 489 additions & 1 deletion

File tree

SPECS/libsoup/CVE-2026-1467.patch

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
From 167ef0c6817658c1a089c75c462482209e207db4 Mon Sep 17 00:00:00 2001
2+
From: Carlos Garcia Campos <cgarcia@igalia.com>
3+
Date: Thu, 22 Jan 2026 15:26:18 +0100
4+
Subject: [PATCH] uri-utils: do host validation when checking if a GUri is
5+
valid
6+
7+
Currently we only check if the host is not NULL and not empty, but it
8+
might contain invalid characters not allowed for a host name in a URL.
9+
This patch replaces the SOUP_URI_IS_VALID internal macro by a function
10+
that in addition to the existing checks, it also validates the host.
11+
12+
Closes #488
13+
Upstream Patch Reference: https://gitlab.gnome.org/GNOME/libsoup/-/commit/167ef0c6817658c1a089c75c462482209e207db4.patch
14+
---
15+
libsoup/auth/soup-auth.c | 2 +-
16+
libsoup/soup-message.c | 9 ++---
17+
libsoup/soup-uri-utils-private.h | 4 +--
18+
libsoup/soup-uri-utils.c | 60 ++++++++++++++++++++++++++++++++
19+
tests/uri-parsing-test.c | 46 ++++++++++++++++++++++++
20+
5 files changed, 114 insertions(+), 7 deletions(-)
21+
22+
diff --git a/libsoup/auth/soup-auth.c b/libsoup/auth/soup-auth.c
23+
index d9bf4af..278baa1 100644
24+
--- a/libsoup/auth/soup-auth.c
25+
+++ b/libsoup/auth/soup-auth.c
26+
@@ -643,7 +643,7 @@ GSList *
27+
soup_auth_get_protection_space (SoupAuth *auth, GUri *source_uri)
28+
{
29+
g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL);
30+
- g_return_val_if_fail (SOUP_URI_IS_VALID (source_uri), NULL);
31+
+ g_return_val_if_fail (soup_uri_is_valid (source_uri), NULL);
32+
33+
GUri *source_uri_normalized = soup_uri_copy_with_normalized_flags (source_uri);
34+
GSList *ret = SOUP_AUTH_GET_CLASS (auth)->get_protection_space (auth, source_uri_normalized);
35+
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
36+
index 2f5b267..292e4e5 100644
37+
--- a/libsoup/soup-message.c
38+
+++ b/libsoup/soup-message.c
39+
@@ -923,7 +923,8 @@ soup_message_new (const char *method, const char *uri_string)
40+
uri = g_uri_parse (uri_string, SOUP_HTTP_URI_FLAGS, NULL);
41+
if (!uri)
42+
return NULL;
43+
- if (!g_uri_get_host (uri)) {
44+
+
45+
+ if (!soup_uri_is_valid (uri)) {
46+
g_uri_unref (uri);
47+
return NULL;
48+
}
49+
@@ -946,7 +947,7 @@ SoupMessage *
50+
soup_message_new_from_uri (const char *method, GUri *uri)
51+
{
52+
g_return_val_if_fail (method != NULL, NULL);
53+
- g_return_val_if_fail (SOUP_URI_IS_VALID (uri), NULL);
54+
+ g_return_val_if_fail (soup_uri_is_valid (uri), NULL);
55+
56+
return g_object_new (SOUP_TYPE_MESSAGE,
57+
"method", method,
58+
@@ -966,7 +967,7 @@ soup_message_new_from_uri (const char *method, GUri *uri)
59+
SoupMessage *
60+
soup_message_new_options_ping (GUri *base_uri)
61+
{
62+
- g_return_val_if_fail (SOUP_URI_IS_VALID (base_uri), NULL);
63+
+ g_return_val_if_fail (soup_uri_is_valid (base_uri), NULL);
64+
65+
return g_object_new (SOUP_TYPE_MESSAGE,
66+
"method", SOUP_METHOD_OPTIONS,
67+
@@ -2039,7 +2040,7 @@ soup_message_set_uri (SoupMessage *msg, GUri *uri)
68+
GUri *normalized_uri;
69+
70+
g_return_if_fail (SOUP_IS_MESSAGE (msg));
71+
- g_return_if_fail (SOUP_URI_IS_VALID (uri));
72+
+ g_return_if_fail (soup_uri_is_valid (uri));
73+
74+
priv = soup_message_get_instance_private (msg);
75+
76+
diff --git a/libsoup/soup-uri-utils-private.h b/libsoup/soup-uri-utils-private.h
77+
index 3dbdb85..a73e882 100644
78+
--- a/libsoup/soup-uri-utils-private.h
79+
+++ b/libsoup/soup-uri-utils-private.h
80+
@@ -10,6 +10,8 @@
81+
82+
G_BEGIN_DECLS
83+
84+
+gboolean soup_uri_is_valid (GUri *uri);
85+
+
86+
gboolean soup_uri_is_http (GUri *uri);
87+
88+
gboolean soup_uri_is_https (GUri *uri);
89+
@@ -28,6 +30,4 @@ GUri *soup_uri_copy_with_normalized_flags (GUri *uri);
90+
91+
char *soup_uri_get_host_for_headers (GUri *uri);
92+
93+
-#define SOUP_URI_IS_VALID(x) (x && g_uri_get_host(x) && g_uri_get_host(x)[0])
94+
-
95+
G_END_DECLS
96+
diff --git a/libsoup/soup-uri-utils.c b/libsoup/soup-uri-utils.c
97+
index ce9b2a1..cfac991 100644
98+
--- a/libsoup/soup-uri-utils.c
99+
+++ b/libsoup/soup-uri-utils.c
100+
@@ -244,6 +244,66 @@ soup_uri_host_equal (gconstpointer v1, gconstpointer v2)
101+
return g_ascii_strcasecmp (one_host, two_host) == 0;
102+
}
103+
104+
+static gboolean
105+
+is_valid_character_for_host (char c)
106+
+{
107+
+ static const char forbidden_chars[] = { '\t', '\n', '\r', ' ', '#', '/', ':', '<', '>', '?', '@', '[', '\\', ']', '^', '|' };
108+
+ int i;
109+
+
110+
+ for (i = 0; i < G_N_ELEMENTS (forbidden_chars); ++i) {
111+
+ if (c == forbidden_chars[i])
112+
+ return FALSE;
113+
+ }
114+
+
115+
+ return TRUE;
116+
+}
117+
+
118+
+static gboolean
119+
+is_host_valid (const char* host)
120+
+{
121+
+ int i;
122+
+ gboolean is_valid;
123+
+ char *ascii_host = NULL;
124+
+
125+
+ if (!host || !host[0])
126+
+ return FALSE;
127+
+
128+
+ if (g_hostname_is_non_ascii (host)) {
129+
+ ascii_host = g_hostname_to_ascii (host);
130+
+ if (!ascii_host)
131+
+ return FALSE;
132+
+
133+
+ host = ascii_host;
134+
+ }
135+
+
136+
+ if ((g_ascii_isdigit (host[0]) || strchr (host, ':')) && g_hostname_is_ip_address (host)) {
137+
+ g_free (ascii_host);
138+
+ return TRUE;
139+
+ }
140+
+
141+
+ is_valid = TRUE;
142+
+ for (i = 0; host[i] && is_valid; i++)
143+
+ is_valid = is_valid_character_for_host (host[i]);
144+
+
145+
+ g_free (ascii_host);
146+
+
147+
+ return is_valid;
148+
+}
149+
+
150+
+gboolean
151+
+soup_uri_is_valid (GUri *uri)
152+
+{
153+
+ if (!uri)
154+
+ return FALSE;
155+
+
156+
+ if (!is_host_valid (g_uri_get_host (uri)))
157+
+ return FALSE;
158+
+
159+
+ /* FIXME: validate other URI components? */
160+
+
161+
+ return TRUE;
162+
+}
163+
+
164+
gboolean
165+
soup_uri_is_https (GUri *uri)
166+
{
167+
diff --git a/tests/uri-parsing-test.c b/tests/uri-parsing-test.c
168+
index 4c16d7e..a0e9cc2 100644
169+
--- a/tests/uri-parsing-test.c
170+
+++ b/tests/uri-parsing-test.c
171+
@@ -116,6 +116,51 @@ do_copy_tests (void)
172+
g_uri_unref (uri);
173+
}
174+
175+
+static struct {
176+
+ const char *scheme;
177+
+ const char *host;
178+
+ const char *as_string;
179+
+ gboolean valid;
180+
+} valid_tests[] = {
181+
+ { "http", "example.com", "http://example.com/", TRUE },
182+
+ { "http", "localhost", "http://localhost/", TRUE },
183+
+ { "http", "127.0.0.1", "http://127.0.0.1/", TRUE },
184+
+ { "http", "::1", "http://[::1]/", TRUE },
185+
+ { "http", "::192.168.0.10", "http://[::192.168.0.10]/", TRUE },
186+
+ { "http", "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/", TRUE },
187+
+ { "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 },
188+
+ { "http", "012x:4567:89AB:cdef:3210:7654:ba98:FeDc", "http://012x:4567:89AB:cdef:3210:7654:ba98:FeDc/", FALSE },
189+
+ { "http", "\texample.com", "http://\texample.com/", FALSE },
190+
+ { "http", "example.com\n", "http://example.com\n/", FALSE },
191+
+ { "http", "\r\nexample.com", "http://\r\nexample.com/", FALSE },
192+
+ { "http", "example .com", "http://example .com/", FALSE },
193+
+ { "http", "example:com", "http://example:com/", FALSE },
194+
+ { "http", "exampl<e>.com", "http://exampl<e>.com/", FALSE },
195+
+ { "http", "exampl[e].com", "http://exampl[e].com/", FALSE },
196+
+ { "http", "exampl^e.com", "http://exampl^e.com/", FALSE },
197+
+ { "http", "examp|e.com", "http://examp|e.com/", FALSE },
198+
+};
199+
+
200+
+static void
201+
+do_valid_tests (void)
202+
+{
203+
+ int i;
204+
+
205+
+ for (i = 0; i < G_N_ELEMENTS (valid_tests); ++i) {
206+
+ GUri *uri;
207+
+ char *uri_str;
208+
+
209+
+ uri = g_uri_build (SOUP_HTTP_URI_FLAGS | G_URI_FLAGS_ENCODED, valid_tests[i].scheme, NULL, valid_tests[i].host, -1, "", NULL, NULL);
210+
+ uri_str = g_uri_to_string (uri);
211+
+
212+
+ g_assert_cmpstr (uri_str, ==, valid_tests[i].as_string);
213+
+ g_assert_true (soup_uri_is_valid (uri) == valid_tests[i].valid);
214+
+
215+
+ g_free (uri_str);
216+
+ g_uri_unref (uri);
217+
+ }
218+
+}
219+
+
220+
#define CONTENT_TYPE_DEFAULT "text/plain;charset=US-ASCII"
221+
222+
static struct {
223+
@@ -192,6 +237,7 @@ main (int argc, char **argv)
224+
225+
g_test_add_func ("/uri/equality", do_equality_tests);
226+
g_test_add_func ("/uri/copy", do_copy_tests);
227+
+ g_test_add_func ("/uri/valid", do_valid_tests);
228+
g_test_add_func ("/data", do_data_uri_tests);
229+
g_test_add_func ("/path_and_query", do_path_and_query_tests);
230+
231+
--
232+
2.45.4
233+

SPECS/libsoup/CVE-2026-1761.patch

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
From cfa9d90d1a5c274233554a264c56551c13d6a6f0 Mon Sep 17 00:00:00 2001
2+
From: Carlos Garcia Campos <cgarcia@igalia.com>
3+
Date: Mon, 19 Jan 2026 15:14:58 +0100
4+
Subject: [PATCH] multipart: check length of bytes read
5+
soup_filter_input_stream_read_until()
6+
7+
We do make sure the read length is smaller than the buffer length when
8+
the boundary is not found, but we should do the same when the boundary
9+
is found.
10+
11+
Spotted in #YWH-PGM9867-149
12+
Closes #493
13+
Upstream Patch Reference: https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/496.patch
14+
---
15+
libsoup/soup-filter-input-stream.c | 3 +-
16+
tests/multipart-test.c | 46 ++++++++++++++++++++++++++++++
17+
2 files changed, 48 insertions(+), 1 deletion(-)
18+
19+
diff --git a/libsoup/soup-filter-input-stream.c b/libsoup/soup-filter-input-stream.c
20+
index b1e616c..22541aa 100644
21+
--- a/libsoup/soup-filter-input-stream.c
22+
+++ b/libsoup/soup-filter-input-stream.c
23+
@@ -337,6 +337,7 @@ soup_filter_input_stream_read_until (SoupFilterInputStream *fstream,
24+
if (eof && !*got_boundary)
25+
read_length = MIN (priv->buf->len, length);
26+
else
27+
- read_length = p - buf;
28+
+ read_length = MIN ((gsize)(p - buf), length);
29+
+
30+
return read_from_buf (fstream, buffer, read_length);
31+
}
32+
diff --git a/tests/multipart-test.c b/tests/multipart-test.c
33+
index d05000f..a83fc64 100644
34+
--- a/tests/multipart-test.c
35+
+++ b/tests/multipart-test.c
36+
@@ -550,6 +550,51 @@ test_multipart_bounds_bad_2 (void)
37+
g_bytes_unref (bytes);
38+
}
39+
40+
+static void
41+
+test_multipart_bounds_bad_3 (void)
42+
+{
43+
+ SoupMessage *msg;
44+
+ SoupMessageHeaders *headers;
45+
+ GInputStream *in;
46+
+ SoupMultipartInputStream *multipart;
47+
+ GError *error = NULL;
48+
+ const char raw_data[] = "\0$--A\r\nContent-Disposition: form-data; name=\"f\"\r\n\r\nXXXXXXXXX\r\n--A--\r\n";
49+
+
50+
+ msg = soup_message_new(SOUP_METHOD_POST, "http://foo/upload");
51+
+ headers = soup_message_get_response_headers (msg);
52+
+ soup_message_headers_replace (headers, "Content-Type", "multipart/form-data; boundary=\"A\"");
53+
+
54+
+ in = g_memory_input_stream_new_from_data (raw_data + 2, sizeof(raw_data) - 2, NULL);
55+
+ multipart = soup_multipart_input_stream_new (msg, in);
56+
+ g_object_unref (in);
57+
+
58+
+ while (TRUE) {
59+
+ in = soup_multipart_input_stream_next_part (multipart, NULL, &error);
60+
+ g_assert_no_error (error);
61+
+ if (!in) {
62+
+ g_clear_error (&error);
63+
+ break;
64+
+ }
65+
+
66+
+ char buffer[10];
67+
+ while (TRUE) {
68+
+ gssize bytes_read;
69+
+
70+
+ bytes_read = g_input_stream_read (in, buffer, sizeof(buffer), NULL, &error);
71+
+ g_assert_no_error (error);
72+
+ if (bytes_read <= 0) {
73+
+ g_clear_error (&error);
74+
+ break;
75+
+ }
76+
+ }
77+
+
78+
+ g_object_unref (in);
79+
+ }
80+
+
81+
+ g_object_unref (multipart);
82+
+ g_object_unref (msg);
83+
+}
84+
+
85+
static void
86+
test_multipart_too_large (void)
87+
{
88+
@@ -619,6 +664,7 @@ main (int argc, char **argv)
89+
g_test_add_func ("/multipart/bounds-good", test_multipart_bounds_good);
90+
g_test_add_func ("/multipart/bounds-bad", test_multipart_bounds_bad);
91+
g_test_add_func ("/multipart/bounds-bad-2", test_multipart_bounds_bad_2);
92+
+ g_test_add_func ("/multipart/bounds-bad-3", test_multipart_bounds_bad_3);
93+
g_test_add_func ("/multipart/too-large", test_multipart_too_large);
94+
95+
ret = g_test_run ();
96+
--
97+
2.45.4
98+

0 commit comments

Comments
 (0)