+ libsoup-CVE-2025-32049.patch (bsc#1240751 CVE-2025-32049
glgo#GNOME/libsoup#390)
+ libsoup-CVE-2026-2443.patch (bsc#1258170 CVE-2026-2443
glgo#GNOME/libsoup#487)
+ libsoup-CVE-2026-2369.patch (bsc#1258120 CVE-2026-2369
glgo#GNOME/libsoup!508)
OBS-URL: https://build.opensuse.org/package/show/GNOME:Factory/libsoup?expand=0&rev=327
345 lines
12 KiB
Diff
345 lines
12 KiB
Diff
diff -urp libsoup-3.6.5.orig/libsoup/soup-message-headers.c libsoup-3.6.5/libsoup/soup-message-headers.c
|
||
--- libsoup-3.6.5.orig/libsoup/soup-message-headers.c 2026-02-14 04:14:09.575357979 -0600
|
||
+++ libsoup-3.6.5/libsoup/soup-message-headers.c 2026-02-14 04:17:20.278921604 -0600
|
||
@@ -1176,10 +1176,16 @@ sort_ranges (gconstpointer a, gconstpoin
|
||
}
|
||
|
||
/* like soup_message_headers_get_ranges(), except it returns:
|
||
- * SOUP_STATUS_OK if there is no Range or it should be ignored.
|
||
- * SOUP_STATUS_PARTIAL_CONTENT if there is at least one satisfiable range.
|
||
- * SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE if @check_satisfiable
|
||
- * is %TRUE and the request is not satisfiable given @total_length.
|
||
+ * - SOUP_STATUS_OK if there is no Range or it should be ignored due to being
|
||
+ * entirely invalid.
|
||
+ * - SOUP_STATUS_PARTIAL_CONTENT if there is at least one satisfiable range.
|
||
+ * - SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE if @check_satisfiable
|
||
+ * is %TRUE, the Range is valid, but no part of the request is satisfiable
|
||
+ * given @total_length.
|
||
+ *
|
||
+ * @ranges and @length are only set if SOUP_STATUS_PARTIAL_CONTENT is returned.
|
||
+ *
|
||
+ * See https://httpwg.org/specs/rfc9110.html#field.range
|
||
*/
|
||
guint
|
||
soup_message_headers_get_ranges_internal (SoupMessageHeaders *hdrs,
|
||
@@ -1193,22 +1199,28 @@ soup_message_headers_get_ranges_internal
|
||
GArray *array;
|
||
char *spec, *end;
|
||
guint status = SOUP_STATUS_OK;
|
||
+ gboolean is_all_valid = TRUE;
|
||
|
||
if (!range || strncmp (range, "bytes", 5) != 0)
|
||
- return status;
|
||
+ return SOUP_STATUS_OK; /* invalid header or unknown range unit */
|
||
|
||
range += 5;
|
||
while (g_ascii_isspace (*range))
|
||
range++;
|
||
if (*range++ != '=')
|
||
- return status;
|
||
+ return SOUP_STATUS_OK; /* invalid header */
|
||
while (g_ascii_isspace (*range))
|
||
range++;
|
||
|
||
range_list = soup_header_parse_list (range);
|
||
if (!range_list)
|
||
- return status;
|
||
+ return SOUP_STATUS_OK; /* invalid list */
|
||
|
||
+ /* Loop through the ranges and modify the status accordingly. Default to
|
||
+ * status 200 (OK, ignoring the ranges). Switch to status 206 (Partial
|
||
+ * Content) if there is at least one partially valid range. Switch to
|
||
+ * status 416 (Range Not Satisfiable) if there are no partially valid
|
||
+ * ranges at all. */
|
||
array = g_array_new (FALSE, FALSE, sizeof (SoupRange));
|
||
for (r = range_list; r; r = r->next) {
|
||
SoupRange cur;
|
||
@@ -1221,30 +1233,44 @@ soup_message_headers_get_ranges_internal
|
||
cur.start = g_ascii_strtoull (spec, &end, 10);
|
||
if (*end == '-')
|
||
end++;
|
||
- if (*end) {
|
||
+ if (*end)
|
||
cur.end = g_ascii_strtoull (end, &end, 10);
|
||
- if (cur.end < cur.start) {
|
||
- status = SOUP_STATUS_OK;
|
||
- break;
|
||
- }
|
||
- } else
|
||
+ else
|
||
cur.end = total_length - 1;
|
||
}
|
||
+
|
||
if (*end) {
|
||
- status = SOUP_STATUS_OK;
|
||
- break;
|
||
- } else if (check_satisfiable && cur.start >= total_length) {
|
||
- if (status == SOUP_STATUS_OK)
|
||
- status = SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE;
|
||
+ /* Junk after the range */
|
||
+ is_all_valid = FALSE;
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ if (cur.end < cur.start) {
|
||
+ is_all_valid = FALSE;
|
||
continue;
|
||
}
|
||
|
||
+ g_assert (cur.start >= 0);
|
||
+ if (cur.end >= total_length)
|
||
+ cur.end = total_length - 1;
|
||
+
|
||
+ if (cur.start >= total_length) {
|
||
+ /* Range is valid, but unsatisfiable */
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ /* We have at least one (at least partially) satisfiable range */
|
||
g_array_append_val (array, cur);
|
||
status = SOUP_STATUS_PARTIAL_CONTENT;
|
||
}
|
||
soup_header_free_list (range_list);
|
||
|
||
if (status != SOUP_STATUS_PARTIAL_CONTENT) {
|
||
+ g_assert (status == SOUP_STATUS_OK);
|
||
+
|
||
+ if (is_all_valid && check_satisfiable)
|
||
+ status = SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE;
|
||
+
|
||
g_array_free (array, TRUE);
|
||
return status;
|
||
}
|
||
diff -urp libsoup-3.6.5.orig/tests/range-test.c libsoup-3.6.5/tests/range-test.c
|
||
--- libsoup-3.6.5.orig/tests/range-test.c 2026-02-14 03:51:18.021654823 -0600
|
||
+++ libsoup-3.6.5/tests/range-test.c 2026-02-14 04:17:20.279517774 -0600
|
||
@@ -61,7 +61,8 @@ check_part (SoupMessageHeaders *headers,
|
||
|
||
static void
|
||
do_single_range (SoupSession *session, SoupMessage *msg,
|
||
- int start, int end, gboolean succeed)
|
||
+ int start, int end, SoupStatus expected_status,
|
||
+ int expected_start, int expected_end)
|
||
{
|
||
const char *content_type;
|
||
GBytes *body;
|
||
@@ -71,7 +72,7 @@ do_single_range (SoupSession *session, S
|
||
|
||
body = soup_test_session_async_send (session, msg, NULL, NULL);
|
||
|
||
- if (!succeed) {
|
||
+ if (expected_status == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
|
||
soup_test_assert_message_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||
if (soup_message_get_status (msg) != SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
|
||
const char *content_range;
|
||
@@ -81,31 +82,81 @@ do_single_range (SoupSession *session, S
|
||
if (content_range)
|
||
debug_printf (1, " Content-Range: %s\n", content_range);
|
||
}
|
||
+ } else if (expected_status == SOUP_STATUS_OK) {
|
||
+ soup_test_assert_message_status (msg, SOUP_STATUS_OK);
|
||
|
||
- g_object_unref (msg);
|
||
- return;
|
||
- }
|
||
-
|
||
- soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
|
||
+ content_type = soup_message_headers_get_content_type (
|
||
+ soup_message_get_response_headers (msg), NULL);
|
||
+ g_assert_cmpstr (content_type, !=, "multipart/byteranges");
|
||
+
|
||
+ g_assert_false (soup_message_headers_get_content_range (
|
||
+ soup_message_get_response_headers (msg), NULL, NULL, NULL));
|
||
+ g_assert_cmpint (soup_message_headers_get_content_length (
|
||
+ soup_message_get_response_headers (msg)), ==, g_bytes_get_size (full_response));
|
||
+ } else {
|
||
+ soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
|
||
+
|
||
+ content_type = soup_message_headers_get_content_type (
|
||
+ soup_message_get_response_headers (msg), NULL);
|
||
+ g_assert_cmpstr (content_type, !=, "multipart/byteranges");
|
||
|
||
- content_type = soup_message_headers_get_content_type (
|
||
- soup_message_get_response_headers (msg), NULL);
|
||
- g_assert_cmpstr (content_type, !=, "multipart/byteranges");
|
||
+ check_part (soup_message_get_response_headers (msg), body, TRUE, expected_start, expected_end);
|
||
+ }
|
||
|
||
- check_part (soup_message_get_response_headers (msg), body, TRUE, start, end);
|
||
- g_bytes_unref (body);
|
||
+ g_clear_pointer (&body, g_bytes_unref);
|
||
g_object_unref (msg);
|
||
}
|
||
|
||
static void
|
||
request_single_range (SoupSession *session, const char *uri,
|
||
- int start, int end, gboolean succeed)
|
||
+ int start, int end, SoupStatus expected_status,
|
||
+ int expected_start, int expected_end)
|
||
{
|
||
SoupMessage *msg;
|
||
|
||
msg = soup_message_new ("GET", uri);
|
||
soup_message_headers_set_range (soup_message_get_request_headers (msg), start, end);
|
||
- do_single_range (session, msg, start, end, succeed);
|
||
+ do_single_range (session, msg, start, end, expected_status, expected_start, expected_end);
|
||
+}
|
||
+
|
||
+/* This always asserts failure (either 406 or 200 with no Content-Range); it’s
|
||
+ * intended to be used for passing invalid
|
||
+ * Range header formats which can’t be built by calling
|
||
+ * soup_message_headers_set_range(). */
|
||
+static void
|
||
+request_single_range_by_string (SoupSession *session, const char *uri,
|
||
+ const char *range, SoupStatus expected_status)
|
||
+{
|
||
+ SoupMessage *msg;
|
||
+ GBytes *body;
|
||
+
|
||
+ msg = soup_message_new ("GET", uri);
|
||
+ soup_message_headers_replace (soup_message_get_request_headers (msg), "Range", range);
|
||
+
|
||
+ debug_printf (1, " Range: %s\n",
|
||
+ soup_message_headers_get_one (soup_message_get_request_headers (msg), "Range"));
|
||
+
|
||
+ body = soup_test_session_async_send (session, msg, NULL, NULL);
|
||
+
|
||
+ if (expected_status == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
|
||
+ soup_test_assert_message_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||
+ } else {
|
||
+ const char *content_type;
|
||
+
|
||
+ soup_test_assert_message_status (msg, SOUP_STATUS_OK);
|
||
+
|
||
+ content_type = soup_message_headers_get_content_type (
|
||
+ soup_message_get_response_headers (msg), NULL);
|
||
+ g_assert_cmpstr (content_type, !=, "multipart/byteranges");
|
||
+
|
||
+ g_assert_false (soup_message_headers_get_content_range (
|
||
+ soup_message_get_response_headers (msg), NULL, NULL, NULL));
|
||
+ g_assert_cmpint (soup_message_headers_get_content_length (
|
||
+ soup_message_get_response_headers (msg)), ==, g_bytes_get_size (full_response));
|
||
+ }
|
||
+
|
||
+ g_clear_pointer (&body, g_bytes_unref);
|
||
+ g_object_unref (msg);
|
||
}
|
||
|
||
static void
|
||
@@ -172,7 +223,9 @@ request_double_range (SoupSession *sessi
|
||
do_single_range (session, msg,
|
||
MIN (first_start, second_start),
|
||
MAX (first_end, second_end),
|
||
- TRUE);
|
||
+ SOUP_STATUS_PARTIAL_CONTENT,
|
||
+ MIN (first_start, second_start),
|
||
+ MAX (first_end, second_end));
|
||
} else
|
||
do_multi_range (session, msg, expected_return_ranges);
|
||
}
|
||
@@ -200,7 +253,9 @@ request_triple_range (SoupSession *sessi
|
||
do_single_range (session, msg,
|
||
MIN (first_start, MIN (second_start, third_start)),
|
||
MAX (first_end, MAX (second_end, third_end)),
|
||
- TRUE);
|
||
+ SOUP_STATUS_PARTIAL_CONTENT,
|
||
+ MIN (first_start, MIN (second_start, third_start)),
|
||
+ MAX (first_end, MAX (second_end, third_end)));
|
||
} else
|
||
do_multi_range (session, msg, expected_return_ranges);
|
||
}
|
||
@@ -256,7 +311,8 @@ do_range_test (SoupSession *session, con
|
||
debug_printf (1, "Requesting %d-%d\n", 0 * twelfths, 1 * twelfths);
|
||
request_single_range (session, uri,
|
||
0 * twelfths, 1 * twelfths,
|
||
- TRUE);
|
||
+ SOUP_STATUS_PARTIAL_CONTENT,
|
||
+ 0 * twelfths, 1 * twelfths);
|
||
|
||
/* B: 11, end-relative request. These two are mostly redundant
|
||
* in terms of data coverage, but they may still catch
|
||
@@ -265,11 +321,13 @@ do_range_test (SoupSession *session, con
|
||
debug_printf (1, "Requesting %d-\n", 11 * twelfths);
|
||
request_single_range (session, uri,
|
||
11 * twelfths, -1,
|
||
- TRUE);
|
||
+ SOUP_STATUS_PARTIAL_CONTENT,
|
||
+ 11 * twelfths, -1);
|
||
debug_printf (1, "Requesting -%d\n", 1 * twelfths);
|
||
request_single_range (session, uri,
|
||
-1 * twelfths, -1,
|
||
- TRUE);
|
||
+ SOUP_STATUS_PARTIAL_CONTENT,
|
||
+ -1 * twelfths, -1);
|
||
|
||
/* C: 2 and 5 */
|
||
debug_printf (1, "Requesting %d-%d,%d-%d\n",
|
||
@@ -322,7 +380,8 @@ do_range_test (SoupSession *session, con
|
||
(int) full_response_length + 100);
|
||
request_single_range (session, uri,
|
||
full_response_length + 1, full_response_length + 100,
|
||
- FALSE);
|
||
+ SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE,
|
||
+ 0, 0);
|
||
|
||
debug_printf (1, "Requesting (semi-invalid) 1-10,%d-%d,20-30\n",
|
||
(int) full_response_length + 1,
|
||
@@ -331,6 +390,57 @@ do_range_test (SoupSession *session, con
|
||
1, 10,
|
||
full_response_length + 1, full_response_length + 100,
|
||
20, 30);
|
||
+
|
||
+ debug_printf (1, "Requesting (invalid end) %d-%d\n",
|
||
+ 1,
|
||
+ (int) full_response_length + 1000);
|
||
+ request_single_range (session, uri,
|
||
+ 1, full_response_length + 1000,
|
||
+ SOUP_STATUS_PARTIAL_CONTENT,
|
||
+ 1, full_response_length - 1);
|
||
+
|
||
+ debug_printf (1, "Requesting (end before start) %d-%d\n",
|
||
+ 10,
|
||
+ 1);
|
||
+ request_single_range (session, uri,
|
||
+ 10, 1,
|
||
+ SOUP_STATUS_OK,
|
||
+ 1, full_response_length);
|
||
+
|
||
+ debug_printf (1, "Requesting (malformed suffix length) -0\n");
|
||
+ request_single_range_by_string (session, uri,
|
||
+ "bytes=-0",
|
||
+ SOUP_STATUS_OK);
|
||
+
|
||
+ debug_printf (1, "Requesting (extra content after valid header value) 0-10\n");
|
||
+ request_single_range_by_string (session, uri,
|
||
+ "bytes=0-10 but with weird trailing content",
|
||
+ SOUP_STATUS_OK);
|
||
+
|
||
+ debug_printf (1, "Requesting (invalid range dash) 0a10\n");
|
||
+ request_single_range_by_string (session, uri,
|
||
+ "bytes=0a10",
|
||
+ SOUP_STATUS_OK);
|
||
+
|
||
+ debug_printf (1, "Requesting (invalid range unit) 0-10\n");
|
||
+ request_single_range_by_string (session, uri,
|
||
+ "horses=0-10",
|
||
+ SOUP_STATUS_OK);
|
||
+
|
||
+ debug_printf (1, "Requesting (missing equals) 0-10\n");
|
||
+ request_single_range_by_string (session, uri,
|
||
+ "bytes 0-10",
|
||
+ SOUP_STATUS_OK);
|
||
+
|
||
+ debug_printf (1, "Requesting (end before start but with whitespace) 10-1\n");
|
||
+ request_single_range_by_string (session, uri,
|
||
+ "bytes \t = \t 10-1",
|
||
+ SOUP_STATUS_OK);
|
||
+
|
||
+ debug_printf (1, "Requesting (delimiters but no ranges)\n");
|
||
+ request_single_range_by_string (session, uri,
|
||
+ "bytes=, ,,\t, ",
|
||
+ SOUP_STATUS_OK);
|
||
}
|
||
|
||
static void
|