Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Step:15
glib2
0002-gvarianttype-Impose-a-recursion-limit-of-1...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 0002-gvarianttype-Impose-a-recursion-limit-of-128-on-vari.patch of Package glib2
From 7b84790be37360dd73728aad19cea2ae7667ddce Mon Sep 17 00:00:00 2001 From: Philip Withnall <withnall@endlessm.com> Date: Fri, 7 Sep 2018 20:27:39 +0100 Subject: [PATCH 02/15] gvarianttype: Impose a recursion limit of 128 on variant types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, GVariant has allowed ‘arbitrary’ recursion on GVariantTypes, but this isn’t really feasible. We have to deal with GVariants from untrusted sources, and the nature of GVariantType means that another level of recursion (and hence, for example, another stack frame in your application) can be added with a single byte in a variant type signature in the input. This gives malicious input sources far too much leverage to cause deep stack recursion or massive memory allocations which can DoS an application. Limit recursion to 128 levels (which should be more than enough for anyone™), document it and add a test. This is, handily, also the limit of 64 applied by the D-Bus specification (§(Valid Signatures)), plus a bit to allow wrapping of D-Bus messages in additional layers of variants. oss-fuzz#9857 Signed-off-by: Philip Withnall <withnall@endlessm.com> --- docs/reference/glib/glib-sections.txt | 1 + glib/gvariant-core.c | 46 ++++++- glib/gvariant-internal.h | 18 +++ glib/gvariant-serialiser.c | 38 +++++- glib/gvariant-serialiser.h | 1 + glib/gvarianttype.c | 137 +++++++++++++++------ glib/gvarianttype.h | 2 + glib/gvarianttypeinfo.c | 25 ++++ glib/gvarianttypeinfo.h | 2 + glib/tests/gvariant.c | 171 ++++++++++++++++++++++++++ 10 files changed, 397 insertions(+), 44 deletions(-) diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index e09d4d3aa..8e39d869d 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -3400,6 +3400,7 @@ g_variant_parse_error_print_context g_variant_parse_error_quark g_variant_parser_get_error_quark g_variant_type_checked_ +g_variant_type_string_get_depth_ </SECTION> diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c index bdf09696f..815bdf9e0 100644 --- a/glib/gvariant-core.c +++ b/glib/gvariant-core.c @@ -20,6 +20,7 @@ #include <glib/gvariant-core.h> +#include <glib/gvariant-internal.h> #include <glib/gvariant-serialiser.h> #include <glib/gtestutils.h> #include <glib/gbitlock.h> @@ -74,6 +75,7 @@ struct _GVariant gint state; gint ref_count; + gsize depth; }; /* struct GVariant: @@ -202,6 +204,11 @@ struct _GVariant * reference. See g_variant_ref_sink(). * * ref_count: the reference count of the instance + * + * depth: the depth of the GVariant in a hierarchy of nested containers, + * increasing with the level of nesting. The top-most GVariant has depth + * zero. This is used to avoid recursing too deeply and overflowing the + * stack when handling deeply nested untrusted serialised GVariants. */ #define STATE_LOCKED 1 #define STATE_SERIALISED 2 @@ -366,6 +373,7 @@ g_variant_serialise (GVariant *value, serialised.type_info = value->type_info; serialised.size = value->size; serialised.data = data; + serialised.depth = value->depth; children = (gpointer *) value->contents.tree.children; n_children = value->contents.tree.n_children; @@ -407,6 +415,7 @@ g_variant_fill_gvs (GVariantSerialised *serialised, if (serialised->size == 0) serialised->size = value->size; g_assert (serialised->size == value->size); + serialised->depth = value->depth; if (serialised->data) /* g_variant_store() is a public API, so it @@ -480,6 +489,7 @@ g_variant_alloc (const GVariantType *type, STATE_FLOATING; value->size = (gssize) -1; value->ref_count = 1; + value->depth = 0; return value; } @@ -933,7 +943,8 @@ g_variant_n_children (GVariant *value) GVariantSerialised serialised = { value->type_info, (gpointer) value->contents.serialised.data, - value->size + value->size, + value->depth, }; n_children = g_variant_serialised_n_children (serialised); @@ -962,6 +973,11 @@ g_variant_n_children (GVariant *value) * The returned value is never floating. You should free it with * g_variant_unref() when you're done with it. * + * There may be implementation specific restrictions on deeply nested values, + * which would result in the unit tuple being returned as the child value, + * instead of further nested children. #GVariant is guaranteed to handle + * nesting up to at least 64 levels. + * * This function is O(1). * * Returns: (transfer full): the child at the specified index @@ -973,6 +989,7 @@ g_variant_get_child_value (GVariant *value, gsize index_) { g_return_val_if_fail (index_ < g_variant_n_children (value), NULL); + g_return_val_if_fail (value->depth < G_MAXSIZE, NULL); if (~g_atomic_int_get (&value->state) & STATE_SERIALISED) { @@ -995,7 +1012,8 @@ g_variant_get_child_value (GVariant *value, GVariantSerialised serialised = { value->type_info, (gpointer) value->contents.serialised.data, - value->size + value->size, + value->depth, }; GVariantSerialised s_child; GVariant *child; @@ -1005,6 +1023,20 @@ g_variant_get_child_value (GVariant *value, */ s_child = g_variant_serialised_get_child (serialised, index_); + /* Check whether this would cause nesting too deep. If so, return a fake + * child. The only situation we expect this to happen in is with a variant, + * as all other deeply-nested types have a static type, and hence should + * have been rejected earlier. In the case of a variant whose nesting plus + * the depth of its child is too great, return a unit variant () instead of + * the real child. */ + if (!(value->state & STATE_TRUSTED) && + g_variant_type_info_query_depth (s_child.type_info) >= + G_VARIANT_MAX_RECURSION_DEPTH - value->depth) + { + g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_VARIANT)); + return g_variant_new_tuple (NULL, 0); + } + /* create a new serialised instance out of it */ child = g_slice_new (GVariant); child->type_info = s_child.type_info; @@ -1012,6 +1044,7 @@ g_variant_get_child_value (GVariant *value, STATE_SERIALISED; child->size = s_child.size; child->ref_count = 1; + child->depth = value->depth + 1; child->contents.serialised.bytes = g_bytes_ref (value->contents.serialised.bytes); child->contents.serialised.data = s_child.data; @@ -1074,6 +1107,9 @@ g_variant_store (GVariant *value, * being trusted. If the value was already marked as being trusted then * this function will immediately return %TRUE. * + * There may be implementation specific restrictions on deeply nested values. + * GVariant is guaranteed to handle nesting up to at least 64 levels. + * * Returns: %TRUE if @value is in normal form * * Since: 2.24 @@ -1086,12 +1122,16 @@ g_variant_is_normal_form (GVariant *value) g_variant_lock (value); + if (value->depth >= G_VARIANT_MAX_RECURSION_DEPTH) + return FALSE; + if (value->state & STATE_SERIALISED) { GVariantSerialised serialised = { value->type_info, (gpointer) value->contents.serialised.data, - value->size + value->size, + value->depth }; if (g_variant_serialised_is_normal (serialised)) diff --git a/glib/gvariant-internal.h b/glib/gvariant-internal.h index 600be1c1a..f7536da71 100644 --- a/glib/gvariant-internal.h +++ b/glib/gvariant-internal.h @@ -47,4 +47,22 @@ GVariantType * g_variant_format_string_scan_type (const g const gchar *limit, const gchar **endptr); +/* The maximum number of levels of nested container which this implementation + * of #GVariant will handle. + * + * The limit must be at least 64 + 1, to allow D-Bus messages to be wrapped in + * a top-level #GVariant. This comes from the D-Bus specification (§(Valid + * Signatures)), but also seems generally reasonable. #GDBusMessage wraps its + * payload in a top-level tuple. + * + * The limit is actually set to be a lot greater than 64, to allow much greater + * nesting of values off D-Bus. It cannot be set over around 80000, or the risk + * of overflowing the stack when parsing type strings becomes too great. + * + * Aside from those constraints, the choice of this value is arbitrary. The + * only restrictions on it from the API are that it has to be greater than 64 + * (due to D-Bus). +*/ +#define G_VARIANT_MAX_RECURSION_DEPTH ((gsize) 128) + #endif /* __G_VARIANT_INTERNAL_H__ */ diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c index 96df54e23..fe0bcf0aa 100644 --- a/glib/gvariant-serialiser.c +++ b/glib/gvariant-serialiser.c @@ -23,6 +23,7 @@ #include "gvariant-serialiser.h" +#include <glib/gvariant-internal.h> #include <glib/gtestutils.h> #include <glib/gstrfuncs.h> #include <glib/gtypes.h> @@ -81,7 +82,9 @@ * values is permitted (eg: 0 to 255 is a valid byte). Special checks * need to be performed for booleans (only 0 or 1 allowed), strings * (properly nul-terminated) and object paths and signature strings - * (meeting the D-Bus specification requirements). + * (meeting the D-Bus specification requirements). Depth checks need to be + * performed for nested types (arrays, tuples, and variants), to avoid massive + * recursion which could exhaust our stack when handling untrusted input. */ /* < private > @@ -113,6 +116,9 @@ * fixed-sized type, yet @size must be non-zero. The effect of this * combination should be as if @data were a pointer to an * appropriately-sized zero-filled region. + * + * @depth has no restrictions; the depth of a top-level serialised #GVariant is + * zero, and it increases for each level of nested child. */ /* < private > @@ -255,6 +261,7 @@ gvs_fixed_sized_maybe_get_child (GVariantSerialised value, */ value.type_info = g_variant_type_info_element (value.type_info); g_variant_type_info_ref (value.type_info); + value.depth++; return value; } @@ -286,7 +293,7 @@ gvs_fixed_sized_maybe_serialise (GVariantSerialised value, { if (n_children) { - GVariantSerialised child = { NULL, value.data, value.size }; + GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1 }; gvs_filler (&child, children[0]); } @@ -307,6 +314,7 @@ gvs_fixed_sized_maybe_is_normal (GVariantSerialised value) /* proper element size: "Just". recurse to the child. */ value.type_info = g_variant_type_info_element (value.type_info); + value.depth++; return g_variant_serialised_is_normal (value); } @@ -347,6 +355,8 @@ gvs_variable_sized_maybe_get_child (GVariantSerialised value, if (value.size == 0) value.data = NULL; + value.depth++; + return value; } @@ -376,7 +386,7 @@ gvs_variable_sized_maybe_serialise (GVariantSerialised value, { if (n_children) { - GVariantSerialised child = { NULL, value.data, value.size - 1 }; + GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1 }; /* write the data for the child. */ gvs_filler (&child, children[0]); @@ -395,6 +405,7 @@ gvs_variable_sized_maybe_is_normal (GVariantSerialised value) value.type_info = g_variant_type_info_element (value.type_info); value.size--; + value.depth++; return g_variant_serialised_is_normal (value); } @@ -445,6 +456,7 @@ gvs_fixed_sized_array_get_child (GVariantSerialised value, g_variant_type_info_query (child.type_info, NULL, &child.size); child.data = value.data + (child.size * index_); g_variant_type_info_ref (child.type_info); + child.depth = value.depth + 1; return child; } @@ -474,6 +486,7 @@ gvs_fixed_sized_array_serialise (GVariantSerialised value, child.type_info = g_variant_type_info_element (value.type_info); g_variant_type_info_query (child.type_info, NULL, &child.size); child.data = value.data; + child.depth = value.depth + 1; for (i = 0; i < n_children; i++) { @@ -489,6 +502,7 @@ gvs_fixed_sized_array_is_normal (GVariantSerialised value) child.type_info = g_variant_type_info_element (value.type_info); g_variant_type_info_query (child.type_info, NULL, &child.size); + child.depth = value.depth + 1; if (value.size % child.size != 0) return FALSE; @@ -655,6 +669,7 @@ gvs_variable_sized_array_get_child (GVariantSerialised value, child.type_info = g_variant_type_info_element (value.type_info); g_variant_type_info_ref (child.type_info); + child.depth = value.depth + 1; offset_size = gvs_get_offset_size (value.size); @@ -783,6 +798,7 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value) child.type_info = g_variant_type_info_element (value.type_info); g_variant_type_info_query (child.type_info, &alignment, NULL); + child.depth = value.depth + 1; offset = 0; for (i = 0; i < length; i++) @@ -858,6 +874,7 @@ gvs_tuple_get_child (GVariantSerialised value, member_info = g_variant_type_info_member_info (value.type_info, index_); child.type_info = g_variant_type_info_ref (member_info->type_info); + child.depth = value.depth + 1; offset_size = gvs_get_offset_size (value.size); /* tuples are the only (potentially) fixed-sized containers, so the @@ -1042,6 +1059,7 @@ gvs_tuple_is_normal (GVariantSerialised value) member_info = g_variant_type_info_member_info (value.type_info, i); child.type_info = member_info->type_info; + child.depth = value.depth + 1; g_variant_type_info_query (child.type_info, &alignment, &fixed_size); @@ -1171,8 +1189,10 @@ gvs_variant_get_child (GVariantSerialised value, if (g_variant_type_is_definite (type)) { gsize fixed_size; + gsize child_type_depth; child.type_info = g_variant_type_info_get (type); + child.depth = value.depth + 1; if (child.size != 0) /* only set to non-%NULL if size > 0 */ @@ -1180,8 +1200,10 @@ gvs_variant_get_child (GVariantSerialised value, g_variant_type_info_query (child.type_info, NULL, &fixed_size); + child_type_depth = g_variant_type_info_query_depth (child.type_info); - if (!fixed_size || fixed_size == child.size) + if ((!fixed_size || fixed_size == child.size) && + value.depth < G_VARIANT_MAX_RECURSION_DEPTH - child_type_depth) return child; g_variant_type_info_unref (child.type_info); @@ -1193,6 +1215,7 @@ gvs_variant_get_child (GVariantSerialised value, child.type_info = g_variant_type_info_get (G_VARIANT_TYPE_UNIT); child.data = NULL; child.size = 1; + child.depth = value.depth + 1; return child; } @@ -1234,10 +1257,13 @@ gvs_variant_is_normal (GVariantSerialised value) { GVariantSerialised child; gboolean normal; + gsize child_type_depth; child = gvs_variant_get_child (value, 0); + child_type_depth = g_variant_type_info_query_depth (child.type_info); - normal = (child.data != NULL || child.size == 0) && + normal = (value.depth < G_VARIANT_MAX_RECURSION_DEPTH - child_type_depth) && + (child.data != NULL || child.size == 0) && g_variant_serialised_is_normal (child); g_variant_type_info_unref (child.type_info); @@ -1555,6 +1581,8 @@ g_variant_serialised_is_normal (GVariantSerialised serialised) if (serialised.data == NULL) return FALSE; + if (serialised.depth >= G_VARIANT_MAX_RECURSION_DEPTH) + return FALSE; /* some hard-coded terminal cases */ switch (g_variant_type_info_get_type_char (serialised.type_info)) diff --git a/glib/gvariant-serialiser.h b/glib/gvariant-serialiser.h index c49708662..fa4252d36 100644 --- a/glib/gvariant-serialiser.h +++ b/glib/gvariant-serialiser.h @@ -28,6 +28,7 @@ typedef struct GVariantTypeInfo *type_info; guchar *data; gsize size; + gsize depth; /* same semantics as GVariant.depth */ } GVariantSerialised; /* deserialisation */ diff --git a/glib/gvarianttype.c b/glib/gvarianttype.c index 7de4202cb..15a04947c 100644 --- a/glib/gvarianttype.c +++ b/glib/gvarianttype.c @@ -24,6 +24,7 @@ #include <glib/gtestutils.h> #include <glib/gstrfuncs.h> +#include <glib/gvariant-internal.h> #include <string.h> @@ -115,7 +116,10 @@ * * The above definition is recursive to arbitrary depth. "aaaaai" and * "(ui(nq((y)))s)" are both valid type strings, as is - * "a(aa(ui)(qna{ya(yd)}))". + * "a(aa(ui)(qna{ya(yd)}))". In order to not hit memory limits, #GVariant + * imposes a limit on recursion depth of 65 nested containers. This is the + * limit in the D-Bus specification (64) plus one to allow a #GDBusMessage to + * be nested in a top-level tuple. * * The meaning of each of the characters is as follows: * - `b`: the type string of %G_VARIANT_TYPE_BOOLEAN; a boolean value. @@ -195,35 +199,15 @@ g_variant_type_check (const GVariantType *type) #endif } -/** - * g_variant_type_string_scan: - * @string: a pointer to any string - * @limit: (nullable): the end of @string, or %NULL - * @endptr: (out) (optional): location to store the end pointer, or %NULL - * - * Scan for a single complete and valid GVariant type string in @string. - * The memory pointed to by @limit (or bytes beyond it) is never - * accessed. - * - * If a valid type string is found, @endptr is updated to point to the - * first character past the end of the string that was found and %TRUE - * is returned. - * - * If there is no valid type string starting at @string, or if the type - * string does not end before @limit then %FALSE is returned. - * - * For the simple case of checking if a string is a valid type string, - * see g_variant_type_string_is_valid(). - * - * Returns: %TRUE if a valid type string was found - * - * Since: 2.24 - **/ -gboolean -g_variant_type_string_scan (const gchar *string, - const gchar *limit, - const gchar **endptr) +static gboolean +variant_type_string_scan_internal (const gchar *string, + const gchar *limit, + const gchar **endptr, + gsize *depth, + gsize depth_limit) { + gsize max_depth = 0, child_depth; + g_return_val_if_fail (string != NULL, FALSE); if (string == limit || *string == '\0') @@ -233,27 +217,44 @@ g_variant_type_string_scan (const gchar *string, { case '(': while (string == limit || *string != ')') - if (!g_variant_type_string_scan (string, limit, &string)) - return FALSE; + { + if (depth_limit == 0 || + !variant_type_string_scan_internal (string, limit, &string, + &child_depth, + depth_limit - 1)) + return FALSE; + + max_depth = MAX (max_depth, child_depth + 1); + } string++; break; case '{': - if (string == limit || *string == '\0' || /* { */ - !strchr ("bynqihuxtdsog?", *string++) || /* key */ - !g_variant_type_string_scan (string, limit, &string) || /* value */ - string == limit || *string++ != '}') /* } */ + if (depth_limit == 0 || + string == limit || *string == '\0' || /* { */ + !strchr ("bynqihuxtdsog?", *string++) || /* key */ + !variant_type_string_scan_internal (string, limit, &string, + &child_depth, depth_limit - 1) || /* value */ + string == limit || *string++ != '}') /* } */ return FALSE; + max_depth = MAX (max_depth, child_depth + 1); break; case 'm': case 'a': - return g_variant_type_string_scan (string, limit, endptr); + if (depth_limit == 0 || + !variant_type_string_scan_internal (string, limit, &string, + &child_depth, depth_limit - 1)) + return FALSE; + + max_depth = MAX (max_depth, child_depth + 1); + break; case 'b': case 'y': case 'n': case 'q': case 'i': case 'u': case 'x': case 't': case 'd': case 's': case 'o': case 'g': case 'v': case 'r': case '*': case '?': case 'h': + max_depth = MAX (max_depth, 1); break; default: @@ -262,10 +263,74 @@ g_variant_type_string_scan (const gchar *string, if (endptr != NULL) *endptr = string; + if (depth != NULL) + *depth = max_depth; return TRUE; } +/** + * g_variant_type_string_scan: + * @string: a pointer to any string + * @limit: (nullable): the end of @string, or %NULL + * @endptr: (out) (optional): location to store the end pointer, or %NULL + * + * Scan for a single complete and valid GVariant type string in @string. + * The memory pointed to by @limit (or bytes beyond it) is never + * accessed. + * + * If a valid type string is found, @endptr is updated to point to the + * first character past the end of the string that was found and %TRUE + * is returned. + * + * If there is no valid type string starting at @string, or if the type + * string does not end before @limit then %FALSE is returned. + * + * For the simple case of checking if a string is a valid type string, + * see g_variant_type_string_is_valid(). + * + * Returns: %TRUE if a valid type string was found + * + * Since: 2.24 + **/ +gboolean +g_variant_type_string_scan (const gchar *string, + const gchar *limit, + const gchar **endptr) +{ + return variant_type_string_scan_internal (string, limit, endptr, NULL, + G_VARIANT_MAX_RECURSION_DEPTH); +} + +/* < private > + * g_variant_type_string_get_depth_: + * @type_string: a pointer to any string + * + * Get the maximum depth of the nested types in @type_string. A basic type will + * return depth 1, and a container type will return a greater value. The depth + * of a tuple is 1 plus the depth of its deepest child type. + * + * If @type_string is not a valid #GVariant type string, 0 will be returned. + * + * Returns: depth of @type_string, or 0 on error + * Since: 2.60 + */ +gsize +g_variant_type_string_get_depth_ (const gchar *type_string) +{ + const gchar *endptr; + gsize depth = 0; + + g_return_val_if_fail (type_string != NULL, 0); + + if (!variant_type_string_scan_internal (type_string, NULL, &endptr, &depth, + G_VARIANT_MAX_RECURSION_DEPTH) || + *endptr != '\0') + return 0; + + return depth; +} + /** * g_variant_type_string_is_valid: * @type_string: a pointer to any string diff --git a/glib/gvarianttype.h b/glib/gvarianttype.h index 6bb5e07b5..fdc364166 100644 --- a/glib/gvarianttype.h +++ b/glib/gvarianttype.h @@ -374,6 +374,8 @@ GVariantType * g_variant_type_new_dict_entry (const G /*< private >*/ GLIB_AVAILABLE_IN_ALL const GVariantType * g_variant_type_checked_ (const gchar *); +GLIB_AVAILABLE_IN_2_60 +gsize g_variant_type_string_get_depth_ (const gchar *type_string); G_END_DECLS diff --git a/glib/gvarianttypeinfo.c b/glib/gvarianttypeinfo.c index 9dade7064..78d029425 100644 --- a/glib/gvarianttypeinfo.c +++ b/glib/gvarianttypeinfo.c @@ -251,6 +251,31 @@ g_variant_type_info_query (GVariantTypeInfo *info, *fixed_size = info->fixed_size; } +/* < private > + * g_variant_type_info_query_depth: + * @info: a #GVariantTypeInfo + * + * Queries @info to determine the depth of the type. + * + * See g_variant_type_string_get_depth_() for more details. + * + * Returns: depth of @info + * Since: 2.60 + */ +gsize +g_variant_type_info_query_depth (GVariantTypeInfo *info) +{ + g_variant_type_info_check (info, 0); + + if (info->container_class) + { + ContainerInfo *container = (ContainerInfo *) info; + return g_variant_type_string_get_depth_ (container->type_string); + } + + return 1; +} + /* == array == */ #define GV_ARRAY_INFO_CLASS 'a' static ArrayInfo * diff --git a/glib/gvarianttypeinfo.h b/glib/gvarianttypeinfo.h index cc60f36e2..6ecd1d4bb 100644 --- a/glib/gvarianttypeinfo.h +++ b/glib/gvarianttypeinfo.h @@ -130,6 +130,8 @@ GLIB_AVAILABLE_IN_ALL void g_variant_type_info_query (GVariantTypeInfo *typeinfo, guint *alignment, gsize *size); +GLIB_AVAILABLE_IN_2_60 +gsize g_variant_type_info_query_depth (GVariantTypeInfo *typeinfo); /* array */ GLIB_AVAILABLE_IN_ALL diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c index 4c9474a56..0d996ae72 100644 --- a/glib/tests/gvariant.c +++ b/glib/tests/gvariant.c @@ -661,6 +661,57 @@ test_gvarianttype (void) } } +/* Test that scanning a deeply recursive type string doesn’t exhaust our + * stack space (which it would if the type string scanner was recursive). */ +static void +test_gvarianttype_string_scan_recursion_tuple (void) +{ + gchar *type_string = NULL; + gsize type_string_len = 1000001; /* not including nul terminator */ + gsize i; + + /* Build a long type string of ‘((…u…))’. */ + type_string = g_new0 (gchar, type_string_len + 1); + for (i = 0; i < type_string_len; i++) + { + if (i < type_string_len / 2) + type_string[i] = '('; + else if (i == type_string_len / 2) + type_string[i] = 'u'; + else + type_string[i] = ')'; + } + + /* Goes (way) over allowed recursion limit. */ + g_assert_false (g_variant_type_string_is_valid (type_string)); + + g_free (type_string); +} + +/* Same as above, except with an array rather than a tuple. */ +static void +test_gvarianttype_string_scan_recursion_array (void) +{ + gchar *type_string = NULL; + gsize type_string_len = 1000001; /* not including nul terminator */ + gsize i; + + /* Build a long type string of ‘aaa…aau’. */ + type_string = g_new0 (gchar, type_string_len + 1); + for (i = 0; i < type_string_len; i++) + { + if (i < type_string_len - 1) + type_string[i] = 'a'; + else + type_string[i] = 'u'; + } + + /* Goes (way) over allowed recursion limit. */ + g_assert_false (g_variant_type_string_is_valid (type_string)); + + g_free (type_string); +} + #define ALIGNED(x, y) (((x + (y - 1)) / y) * y) /* do our own calculation of the fixed_size and alignment of a type @@ -1230,6 +1281,8 @@ random_instance_filler (GVariantSerialised *serialised, if (serialised->size == 0) serialised->size = instance->size; + serialised->depth = 0; + g_assert (serialised->type_info == instance->type_info); g_assert (serialised->size == instance->size); @@ -1394,6 +1447,7 @@ test_maybe (void) serialised.type_info = type_info; serialised.data = flavoured_malloc (needed_size, flavour); serialised.size = needed_size; + serialised.depth = 0; g_variant_serialiser_serialise (serialised, random_instance_filler, @@ -1516,6 +1570,7 @@ test_array (void) serialised.type_info = array_info; serialised.data = flavoured_malloc (needed_size, flavour); serialised.size = needed_size; + serialised.depth = 0; g_variant_serialiser_serialise (serialised, random_instance_filler, (gpointer *) instances, n_children); @@ -1679,6 +1734,7 @@ test_tuple (void) serialised.type_info = type_info; serialised.data = flavoured_malloc (needed_size, flavour); serialised.size = needed_size; + serialised.depth = 0; g_variant_serialiser_serialise (serialised, random_instance_filler, (gpointer *) instances, n_children); @@ -1774,6 +1830,7 @@ test_variant (void) serialised.type_info = type_info; serialised.data = flavoured_malloc (needed_size, flavour); serialised.size = needed_size; + serialised.depth = 0; g_variant_serialiser_serialise (serialised, random_instance_filler, (gpointer *) &instance, 1); @@ -2055,6 +2112,8 @@ tree_filler (GVariantSerialised *serialised, if (serialised->type_info == NULL) serialised->type_info = instance->info; + serialised->depth = 0; + if (instance->data_size == 0) /* is a container */ { @@ -2225,6 +2284,7 @@ test_byteswap (void) g_variant_serialised_byteswap (two); g_assert_cmpmem (one.data, one.size, two.data, two.size); + g_assert_cmpuint (one.depth, ==, two.depth); tree_instance_free (tree); g_free (one.data); @@ -4623,6 +4683,108 @@ test_normal_checking_tuples (void) g_variant_unref (variant); } +/* Check that deeply nested variants are not considered in normal form when + * deserialised from untrusted data.*/ +static void +test_recursion_limits_variant_in_variant (void) +{ + GVariant *wrapper_variant = NULL; + gsize i; + GBytes *bytes = NULL; + GVariant *deserialised_variant = NULL; + + /* Construct a hierarchy of variants, containing a single string. This is just + * below the maximum recursion level, as a series of nested variant types. */ + wrapper_variant = g_variant_new_string ("hello"); + + for (i = 0; i < G_VARIANT_MAX_RECURSION_DEPTH - 1; i++) + wrapper_variant = g_variant_new_variant (g_steal_pointer (&wrapper_variant)); + + /* Serialise and deserialise it as untrusted data, to force normalisation. */ + bytes = g_variant_get_data_as_bytes (wrapper_variant); + deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT, + bytes, FALSE); + g_assert_nonnull (deserialised_variant); + g_assert_true (g_variant_is_normal_form (deserialised_variant)); + + g_bytes_unref (bytes); + g_variant_unref (deserialised_variant); + + /* Wrap it once more. Normalisation should now fail. */ + wrapper_variant = g_variant_new_variant (g_steal_pointer (&wrapper_variant)); + + bytes = g_variant_get_data_as_bytes (wrapper_variant); + deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT, + bytes, FALSE); + g_assert_nonnull (deserialised_variant); + g_assert_false (g_variant_is_normal_form (deserialised_variant)); + + g_variant_unref (deserialised_variant); + + /* Deserialise it again, but trusted this time. This should succeed. */ + deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT, + bytes, TRUE); + g_assert_nonnull (deserialised_variant); + g_assert_true (g_variant_is_normal_form (deserialised_variant)); + + g_bytes_unref (bytes); + g_variant_unref (deserialised_variant); + g_variant_unref (wrapper_variant); +} + +/* Check that deeply nested arrays are not considered in normal form when + * deserialised from untrusted data after being wrapped in a variant. This is + * worth testing, because neither the deeply nested array, nor the variant, + * have a static #GVariantType which is too deep — only when nested together do + * they become too deep. */ +static void +test_recursion_limits_array_in_variant (void) +{ + GVariant *child_variant = NULL; + GVariant *wrapper_variant = NULL; + gsize i; + GBytes *bytes = NULL; + GVariant *deserialised_variant = NULL; + + /* Construct a hierarchy of arrays, containing a single string. This is just + * below the maximum recursion level, all in a single definite type. */ + child_variant = g_variant_new_string ("hello"); + + for (i = 0; i < G_VARIANT_MAX_RECURSION_DEPTH - 1; i++) + child_variant = g_variant_new_array (NULL, &child_variant, 1); + + /* Serialise and deserialise it as untrusted data, to force normalisation. */ + bytes = g_variant_get_data_as_bytes (child_variant); + deserialised_variant = g_variant_new_from_bytes (g_variant_get_type (child_variant), + bytes, FALSE); + g_assert_nonnull (deserialised_variant); + g_assert_true (g_variant_is_normal_form (deserialised_variant)); + + g_bytes_unref (bytes); + g_variant_unref (deserialised_variant); + + /* Wrap it in a variant. Normalisation should now fail. */ + wrapper_variant = g_variant_new_variant (g_steal_pointer (&child_variant)); + + bytes = g_variant_get_data_as_bytes (wrapper_variant); + deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT, + bytes, FALSE); + g_assert_nonnull (deserialised_variant); + g_assert_false (g_variant_is_normal_form (deserialised_variant)); + + g_variant_unref (deserialised_variant); + + /* Deserialise it again, but trusted this time. This should succeed. */ + deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT, + bytes, TRUE); + g_assert_nonnull (deserialised_variant); + g_assert_true (g_variant_is_normal_form (deserialised_variant)); + + g_bytes_unref (bytes); + g_variant_unref (deserialised_variant); + g_variant_unref (wrapper_variant); +} + int main (int argc, char **argv) { @@ -4631,6 +4793,10 @@ main (int argc, char **argv) g_test_init (&argc, &argv, NULL); g_test_add_func ("/gvariant/type", test_gvarianttype); + g_test_add_func ("/gvariant/type/string-scan/recursion/tuple", + test_gvarianttype_string_scan_recursion_tuple); + g_test_add_func ("/gvariant/type/string-scan/recursion/array", + test_gvarianttype_string_scan_recursion_array); g_test_add_func ("/gvariant/typeinfo", test_gvarianttypeinfo); g_test_add_func ("/gvariant/serialiser/maybe", test_maybes); g_test_add_func ("/gvariant/serialiser/array", test_arrays); @@ -4688,5 +4854,10 @@ main (int argc, char **argv) g_test_add_func ("/gvariant/normal-checking/tuples", test_normal_checking_tuples); + g_test_add_func ("/gvariant/recursion-limits/variant-in-variant", + test_recursion_limits_variant_in_variant); + g_test_add_func ("/gvariant/recursion-limits/array-in-variant", + test_recursion_limits_array_in_variant); + return g_test_run (); } -- 2.41.0
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor