From 7bb3115a296154e3f465900ea5c984a493385a7f Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 19 Dec 2025 23:49:05 +0000 Subject: [PATCH] Fix CVE-2026-2443 Upstream-Status: Backport [ c1796442 soup-message-headers: Rework Range response statuses to match Apache 191ef313 soup-message-headers: Fix rejection of Range headers with trailing garbage be677bea soup-message-headers: Fix parsing of invalid Range suffix lengths 2bbfdfe8 soup-message-headers: Reject ranges where end is before start 739bf7cb soup-message-headers: Reject invalid Range ends longer than the content ] CVE: CVE-2026-2443 Signed-off-by: Changqing Li --- libsoup/soup-message-headers.c | 62 ++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c index bb20bbb..535cf14 100644 --- a/libsoup/soup-message-headers.c +++ b/libsoup/soup-message-headers.c @@ -943,10 +943,16 @@ sort_ranges (gconstpointer a, gconstpointer b) } /* 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, @@ -960,22 +966,28 @@ soup_message_headers_get_ranges_internal (SoupMessageHeaders *hdrs, 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; @@ -988,30 +1000,44 @@ soup_message_headers_get_ranges_internal (SoupMessageHeaders *hdrs, 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; } -- 2.34.1