|
| 1 | +From 88639d13cbcc4e9ccdb1d35c2c0b4a859f84e3f9 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Kshitiz Godara < [email protected]> |
| 3 | +Date: Sun, 4 May 2025 12:46:20 +0000 |
| 4 | +Subject: [PATCH 1/2] Combined two patches to address CVE-2025-2784 |
| 5 | + |
| 6 | +Upstream references: |
| 7 | +https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/435/diffs |
| 8 | +https://gitlab.gnome.org/GNOME/libsoup/-/commit/c415ad0b6771992e66c70edf373566c6e247089d |
| 9 | +--- |
| 10 | + .../content-sniffer/soup-content-sniffer.c | 10 +-- |
| 11 | + libsoup/soup-session.c | 6 ++ |
| 12 | + tests/auth-test.c | 76 +++++++++++++++++++ |
| 13 | + tests/meson.build | 3 + |
| 14 | + tests/sniffing-test.c | 48 ++++++++++++ |
| 15 | + 5 files changed, 138 insertions(+), 5 deletions(-) |
| 16 | + |
| 17 | +diff --git a/libsoup/content-sniffer/soup-content-sniffer.c b/libsoup/content-sniffer/soup-content-sniffer.c |
| 18 | +index f6629ad..3c072c1 100644 |
| 19 | +--- a/libsoup/content-sniffer/soup-content-sniffer.c |
| 20 | ++++ b/libsoup/content-sniffer/soup-content-sniffer.c |
| 21 | +@@ -630,8 +630,11 @@ sniff_text_or_binary (SoupContentSniffer *sniffer, GBytes *buffer) |
| 22 | + } |
| 23 | + |
| 24 | + static gboolean |
| 25 | +-skip_insignificant_space (const char *resource, int *pos, int resource_length) |
| 26 | ++skip_insignificant_space (const char *resource, gsize *pos, gsize resource_length) |
| 27 | + { |
| 28 | ++ if (*pos >= resource_length) |
| 29 | ++ return TRUE; |
| 30 | ++ |
| 31 | + while ((resource[*pos] == '\x09') || |
| 32 | + (resource[*pos] == '\x20') || |
| 33 | + (resource[*pos] == '\x0A') || |
| 34 | +@@ -651,7 +654,7 @@ sniff_feed_or_html (SoupContentSniffer *sniffer, GBytes *buffer) |
| 35 | + gsize resource_length; |
| 36 | + const char *resource = g_bytes_get_data (buffer, &resource_length); |
| 37 | + resource_length = MIN (512, resource_length); |
| 38 | +- int pos = 0; |
| 39 | ++ gsize pos = 0; |
| 40 | + |
| 41 | + if (resource_length < 3) |
| 42 | + goto text_html; |
| 43 | +@@ -661,9 +664,6 @@ sniff_feed_or_html (SoupContentSniffer *sniffer, GBytes *buffer) |
| 44 | + pos = 3; |
| 45 | + |
| 46 | + look_for_tag: |
| 47 | +- if (pos > resource_length) |
| 48 | +- goto text_html; |
| 49 | +- |
| 50 | + if (skip_insignificant_space (resource, &pos, resource_length)) |
| 51 | + goto text_html; |
| 52 | + |
| 53 | +diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c |
| 54 | +index ce9bbf9..185644a 100644 |
| 55 | +--- a/libsoup/soup-session.c |
| 56 | ++++ b/libsoup/soup-session.c |
| 57 | +@@ -1264,6 +1264,12 @@ soup_session_redirect_message (SoupSession *session, |
| 58 | + SOUP_ENCODING_NONE); |
| 59 | + } |
| 60 | + |
| 61 | ++ /* Strip all credentials on cross-origin redirect. */ |
| 62 | ++ if (!soup_uri_host_equal (soup_message_get_uri (msg), new_uri)) { |
| 63 | ++ soup_message_headers_remove_common (soup_message_get_request_headers (msg), SOUP_HEADER_AUTHORIZATION); |
| 64 | ++ soup_message_set_auth (msg, NULL); |
| 65 | ++ } |
| 66 | ++ |
| 67 | + soup_message_set_request_host_from_uri (msg, new_uri); |
| 68 | + soup_message_set_uri (msg, new_uri); |
| 69 | + g_uri_unref (new_uri); |
| 70 | +diff --git a/tests/auth-test.c b/tests/auth-test.c |
| 71 | +index 5dbc319..51431f2 100644 |
| 72 | +--- a/tests/auth-test.c |
| 73 | ++++ b/tests/auth-test.c |
| 74 | +@@ -1,6 +1,7 @@ |
| 75 | + /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ |
| 76 | + |
| 77 | + #include "test-utils.h" |
| 78 | ++#include "soup-uri-utils-private.h" |
| 79 | + |
| 80 | + static const char *base_uri; |
| 81 | + static GMainLoop *loop; |
| 82 | +@@ -1830,6 +1831,81 @@ do_multiple_digest_algorithms (void) |
| 83 | + soup_test_server_quit_unref (server); |
| 84 | + } |
| 85 | + |
| 86 | ++static void |
| 87 | ++redirect_server_callback (SoupServer *server, |
| 88 | ++ SoupServerMessage *msg, |
| 89 | ++ const char *path, |
| 90 | ++ GHashTable *query, |
| 91 | ++ gpointer user_data) |
| 92 | ++{ |
| 93 | ++ static gboolean redirected = FALSE; |
| 94 | ++ |
| 95 | ++ if (!redirected) { |
| 96 | ++ char *redirect_uri = g_uri_to_string (user_data); |
| 97 | ++ soup_server_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY, redirect_uri); |
| 98 | ++ g_free (redirect_uri); |
| 99 | ++ redirected = TRUE; |
| 100 | ++ return; |
| 101 | ++ } |
| 102 | ++ |
| 103 | ++ g_assert_not_reached (); |
| 104 | ++} |
| 105 | ++ |
| 106 | ++static gboolean |
| 107 | ++auth_for_redirect_callback (SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data) |
| 108 | ++{ |
| 109 | ++ GUri *known_server_uri = user_data; |
| 110 | ++ |
| 111 | ++ if (!soup_uri_host_equal (known_server_uri, soup_message_get_uri (msg))) |
| 112 | ++ return FALSE; |
| 113 | ++ |
| 114 | ++ soup_auth_authenticate (auth, "user", "good-basic"); |
| 115 | ++ |
| 116 | ++ return TRUE; |
| 117 | ++} |
| 118 | ++ |
| 119 | ++static void |
| 120 | ++do_strip_on_crossorigin_redirect (void) |
| 121 | ++{ |
| 122 | ++ SoupSession *session; |
| 123 | ++ SoupMessage *msg; |
| 124 | ++ SoupServer *server1, *server2; |
| 125 | ++ SoupAuthDomain *auth_domain; |
| 126 | ++ GUri *uri; |
| 127 | ++ gint status; |
| 128 | ++ |
| 129 | ++ server1 = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD); |
| 130 | ++ server2 = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD); |
| 131 | ++ |
| 132 | ++ /* Both servers have the same credentials. */ |
| 133 | ++ auth_domain = soup_auth_domain_basic_new ("realm", "auth-test", "auth-callback", server_basic_auth_callback, NULL); |
| 134 | ++ soup_auth_domain_add_path (auth_domain, "/"); |
| 135 | ++ soup_server_add_auth_domain (server1, auth_domain); |
| 136 | ++ soup_server_add_auth_domain (server2, auth_domain); |
| 137 | ++ g_object_unref (auth_domain); |
| 138 | ++ |
| 139 | ++ /* Server 1 asks for auth, then redirects to Server 2. */ |
| 140 | ++ soup_server_add_handler (server1, NULL, |
| 141 | ++ redirect_server_callback, |
| 142 | ++ soup_test_server_get_uri (server2, "http", NULL), (GDestroyNotify)g_uri_unref); |
| 143 | ++ /* Server 2 requires auth. */ |
| 144 | ++ soup_server_add_handler (server2, NULL, server_callback, NULL, NULL); |
| 145 | ++ |
| 146 | ++ session = soup_test_session_new (NULL); |
| 147 | ++ uri = soup_test_server_get_uri (server1, "http", NULL); |
| 148 | ++ msg = soup_message_new_from_uri ("GET", uri); |
| 149 | ++ /* The client only sends credentials for the host it knows. */ |
| 150 | ++ g_signal_connect (msg, "authenticate", G_CALLBACK (auth_for_redirect_callback), uri); |
| 151 | ++ |
| 152 | ++ status = soup_test_session_send_message (session, msg); |
| 153 | ++ |
| 154 | ++ g_assert_cmpint (status, ==, SOUP_STATUS_UNAUTHORIZED); |
| 155 | ++ |
| 156 | ++ g_uri_unref (uri); |
| 157 | ++ soup_test_server_quit_unref (server1); |
| 158 | ++ soup_test_server_quit_unref (server2); |
| 159 | ++} |
| 160 | ++ |
| 161 | + int |
| 162 | + main (int argc, char **argv) |
| 163 | + { |
| 164 | +diff --git a/tests/meson.build b/tests/meson.build |
| 165 | +index fc1cb3f..ff94a4f 100644 |
| 166 | +--- a/tests/meson.build |
| 167 | ++++ b/tests/meson.build |
| 168 | +@@ -91,6 +91,9 @@ tests = [ |
| 169 | + {'name': 'server-auth'}, |
| 170 | + {'name': 'server'}, |
| 171 | + {'name': 'sniffing'}, |
| 172 | ++ {'name': 'sniffing', |
| 173 | ++ 'depends': [test_resources], |
| 174 | ++ }, |
| 175 | + {'name': 'socket'}, |
| 176 | + {'name': 'ssl', |
| 177 | + 'dependencies': [gnutls_dep], |
| 178 | +diff --git a/tests/sniffing-test.c b/tests/sniffing-test.c |
| 179 | +index 6116719..7857732 100644 |
| 180 | +--- a/tests/sniffing-test.c |
| 181 | ++++ b/tests/sniffing-test.c |
| 182 | +@@ -342,6 +342,52 @@ test_disabled (gconstpointer data) |
| 183 | + g_uri_unref (uri); |
| 184 | + } |
| 185 | + |
| 186 | ++static const gsize MARKUP_LENGTH = strlen ("<!--") + strlen ("-->"); |
| 187 | ++ |
| 188 | ++static void |
| 189 | ++do_skip_whitespace_test (void) |
| 190 | ++{ |
| 191 | ++ SoupContentSniffer *sniffer = soup_content_sniffer_new (); |
| 192 | ++ SoupMessage *msg = soup_message_new (SOUP_METHOD_GET, "http://example.org"); |
| 193 | ++ const char *test_cases[] = { |
| 194 | ++ "", |
| 195 | ++ "<rdf:RDF", |
| 196 | ++ "<rdf:RDFxmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"", |
| 197 | ++ "<rdf:RDFxmlns=\"http://purl.org/rss/1.0/\"", |
| 198 | ++ }; |
| 199 | ++ |
| 200 | ++ soup_message_headers_set_content_type (soup_message_get_response_headers (msg), "text/html", NULL); |
| 201 | ++ |
| 202 | ++ for (guint i = 0; i < G_N_ELEMENTS (test_cases); i++) { |
| 203 | ++ const char *trailing_data = test_cases[i]; |
| 204 | ++ gsize leading_zeros = 512 - MARKUP_LENGTH - strlen (trailing_data); |
| 205 | ++ gsize testsize = MARKUP_LENGTH + leading_zeros + strlen (trailing_data); |
| 206 | ++ guint8 *data = g_malloc0 (testsize); |
| 207 | ++ guint8 *p = data; |
| 208 | ++ char *content_type; |
| 209 | ++ GBytes *buffer; |
| 210 | ++ |
| 211 | ++ // Format of <!--[0x00 * $leading_zeros]-->$trailing_data |
| 212 | ++ memcpy (p, "<!--", strlen ("<!--")); |
| 213 | ++ p += strlen ("<!--"); |
| 214 | ++ p += leading_zeros; |
| 215 | ++ memcpy (p, "-->", strlen ("-->")); |
| 216 | ++ p += strlen ("-->"); |
| 217 | ++ if (strlen (trailing_data)) |
| 218 | ++ memcpy (p, trailing_data, strlen (trailing_data)); |
| 219 | ++ // Purposefully not NUL terminated. |
| 220 | ++ |
| 221 | ++ buffer = g_bytes_new_take (g_steal_pointer (&data), testsize); |
| 222 | ++ content_type = soup_content_sniffer_sniff (sniffer, msg, buffer, NULL); |
| 223 | ++ |
| 224 | ++ g_free (content_type); |
| 225 | ++ g_bytes_unref (buffer); |
| 226 | ++ } |
| 227 | ++ |
| 228 | ++ g_object_unref (msg); |
| 229 | ++ g_object_unref (sniffer); |
| 230 | ++} |
| 231 | ++ |
| 232 | + int |
| 233 | + main (int argc, char **argv) |
| 234 | + { |
| 235 | +@@ -517,6 +563,8 @@ main (int argc, char **argv) |
| 236 | + "/text_or_binary/home.gif", |
| 237 | + test_disabled); |
| 238 | + |
| 239 | ++ g_test_add_func ("/sniffing/whitespace", do_skip_whitespace_test); |
| 240 | ++ |
| 241 | + ret = g_test_run (); |
| 242 | + |
| 243 | + g_uri_unref (base_uri); |
| 244 | +-- |
| 245 | +2.45.3 |
| 246 | + |
0 commit comments