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

