diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 8c733e0aa..336dd981c 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -3430,6 +3430,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_ 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 +#include #include #include #include @@ -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 #include #include #include @@ -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 f473ad008..f64be97f9 100644 --- a/glib/gvarianttype.c +++ b/glib/gvarianttype.c @@ -24,6 +24,7 @@ #include #include +#include #include @@ -114,7 +115,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. @@ -194,6 +198,76 @@ g_variant_type_check (const GVariantType *type) #endif } +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') + return FALSE; + + switch (*string++) + { + case '(': + while (string == limit || *string != ')') + { + 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 (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': + 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: + return FALSE; + } + + if (endptr != NULL) + *endptr = string; + if (depth != NULL) + *depth = max_depth; + + return TRUE; +} + /** * g_variant_type_string_scan: * @string: a pointer to any string @@ -223,46 +297,37 @@ g_variant_type_string_scan (const gchar *string, const gchar *limit, const gchar **endptr) { - g_return_val_if_fail (string != NULL, FALSE); + return variant_type_string_scan_internal (string, limit, endptr, NULL, + G_VARIANT_MAX_RECURSION_DEPTH); +} - if (string == limit || *string == '\0') - return FALSE; +/* < 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 (backported to 2.58) + */ +gsize +g_variant_type_string_get_depth_ (const gchar *type_string) +{ + const gchar *endptr; + gsize depth = 0; - switch (*string++) - { - case '(': - while (string == limit || *string != ')') - if (!g_variant_type_string_scan (string, limit, &string)) - return FALSE; + g_return_val_if_fail (type_string != NULL, 0); - string++; - break; + if (!variant_type_string_scan_internal (type_string, NULL, &endptr, &depth, + G_VARIANT_MAX_RECURSION_DEPTH) || + *endptr != '\0') + return 0; - case '{': - if (string == limit || *string == '\0' || /* { */ - !strchr ("bynqihuxtdsog?", *string++) || /* key */ - !g_variant_type_string_scan (string, limit, &string) || /* value */ - string == limit || *string++ != '}') /* } */ - return FALSE; - - break; - - case 'm': case 'a': - return g_variant_type_string_scan (string, limit, endptr); - - 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': - break; - - default: - return FALSE; - } - - if (endptr != NULL) - *endptr = string; - - return TRUE; + return depth; } /** diff --git a/glib/gvarianttype.h b/glib/gvarianttype.h index 6bb5e07b5..cd3041947 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_58 +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..764da63f1 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 (backported to 2.58) + */ +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..66ec0bd7a 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_58 +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 a5095a380..8b1f7a6cb 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); @@ -4655,6 +4715,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) { @@ -4663,6 +4825,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); @@ -4720,5 +4886,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 (); }