From 9bcd65ba5fa1b92ff0fb8380faea335ccef56253 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 13 Nov 2025 18:27:22 +0000 Subject: [PATCH 1/2] gconvert: Error out if g_escape_uri_string() would overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the string to escape contains a very large number of unacceptable characters (which would need escaping), the calculation of the length of the escaped string could overflow, leading to a potential write off the end of the newly allocated string. In addition to that, the number of unacceptable characters was counted in a signed integer, which would overflow to become negative, making it easier for an attacker to craft an input string which would cause an out-of-bounds write. Fix that by validating the allocation length, and using an unsigned integer to count the number of unacceptable characters. Spotted by treeplus. Thanks to the Sovereign Tech Resilience programme from the Sovereign Tech Agency. ID: #YWH-PGM9867-134 Signed-off-by: Philip Withnall Fixes: #3827 Backport 2.86: Changed the translatable error message to re-use an existing translatable string, to avoid adding new translatable strings to a stable branch. The re-used string doesn’t perfectly match the error, but it’s good enough given that no users will ever see it. --- glib/gconvert.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/glib/gconvert.c b/glib/gconvert.c index 7ad8ca018..367e9b466 100644 --- a/glib/gconvert.c +++ b/glib/gconvert.c @@ -1336,8 +1336,9 @@ static const gchar hex[] = "0123456789ABCDEF"; /* Note: This escape function works on file: URIs, but if you want to * escape something else, please read RFC-2396 */ static gchar * -g_escape_uri_string (const gchar *string, - UnsafeCharacterSet mask) +g_escape_uri_string (const gchar *string, + UnsafeCharacterSet mask, + GError **error) { #define ACCEPTABLE(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask)) @@ -1345,7 +1346,7 @@ g_escape_uri_string (const gchar *string, gchar *q; gchar *result; int c; - gint unacceptable; + size_t unacceptable; UnsafeCharacterSet use_mask; g_return_val_if_fail (mask == UNSAFE_ALL @@ -1362,7 +1363,14 @@ g_escape_uri_string (const gchar *string, if (!ACCEPTABLE (c)) unacceptable++; } - + + if (unacceptable >= (G_MAXSIZE - (p - string)) / 2) + { + g_set_error_literal (error, G_CONVERT_ERROR, G_CONVERT_ERROR_BAD_URI, + _("Invalid hostname")); + return NULL; + } + result = g_malloc (p - string + unacceptable * 2 + 1); use_mask = mask; @@ -1387,12 +1395,13 @@ g_escape_uri_string (const gchar *string, static gchar * -g_escape_file_uri (const gchar *hostname, - const gchar *pathname) +g_escape_file_uri (const gchar *hostname, + const gchar *pathname, + GError **error) { char *escaped_hostname = NULL; - char *escaped_path; - char *res; + char *escaped_path = NULL; + char *res = NULL; #ifdef G_OS_WIN32 char *p, *backslash; @@ -1413,10 +1422,14 @@ g_escape_file_uri (const gchar *hostname, if (hostname && *hostname != '\0') { - escaped_hostname = g_escape_uri_string (hostname, UNSAFE_HOST); + escaped_hostname = g_escape_uri_string (hostname, UNSAFE_HOST, error); + if (escaped_hostname == NULL) + goto out; } - escaped_path = g_escape_uri_string (pathname, UNSAFE_PATH); + escaped_path = g_escape_uri_string (pathname, UNSAFE_PATH, error); + if (escaped_path == NULL) + goto out; res = g_strconcat ("file://", (escaped_hostname) ? escaped_hostname : "", @@ -1424,6 +1437,7 @@ g_escape_file_uri (const gchar *hostname, escaped_path, NULL); +out: #ifdef G_OS_WIN32 g_free ((char *) pathname); #endif @@ -1757,7 +1771,7 @@ g_filename_to_uri (const gchar *filename, hostname = NULL; #endif - escaped_uri = g_escape_file_uri (hostname, filename); + escaped_uri = g_escape_file_uri (hostname, filename, error); return escaped_uri; } From 7e5489cb921d0531ee4ebc9938da30a02084b2fa Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 13 Nov 2025 18:31:43 +0000 Subject: [PATCH 2/2] fuzzing: Add fuzz tests for g_filename_{to,from}_uri() These functions could be called on untrusted input data, and since they do URI escaping/unescaping, they have non-trivial string handling code. Signed-off-by: Philip Withnall See: #3827 --- fuzzing/fuzz_filename_from_uri.c | 40 ++++++++++++++++++++++++++++++++ fuzzing/fuzz_filename_to_uri.c | 40 ++++++++++++++++++++++++++++++++ fuzzing/meson.build | 2 ++ 3 files changed, 82 insertions(+) create mode 100644 fuzzing/fuzz_filename_from_uri.c create mode 100644 fuzzing/fuzz_filename_to_uri.c diff --git a/fuzzing/fuzz_filename_from_uri.c b/fuzzing/fuzz_filename_from_uri.c new file mode 100644 index 000000000..9b7a715f0 --- /dev/null +++ b/fuzzing/fuzz_filename_from_uri.c @@ -0,0 +1,40 @@ +/* + * Copyright 2025 GNOME Foundation, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "fuzz.h" + +int +LLVMFuzzerTestOneInput (const unsigned char *data, size_t size) +{ + unsigned char *nul_terminated_data = NULL; + char *filename = NULL; + GError *local_error = NULL; + + fuzz_set_logging_func (); + + /* ignore @size (g_filename_from_uri() doesn’t support it); ensure @data is nul-terminated */ + nul_terminated_data = (unsigned char *) g_strndup ((const char *) data, size); + filename = g_filename_from_uri ((const char *) nul_terminated_data, NULL, &local_error); + g_free (nul_terminated_data); + + g_free (filename); + g_clear_error (&local_error); + + return 0; +} diff --git a/fuzzing/fuzz_filename_to_uri.c b/fuzzing/fuzz_filename_to_uri.c new file mode 100644 index 000000000..acb319203 --- /dev/null +++ b/fuzzing/fuzz_filename_to_uri.c @@ -0,0 +1,40 @@ +/* + * Copyright 2025 GNOME Foundation, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "fuzz.h" + +int +LLVMFuzzerTestOneInput (const unsigned char *data, size_t size) +{ + unsigned char *nul_terminated_data = NULL; + char *uri = NULL; + GError *local_error = NULL; + + fuzz_set_logging_func (); + + /* ignore @size (g_filename_to_uri() doesn’t support it); ensure @data is nul-terminated */ + nul_terminated_data = (unsigned char *) g_strndup ((const char *) data, size); + uri = g_filename_to_uri ((const char *) nul_terminated_data, NULL, &local_error); + g_free (nul_terminated_data); + + g_free (uri); + g_clear_error (&local_error); + + return 0; +} diff --git a/fuzzing/meson.build b/fuzzing/meson.build index addbe9071..05f936eeb 100644 --- a/fuzzing/meson.build +++ b/fuzzing/meson.build @@ -25,6 +25,8 @@ fuzz_targets = [ 'fuzz_date_parse', 'fuzz_date_time_new_from_iso8601', 'fuzz_dbus_message', + 'fuzz_filename_from_uri', + 'fuzz_filename_to_uri', 'fuzz_get_locale_variants', 'fuzz_inet_address_mask_new_from_string', 'fuzz_inet_address_new_from_string',