mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-25 15:06:14 +01:00
gvarianttype: Impose a recursion limit of 128 on variant types
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>
This commit is contained in:
parent
eb7c9adc3b
commit
7c4e6e9fbe
@ -3431,6 +3431,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>
|
||||
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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__ */
|
||||
|
@ -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))
|
||||
|
@ -28,6 +28,7 @@ typedef struct
|
||||
GVariantTypeInfo *type_info;
|
||||
guchar *data;
|
||||
gsize size;
|
||||
gsize depth; /* same semantics as GVariant.depth */
|
||||
} GVariantSerialised;
|
||||
|
||||
/* deserialisation */
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <glib/gtestutils.h>
|
||||
#include <glib/gstrfuncs.h>
|
||||
#include <glib/gvariant-internal.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
@ -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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 *
|
||||
|
@ -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
|
||||
|
@ -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 ();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user