File libsoup-CVE-2026-2443.patch of Package libsoup
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