Merge branch 'backport-4933-variant-parse-string-glib-2-86' into 'glib-2-86'

Backport !4933 “gvariant-parser: Fix potential integer overflow parsing (byte)strings” to glib-2-86

See merge request GNOME/glib!4934
This commit is contained in:
Philip Withnall
2025-12-04 16:19:25 +00:00

View File

@@ -91,7 +91,9 @@ g_variant_parser_get_error_quark (void)
typedef struct 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; } SourceRef;
G_GNUC_PRINTF(5, 0) G_GNUC_PRINTF(5, 0)
@@ -106,14 +108,16 @@ parser_set_error_va (GError **error,
GString *msg = g_string_new (NULL); GString *msg = g_string_new (NULL);
if (location->start == location->end) if (location->start == location->end)
g_string_append_printf (msg, "%d", location->start); g_string_append_printf (msg, "%" G_GSIZE_FORMAT, location->start);
else 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) if (other != NULL)
{ {
g_assert (other->start != other->end); 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, ':'); g_string_append_c (msg, ':');
@@ -140,11 +144,15 @@ parser_set_error (GError **error,
typedef struct 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 *start;
const gchar *stream; const gchar *stream;
const gchar *end; const gchar *end;
const gchar *this; const gchar *this; /* (nullable) */
} TokenStream; } TokenStream;
@@ -175,7 +183,7 @@ token_stream_set_error (TokenStream *stream,
static gboolean static gboolean
token_stream_prepare (TokenStream *stream) token_stream_prepare (TokenStream *stream)
{ {
gint brackets = 0; gssize brackets = 0;
const gchar *end; const gchar *end;
if (stream->this != NULL) if (stream->this != NULL)
@@ -407,7 +415,7 @@ static void
pattern_copy (gchar **out, pattern_copy (gchar **out,
const gchar **in) const gchar **in)
{ {
gint brackets = 0; gssize brackets = 0;
while (**in == 'a' || **in == 'm' || **in == 'M') while (**in == 'a' || **in == 'm' || **in == 'M')
*(*out)++ = *(*in)++; *(*out)++ = *(*in)++;
@@ -609,7 +617,7 @@ ast_resolve (AST *ast,
{ {
GVariant *value; GVariant *value;
gchar *pattern; gchar *pattern;
gint i, j = 0; size_t i, j = 0;
pattern = ast_get_pattern (ast, error); pattern = ast_get_pattern (ast, error);
@@ -662,9 +670,9 @@ static AST *parse (TokenStream *stream,
GError **error); GError **error);
static void static void
ast_array_append (AST ***array, ast_array_append (AST ***array,
gint *n_items, size_t *n_items,
AST *ast) AST *ast)
{ {
if ((*n_items & (*n_items - 1)) == 0) if ((*n_items & (*n_items - 1)) == 0)
*array = g_renew (AST *, *array, *n_items ? 2 ** n_items : 1); *array = g_renew (AST *, *array, *n_items ? 2 ** n_items : 1);
@@ -673,10 +681,10 @@ ast_array_append (AST ***array,
} }
static void static void
ast_array_free (AST **array, ast_array_free (AST **array,
gint n_items) size_t n_items)
{ {
gint i; size_t i;
for (i = 0; i < n_items; i++) for (i = 0; i < n_items; i++)
ast_free (array[i]); ast_free (array[i]);
@@ -685,11 +693,11 @@ ast_array_free (AST **array,
static gchar * static gchar *
ast_array_get_pattern (AST **array, ast_array_get_pattern (AST **array,
gint n_items, size_t n_items,
GError **error) GError **error)
{ {
gchar *pattern; gchar *pattern;
gint i; size_t i;
/* Find the pattern which applies to all children in the array, by l-folding a /* Find the pattern which applies to all children in the array, by l-folding a
* coalesce operation. * coalesce operation.
@@ -721,7 +729,7 @@ ast_array_get_pattern (AST **array,
* pair of values. * pair of values.
*/ */
{ {
int j = 0; size_t j = 0;
while (TRUE) while (TRUE)
{ {
@@ -969,7 +977,7 @@ typedef struct
AST ast; AST ast;
AST **children; AST **children;
gint n_children; size_t n_children;
} Array; } Array;
static gchar * static gchar *
@@ -1002,7 +1010,7 @@ array_get_value (AST *ast,
Array *array = (Array *) ast; Array *array = (Array *) ast;
const GVariantType *childtype; const GVariantType *childtype;
GVariantBuilder builder; GVariantBuilder builder;
gint i; size_t i;
if (!g_variant_type_is_array (type)) if (!g_variant_type_is_array (type))
return ast_type_error (ast, type, error); return ast_type_error (ast, type, error);
@@ -1088,7 +1096,7 @@ typedef struct
AST ast; AST ast;
AST **children; AST **children;
gint n_children; size_t n_children;
} Tuple; } Tuple;
static gchar * static gchar *
@@ -1098,7 +1106,7 @@ tuple_get_pattern (AST *ast,
Tuple *tuple = (Tuple *) ast; Tuple *tuple = (Tuple *) ast;
gchar *result = NULL; gchar *result = NULL;
gchar **parts; gchar **parts;
gint i; size_t i;
parts = g_new (gchar *, tuple->n_children + 4); parts = g_new (gchar *, tuple->n_children + 4);
parts[tuple->n_children + 1] = (gchar *) ")"; parts[tuple->n_children + 1] = (gchar *) ")";
@@ -1128,7 +1136,7 @@ tuple_get_value (AST *ast,
Tuple *tuple = (Tuple *) ast; Tuple *tuple = (Tuple *) ast;
const GVariantType *childtype; const GVariantType *childtype;
GVariantBuilder builder; GVariantBuilder builder;
gint i; size_t i;
if (!g_variant_type_is_tuple (type)) if (!g_variant_type_is_tuple (type))
return ast_type_error (ast, type, error); return ast_type_error (ast, type, error);
@@ -1320,9 +1328,16 @@ typedef struct
AST **keys; AST **keys;
AST **values; 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; } Dictionary;
#define DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY ((size_t) -1)
static gchar * static gchar *
dictionary_get_pattern (AST *ast, dictionary_get_pattern (AST *ast,
GError **error) GError **error)
@@ -1337,7 +1352,7 @@ dictionary_get_pattern (AST *ast,
return g_strdup ("Ma{**}"); return g_strdup ("Ma{**}");
key_pattern = ast_array_get_pattern (dict->keys, 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); error);
if (key_pattern == NULL) if (key_pattern == NULL)
@@ -1368,7 +1383,7 @@ dictionary_get_pattern (AST *ast,
return NULL; return NULL;
result = g_strdup_printf ("M%s{%c%s}", 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); key_char, value_pattern);
g_free (value_pattern); g_free (value_pattern);
@@ -1382,7 +1397,7 @@ dictionary_get_value (AST *ast,
{ {
Dictionary *dict = (Dictionary *) ast; Dictionary *dict = (Dictionary *) ast;
if (dict->n_children == -1) if (dict->n_children == DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY)
{ {
const GVariantType *subtype; const GVariantType *subtype;
GVariantBuilder builder; GVariantBuilder builder;
@@ -1415,7 +1430,7 @@ dictionary_get_value (AST *ast,
{ {
const GVariantType *entry, *key, *val; const GVariantType *entry, *key, *val;
GVariantBuilder builder; GVariantBuilder builder;
gint i; size_t i;
if (!g_variant_type_is_subtype_of (type, G_VARIANT_TYPE_DICTIONARY)) if (!g_variant_type_is_subtype_of (type, G_VARIANT_TYPE_DICTIONARY))
return ast_type_error (ast, type, error); return ast_type_error (ast, type, error);
@@ -1456,12 +1471,12 @@ static void
dictionary_free (AST *ast) dictionary_free (AST *ast)
{ {
Dictionary *dict = (Dictionary *) ast; Dictionary *dict = (Dictionary *) ast;
gint n_children; size_t n_children;
if (dict->n_children > -1) if (dict->n_children == DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY)
n_children = dict->n_children;
else
n_children = 1; n_children = 1;
else
n_children = dict->n_children;
ast_array_free (dict->keys, n_children); ast_array_free (dict->keys, n_children);
ast_array_free (dict->values, n_children); ast_array_free (dict->values, n_children);
@@ -1479,7 +1494,7 @@ dictionary_parse (TokenStream *stream,
maybe_wrapper, dictionary_get_value, maybe_wrapper, dictionary_get_value,
dictionary_free dictionary_free
}; };
gint n_keys, n_values; size_t n_keys, n_values;
gboolean only_one; gboolean only_one;
Dictionary *dict; Dictionary *dict;
AST *first; AST *first;
@@ -1522,7 +1537,7 @@ dictionary_parse (TokenStream *stream,
goto error; goto error;
g_assert (n_keys == 1 && n_values == 1); g_assert (n_keys == 1 && n_values == 1);
dict->n_children = -1; dict->n_children = DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY;
return (AST *) dict; return (AST *) dict;
} }
@@ -1555,6 +1570,7 @@ dictionary_parse (TokenStream *stream,
} }
g_assert (n_keys == n_values); g_assert (n_keys == n_values);
g_assert (n_keys != DICTIONARY_N_CHILDREN_FREESTANDING_ENTRY);
dict->n_children = n_keys; dict->n_children = n_keys;
return (AST *) dict; return (AST *) dict;
@@ -1637,9 +1653,9 @@ string_free (AST *ast)
*/ */
static gboolean static gboolean
unicode_unescape (const gchar *src, unicode_unescape (const gchar *src,
gint *src_ofs, size_t *src_ofs,
gchar *dest, gchar *dest,
gint *dest_ofs, size_t *dest_ofs,
gsize length, gsize length,
SourceRef *ref, SourceRef *ref,
GError **error) GError **error)
@@ -1700,7 +1716,7 @@ string_parse (TokenStream *stream,
gsize length; gsize length;
gchar quote; gchar quote;
gchar *str; gchar *str;
gint i, j; size_t i, j;
token_stream_start_ref (stream, &ref); token_stream_start_ref (stream, &ref);
token = token_stream_get (stream); token = token_stream_get (stream);
@@ -1833,7 +1849,7 @@ bytestring_parse (TokenStream *stream,
gsize length; gsize length;
gchar quote; gchar quote;
gchar *str; gchar *str;
gint i, j; size_t i, j;
token_stream_start_ref (stream, &ref); token_stream_start_ref (stream, &ref);
token = token_stream_get (stream); token = token_stream_get (stream);
@@ -2757,7 +2773,7 @@ g_variant_builder_add_parsed (GVariantBuilder *builder,
static gboolean static gboolean
parse_num (const gchar *num, parse_num (const gchar *num,
const gchar *limit, const gchar *limit,
guint *result) size_t *result)
{ {
gchar *endptr; gchar *endptr;
gint64 bignum; gint64 bignum;
@@ -2767,10 +2783,12 @@ parse_num (const gchar *num,
if (endptr != limit) if (endptr != limit)
return FALSE; 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) if (bignum < 0 || bignum > G_MAXINT)
return FALSE; return FALSE;
*result = (guint) bignum; *result = (size_t) bignum;
return TRUE; return TRUE;
} }
@@ -2781,7 +2799,7 @@ add_last_line (GString *err,
{ {
const gchar *last_nl; const gchar *last_nl;
gchar *chomped; gchar *chomped;
gint i; size_t i;
/* This is an error at the end of input. If we have a file /* This is an error at the end of input. If we have a file
* with newlines, that's probably the empty string after the * with newlines, that's probably the empty string after the
@@ -2926,7 +2944,7 @@ g_variant_parse_error_print_context (GError *error,
if (dash == NULL || colon < dash) if (dash == NULL || colon < dash)
{ {
guint point; size_t point;
/* we have a single point */ /* we have a single point */
if (!parse_num (error->message, colon, &point)) if (!parse_num (error->message, colon, &point))
@@ -2944,7 +2962,7 @@ g_variant_parse_error_print_context (GError *error,
/* We have one or two ranges... */ /* We have one or two ranges... */
if (comma && comma < colon) if (comma && comma < colon)
{ {
guint start1, end1, start2, end2; size_t start1, end1, start2, end2;
const gchar *dash2; const gchar *dash2;
/* Two ranges */ /* Two ranges */
@@ -2960,7 +2978,7 @@ g_variant_parse_error_print_context (GError *error,
} }
else else
{ {
guint start, end; size_t start, end;
/* One range */ /* One range */
if (!parse_num (error->message, dash, &start) || !parse_num (dash + 1, colon, &end)) if (!parse_num (error->message, dash, &start) || !parse_num (dash + 1, colon, &end))