mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-25 15:06:14 +01:00
gvariant: Limit recursion in g_variant_parse()
The token parsing done by g_variant_parse() uses recursive function
calls, so at some point it will hit the stack limit. As with previous
changes to `GVariantType` parsing (commit 7c4e6e9fbe
), limit the level
of nesting of containers parsed by g_variant_parse() to something
reasonable. We guarantee 64 levels of nesting, which should be enough
for anyone, and is the same as what we guarantee for types.
oss-fuzz#10286
Signed-off-by: Philip Withnall <withnall@endlessm.com>
This commit is contained in:
parent
3dec72b946
commit
25c2266a33
@ -29,6 +29,7 @@
|
||||
#include "gstrfuncs.h"
|
||||
#include "gtestutils.h"
|
||||
#include "gvariant.h"
|
||||
#include "gvariant-internal.h"
|
||||
#include "gvarianttype.h"
|
||||
#include "gslice.h"
|
||||
#include "gthread.h"
|
||||
@ -66,6 +67,7 @@
|
||||
* @G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD: an unknown keyword was encountered
|
||||
* @G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT: unterminated string constant
|
||||
* @G_VARIANT_PARSE_ERROR_VALUE_EXPECTED: no value given
|
||||
* @G_VARIANT_PARSE_ERROR_RECURSION: variant was too deeply nested; #GVariant is only guaranteed to handle nesting up to 64 levels (Since: 2.64)
|
||||
*
|
||||
* Error codes returned by parsing text-format GVariants.
|
||||
**/
|
||||
@ -640,6 +642,7 @@ ast_resolve (AST *ast,
|
||||
|
||||
|
||||
static AST *parse (TokenStream *stream,
|
||||
guint max_depth,
|
||||
va_list *app,
|
||||
GError **error);
|
||||
|
||||
@ -825,6 +828,7 @@ maybe_free (AST *ast)
|
||||
|
||||
static AST *
|
||||
maybe_parse (TokenStream *stream,
|
||||
guint max_depth,
|
||||
va_list *app,
|
||||
GError **error)
|
||||
{
|
||||
@ -838,7 +842,7 @@ maybe_parse (TokenStream *stream,
|
||||
|
||||
if (token_stream_consume (stream, "just"))
|
||||
{
|
||||
child = parse (stream, app, error);
|
||||
child = parse (stream, max_depth - 1, app, error);
|
||||
if (child == NULL)
|
||||
return NULL;
|
||||
}
|
||||
@ -955,6 +959,7 @@ array_free (AST *ast)
|
||||
|
||||
static AST *
|
||||
array_parse (TokenStream *stream,
|
||||
guint max_depth,
|
||||
va_list *app,
|
||||
GError **error)
|
||||
{
|
||||
@ -982,7 +987,7 @@ array_parse (TokenStream *stream,
|
||||
error))
|
||||
goto error;
|
||||
|
||||
child = parse (stream, app, error);
|
||||
child = parse (stream, max_depth - 1, app, error);
|
||||
|
||||
if (!child)
|
||||
goto error;
|
||||
@ -1093,6 +1098,7 @@ tuple_free (AST *ast)
|
||||
|
||||
static AST *
|
||||
tuple_parse (TokenStream *stream,
|
||||
guint max_depth,
|
||||
va_list *app,
|
||||
GError **error)
|
||||
{
|
||||
@ -1121,7 +1127,7 @@ tuple_parse (TokenStream *stream,
|
||||
error))
|
||||
goto error;
|
||||
|
||||
child = parse (stream, app, error);
|
||||
child = parse (stream, max_depth - 1, app, error);
|
||||
|
||||
if (!child)
|
||||
goto error;
|
||||
@ -1199,6 +1205,7 @@ variant_free (AST *ast)
|
||||
|
||||
static AST *
|
||||
variant_parse (TokenStream *stream,
|
||||
guint max_depth,
|
||||
va_list *app,
|
||||
GError **error)
|
||||
{
|
||||
@ -1211,7 +1218,7 @@ variant_parse (TokenStream *stream,
|
||||
AST *value;
|
||||
|
||||
token_stream_assert (stream, "<");
|
||||
value = parse (stream, app, error);
|
||||
value = parse (stream, max_depth - 1, app, error);
|
||||
|
||||
if (!value)
|
||||
return NULL;
|
||||
@ -1385,6 +1392,7 @@ dictionary_free (AST *ast)
|
||||
|
||||
static AST *
|
||||
dictionary_parse (TokenStream *stream,
|
||||
guint max_depth,
|
||||
va_list *app,
|
||||
GError **error)
|
||||
{
|
||||
@ -1412,7 +1420,7 @@ dictionary_parse (TokenStream *stream,
|
||||
return (AST *) dict;
|
||||
}
|
||||
|
||||
if ((first = parse (stream, app, error)) == NULL)
|
||||
if ((first = parse (stream, max_depth - 1, app, error)) == NULL)
|
||||
goto error;
|
||||
|
||||
ast_array_append (&dict->keys, &n_keys, first);
|
||||
@ -1424,7 +1432,7 @@ dictionary_parse (TokenStream *stream,
|
||||
error))
|
||||
goto error;
|
||||
|
||||
if ((first = parse (stream, app, error)) == NULL)
|
||||
if ((first = parse (stream, max_depth - 1, app, error)) == NULL)
|
||||
goto error;
|
||||
|
||||
ast_array_append (&dict->values, &n_values, first);
|
||||
@ -1449,7 +1457,7 @@ dictionary_parse (TokenStream *stream,
|
||||
" or '}' to follow dictionary entry", error))
|
||||
goto error;
|
||||
|
||||
child = parse (stream, app, error);
|
||||
child = parse (stream, max_depth - 1, app, error);
|
||||
|
||||
if (!child)
|
||||
goto error;
|
||||
@ -1460,7 +1468,7 @@ dictionary_parse (TokenStream *stream,
|
||||
" to follow dictionary entry key", error))
|
||||
goto error;
|
||||
|
||||
child = parse (stream, app, error);
|
||||
child = parse (stream, max_depth - 1, app, error);
|
||||
|
||||
if (!child)
|
||||
goto error;
|
||||
@ -2192,6 +2200,7 @@ typedecl_free (AST *ast)
|
||||
|
||||
static AST *
|
||||
typedecl_parse (TokenStream *stream,
|
||||
guint max_depth,
|
||||
va_list *app,
|
||||
GError **error)
|
||||
{
|
||||
@ -2286,7 +2295,7 @@ typedecl_parse (TokenStream *stream,
|
||||
}
|
||||
}
|
||||
|
||||
if ((child = parse (stream, app, error)) == NULL)
|
||||
if ((child = parse (stream, max_depth - 1, app, error)) == NULL)
|
||||
{
|
||||
g_variant_type_free (type);
|
||||
return NULL;
|
||||
@ -2302,26 +2311,35 @@ typedecl_parse (TokenStream *stream,
|
||||
|
||||
static AST *
|
||||
parse (TokenStream *stream,
|
||||
guint max_depth,
|
||||
va_list *app,
|
||||
GError **error)
|
||||
{
|
||||
SourceRef source_ref;
|
||||
AST *result;
|
||||
|
||||
if (max_depth == 0)
|
||||
{
|
||||
token_stream_set_error (stream, error, FALSE,
|
||||
G_VARIANT_PARSE_ERROR_RECURSION,
|
||||
"variant nested too deeply");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
token_stream_prepare (stream);
|
||||
token_stream_start_ref (stream, &source_ref);
|
||||
|
||||
if (token_stream_peek (stream, '['))
|
||||
result = array_parse (stream, app, error);
|
||||
result = array_parse (stream, max_depth, app, error);
|
||||
|
||||
else if (token_stream_peek (stream, '('))
|
||||
result = tuple_parse (stream, app, error);
|
||||
result = tuple_parse (stream, max_depth, app, error);
|
||||
|
||||
else if (token_stream_peek (stream, '<'))
|
||||
result = variant_parse (stream, app, error);
|
||||
result = variant_parse (stream, max_depth, app, error);
|
||||
|
||||
else if (token_stream_peek (stream, '{'))
|
||||
result = dictionary_parse (stream, app, error);
|
||||
result = dictionary_parse (stream, max_depth, app, error);
|
||||
|
||||
else if (app && token_stream_peek (stream, '%'))
|
||||
result = positional_parse (stream, app, error);
|
||||
@ -2339,11 +2357,11 @@ parse (TokenStream *stream,
|
||||
|
||||
else if (token_stream_peek (stream, 'n') ||
|
||||
token_stream_peek (stream, 'j'))
|
||||
result = maybe_parse (stream, app, error);
|
||||
result = maybe_parse (stream, max_depth, app, error);
|
||||
|
||||
else if (token_stream_peek (stream, '@') ||
|
||||
token_stream_is_keyword (stream))
|
||||
result = typedecl_parse (stream, app, error);
|
||||
result = typedecl_parse (stream, max_depth, app, error);
|
||||
|
||||
else if (token_stream_peek (stream, '\'') ||
|
||||
token_stream_peek (stream, '"'))
|
||||
@ -2410,6 +2428,10 @@ parse (TokenStream *stream,
|
||||
* Officially, the language understood by the parser is "any string
|
||||
* produced by g_variant_print()".
|
||||
*
|
||||
* There may be implementation specific restrictions on deeply nested values,
|
||||
* which would result in a %G_VARIANT_PARSE_ERROR_RECURSION error. #GVariant is
|
||||
* guaranteed to handle nesting up to at least 64 levels.
|
||||
*
|
||||
* Returns: a non-floating reference to a #GVariant, or %NULL
|
||||
**/
|
||||
GVariant *
|
||||
@ -2430,7 +2452,7 @@ g_variant_parse (const GVariantType *type,
|
||||
stream.stream = text;
|
||||
stream.end = limit;
|
||||
|
||||
if ((ast = parse (&stream, NULL, error)))
|
||||
if ((ast = parse (&stream, G_VARIANT_MAX_RECURSION_DEPTH, NULL, error)))
|
||||
{
|
||||
if (type == NULL)
|
||||
result = ast_resolve (ast, error);
|
||||
@ -2515,7 +2537,7 @@ g_variant_new_parsed_va (const gchar *format,
|
||||
stream.stream = format;
|
||||
stream.end = NULL;
|
||||
|
||||
if ((ast = parse (&stream, app, &error)))
|
||||
if ((ast = parse (&stream, G_VARIANT_MAX_RECURSION_DEPTH, app, &error)))
|
||||
{
|
||||
result = ast_resolve (ast, &error);
|
||||
ast_free (ast);
|
||||
|
@ -327,7 +327,8 @@ typedef enum
|
||||
G_VARIANT_PARSE_ERROR_UNEXPECTED_TOKEN,
|
||||
G_VARIANT_PARSE_ERROR_UNKNOWN_KEYWORD,
|
||||
G_VARIANT_PARSE_ERROR_UNTERMINATED_STRING_CONSTANT,
|
||||
G_VARIANT_PARSE_ERROR_VALUE_EXPECTED
|
||||
G_VARIANT_PARSE_ERROR_VALUE_EXPECTED,
|
||||
G_VARIANT_PARSE_ERROR_RECURSION
|
||||
} GVariantParseError;
|
||||
#define G_VARIANT_PARSE_ERROR (g_variant_parse_error_quark ())
|
||||
|
||||
|
@ -4180,6 +4180,29 @@ test_parser_integer_bounds (void)
|
||||
#undef test_bound
|
||||
}
|
||||
|
||||
/* Test that #GVariants which recurse too deeply are rejected. */
|
||||
static void
|
||||
test_parser_recursion (void)
|
||||
{
|
||||
GVariant *value = NULL;
|
||||
GError *local_error = NULL;
|
||||
const guint recursion_depth = G_VARIANT_MAX_RECURSION_DEPTH + 1;
|
||||
gchar *silly_dict = g_malloc0 (recursion_depth * 2 + 1);
|
||||
gsize i;
|
||||
|
||||
for (i = 0; i < recursion_depth; i++)
|
||||
{
|
||||
silly_dict[i] = '{';
|
||||
silly_dict[recursion_depth * 2 - i - 1] = '}';
|
||||
}
|
||||
|
||||
value = g_variant_parse (NULL, silly_dict, NULL, NULL, &local_error);
|
||||
g_assert_error (local_error, G_VARIANT_PARSE_ERROR, G_VARIANT_PARSE_ERROR_RECURSION);
|
||||
g_assert_null (value);
|
||||
g_error_free (local_error);
|
||||
g_free (silly_dict);
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse_bad_format_char (void)
|
||||
{
|
||||
@ -5154,6 +5177,7 @@ main (int argc, char **argv)
|
||||
g_test_add_func ("/gvariant/byteswap", test_gv_byteswap);
|
||||
g_test_add_func ("/gvariant/parser", test_parses);
|
||||
g_test_add_func ("/gvariant/parser/integer-bounds", test_parser_integer_bounds);
|
||||
g_test_add_func ("/gvariant/parser/recursion", test_parser_recursion);
|
||||
g_test_add_func ("/gvariant/parse-failures", test_parse_failures);
|
||||
g_test_add_func ("/gvariant/parse-positional", test_parse_positional);
|
||||
g_test_add_func ("/gvariant/parse/subprocess/bad-format-char", test_parse_bad_format_char);
|
||||
|
Loading…
Reference in New Issue
Block a user