summaryrefslogtreecommitdiff
path: root/dev-libs/glib/files
diff options
context:
space:
mode:
Diffstat (limited to 'dev-libs/glib/files')
-rw-r--r--dev-libs/glib/files/glib-2.86-MR-4912.patch186
-rw-r--r--dev-libs/glib/files/glib-2.86-MR-4915-CVE-2025-13601.patch261
-rw-r--r--dev-libs/glib/files/glib-2.86-MR-4934-CVE-2025-14087.patch459
-rw-r--r--dev-libs/glib/files/glib-2.86-MR-4936.patch69
4 files changed, 975 insertions, 0 deletions
diff --git a/dev-libs/glib/files/glib-2.86-MR-4912.patch b/dev-libs/glib/files/glib-2.86-MR-4912.patch
new file mode 100644
index 000000000000..4fdbb3a31195
--- /dev/null
+++ b/dev-libs/glib/files/glib-2.86-MR-4912.patch
@@ -0,0 +1,186 @@
+From d3a16bc03c58a4f7c3222462110509e39c209ebf Mon Sep 17 00:00:00 2001
+From: Jehan <jehan@girinstud.io>
+Date: Fri, 7 Nov 2025 12:09:43 +0100
+Subject: [PATCH] Issue #3819: G_FILE_MONITOR_WATCH_HARD_LINK does not monitor
+ files on Windows.
+
+Current code was clearly considering the case of having only a filename
+as a directory monitoring, instead of a hard-link monitoring. As I
+assume that hard links don't exist on Windows, this case should simply
+revert back to the basic file monitoring code path.
+---
+ gio/win32/gwin32fsmonitorutils.c | 121 ++++++++++++++++---------------
+ 1 file changed, 63 insertions(+), 58 deletions(-)
+
+diff --git a/gio/win32/gwin32fsmonitorutils.c b/gio/win32/gwin32fsmonitorutils.c
+index d06dc458a0..cc2a60b116 100644
+--- a/gio/win32/gwin32fsmonitorutils.c
++++ b/gio/win32/gwin32fsmonitorutils.c
+@@ -245,9 +245,9 @@ g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor,
+ const gchar *filename,
+ gboolean isfile)
+ {
+- wchar_t *wdirname_with_long_prefix = NULL;
++ gchar *dirname_with_long_prefix;
++ wchar_t *wdirname_with_long_prefix;
+ const gchar LONGPFX[] = "\\\\?\\";
+- gchar *fullpath_with_long_prefix, *dirname_with_long_prefix;
+ DWORD notify_filter = isfile ?
+ (FILE_NOTIFY_CHANGE_FILE_NAME |
+ FILE_NOTIFY_CHANGE_ATTRIBUTES |
+@@ -260,83 +260,88 @@ g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor,
+ gboolean success_attribs;
+ WIN32_FILE_ATTRIBUTE_DATA attrib_data = {0, };
+
++ g_return_if_fail ((filename && isfile) || (dirname && ! isfile));
+
+ if (dirname != NULL)
+ {
+ dirname_with_long_prefix = g_strconcat (LONGPFX, dirname, NULL);
+- wdirname_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
+-
+- if (isfile)
+- {
+- gchar *fullpath;
+- wchar_t wlongname[MAX_PATH_LONG];
+- wchar_t wshortname[MAX_PATH_LONG];
+- wchar_t *wfullpath, *wbasename_long, *wbasename_short;
++ }
++ else
++ {
++ gchar *tmp_dirname = g_path_get_dirname (filename);
++ dirname_with_long_prefix = g_strconcat (LONGPFX, tmp_dirname, NULL);
++ g_free (tmp_dirname);
++ }
++ wdirname_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
+
+- fullpath = g_build_filename (dirname, filename, NULL);
+- fullpath_with_long_prefix = g_strconcat (LONGPFX, fullpath, NULL);
++ if (isfile)
++ {
++ gchar *fullpath;
++ gchar *fullpath_with_long_prefix;
++ wchar_t wlongname[MAX_PATH_LONG];
++ wchar_t wshortname[MAX_PATH_LONG];
++ wchar_t *wfullpath, *wbasename_long, *wbasename_short;
++
++ if (dirname)
++ fullpath = g_build_filename (dirname, filename, NULL);
++ else
++ fullpath = g_strdup (filename);
+
+- wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL);
++ fullpath_with_long_prefix = g_strconcat (LONGPFX, fullpath, NULL);
+
+- monitor->wfullpath_with_long_prefix =
+- g_utf8_to_utf16 (fullpath_with_long_prefix, -1, NULL, NULL, NULL);
++ wfullpath = g_utf8_to_utf16 (fullpath, -1, NULL, NULL, NULL);
+
+- /* ReadDirectoryChangesW() can return the normal filename or the
+- * "8.3" format filename, so we need to keep track of both these names
+- * so that we can check against them later when it returns
+- */
+- if (GetLongPathNameW (monitor->wfullpath_with_long_prefix, wlongname, MAX_PATH_LONG) == 0)
+- {
+- wbasename_long = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
+- monitor->wfilename_long = wbasename_long != NULL ?
+- wcsdup (wbasename_long + 1) :
+- wcsdup (wfullpath);
+- }
+- else
+- {
+- wbasename_long = wcsrchr (wlongname, L'\\');
+- monitor->wfilename_long = wbasename_long != NULL ?
+- wcsdup (wbasename_long + 1) :
+- wcsdup (wlongname);
++ monitor->wfullpath_with_long_prefix =
++ g_utf8_to_utf16 (fullpath_with_long_prefix, -1, NULL, NULL, NULL);
+
+- }
++ /* ReadDirectoryChangesW() can return the normal filename or the
++ * "8.3" format filename, so we need to keep track of both these names
++ * so that we can check against them later when it returns
++ */
++ if (GetLongPathNameW (monitor->wfullpath_with_long_prefix, wlongname, MAX_PATH_LONG) == 0)
++ {
++ wbasename_long = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
++ monitor->wfilename_long = wbasename_long != NULL ?
++ wcsdup (wbasename_long + 1) :
++ wcsdup (wfullpath);
++ }
++ else
++ {
++ wbasename_long = wcsrchr (wlongname, L'\\');
++ monitor->wfilename_long = wbasename_long != NULL ?
++ wcsdup (wbasename_long + 1) :
++ wcsdup (wlongname);
+
+- if (GetShortPathNameW (monitor->wfullpath_with_long_prefix, wshortname, MAX_PATH_LONG) == 0)
+- {
+- wbasename_short = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
+- monitor->wfilename_short = wbasename_short != NULL ?
+- wcsdup (wbasename_short + 1) :
+- wcsdup (wfullpath);
+- }
+- else
+- {
+- wbasename_short = wcsrchr (wshortname, L'\\');
+- monitor->wfilename_short = wbasename_short != NULL ?
+- wcsdup (wbasename_short + 1) :
+- wcsdup (wshortname);
+- }
++ }
+
+- g_free (wfullpath);
+- g_free (fullpath);
++ if (GetShortPathNameW (monitor->wfullpath_with_long_prefix, wshortname, MAX_PATH_LONG) == 0)
++ {
++ wbasename_short = wcsrchr (monitor->wfullpath_with_long_prefix, L'\\');
++ monitor->wfilename_short = wbasename_short != NULL ?
++ wcsdup (wbasename_short + 1) :
++ wcsdup (wfullpath);
+ }
+ else
+ {
+- monitor->wfilename_short = NULL;
+- monitor->wfilename_long = NULL;
+- monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
++ wbasename_short = wcsrchr (wshortname, L'\\');
++ monitor->wfilename_short = wbasename_short != NULL ?
++ wcsdup (wbasename_short + 1) :
++ wcsdup (wshortname);
+ }
+
+- monitor->isfile = isfile;
++ g_free (wfullpath);
++ g_free (fullpath);
++ g_free (fullpath_with_long_prefix);
+ }
+ else
+ {
+- dirname_with_long_prefix = g_strconcat (LONGPFX, filename, NULL);
+- monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
+- monitor->wfilename_long = NULL;
+ monitor->wfilename_short = NULL;
+- monitor->isfile = FALSE;
++ monitor->wfilename_long = NULL;
++ monitor->wfullpath_with_long_prefix = g_utf8_to_utf16 (dirname_with_long_prefix, -1, NULL, NULL, NULL);
+ }
+
++ monitor->isfile = isfile;
++
+ success_attribs = GetFileAttributesExW (monitor->wfullpath_with_long_prefix,
+ GetFileExInfoStandard,
+ &attrib_data);
+@@ -345,7 +350,7 @@ g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor,
+ else
+ monitor->file_attribs = INVALID_FILE_ATTRIBUTES;
+ monitor->pfni_prev = NULL;
+- monitor->hDirectory = CreateFileW (wdirname_with_long_prefix != NULL ? wdirname_with_long_prefix : monitor->wfullpath_with_long_prefix,
++ monitor->hDirectory = CreateFileW (wdirname_with_long_prefix,
+ FILE_LIST_DIRECTORY,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+--
+GitLab
+
diff --git a/dev-libs/glib/files/glib-2.86-MR-4915-CVE-2025-13601.patch b/dev-libs/glib/files/glib-2.86-MR-4915-CVE-2025-13601.patch
new file mode 100644
index 000000000000..acba3fd7bd2d
--- /dev/null
+++ b/dev-libs/glib/files/glib-2.86-MR-4915-CVE-2025-13601.patch
@@ -0,0 +1,261 @@
+From 9bcd65ba5fa1b92ff0fb8380faea335ccef56253 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@gnome.org>
+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 <pwithnall@gnome.org>
+
+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 7ad8ca018f..367e9b4661 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;
+ }
+--
+GitLab
+
+
+From 7e5489cb921d0531ee4ebc9938da30a02084b2fa Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@gnome.org>
+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 <pwithnall@gnome.org>
+
+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 0000000000..9b7a715f07
+--- /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 <http://www.gnu.org/licenses/>.
++ */
++
++#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 0000000000..acb3192035
+--- /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 <http://www.gnu.org/licenses/>.
++ */
++
++#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 addbe90717..05f936eeb2 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',
+--
+GitLab
+
diff --git a/dev-libs/glib/files/glib-2.86-MR-4934-CVE-2025-14087.patch b/dev-libs/glib/files/glib-2.86-MR-4934-CVE-2025-14087.patch
new file mode 100644
index 000000000000..018c4fbffcd8
--- /dev/null
+++ b/dev-libs/glib/files/glib-2.86-MR-4934-CVE-2025-14087.patch
@@ -0,0 +1,459 @@
+From 3e72fe0fbb32c18a66486c4da8bc851f656af287 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@gnome.org>
+Date: Tue, 25 Nov 2025 19:02:56 +0000
+Subject: [PATCH 1/3] gvariant-parser: Fix potential integer overflow parsing
+ (byte)strings
+
+The termination condition for parsing string and bytestring literals in
+GVariant text format input was subject to an integer overflow for input
+string (or bytestring) literals longer than `INT_MAX`.
+
+Fix that by counting as a `size_t` rather than as an `int`. The counter
+can never correctly be negative.
+
+Spotted by treeplus. Thanks to the Sovereign Tech Resilience programme
+from the Sovereign Tech Agency. ID: #YWH-PGM9867-145
+
+Signed-off-by: Philip Withnall <pwithnall@gnome.org>
+Fixes: #3834
+---
+ glib/gvariant-parser.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c
+index 2f1d3db9f6..2d6e9856f8 100644
+--- a/glib/gvariant-parser.c
++++ b/glib/gvariant-parser.c
+@@ -609,7 +609,7 @@ ast_resolve (AST *ast,
+ {
+ GVariant *value;
+ gchar *pattern;
+- gint i, j = 0;
++ size_t i, j = 0;
+
+ pattern = ast_get_pattern (ast, error);
+
+@@ -1637,9 +1637,9 @@ string_free (AST *ast)
+ */
+ static gboolean
+ unicode_unescape (const gchar *src,
+- gint *src_ofs,
++ size_t *src_ofs,
+ gchar *dest,
+- gint *dest_ofs,
++ size_t *dest_ofs,
+ gsize length,
+ SourceRef *ref,
+ GError **error)
+@@ -1700,7 +1700,7 @@ string_parse (TokenStream *stream,
+ gsize length;
+ gchar quote;
+ gchar *str;
+- gint i, j;
++ size_t i, j;
+
+ token_stream_start_ref (stream, &ref);
+ token = token_stream_get (stream);
+@@ -1833,7 +1833,7 @@ bytestring_parse (TokenStream *stream,
+ gsize length;
+ gchar quote;
+ gchar *str;
+- gint i, j;
++ size_t i, j;
+
+ token_stream_start_ref (stream, &ref);
+ token = token_stream_get (stream);
+--
+GitLab
+
+
+From 6fe481cec709ec65b5846113848723bc25a8782a Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@gnome.org>
+Date: Tue, 25 Nov 2025 19:19:16 +0000
+Subject: [PATCH 2/3] gvariant-parser: Use size_t to count numbers of child
+ elements
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Rather than using `gint`, which could overflow for arrays (or dicts, or
+tuples) longer than `INT_MAX`. There may be other limits which prevent
+parsed containers becoming that long, but we might as well make the type
+system reflect the programmer’s intention as best it can anyway.
+
+For arrays and tuples this is straightforward. For dictionaries, it’s
+slightly complicated by the fact that the code used
+`dict->n_children == -1` to indicate that the `Dictionary` struct in
+question actually represented a single freestanding dict entry. In
+GVariant text format, that would be `{1, "one"}`.
+
+The implementation previously didn’t define the semantics of
+`dict->n_children < -1`.
+
+Now, instead, change `Dictionary.n_children` to `size_t`, and define a
+magic value `DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY` to indicate that
+the `Dictionary` represents a single freestanding dict entry.
+
+This magic value is `SIZE_MAX`, and given that a dictionary entry takes
+more than one byte to represent in GVariant text format, that means it’s
+not possible to have that many entries in a parsed dictionary, so this
+magic value won’t be hit by a normal dictionary. An assertion checks
+this anyway.
+
+Spotted while working on #3834.
+
+Signed-off-by: Philip Withnall <pwithnall@gnome.org>
+---
+ glib/gvariant-parser.c | 58 ++++++++++++++++++++++++------------------
+ 1 file changed, 33 insertions(+), 25 deletions(-)
+
+diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c
+index 2d6e9856f8..519baa3f36 100644
+--- a/glib/gvariant-parser.c
++++ b/glib/gvariant-parser.c
+@@ -662,9 +662,9 @@ static AST *parse (TokenStream *stream,
+ GError **error);
+
+ static void
+-ast_array_append (AST ***array,
+- gint *n_items,
+- AST *ast)
++ast_array_append (AST ***array,
++ size_t *n_items,
++ AST *ast)
+ {
+ if ((*n_items & (*n_items - 1)) == 0)
+ *array = g_renew (AST *, *array, *n_items ? 2 ** n_items : 1);
+@@ -673,10 +673,10 @@ ast_array_append (AST ***array,
+ }
+
+ static void
+-ast_array_free (AST **array,
+- gint n_items)
++ast_array_free (AST **array,
++ size_t n_items)
+ {
+- gint i;
++ size_t i;
+
+ for (i = 0; i < n_items; i++)
+ ast_free (array[i]);
+@@ -685,11 +685,11 @@ ast_array_free (AST **array,
+
+ static gchar *
+ ast_array_get_pattern (AST **array,
+- gint n_items,
++ size_t n_items,
+ GError **error)
+ {
+ gchar *pattern;
+- gint i;
++ size_t i;
+
+ /* Find the pattern which applies to all children in the array, by l-folding a
+ * coalesce operation.
+@@ -721,7 +721,7 @@ ast_array_get_pattern (AST **array,
+ * pair of values.
+ */
+ {
+- int j = 0;
++ size_t j = 0;
+
+ while (TRUE)
+ {
+@@ -969,7 +969,7 @@ typedef struct
+ AST ast;
+
+ AST **children;
+- gint n_children;
++ size_t n_children;
+ } Array;
+
+ static gchar *
+@@ -1002,7 +1002,7 @@ array_get_value (AST *ast,
+ Array *array = (Array *) ast;
+ const GVariantType *childtype;
+ GVariantBuilder builder;
+- gint i;
++ size_t i;
+
+ if (!g_variant_type_is_array (type))
+ return ast_type_error (ast, type, error);
+@@ -1088,7 +1088,7 @@ typedef struct
+ AST ast;
+
+ AST **children;
+- gint n_children;
++ size_t n_children;
+ } Tuple;
+
+ static gchar *
+@@ -1098,7 +1098,7 @@ tuple_get_pattern (AST *ast,
+ Tuple *tuple = (Tuple *) ast;
+ gchar *result = NULL;
+ gchar **parts;
+- gint i;
++ size_t i;
+
+ parts = g_new (gchar *, tuple->n_children + 4);
+ parts[tuple->n_children + 1] = (gchar *) ")";
+@@ -1128,7 +1128,7 @@ tuple_get_value (AST *ast,
+ Tuple *tuple = (Tuple *) ast;
+ const GVariantType *childtype;
+ GVariantBuilder builder;
+- gint i;
++ size_t i;
+
+ if (!g_variant_type_is_tuple (type))
+ return ast_type_error (ast, type, error);
+@@ -1320,9 +1320,16 @@ typedef struct
+
+ AST **keys;
+ AST **values;
+- gint n_children;
++
++ /* Iff this is DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY then this struct
++ * represents a single freestanding dict entry (`{1, "one"}`) rather than a
++ * full dict. In the freestanding case, @keys and @values have exactly one
++ * member each. */
++ size_t n_children;
+ } Dictionary;
+
++#define DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY ((size_t) -1)
++
+ static gchar *
+ dictionary_get_pattern (AST *ast,
+ GError **error)
+@@ -1337,7 +1344,7 @@ dictionary_get_pattern (AST *ast,
+ return g_strdup ("Ma{**}");
+
+ key_pattern = ast_array_get_pattern (dict->keys,
+- abs (dict->n_children),
++ (dict->n_children == DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY) ? 1 : dict->n_children,
+ error);
+
+ if (key_pattern == NULL)
+@@ -1368,7 +1375,7 @@ dictionary_get_pattern (AST *ast,
+ return NULL;
+
+ result = g_strdup_printf ("M%s{%c%s}",
+- dict->n_children > 0 ? "a" : "",
++ (dict->n_children > 0 && dict->n_children != DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY) ? "a" : "",
+ key_char, value_pattern);
+ g_free (value_pattern);
+
+@@ -1382,7 +1389,7 @@ dictionary_get_value (AST *ast,
+ {
+ Dictionary *dict = (Dictionary *) ast;
+
+- if (dict->n_children == -1)
++ if (dict->n_children == DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY)
+ {
+ const GVariantType *subtype;
+ GVariantBuilder builder;
+@@ -1415,7 +1422,7 @@ dictionary_get_value (AST *ast,
+ {
+ const GVariantType *entry, *key, *val;
+ GVariantBuilder builder;
+- gint i;
++ size_t i;
+
+ if (!g_variant_type_is_subtype_of (type, G_VARIANT_TYPE_DICTIONARY))
+ return ast_type_error (ast, type, error);
+@@ -1456,12 +1463,12 @@ static void
+ dictionary_free (AST *ast)
+ {
+ Dictionary *dict = (Dictionary *) ast;
+- gint n_children;
++ size_t n_children;
+
+- if (dict->n_children > -1)
+- n_children = dict->n_children;
+- else
++ if (dict->n_children == DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY)
+ n_children = 1;
++ else
++ n_children = dict->n_children;
+
+ ast_array_free (dict->keys, n_children);
+ ast_array_free (dict->values, n_children);
+@@ -1479,7 +1486,7 @@ dictionary_parse (TokenStream *stream,
+ maybe_wrapper, dictionary_get_value,
+ dictionary_free
+ };
+- gint n_keys, n_values;
++ size_t n_keys, n_values;
+ gboolean only_one;
+ Dictionary *dict;
+ AST *first;
+@@ -1522,7 +1529,7 @@ dictionary_parse (TokenStream *stream,
+ goto error;
+
+ g_assert (n_keys == 1 && n_values == 1);
+- dict->n_children = -1;
++ dict->n_children = DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY;
+
+ return (AST *) dict;
+ }
+@@ -1555,6 +1562,7 @@ dictionary_parse (TokenStream *stream,
+ }
+
+ g_assert (n_keys == n_values);
++ g_assert (n_keys != DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY);
+ dict->n_children = n_keys;
+
+ return (AST *) dict;
+--
+GitLab
+
+
+From dd333a40aa95819720a01caf6de564cd8a4a6310 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@gnome.org>
+Date: Tue, 25 Nov 2025 19:25:58 +0000
+Subject: [PATCH 3/3] gvariant-parser: Convert error handling code to use
+ size_t
+
+The error handling code allows for printing out the range of input bytes
+related to a parsing error. This was previously done using `gint`, but
+the input could be longer than `INT_MAX`, so it should really be done
+using `size_t`.
+
+Spotted while working on #3834.
+
+Signed-off-by: Philip Withnall <pwithnall@gnome.org>
+---
+ glib/gvariant-parser.c | 36 +++++++++++++++++++++++-------------
+ 1 file changed, 23 insertions(+), 13 deletions(-)
+
+diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c
+index 519baa3f36..1b1ddd654b 100644
+--- a/glib/gvariant-parser.c
++++ b/glib/gvariant-parser.c
+@@ -91,7 +91,9 @@ g_variant_parser_get_error_quark (void)
+
+ typedef struct
+ {
+- gint start, end;
++ /* Offsets from the start of the input, in bytes. Can be equal when referring
++ * to a point rather than a range. The invariant `end >= start` always holds. */
++ size_t start, end;
+ } SourceRef;
+
+ G_GNUC_PRINTF(5, 0)
+@@ -106,14 +108,16 @@ parser_set_error_va (GError **error,
+ GString *msg = g_string_new (NULL);
+
+ if (location->start == location->end)
+- g_string_append_printf (msg, "%d", location->start);
++ g_string_append_printf (msg, "%" G_GSIZE_FORMAT, location->start);
+ else
+- g_string_append_printf (msg, "%d-%d", location->start, location->end);
++ g_string_append_printf (msg, "%" G_GSIZE_FORMAT "-%" G_GSIZE_FORMAT,
++ location->start, location->end);
+
+ if (other != NULL)
+ {
+ g_assert (other->start != other->end);
+- g_string_append_printf (msg, ",%d-%d", other->start, other->end);
++ g_string_append_printf (msg, ",%" G_GSIZE_FORMAT "-%" G_GSIZE_FORMAT,
++ other->start, other->end);
+ }
+ g_string_append_c (msg, ':');
+
+@@ -140,11 +144,15 @@ parser_set_error (GError **error,
+
+ typedef struct
+ {
++ /* We should always have the following ordering constraint:
++ * start <= this <= stream <= end
++ * Additionally, unless in an error or EOF state, `this < stream`.
++ */
+ const gchar *start;
+ const gchar *stream;
+ const gchar *end;
+
+- const gchar *this;
++ const gchar *this; /* (nullable) */
+ } TokenStream;
+
+
+@@ -175,7 +183,7 @@ token_stream_set_error (TokenStream *stream,
+ static gboolean
+ token_stream_prepare (TokenStream *stream)
+ {
+- gint brackets = 0;
++ gssize brackets = 0;
+ const gchar *end;
+
+ if (stream->this != NULL)
+@@ -407,7 +415,7 @@ static void
+ pattern_copy (gchar **out,
+ const gchar **in)
+ {
+- gint brackets = 0;
++ gssize brackets = 0;
+
+ while (**in == 'a' || **in == 'm' || **in == 'M')
+ *(*out)++ = *(*in)++;
+@@ -2765,7 +2773,7 @@ g_variant_builder_add_parsed (GVariantBuilder *builder,
+ static gboolean
+ parse_num (const gchar *num,
+ const gchar *limit,
+- guint *result)
++ size_t *result)
+ {
+ gchar *endptr;
+ gint64 bignum;
+@@ -2775,10 +2783,12 @@ parse_num (const gchar *num,
+ if (endptr != limit)
+ return FALSE;
+
++ /* The upper bound here is more restrictive than it technically needs to be,
++ * but should be enough for any practical situation: */
+ if (bignum < 0 || bignum > G_MAXINT)
+ return FALSE;
+
+- *result = (guint) bignum;
++ *result = (size_t) bignum;
+
+ return TRUE;
+ }
+@@ -2789,7 +2799,7 @@ add_last_line (GString *err,
+ {
+ const gchar *last_nl;
+ gchar *chomped;
+- gint i;
++ size_t i;
+
+ /* This is an error at the end of input. If we have a file
+ * with newlines, that's probably the empty string after the
+@@ -2934,7 +2944,7 @@ g_variant_parse_error_print_context (GError *error,
+
+ if (dash == NULL || colon < dash)
+ {
+- guint point;
++ size_t point;
+
+ /* we have a single point */
+ if (!parse_num (error->message, colon, &point))
+@@ -2952,7 +2962,7 @@ g_variant_parse_error_print_context (GError *error,
+ /* We have one or two ranges... */
+ if (comma && comma < colon)
+ {
+- guint start1, end1, start2, end2;
++ size_t start1, end1, start2, end2;
+ const gchar *dash2;
+
+ /* Two ranges */
+@@ -2968,7 +2978,7 @@ g_variant_parse_error_print_context (GError *error,
+ }
+ else
+ {
+- guint start, end;
++ size_t start, end;
+
+ /* One range */
+ if (!parse_num (error->message, dash, &start) || !parse_num (dash + 1, colon, &end))
+--
+GitLab
+
diff --git a/dev-libs/glib/files/glib-2.86-MR-4936.patch b/dev-libs/glib/files/glib-2.86-MR-4936.patch
new file mode 100644
index 000000000000..ce1d9b1328f4
--- /dev/null
+++ b/dev-libs/glib/files/glib-2.86-MR-4936.patch
@@ -0,0 +1,69 @@
+From 4f0399c0aaf3ffc86b5625424580294bc7460404 Mon Sep 17 00:00:00 2001
+From: Philip Withnall <pwithnall@gnome.org>
+Date: Thu, 4 Dec 2025 16:37:19 +0000
+Subject: [PATCH] gfileattribute: Fix integer overflow calculating escaping for
+ byte strings
+
+The number of invalid characters in the byte string (characters which
+would have to be percent-encoded) was only stored in an `int`, which
+gave the possibility of a long string largely full of invalid
+characters overflowing this and allowing an attacker-controlled buffer
+size to be allocated.
+
+This could be triggered by an attacker controlled file attribute (of
+type `G_FILE_ATTRIBUTE_TYPE_BYTE_STRING`), such as
+`G_FILE_ATTRIBUTE_THUMBNAIL_PATH` or `G_FILE_ATTRIBUTE_STANDARD_NAME`,
+being read by user code.
+
+Spotted by Codean Labs.
+
+Signed-off-by: Philip Withnall <pwithnall@gnome.org>
+
+Fixes: #3845
+---
+ gio/gfileattribute.c | 11 +++++++++--
+ 1 file changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/gio/gfileattribute.c b/gio/gfileattribute.c
+index c6fde60fa9..d3083e5bd8 100644
+--- a/gio/gfileattribute.c
++++ b/gio/gfileattribute.c
+@@ -22,6 +22,7 @@
+
+ #include "config.h"
+
++#include <stdint.h>
+ #include <string.h>
+
+ #include "gfileattribute.h"
+@@ -166,11 +167,12 @@ valid_char (char c)
+ return c >= 32 && c <= 126 && c != '\\';
+ }
+
++/* Returns NULL on error */
+ static char *
+ escape_byte_string (const char *str)
+ {
+ size_t i, len;
+- int num_invalid;
++ size_t num_invalid;
+ char *escaped_val, *p;
+ unsigned char c;
+ const char hex_digits[] = "0123456789abcdef";
+@@ -188,7 +190,12 @@ escape_byte_string (const char *str)
+ return g_strdup (str);
+ else
+ {
+- escaped_val = g_malloc (len + num_invalid*3 + 1);
++ /* Check for overflow. We want to check the inequality:
++ * !(len + num_invalid * 3 + 1 > SIZE_MAX) */
++ if (num_invalid >= (SIZE_MAX - len) / 3)
++ return NULL;
++
++ escaped_val = g_malloc (len + num_invalid * 3 + 1);
+
+ p = escaped_val;
+ for (i = 0; i < len; i++)
+--
+GitLab
+