Skip to content

Commit f840cfd

Browse files
[AutoPR- Security] Patch libsoup for CVE-2026-2708, CVE-2026-6324 [MEDIUM] (microsoft#16941)
Co-authored-by: Durga Jagadeesh Palli <v-dpalli@microsoft.com>
1 parent 927fed8 commit f840cfd

3 files changed

Lines changed: 384 additions & 1 deletion

File tree

SPECS/libsoup/CVE-2026-2708.patch

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
From d9d8d64f8002ce40742d95447c0c5fbe9dac76af Mon Sep 17 00:00:00 2001
2+
From: AllSpark <allspark@microsoft.com>
3+
Date: Wed, 29 Apr 2026 18:28:19 +0000
4+
Subject: [PATCH] Do not allow adding multiple content length values to headers
5+
6+
Closes #500
7+
8+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
9+
Upstream-reference: AI Backport of https://gitlab.gnome.org/GNOME/libsoup/-/commit/e032d3e9b0a27d10597398023532dd8f9b6654cf.patch
10+
11+
---
12+
libsoup/soup-message-headers.c | 27 ++++++++++++++
13+
tests/header-parsing-test.c | 50 ++++++++++++++++++++++++++
14+
tests/server-test.c | 64 ++++++++++++++++++++++++++++++++++
15+
3 files changed, 141 insertions(+)
16+
17+
diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c
18+
index 41d56b8..236f64d 100644
19+
--- a/libsoup/soup-message-headers.c
20+
+++ b/libsoup/soup-message-headers.c
21+
@@ -263,6 +263,33 @@ soup_message_headers_append_common (SoupMessageHeaders *hdrs,
22+
return FALSE;
23+
}
24+
25+
+ if (name == SOUP_HEADER_CONTENT_LENGTH) {
26+
+ /* RFC 9110 - 7.7. Content-Length
27+
+ * If a message is received that has a Content-Length header field value consisting of
28+
+ * the same decimal value as a comma-separated list (Section 5.7.1) — for example,
29+
+ * "Content-Length: 42, 42" — indicating that duplicate Content-Length header fields have
30+
+ * been generated or combined by an upstream message processor, then the recipient must either
31+
+ * reject the message as invalid or replace the duplicated field values with a single valid
32+
+ * Content-Length field containing that decimal value prior to determining the message body
33+
+ * length or forwarding the message.
34+
+ */
35+
+ const char *content_length = soup_message_headers_get_one_common (hdrs, SOUP_HEADER_CONTENT_LENGTH);
36+
+ if (content_length) {
37+
+ guint64 decimal_value1, decimal_value2;
38+
+ char *end;
39+
+
40+
+ decimal_value1 = g_ascii_strtoull (content_length, &end, 10);
41+
+ if (*end)
42+
+ return FALSE;
43+
+
44+
+ decimal_value2 = g_ascii_strtoull (value, &end, 10);
45+
+ if (*end)
46+
+ return FALSE;
47+
+
48+
+ return decimal_value1 == decimal_value2;
49+
+ }
50+
+ }
51+
+
52+
if (!trusted_value && !is_valid_header_value (value)) {
53+
g_warning ("soup_message_headers_append: Rejecting bad value '%s'", value);
54+
return FALSE;
55+
diff --git a/tests/header-parsing-test.c b/tests/header-parsing-test.c
56+
index 838baa6..55d47f8 100644
57+
--- a/tests/header-parsing-test.c
58+
+++ b/tests/header-parsing-test.c
59+
@@ -359,6 +359,23 @@ static struct RequestTest {
60+
}, 0
61+
},
62+
63+
+ { "Duplicate Content-Length with the same value", NULL,
64+
+ "POST / HTTP/1.1\r\nContent-Length: 4\r\nContent-Length: 4\r\n",
65+
+ -1,
66+
+ SOUP_STATUS_OK,
67+
+ "POST", "/", SOUP_HTTP_1_1,
68+
+ { { "Content-Length", "4" } }, 0
69+
+ },
70+
+
71+
+ { "Duplicate Content-Length with the same decimal value", NULL,
72+
+ "POST / HTTP/1.1\r\nContent-Length: 04\r\nContent-Length: 4\r\n",
73+
+ -1,
74+
+ SOUP_STATUS_OK,
75+
+ "POST", "/", SOUP_HTTP_1_1,
76+
+ { { "Content-Length", "04" } }, 0
77+
+ },
78+
+
79+
+
80+
/************************/
81+
/*** INVALID REQUESTS ***/
82+
/************************/
83+
@@ -448,6 +465,15 @@ static struct RequestTest {
84+
{ { NULL } }, 0
85+
},
86+
87+
+ { "Duplicate Content-Length with different value",
88+
+ "https://gitlab.gnome.org/GNOME/libsoup/-/issues/500",
89+
+ "POST / HTTP/1.1\r\nContent-Length: 2\r\nContent-Length: 4\r\n",
90+
+ -1,
91+
+ SOUP_STATUS_BAD_REQUEST,
92+
+ NULL, NULL, -1,
93+
+ { { NULL } }, 0
94+
+ },
95+
+
96+
{ "Duplicate Host headers",
97+
"https://gitlab.gnome.org/GNOME/libsoup/-/issues/472",
98+
"GET / HTTP/1.1\r\nHost: example.com\r\nHost: example.org\r\n",
99+
@@ -1373,6 +1399,28 @@ do_bad_header_tests (void)
100+
soup_message_headers_unref (hdrs);
101+
}
102+
103+
+static void
104+
+do_append_duplicate_content_length_test (void)
105+
+{
106+
+ SoupMessageHeaders *hdrs;
107+
+ const char *list_value;
108+
+
109+
+ hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST);
110+
+ soup_message_headers_append (hdrs, "Content-Length", "42");
111+
+
112+
+ /* Inserting the same value doesn't generate a list */
113+
+ soup_message_headers_append (hdrs, "Content-Length", "42");
114+
+ list_value = soup_message_headers_get_list (hdrs, "Content-Length");
115+
+ g_assert_cmpstr (list_value, ==, "42");
116+
+
117+
+ /* Inserting a different value does nothing */
118+
+ soup_message_headers_append (hdrs, "Content-Length", "45");
119+
+ list_value = soup_message_headers_get_list (hdrs, "Content-Length");
120+
+ g_assert_cmpstr (list_value, ==, "42");
121+
+
122+
+ soup_message_headers_unref (hdrs);
123+
+}
124+
+
125+
static void
126+
do_append_duplicate_host_test (void)
127+
{
128+
@@ -1415,6 +1463,8 @@ main (int argc, char **argv)
129+
g_test_add_func ("/header-parsing/append-param", do_append_param_tests);
130+
g_test_add_func ("/header-parsing/bad", do_bad_header_tests);
131+
g_test_add_func ("/header-parsing/append-duplicate-host", do_append_duplicate_host_test);
132+
+ g_test_add_func ("/header-parsing/append-duplicate-content-length", do_append_duplicate_content_length_test);
133+
+
134+
135+
ret = g_test_run ();
136+
137+
diff --git a/tests/server-test.c b/tests/server-test.c
138+
index 96fb428..858bd8c 100644
139+
--- a/tests/server-test.c
140+
+++ b/tests/server-test.c
141+
@@ -1421,6 +1421,68 @@ do_chunked_test (ServerData *sd, gconstpointer test_data)
142+
g_object_unref (client);
143+
}
144+
}
145+
+static void
146+
+do_multiple_content_length_test (ServerData *sd, gconstpointer test_data)
147+
+{
148+
+ gint i;
149+
+ struct {
150+
+ const char *description;
151+
+ const char *test;
152+
+ const char *expected_response;
153+
+ } tests[] = {
154+
+ { "Double Content-Length with different value", "POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 0\r\nContent-Length: 4\r\nConnection: close\r\n\r\n\r\nABCD", "HTTP/1.0 400 Bad Request" },
155+
+ { "Double Content-Length with the same value", "POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 4\r\nContent-Length: 4\r\nConnection: close\r\n\r\n\r\nABCD", "HTTP/1.1 200 OK" },
156+
+ };
157+
+
158+
+ sd->server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
159+
+ sd->base_uri = soup_test_server_get_uri (sd->server, "http", NULL);
160+
+ server_add_handler (sd, NULL, server_callback, NULL, NULL);
161+
+
162+
+ for (i = 0; i < G_N_ELEMENTS (tests); i++) {
163+
+ GSocketClient *client;
164+
+ GSocketConnection *conn;
165+
+ GInputStream *input;
166+
+ GOutputStream *output;
167+
+ gsize nwritten;
168+
+ char buffer[4096];
169+
+ gssize nread;
170+
+ GString *response;
171+
+ const char *boundary;
172+
+ GError *error = NULL;
173+
+
174+
+ debug_printf (1, " %s\n", tests[i].description);
175+
+
176+
+ client = g_socket_client_new ();
177+
+ conn = g_socket_client_connect_to_host (client, g_uri_get_host (sd->base_uri), g_uri_get_port (sd->base_uri), NULL, &error);
178+
+ g_assert_no_error (error);
179+
+
180+
+ output = g_io_stream_get_output_stream (G_IO_STREAM (conn));
181+
+ g_output_stream_write_all (output, tests[i].test, strlen (tests[i].test), &nwritten, NULL, &error);
182+
+ g_assert_no_error (error);
183+
+ g_assert_cmpuint (nwritten, ==, strlen (tests[i].test));
184+
+ g_output_stream_flush (output, NULL, &error);
185+
+ g_assert_no_error (error);
186+
+
187+
+ response = g_string_new (NULL);
188+
+
189+
+ input = g_io_stream_get_input_stream (G_IO_STREAM (conn));
190+
+ do {
191+
+ nread = g_input_stream_read (input, buffer, sizeof(buffer), NULL, NULL);
192+
+ if (nread >= 0)
193+
+ response = g_string_append_len (response, (const char *)buffer, nread);
194+
+ } while (nread > 0);
195+
+
196+
+ boundary = strstr (response->str, "\r\n");
197+
+ g_assert_nonnull (boundary);
198+
+ response = g_string_truncate (response, response->len - strlen (boundary));
199+
+ g_assert_cmpstr (response->str, ==, tests[i].expected_response);
200+
+ g_string_free (response, TRUE);
201+
+
202+
+ g_object_unref (conn);
203+
+ g_object_unref (client);
204+
+ }
205+
+
206+
+}
207+
208+
int
209+
main (int argc, char **argv)
210+
@@ -1462,6 +1524,8 @@ main (int argc, char **argv)
211+
server_setup_nohandler, do_early_multi_test, server_teardown);
212+
g_test_add ("/server/steal/CONNECT", ServerData, NULL,
213+
server_setup, do_steal_connect_test, server_teardown);
214+
+ g_test_add ("/server/multiple-content-length", ServerData, NULL,
215+
+ NULL, do_multiple_content_length_test, server_teardown);
216+
g_test_add ("/server/chunked", ServerData, NULL,
217+
NULL, do_chunked_test, server_teardown);
218+
219+
--
220+
2.45.4
221+

SPECS/libsoup/CVE-2026-6324.patch

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
From 96ac392b444d01bd5de1d1276b187c3ed49d048c Mon Sep 17 00:00:00 2001
2+
From: Carlos Garcia Campos <cgarcia@igalia.com>
3+
Date: Mon, 2 Mar 2026 14:03:41 +0100
4+
Subject: [PATCH] server: improve parsing of chunked request body
5+
6+
Replying with 400 when failing to parse any chunked section and 413 when
7+
the parsed chunked size is too large.
8+
9+
Closes #508
10+
11+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
12+
Upstream-reference: AI Backport of https://gitlab.gnome.org/GNOME/libsoup/-/commit/96ac392b444d01bd5de1d1276b187c3ed49d048c.patch
13+
---
14+
libsoup/http1/soup-body-input-stream.c | 32 +++++++++++++++++--
15+
.../http1/soup-server-message-io-http1.c | 16 ++++++++--
16+
tests/server-test.c | 23 ++++++++++---
17+
3 files changed, 63 insertions(+), 8 deletions(-)
18+
19+
diff --git a/libsoup/http1/soup-body-input-stream.c b/libsoup/http1/soup-body-input-stream.c
20+
index ce80073..691ff42 100644
21+
--- a/libsoup/http1/soup-body-input-stream.c
22+
+++ b/libsoup/http1/soup-body-input-stream.c
23+
@@ -171,6 +171,9 @@ soup_body_input_stream_read_chunked (SoupBodyInputStream *bistream,
24+
SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (priv->base_stream);
25+
char metabuf[128];
26+
gssize nread;
27+
+ guint64 chunk_size;
28+
+ gchar *end;
29+
+ char *boundary;
30+
gboolean got_line;
31+
32+
again:
33+
@@ -183,14 +186,39 @@ again:
34+
if (nread <= 0)
35+
return nread;
36+
37+
- if (!got_line) {
38+
+ if (nread == 0 || !got_line || nread < 3) {
39+
g_set_error_literal (error, G_IO_ERROR,
40+
G_IO_ERROR_PARTIAL_INPUT,
41+
_("Connection terminated unexpectedly"));
42+
return -1;
43+
}
44+
45+
- priv->read_length = strtoul (metabuf, NULL, 16);
46+
+ /* ignore extensions */
47+
+ boundary = strstr (metabuf, ";");
48+
+ if (boundary)
49+
+ *boundary = '\0';
50+
+ else
51+
+ metabuf[nread - 2] = '\0';
52+
+
53+
+ chunk_size = g_ascii_strtoull (metabuf, &end, 16);
54+
+ if (*end) {
55+
+ if (error && *error == NULL) {
56+
+ g_set_error_literal (error, G_IO_ERROR,
57+
+ G_IO_ERROR_INVALID_ARGUMENT,
58+
+ _("Invalid chunk size"));
59+
+ }
60+
+ return -1;
61+
+ }
62+
+
63+
+ if (chunk_size > G_MAXOFFSET) {
64+
+ if (error && *error == NULL) {
65+
+ g_set_error_literal (error, G_IO_ERROR,
66+
+ G_IO_ERROR_MESSAGE_TOO_LARGE,
67+
+ _("Too large chunk"));
68+
+ }
69+
+ return -1;
70+
+ }
71+
+ priv->read_length = (goffset)chunk_size;
72+
if (priv->read_length > 0)
73+
priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK;
74+
else
75+
diff --git a/libsoup/server/http1/soup-server-message-io-http1.c b/libsoup/server/http1/soup-server-message-io-http1.c
76+
index ebcb19d..51c34b6 100644
77+
--- a/libsoup/server/http1/soup-server-message-io-http1.c
78+
+++ b/libsoup/server/http1/soup-server-message-io-http1.c
79+
@@ -826,8 +826,20 @@ io_read (SoupServerMessageIOHTTP1 *server_io,
80+
break;
81+
}
82+
83+
- if (nread == -1)
84+
- return FALSE;
85+
+ if (nread == -1) {
86+
+ if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT) || g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT))
87+
+ soup_server_message_set_status (msg, 400, NULL);
88+
+ else if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE))
89+
+ soup_server_message_set_status (msg, 413, NULL);
90+
+ else
91+
+ return FALSE;
92+
+
93+
+ g_clear_error (error);
94+
+ request_headers = soup_server_message_get_request_headers (msg);
95+
+ soup_message_headers_append_common (request_headers, SOUP_HEADER_CONNECTION, "close", SOUP_HEADER_VALUE_TRUSTED);
96+
+ io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING;
97+
+ break;
98+
+ }
99+
100+
/* else nread == 0 */
101+
io->read_state = SOUP_MESSAGE_IO_STATE_BODY_DONE;
102+
diff --git a/tests/server-test.c b/tests/server-test.c
103+
index 5e15235..4d304f8 100644
104+
--- a/tests/server-test.c
105+
+++ b/tests/server-test.c
106+
@@ -1378,10 +1378,12 @@ do_chunked_test (ServerData *sd, gconstpointer test_data)
107+
struct {
108+
const char *description;
109+
const char *test;
110+
+ const char *expected_response;
111+
} tests[] = {
112+
- { "Single LF", "Transfer-Encoding: chunked\r\n\r\n5;ext\n data\r\n0\r\n\r\n" },
113+
- { "Content-Length and Transfer-Encoding", "Content-Length: 4\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n" },
114+
- { "Content-Length and Transfer-Encoding with keep alive connection", "Content-Length: 4\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\n\r\n0\r\n\r\n" },
115+
+ { "Single LF", "Transfer-Encoding: chunked\r\n\r\n5;ext\n data\r\n0\r\n\r\n", "HTTP/1.1 400 Bad Request" },
116+
+ { "Content-Length and Transfer-Encoding", "Content-Length: 4\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", "HTTP/1.1 200 OK" },
117+
+ { "Content-Length and Transfer-Encoding with keep alive connection", "Content-Length: 4\r\nTransfer-Encoding: chunked\r\nConnection: keep-alive\r\n\r\n0\r\n\r\n", "HTTP/1.1 200 OK" },
118+
+ { "Request Entity Too Large", "Transfer-Encoding: chunked\r\nConnection: keep-alive\r\n\r\n8000000000000001\r\n\r\n\r\n", "HTTP/1.1 413 Request Entity Too Large" },
119+
};
120+
121+
sd->server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
122+
@@ -1396,6 +1398,8 @@ do_chunked_test (ServerData *sd, gconstpointer test_data)
123+
char *request;
124+
char buffer[4096];
125+
gssize nread;
126+
+ GString *response;
127+
+ const char *boundary;
128+
GError *error = NULL;
129+
130+
debug_printf (1, " %s\n", tests[i].description);
131+
@@ -1411,11 +1415,22 @@ do_chunked_test (ServerData *sd, gconstpointer test_data)
132+
g_output_stream_close (output, NULL, NULL);
133+
g_socket_shutdown (g_socket_connection_get_socket (G_SOCKET_CONNECTION (conn)), FALSE, TRUE, &error);
134+
135+
+ response = g_string_new (NULL);
136+
+
137+
input = g_io_stream_get_input_stream (G_IO_STREAM (conn));
138+
do {
139+
- nread = g_input_stream_read (input, buffer, sizeof(buffer), NULL, NULL);
140+
+ nread = g_input_stream_read (input, buffer, sizeof(buffer), NULL, &error);
141+
+ g_assert_no_error (error);
142+
+ if (nread >= 0)
143+
+ response = g_string_append_len (response, (const char *)buffer, nread);
144+
} while (nread > 0);
145+
146+
+ boundary = strstr (response->str, "\r\n");
147+
+ g_assert_nonnull (boundary);
148+
+ response = g_string_truncate (response, response->len - strlen (boundary));
149+
+ g_assert_cmpstr (response->str, ==, tests[i].expected_response);
150+
+ g_string_free (response, TRUE);
151+
+
152+
g_free (request);
153+
g_object_unref (conn);
154+
g_object_unref (client);
155+
--
156+
2.45.4
157+

0 commit comments

Comments
 (0)