File glib2-fix-normal-form-handling-in-gvariant.patch of Package glib2.28512
diff --unified --recursive --text --new-file --color glib-2.70.5.old/glib/gvariant.c glib-2.70.5.new/glib/gvariant.c
--- glib-2.70.5.old/glib/gvariant.c 2022-03-17 21:58:37.000000000 +0800
+++ glib-2.70.5.new/glib/gvariant.c 2023-04-14 10:51:26.494652410 +0800
@@ -2211,20 +2211,22 @@
GString *string,
gboolean type_annotate)
{
+ const gchar *value_type_string = g_variant_get_type_string (value);
+
if G_UNLIKELY (string == NULL)
string = g_string_new (NULL);
- switch (g_variant_classify (value))
+ switch (value_type_string[0])
{
case G_VARIANT_CLASS_MAYBE:
if (type_annotate)
- g_string_append_printf (string, "@%s ",
- g_variant_get_type_string (value));
+ g_string_append_printf (string, "@%s ", value_type_string);
if (g_variant_n_children (value))
{
- gchar *printed_child;
- GVariant *element;
+ const GVariantType *base_type;
+ guint i, depth;
+ GVariant *element = NULL;
/* Nested maybes:
*
@@ -2238,19 +2240,36 @@
* "just" is actually exactly the case where we have a nested
* Nothing.
*
- * Instead of searching for that nested Nothing, we just print
- * the contained value into a separate string and see if we
- * end up with "nothing" at the end of it. If so, we need to
- * add "just" at our level.
+ * Search for the nested Nothing, to save a lot of recursion if there
+ * are multiple levels of maybes.
*/
- element = g_variant_get_child_value (value, 0);
- printed_child = g_variant_print (element, FALSE);
- g_variant_unref (element);
-
- if (g_str_has_suffix (printed_child, "nothing"))
- g_string_append (string, "just ");
- g_string_append (string, printed_child);
- g_free (printed_child);
+ for (depth = 0, base_type = g_variant_get_type (value);
+ g_variant_type_is_maybe (base_type);
+ depth++, base_type = g_variant_type_element (base_type));
+
+ element = g_variant_ref (value);
+ for (i = 0; i < depth && element != NULL; i++)
+ {
+ GVariant *new_element = g_variant_n_children (element) ? g_variant_get_child_value (element, 0) : NULL;
+ g_variant_unref (element);
+ element = g_steal_pointer (&new_element);
+ }
+
+ if (element == NULL)
+ {
+ /* One of the maybes was Nothing, so print out the right number of
+ * justs. */
+ for (; i > 1; i--)
+ g_string_append (string, "just ");
+ g_string_append (string, "nothing");
+ }
+ else
+ {
+ /* There are no Nothings, so print out the child with no prefixes. */
+ g_variant_print_string (element, string, FALSE);
+ }
+
+ g_clear_pointer (&element, g_variant_unref);
}
else
g_string_append (string, "nothing");
@@ -2263,7 +2282,7 @@
* if the first two characters are 'ay' then it's a bytestring.
* under certain conditions we print those as strings.
*/
- if (g_variant_get_type_string (value)[1] == 'y')
+ if (value_type_string[1] == 'y')
{
const gchar *str;
gsize size;
@@ -2305,7 +2324,7 @@
* dictionary entries (ie: a dictionary) so we print that
* differently.
*/
- if (g_variant_get_type_string (value)[1] == '{')
+ if (value_type_string[1] == '{')
/* dictionary */
{
const gchar *comma = "";
@@ -2314,8 +2333,7 @@
if ((n = g_variant_n_children (value)) == 0)
{
if (type_annotate)
- g_string_append_printf (string, "@%s ",
- g_variant_get_type_string (value));
+ g_string_append_printf (string, "@%s ", value_type_string);
g_string_append (string, "{}");
break;
}
@@ -2351,8 +2369,7 @@
if ((n = g_variant_n_children (value)) == 0)
{
if (type_annotate)
- g_string_append_printf (string, "@%s ",
- g_variant_get_type_string (value));
+ g_string_append_printf (string, "@%s ", value_type_string);
g_string_append (string, "[]");
break;
}
@@ -5803,32 +5820,96 @@
/* Serialized data {{{1 */
static GVariant *
-g_variant_deep_copy (GVariant *value)
+g_variant_deep_copy (GVariant *value,
+ gboolean byteswap)
{
switch (g_variant_classify (value))
{
case G_VARIANT_CLASS_MAYBE:
- case G_VARIANT_CLASS_ARRAY:
case G_VARIANT_CLASS_TUPLE:
case G_VARIANT_CLASS_DICT_ENTRY:
case G_VARIANT_CLASS_VARIANT:
{
GVariantBuilder builder;
- GVariantIter iter;
- GVariant *child;
+ gsize i, n_children;
g_variant_builder_init (&builder, g_variant_get_type (value));
- g_variant_iter_init (&iter, value);
- while ((child = g_variant_iter_next_value (&iter)))
+ for (i = 0, n_children = g_variant_n_children (value); i < n_children; i++)
{
- g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
+ GVariant *child = g_variant_get_child_value (value, i);
+ g_variant_builder_add_value (&builder, g_variant_deep_copy (child, byteswap));
g_variant_unref (child);
}
return g_variant_builder_end (&builder);
}
+ case G_VARIANT_CLASS_ARRAY:
+ {
+ GVariantBuilder builder;
+ gsize i, n_children;
+ GVariant *first_invalid_child_deep_copy = NULL;
+
+ /* Arrays are in theory treated the same as maybes, tuples, dict entries
+ * and variants, and could be another case in the above block of code.
+ *
+ * However, they have the property that when dealing with non-normal
+ * data (which is the only time g_variant_deep_copy() is currently
+ * called) in a variable-sized array, the code above can easily end up
+ * creating many default child values in order to return an array which
+ * is of the right length and type, but without containing non-normal
+ * data. This can happen if the offset table for the array is malformed.
+ *
+ * In this case, the code above would end up allocating the same default
+ * value for each one of the child indexes beyond the first malformed
+ * entry in the offset table. This can end up being a lot of identical
+ * allocations of default values, particularly if the non-normal array
+ * is crafted maliciously.
+ *
+ * Avoid that problem by returning a new reference to the same default
+ * value for every child after the first invalid one. This results in
+ * returning an equivalent array, in normal form and trusted — but with
+ * significantly fewer memory allocations.
+ *
+ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2540 */
+
+ g_variant_builder_init (&builder, g_variant_get_type (value));
+
+ for (i = 0, n_children = g_variant_n_children (value); i < n_children; i++)
+ {
+ /* Try maybe_get_child_value() first; if it returns NULL, this child
+ * is non-normal. get_child_value() would have constructed and
+ * returned a default value in that case. */
+ GVariant *child = g_variant_maybe_get_child_value (value, i);
+
+ if (child != NULL)
+ {
+ /* Non-normal children may not always be contiguous, as they may
+ * be non-normal for reasons other than invalid offset table
+ * entries. As they are all the same type, they will all have
+ * the same default value though, so keep that around. */
+ g_variant_builder_add_value (&builder, g_variant_deep_copy (child, byteswap));
+ }
+ else if (child == NULL && first_invalid_child_deep_copy != NULL)
+ {
+ g_variant_builder_add_value (&builder, first_invalid_child_deep_copy);
+ }
+ else if (child == NULL)
+ {
+ child = g_variant_get_child_value (value, i);
+ first_invalid_child_deep_copy = g_variant_ref_sink (g_variant_deep_copy (child, byteswap));
+ g_variant_builder_add_value (&builder, first_invalid_child_deep_copy);
+ }
+
+ g_clear_pointer (&child, g_variant_unref);
+ }
+
+ g_clear_pointer (&first_invalid_child_deep_copy, g_variant_unref);
+
+ return g_variant_builder_end (&builder);
+ }
+
case G_VARIANT_CLASS_BOOLEAN:
return g_variant_new_boolean (g_variant_get_boolean (value));
@@ -5836,28 +5917,63 @@
return g_variant_new_byte (g_variant_get_byte (value));
case G_VARIANT_CLASS_INT16:
- return g_variant_new_int16 (g_variant_get_int16 (value));
+ if (byteswap)
+ return g_variant_new_int16 (GUINT16_SWAP_LE_BE (g_variant_get_int16 (value)));
+ else
+ return g_variant_new_int16 (g_variant_get_int16 (value));
case G_VARIANT_CLASS_UINT16:
- return g_variant_new_uint16 (g_variant_get_uint16 (value));
+ if (byteswap)
+ return g_variant_new_uint16 (GUINT16_SWAP_LE_BE (g_variant_get_uint16 (value)));
+ else
+ return g_variant_new_uint16 (g_variant_get_uint16 (value));
case G_VARIANT_CLASS_INT32:
- return g_variant_new_int32 (g_variant_get_int32 (value));
+ if (byteswap)
+ return g_variant_new_int32 (GUINT32_SWAP_LE_BE (g_variant_get_int32 (value)));
+ else
+ return g_variant_new_int32 (g_variant_get_int32 (value));
case G_VARIANT_CLASS_UINT32:
- return g_variant_new_uint32 (g_variant_get_uint32 (value));
+ if (byteswap)
+ return g_variant_new_uint32 (GUINT32_SWAP_LE_BE (g_variant_get_uint32 (value)));
+ else
+ return g_variant_new_uint32 (g_variant_get_uint32 (value));
case G_VARIANT_CLASS_INT64:
- return g_variant_new_int64 (g_variant_get_int64 (value));
+ if (byteswap)
+ return g_variant_new_int64 (GUINT64_SWAP_LE_BE (g_variant_get_int64 (value)));
+ else
+ return g_variant_new_int64 (g_variant_get_int64 (value));
case G_VARIANT_CLASS_UINT64:
- return g_variant_new_uint64 (g_variant_get_uint64 (value));
+ if (byteswap)
+ return g_variant_new_uint64 (GUINT64_SWAP_LE_BE (g_variant_get_uint64 (value)));
+ else
+ return g_variant_new_uint64 (g_variant_get_uint64 (value));
case G_VARIANT_CLASS_HANDLE:
- return g_variant_new_handle (g_variant_get_handle (value));
+ if (byteswap)
+ return g_variant_new_handle (GUINT32_SWAP_LE_BE (g_variant_get_handle (value)));
+ else
+ return g_variant_new_handle (g_variant_get_handle (value));
case G_VARIANT_CLASS_DOUBLE:
- return g_variant_new_double (g_variant_get_double (value));
+ if (byteswap)
+ {
+ /* We have to convert the double to a uint64 here using a union,
+ * because a cast will round it numerically. */
+ union
+ {
+ guint64 u64;
+ gdouble dbl;
+ } u1, u2;
+ u1.dbl = g_variant_get_double (value);
+ u2.u64 = GUINT64_SWAP_LE_BE (u1.u64);
+ return g_variant_new_double (u2.dbl);
+ }
+ else
+ return g_variant_new_double (g_variant_get_double (value));
case G_VARIANT_CLASS_STRING:
return g_variant_new_string (g_variant_get_string (value, NULL));
@@ -5887,7 +6003,9 @@
* marked as trusted and a new reference to it is returned.
*
* If @value is found not to be in normal form then a new trusted
- * #GVariant is created with the same value as @value.
+ * #GVariant is created with the same value as @value. The non-normal parts of
+ * @value will be replaced with default values which are guaranteed to be in
+ * normal form.
*
* It makes sense to call this function if you've received #GVariant
* data from untrusted sources and you want to ensure your serialized
@@ -5912,7 +6030,7 @@
if (g_variant_is_normal_form (value))
return g_variant_ref (value);
- trusted = g_variant_deep_copy (value);
+ trusted = g_variant_deep_copy (value, FALSE);
g_assert (g_variant_is_trusted (trusted));
return g_variant_ref_sink (trusted);
@@ -5932,6 +6050,11 @@
* contain multi-byte numeric data. That include strings, booleans,
* bytes and containers containing only these things (recursively).
*
+ * While this function can safely handle untrusted, non-normal data, it is
+ * recommended to check whether the input is in normal form beforehand, using
+ * g_variant_is_normal_form(), and to reject non-normal inputs if your
+ * application can be strict about what inputs it rejects.
+ *
* The returned value is always in normal form and is marked as trusted.
*
* Returns: (transfer full): the byteswapped form of @value
@@ -5949,32 +6072,38 @@
g_variant_type_info_query (type_info, &alignment, NULL);
- if (alignment)
- /* (potentially) contains multi-byte numeric data */
+ if (alignment && g_variant_is_normal_form (value))
{
- GVariantSerialised serialised;
- GVariant *trusted;
+ /* (potentially) contains multi-byte numeric data, but is also already in
+ * normal form so we can use a faster byteswapping codepath on the
+ * serialised data */
+ GVariantSerialised serialised = { 0, };
GBytes *bytes;
- trusted = g_variant_get_normal_form (value);
- serialised.type_info = g_variant_get_type_info (trusted);
- serialised.size = g_variant_get_size (trusted);
+ serialised.type_info = g_variant_get_type_info (value);
+ serialised.size = g_variant_get_size (value);
serialised.data = g_malloc (serialised.size);
- serialised.depth = g_variant_get_depth (trusted);
- g_variant_store (trusted, serialised.data);
- g_variant_unref (trusted);
+ serialised.depth = g_variant_get_depth (value);
+ serialised.ordered_offsets_up_to = G_MAXSIZE; /* operating on the normal form */
+ serialised.checked_offsets_up_to = G_MAXSIZE;
+ g_variant_store (value, serialised.data);
g_variant_serialised_byteswap (serialised);
bytes = g_bytes_new_take (serialised.data, serialised.size);
- new = g_variant_new_from_bytes (g_variant_get_type (value), bytes, TRUE);
+ new = g_variant_ref_sink (g_variant_new_from_bytes (g_variant_get_type (value), bytes, TRUE));
g_bytes_unref (bytes);
}
+ else if (alignment)
+ /* (potentially) contains multi-byte numeric data */
+ new = g_variant_ref_sink (g_variant_deep_copy (value, TRUE));
else
/* contains no multi-byte data */
- new = value;
+ new = g_variant_get_normal_form (value);
+
+ g_assert (g_variant_is_trusted (new));
- return g_variant_ref_sink (new);
+ return g_steal_pointer (&new);
}
/**
diff --unified --recursive --text --new-file --color glib-2.70.5.old/glib/gvariant-core.c glib-2.70.5.new/glib/gvariant-core.c
--- glib-2.70.5.old/glib/gvariant-core.c 2022-03-17 21:58:37.000000000 +0800
+++ glib-2.70.5.new/glib/gvariant-core.c 2023-04-14 10:51:26.494652410 +0800
@@ -1,6 +1,7 @@
/*
* Copyright © 2007, 2008 Ryan Lortie
* Copyright © 2010 Codethink Limited
+ * Copyright © 2022 Endless OS Foundation, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -65,6 +66,8 @@
{
GBytes *bytes;
gconstpointer data;
+ gsize ordered_offsets_up_to;
+ gsize checked_offsets_up_to;
} serialised;
struct
@@ -162,6 +165,42 @@
* if .data pointed to the appropriate number of nul
* bytes.
*
+ * .ordered_offsets_up_to: If ordered_offsets_up_to == n this means that all
+ * the frame offsets up to and including the frame
+ * offset determining the end of element n are in
+ * order. This guarantees that the bytes of element
+ * n don't overlap with any previous element.
+ *
+ * For trusted data this is set to G_MAXSIZE and we
+ * don't check that the frame offsets are in order.
+ *
+ * Note: This doesn't imply the offsets are good in
+ * any way apart from their ordering. In particular
+ * offsets may be out of bounds for this value or
+ * may imply that the data overlaps the frame
+ * offsets themselves.
+ *
+ * This field is only relevant for arrays of non
+ * fixed width types and for tuples.
+ *
+ * .checked_offsets_up_to: Similarly to .ordered_offsets_up_to, this stores
+ * the index of the highest element, n, whose frame
+ * offsets (and all the preceding frame offsets)
+ * have been checked for validity.
+ *
+ * It is always the case that
+ * .checked_offsets_up_to ≥ .ordered_offsets_up_to.
+ *
+ * If .checked_offsets_up_to == .ordered_offsets_up_to,
+ * then a bad offset has not been found so far.
+ *
+ * If .checked_offsets_up_to > .ordered_offsets_up_to,
+ * then a bad offset has been found at
+ * (.ordered_offsets_up_to + 1).
+ *
+ * This field is only relevant for arrays of non
+ * fixed width types and for tuples.
+ *
* .tree: Only valid when the instance is in tree form.
*
* Note that accesses from other threads could result in
@@ -350,6 +389,29 @@
}
/* < private >
+ * g_variant_to_serialised:
+ * @value: a #GVariant
+ *
+ * Gets a GVariantSerialised for a GVariant in state STATE_SERIALISED.
+ */
+inline static GVariantSerialised
+g_variant_to_serialised (GVariant *value)
+{
+ g_assert (value->state & STATE_SERIALISED);
+ {
+ GVariantSerialised serialised = {
+ value->type_info,
+ (gpointer) value->contents.serialised.data,
+ value->size,
+ value->depth,
+ value->contents.serialised.ordered_offsets_up_to,
+ value->contents.serialised.checked_offsets_up_to,
+ };
+ return serialised;
+ }
+}
+
+/* < private >
* g_variant_serialise:
* @value: a #GVariant
* @data: an appropriately-sized buffer
@@ -375,6 +437,8 @@
serialised.size = value->size;
serialised.data = data;
serialised.depth = value->depth;
+ serialised.ordered_offsets_up_to = 0;
+ serialised.checked_offsets_up_to = 0;
children = (gpointer *) value->contents.tree.children;
n_children = value->contents.tree.n_children;
@@ -418,6 +482,17 @@
g_assert (serialised->size == value->size);
serialised->depth = value->depth;
+ if (value->state & STATE_SERIALISED)
+ {
+ serialised->ordered_offsets_up_to = value->contents.serialised.ordered_offsets_up_to;
+ serialised->checked_offsets_up_to = value->contents.serialised.checked_offsets_up_to;
+ }
+ else
+ {
+ serialised->ordered_offsets_up_to = 0;
+ serialised->checked_offsets_up_to = 0;
+ }
+
if (serialised->data)
/* g_variant_store() is a public API, so it
* it will reacquire the lock if it needs to.
@@ -460,6 +535,8 @@
bytes = g_bytes_new_take (data, value->size);
value->contents.serialised.data = g_bytes_get_data (bytes, NULL);
value->contents.serialised.bytes = bytes;
+ value->contents.serialised.ordered_offsets_up_to = G_MAXSIZE;
+ value->contents.serialised.checked_offsets_up_to = G_MAXSIZE;
value->state |= STATE_SERIALISED;
}
}
@@ -540,6 +617,8 @@
serialised.type_info = value->type_info;
serialised.data = (guchar *) g_bytes_get_data (bytes, &serialised.size);
serialised.depth = 0;
+ serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
+ serialised.checked_offsets_up_to = trusted ? G_MAXSIZE : 0;
if (!g_variant_serialised_check (serialised))
{
@@ -590,6 +669,9 @@
value->contents.serialised.data = g_bytes_get_data (bytes, &value->size);
}
+ value->contents.serialised.ordered_offsets_up_to = trusted ? G_MAXSIZE : 0;
+ value->contents.serialised.checked_offsets_up_to = trusted ? G_MAXSIZE : 0;
+
g_clear_pointer (&owned_bytes, g_bytes_unref);
return value;
@@ -1007,16 +1089,8 @@
g_variant_lock (value);
if (value->state & STATE_SERIALISED)
- {
- GVariantSerialised serialised = {
- value->type_info,
- (gpointer) value->contents.serialised.data,
- value->size,
- value->depth,
- };
-
- n_children = g_variant_serialised_n_children (serialised);
- }
+ n_children = g_variant_serialised_n_children (
+ g_variant_to_serialised (value));
else
n_children = value->contents.tree.n_children;
@@ -1062,11 +1136,13 @@
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)
{
+ /* g_variant_serialised_get_child() does its own checks on index_ */
+ g_return_val_if_fail (index_ < g_variant_n_children (value), NULL);
+
g_variant_lock (value);
if (~value->state & STATE_SERIALISED)
@@ -1083,12 +1159,7 @@
}
{
- GVariantSerialised serialised = {
- value->type_info,
- (gpointer) value->contents.serialised.data,
- value->size,
- value->depth,
- };
+ GVariantSerialised serialised = g_variant_to_serialised (value);
GVariantSerialised s_child;
GVariant *child;
@@ -1097,6 +1168,10 @@
*/
s_child = g_variant_serialised_get_child (serialised, index_);
+ /* Update the cached ordered_offsets_up_to, since @serialised will be thrown away when this function exits */
+ value->contents.serialised.ordered_offsets_up_to = MAX (value->contents.serialised.ordered_offsets_up_to, serialised.ordered_offsets_up_to);
+ value->contents.serialised.checked_offsets_up_to = MAX (value->contents.serialised.checked_offsets_up_to, serialised.checked_offsets_up_to);
+
/* 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
@@ -1108,6 +1183,7 @@
G_VARIANT_MAX_RECURSION_DEPTH - value->depth)
{
g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_VARIANT));
+ g_variant_type_info_unref (s_child.type_info);
return g_variant_new_tuple (NULL, 0);
}
@@ -1122,12 +1198,84 @@
child->contents.serialised.bytes =
g_bytes_ref (value->contents.serialised.bytes);
child->contents.serialised.data = s_child.data;
+ child->contents.serialised.ordered_offsets_up_to = (value->state & STATE_TRUSTED) ? G_MAXSIZE : s_child.ordered_offsets_up_to;
+ child->contents.serialised.checked_offsets_up_to = (value->state & STATE_TRUSTED) ? G_MAXSIZE : s_child.checked_offsets_up_to;
return child;
}
}
/**
+ * g_variant_maybe_get_child_value:
+ * @value: a container #GVariant
+ * @index_: the index of the child to fetch
+ *
+ * Reads a child item out of a container #GVariant instance, if it is in normal
+ * form. If it is not in normal form, return %NULL.
+ *
+ * This function behaves the same as g_variant_get_child_value(), except that it
+ * returns %NULL if the child is not in normal form. g_variant_get_child_value()
+ * would instead return a new default value of the correct type.
+ *
+ * This is intended to be used internally to avoid unnecessary #GVariant
+ * allocations.
+ *
+ * The returned value is never floating. You should free it with
+ * g_variant_unref() when you're done with it.
+ *
+ * This function is O(1).
+ *
+ * Returns: (transfer full): the child at the specified index
+ *
+ * Since: 2.74
+ */
+GVariant *
+g_variant_maybe_get_child_value (GVariant *value,
+ gsize index_)
+{
+ g_return_val_if_fail (value->depth < G_MAXSIZE, NULL);
+
+ if (~g_atomic_int_get (&value->state) & STATE_SERIALISED)
+ {
+ /* g_variant_serialised_get_child() does its own checks on index_ */
+ g_return_val_if_fail (index_ < g_variant_n_children (value), NULL);
+
+ g_variant_lock (value);
+
+ if (~value->state & STATE_SERIALISED)
+ {
+ GVariant *child;
+
+ child = g_variant_ref (value->contents.tree.children[index_]);
+ g_variant_unlock (value);
+
+ return child;
+ }
+
+ g_variant_unlock (value);
+ }
+
+ {
+ GVariantSerialised serialised = g_variant_to_serialised (value);
+ GVariantSerialised s_child;
+
+ /* get the serializer to extract the serialized data for the child
+ * from the serialized data for the container
+ */
+ s_child = g_variant_serialised_get_child (serialised, index_);
+
+ if (!(value->state & STATE_TRUSTED) && s_child.data == NULL)
+ {
+ g_variant_type_info_unref (s_child.type_info);
+ return NULL;
+ }
+
+ g_variant_type_info_unref (s_child.type_info);
+ return g_variant_get_child_value (value, index_);
+ }
+}
+
+/**
* g_variant_store:
* @value: the #GVariant to store
* @data: (not nullable): the location to store the serialized data at
@@ -1201,14 +1349,7 @@
if (value->state & STATE_SERIALISED)
{
- GVariantSerialised serialised = {
- value->type_info,
- (gpointer) value->contents.serialised.data,
- value->size,
- value->depth
- };
-
- if (g_variant_serialised_is_normal (serialised))
+ if (g_variant_serialised_is_normal (g_variant_to_serialised (value)))
value->state |= STATE_TRUSTED;
}
else
diff --unified --recursive --text --new-file --color glib-2.70.5.old/glib/gvariant-core.h glib-2.70.5.new/glib/gvariant-core.h
--- glib-2.70.5.old/glib/gvariant-core.h 2022-03-17 21:58:37.000000000 +0800
+++ glib-2.70.5.new/glib/gvariant-core.h 2023-04-14 10:50:39.704647579 +0800
@@ -36,4 +36,7 @@
gsize g_variant_get_depth (GVariant *value);
+GVariant * g_variant_maybe_get_child_value (GVariant *value,
+ gsize index_);
+
#endif /* __G_VARIANT_CORE_H__ */
diff --unified --recursive --text --new-file --color glib-2.70.5.old/glib/gvariant-serialiser.c glib-2.70.5.new/glib/gvariant-serialiser.c
--- glib-2.70.5.old/glib/gvariant-serialiser.c 2022-03-17 21:58:37.000000000 +0800
+++ glib-2.70.5.new/glib/gvariant-serialiser.c 2023-04-14 10:51:19.394651659 +0800
@@ -1,6 +1,7 @@
/*
* Copyright © 2007, 2008 Ryan Lortie
* Copyright © 2010 Codethink Limited
+ * Copyright © 2020 William Manley
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -119,6 +120,8 @@
*
* @depth has no restrictions; the depth of a top-level serialized #GVariant is
* zero, and it increases for each level of nested child.
+ *
+ * @checked_offsets_up_to is always ≥ @ordered_offsets_up_to
*/
/* < private >
@@ -146,6 +149,9 @@
!(serialised.size == 0 || serialised.data != NULL))
return FALSE;
+ if (serialised.ordered_offsets_up_to > serialised.checked_offsets_up_to)
+ return FALSE;
+
/* Depending on the native alignment requirements of the machine, the
* compiler will insert either 3 or 7 padding bytes after the char.
* This will result in the sizeof() the struct being 12 or 16.
@@ -264,6 +270,8 @@
value.type_info = g_variant_type_info_element (value.type_info);
g_variant_type_info_ref (value.type_info);
value.depth++;
+ value.ordered_offsets_up_to = 0;
+ value.checked_offsets_up_to = 0;
return value;
}
@@ -295,7 +303,7 @@
{
if (n_children)
{
- GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1 };
+ GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1, 0, 0 };
gvs_filler (&child, children[0]);
}
@@ -317,6 +325,8 @@
/* proper element size: "Just". recurse to the child. */
value.type_info = g_variant_type_info_element (value.type_info);
value.depth++;
+ value.ordered_offsets_up_to = 0;
+ value.checked_offsets_up_to = 0;
return g_variant_serialised_is_normal (value);
}
@@ -358,6 +368,8 @@
value.data = NULL;
value.depth++;
+ value.ordered_offsets_up_to = 0;
+ value.checked_offsets_up_to = 0;
return value;
}
@@ -388,7 +400,7 @@
{
if (n_children)
{
- GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1 };
+ GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1, 0, 0 };
/* write the data for the child. */
gvs_filler (&child, children[0]);
@@ -408,6 +420,8 @@
value.type_info = g_variant_type_info_element (value.type_info);
value.size--;
value.depth++;
+ value.ordered_offsets_up_to = 0;
+ value.checked_offsets_up_to = 0;
return g_variant_serialised_is_normal (value);
}
@@ -633,39 +647,105 @@
return body_size + 8 * offsets;
}
+struct Offsets
+{
+ gsize data_size;
+
+ guchar *array;
+ gsize length;
+ guint offset_size;
+
+ gboolean is_normal;
+};
+
static gsize
-gvs_variable_sized_array_n_children (GVariantSerialised value)
+gvs_offsets_get_offset_n (struct Offsets *offsets,
+ gsize n)
{
+ return gvs_read_unaligned_le (
+ offsets->array + (offsets->offset_size * n), offsets->offset_size);
+}
+
+static struct Offsets
+gvs_variable_sized_array_get_frame_offsets (GVariantSerialised value)
+{
+ struct Offsets out = { 0, };
gsize offsets_array_size;
- gsize offset_size;
gsize last_end;
if (value.size == 0)
- return 0;
-
- offset_size = gvs_get_offset_size (value.size);
+ {
+ out.is_normal = TRUE;
+ return out;
+ }
- last_end = gvs_read_unaligned_le (value.data + value.size -
- offset_size, offset_size);
+ out.offset_size = gvs_get_offset_size (value.size);
+ last_end = gvs_read_unaligned_le (value.data + value.size - out.offset_size,
+ out.offset_size);
if (last_end > value.size)
- return 0;
+ return out; /* offsets not normal */
offsets_array_size = value.size - last_end;
- if (offsets_array_size % offset_size)
- return 0;
+ if (offsets_array_size % out.offset_size)
+ return out; /* offsets not normal */
+
+ out.data_size = last_end;
+ out.array = value.data + last_end;
+ out.length = offsets_array_size / out.offset_size;
+
+ if (out.length > 0 && gvs_calculate_total_size (last_end, out.length) != value.size)
+ return out; /* offset size not minimal */
- return offsets_array_size / offset_size;
+ out.is_normal = TRUE;
+
+ return out;
+}
+
+static gsize
+gvs_variable_sized_array_n_children (GVariantSerialised value)
+{
+ return gvs_variable_sized_array_get_frame_offsets (value).length;
}
+/* Find the index of the first out-of-order element in @data, assuming that
+ * @data is an array of elements of given @type, starting at index @start and
+ * containing a further @len-@start elements. */
+#define DEFINE_FIND_UNORDERED(type, le_to_native) \
+ static gsize \
+ find_unordered_##type (const guint8 *data, gsize start, gsize len) \
+ { \
+ gsize off; \
+ type current_le, previous_le, current, previous; \
+ \
+ memcpy (&previous_le, data + start * sizeof (current), sizeof (current)); \
+ previous = le_to_native (previous_le); \
+ for (off = (start + 1) * sizeof (current); off < len * sizeof (current); off += sizeof (current)) \
+ { \
+ memcpy (¤t_le, data + off, sizeof (current)); \
+ current = le_to_native (current_le); \
+ if (current < previous) \
+ break; \
+ previous = current; \
+ } \
+ return off / sizeof (current) - 1; \
+ }
+
+#define NO_CONVERSION(x) (x)
+DEFINE_FIND_UNORDERED (guint8, NO_CONVERSION);
+DEFINE_FIND_UNORDERED (guint16, GUINT16_FROM_LE);
+DEFINE_FIND_UNORDERED (guint32, GUINT32_FROM_LE);
+DEFINE_FIND_UNORDERED (guint64, GUINT64_FROM_LE);
+
static GVariantSerialised
gvs_variable_sized_array_get_child (GVariantSerialised value,
gsize index_)
{
GVariantSerialised child = { 0, };
- gsize offset_size;
- gsize last_end;
+
+ struct Offsets offsets = gvs_variable_sized_array_get_frame_offsets (value);
+
gsize start;
gsize end;
@@ -673,18 +753,61 @@
g_variant_type_info_ref (child.type_info);
child.depth = value.depth + 1;
- offset_size = gvs_get_offset_size (value.size);
+ /* If the requested @index_ is beyond the set of indices whose framing offsets
+ * have been checked, check the remaining offsets to see whether they’re
+ * normal (in order, no overlapping array elements).
+ *
+ * Don’t bother checking if the highest known-good offset is lower than the
+ * highest checked offset, as that means there’s an invalid element at that
+ * index, so there’s no need to check further. */
+ if (index_ > value.checked_offsets_up_to &&
+ value.ordered_offsets_up_to == value.checked_offsets_up_to)
+ {
+ switch (offsets.offset_size)
+ {
+ case 1:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint8 (
+ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ case 2:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint16 (
+ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ case 4:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint32 (
+ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ case 8:
+ {
+ value.ordered_offsets_up_to = find_unordered_guint64 (
+ offsets.array, value.checked_offsets_up_to, index_ + 1);
+ break;
+ }
+ default:
+ /* gvs_get_offset_size() only returns maximum 8 */
+ g_assert_not_reached ();
+ }
- last_end = gvs_read_unaligned_le (value.data + value.size -
- offset_size, offset_size);
+ value.checked_offsets_up_to = index_;
+ }
+
+ if (index_ > value.ordered_offsets_up_to)
+ {
+ /* Offsets are invalid somewhere, so return an empty child. */
+ return child;
+ }
if (index_ > 0)
{
guint alignment;
- start = gvs_read_unaligned_le (value.data + last_end +
- (offset_size * (index_ - 1)),
- offset_size);
+ start = gvs_offsets_get_offset_n (&offsets, index_ - 1);
g_variant_type_info_query (child.type_info, &alignment, NULL);
start += (-start) & alignment;
@@ -692,11 +815,9 @@
else
start = 0;
- end = gvs_read_unaligned_le (value.data + last_end +
- (offset_size * index_),
- offset_size);
+ end = gvs_offsets_get_offset_n (&offsets, index_);
- if (start < end && end <= value.size && end <= last_end)
+ if (start < end && end <= value.size && end <= offsets.data_size)
{
child.data = value.data + start;
child.size = end - start;
@@ -768,34 +889,16 @@
gvs_variable_sized_array_is_normal (GVariantSerialised value)
{
GVariantSerialised child = { 0, };
- gsize offsets_array_size;
- guchar *offsets_array;
- guint offset_size;
guint alignment;
- gsize last_end;
- gsize length;
gsize offset;
gsize i;
- if (value.size == 0)
- return TRUE;
-
- offset_size = gvs_get_offset_size (value.size);
- last_end = gvs_read_unaligned_le (value.data + value.size -
- offset_size, offset_size);
+ struct Offsets offsets = gvs_variable_sized_array_get_frame_offsets (value);
- if (last_end > value.size)
+ if (!offsets.is_normal)
return FALSE;
- offsets_array_size = value.size - last_end;
-
- if (offsets_array_size % offset_size)
- return FALSE;
-
- offsets_array = value.data + value.size - offsets_array_size;
- length = offsets_array_size / offset_size;
-
- if (length == 0)
+ if (value.size != 0 && offsets.length == 0)
return FALSE;
child.type_info = g_variant_type_info_element (value.type_info);
@@ -803,14 +906,14 @@
child.depth = value.depth + 1;
offset = 0;
- for (i = 0; i < length; i++)
+ for (i = 0; i < offsets.length; i++)
{
gsize this_end;
- this_end = gvs_read_unaligned_le (offsets_array + offset_size * i,
- offset_size);
+ this_end = gvs_read_unaligned_le (offsets.array + offsets.offset_size * i,
+ offsets.offset_size);
- if (this_end < offset || this_end > last_end)
+ if (this_end < offset || this_end > offsets.data_size)
return FALSE;
while (offset & alignment)
@@ -832,7 +935,11 @@
offset = this_end;
}
- g_assert (offset == last_end);
+ g_assert (offset == offsets.data_size);
+
+ /* All offsets have now been checked. */
+ value.ordered_offsets_up_to = G_MAXSIZE;
+ value.checked_offsets_up_to = G_MAXSIZE;
return TRUE;
}
@@ -859,6 +966,61 @@
* for the tuple. See the notes in gvarianttypeinfo.h.
*/
+/* Note: This doesn’t guarantee that @out_member_end >= @out_member_start; that
+ * condition may not hold true for invalid serialised variants. The caller is
+ * responsible for checking the returned values and handling invalid ones
+ * appropriately. */
+static void
+gvs_tuple_get_member_bounds (GVariantSerialised value,
+ gsize index_,
+ gsize offset_size,
+ gsize *out_member_start,
+ gsize *out_member_end)
+{
+ const GVariantMemberInfo *member_info;
+ gsize member_start, member_end;
+
+ member_info = g_variant_type_info_member_info (value.type_info, index_);
+
+ if (member_info->i + 1 &&
+ offset_size * (member_info->i + 1) <= value.size)
+ member_start = gvs_read_unaligned_le (value.data + value.size -
+ offset_size * (member_info->i + 1),
+ offset_size);
+ else
+ member_start = 0;
+
+ member_start += member_info->a;
+ member_start &= member_info->b;
+ member_start |= member_info->c;
+
+ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST &&
+ offset_size * (member_info->i + 1) <= value.size)
+ member_end = value.size - offset_size * (member_info->i + 1);
+
+ else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
+ {
+ gsize fixed_size;
+
+ g_variant_type_info_query (member_info->type_info, NULL, &fixed_size);
+ member_end = member_start + fixed_size;
+ }
+
+ else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_OFFSET &&
+ offset_size * (member_info->i + 2) <= value.size)
+ member_end = gvs_read_unaligned_le (value.data + value.size -
+ offset_size * (member_info->i + 2),
+ offset_size);
+
+ else /* invalid */
+ member_end = G_MAXSIZE;
+
+ if (out_member_start != NULL)
+ *out_member_start = member_start;
+ if (out_member_end != NULL)
+ *out_member_end = member_end;
+}
+
static gsize
gvs_tuple_n_children (GVariantSerialised value)
{
@@ -879,14 +1041,18 @@
child.depth = value.depth + 1;
offset_size = gvs_get_offset_size (value.size);
+ /* Ensure the size is set for fixed-sized children, or
+ * g_variant_serialised_check() will fail, even if we return
+ * (child.data == NULL) to indicate an error. */
+ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
+ g_variant_type_info_query (child.type_info, NULL, &child.size);
+
/* tuples are the only (potentially) fixed-sized containers, so the
* only ones that have to deal with the possibility of having %NULL
* data with a non-zero %size if errors occurred elsewhere.
*/
if G_UNLIKELY (value.data == NULL && value.size != 0)
{
- g_variant_type_info_query (child.type_info, NULL, &child.size);
-
/* this can only happen in fixed-sized tuples,
* so the child must also be fixed sized.
*/
@@ -896,63 +1062,58 @@
return child;
}
- if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_OFFSET)
- {
- if (offset_size * (member_info->i + 2) > value.size)
- return child;
- }
- else
+ /* If the requested @index_ is beyond the set of indices whose framing offsets
+ * have been checked, check the remaining offsets to see whether they’re
+ * normal (in order, no overlapping tuple elements).
+ *
+ * Unlike the checks in gvs_variable_sized_array_get_child(), we have to check
+ * all the tuple *elements* here, not just all the framing offsets, since
+ * tuples contain a mix of elements which use framing offsets and ones which
+ * don’t. None of them are allowed to overlap. */
+ if (index_ > value.checked_offsets_up_to &&
+ value.ordered_offsets_up_to == value.checked_offsets_up_to)
{
- if (offset_size * (member_info->i + 1) > value.size)
- {
- /* if the child is fixed size, return its size.
- * if child is not fixed-sized, return size = 0.
- */
- g_variant_type_info_query (child.type_info, NULL, &child.size);
+ gsize i, prev_i_end = 0;
- return child;
- }
- }
+ if (value.checked_offsets_up_to > 0)
+ gvs_tuple_get_member_bounds (value, value.checked_offsets_up_to - 1, offset_size, NULL, &prev_i_end);
- if (member_info->i + 1)
- start = gvs_read_unaligned_le (value.data + value.size -
- offset_size * (member_info->i + 1),
- offset_size);
- else
- start = 0;
+ for (i = value.checked_offsets_up_to; i <= index_; i++)
+ {
+ gsize i_start, i_end;
- start += member_info->a;
- start &= member_info->b;
- start |= member_info->c;
+ gvs_tuple_get_member_bounds (value, i, offset_size, &i_start, &i_end);
- if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_LAST)
- end = value.size - offset_size * (member_info->i + 1);
+ if (i_start > i_end || i_start < prev_i_end || i_end > value.size)
+ break;
- else if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_FIXED)
- {
- gsize fixed_size;
+ prev_i_end = i_end;
+ }
- g_variant_type_info_query (child.type_info, NULL, &fixed_size);
- end = start + fixed_size;
- child.size = fixed_size;
+ value.ordered_offsets_up_to = i - 1;
+ value.checked_offsets_up_to = index_;
}
- else /* G_VARIANT_MEMBER_ENDING_OFFSET */
- end = gvs_read_unaligned_le (value.data + value.size -
- offset_size * (member_info->i + 2),
- offset_size);
+ if (index_ > value.ordered_offsets_up_to)
+ {
+ /* Offsets are invalid somewhere, so return an empty child. */
+ return child;
+ }
- /* The child should not extend into the offset table. */
- if (index_ != g_variant_type_info_n_members (value.type_info) - 1)
+ if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_OFFSET)
{
- GVariantSerialised last_child;
- last_child = gvs_tuple_get_child (value,
- g_variant_type_info_n_members (value.type_info) - 1);
- last_end = last_child.data + last_child.size - value.data;
- g_variant_type_info_unref (last_child.type_info);
+ if (offset_size * (member_info->i + 2) > value.size)
+ return child;
}
else
- last_end = end;
+ {
+ if (offset_size * (member_info->i + 1) > value.size)
+ return child;
+ }
+
+ /* The child should not extend into the offset table. */
+ gvs_tuple_get_member_bounds (value, index_, offset_size, &start, &end);
+ gvs_tuple_get_member_bounds (value, g_variant_type_info_n_members (value.type_info) - 1, offset_size, NULL, &last_end);
if (start < end && end <= value.size && end <= last_end)
{
@@ -1053,6 +1214,7 @@
gsize length;
gsize offset;
gsize i;
+ gsize offset_table_size;
/* as per the comment in gvs_tuple_get_child() */
if G_UNLIKELY (value.data == NULL && value.size != 0)
@@ -1066,7 +1228,7 @@
for (i = 0; i < length; i++)
{
const GVariantMemberInfo *member_info;
- GVariantSerialised child;
+ GVariantSerialised child = { 0, };
gsize fixed_size;
guint alignment;
gsize end;
@@ -1126,6 +1288,10 @@
offset = end;
}
+ /* All element bounds have been checked above. */
+ value.ordered_offsets_up_to = G_MAXSIZE;
+ value.checked_offsets_up_to = G_MAXSIZE;
+
{
gsize fixed_size;
guint alignment;
@@ -1153,7 +1319,19 @@
}
}
- return offset_ptr == offset;
+ /* @offset_ptr has been counting backwards from the end of the variant, to
+ * find the beginning of the offset table. @offset has been counting forwards
+ * from the beginning of the variant to find the end of the data. They should
+ * have met in the middle. */
+ if (offset_ptr != offset)
+ return FALSE;
+
+ offset_table_size = value.size - offset_ptr;
+ if (value.size > 0 &&
+ gvs_calculate_total_size (offset, offset_table_size / offset_size) != value.size)
+ return FALSE; /* offset size not minimal */
+
+ return TRUE;
}
/* Variants {{{2
diff --unified --recursive --text --new-file --color glib-2.70.5.old/glib/gvariant-serialiser.h glib-2.70.5.new/glib/gvariant-serialiser.h
--- glib-2.70.5.old/glib/gvariant-serialiser.h 2022-03-17 21:58:37.000000000 +0800
+++ glib-2.70.5.new/glib/gvariant-serialiser.h 2023-04-14 10:50:39.707980911 +0800
@@ -29,6 +29,27 @@
guchar *data;
gsize size;
gsize depth; /* same semantics as GVariant.depth */
+
+ /* If ordered_offsets_up_to == n this means that all the frame offsets up to and
+ * including the frame offset determining the end of element n are in order.
+ * This guarantees that the bytes of element n don't overlap with any previous
+ * element.
+ *
+ * This is both read and set by g_variant_serialised_get_child() for arrays of
+ * non-fixed-width types, and for tuples.
+ *
+ * Even when dealing with tuples, @ordered_offsets_up_to is an element index,
+ * rather than an index into the frame offsets. */
+ gsize ordered_offsets_up_to;
+
+ /* Similar to @ordered_offsets_up_to. This gives the index of the child element
+ * whose frame offset is the highest in the offset table which has been
+ * checked so far.
+ *
+ * This is always ≥ @ordered_offsets_up_to. It is always an element index.
+ *
+ * See documentation in gvariant-core.c for `struct GVariant` for details. */
+ gsize checked_offsets_up_to;
} GVariantSerialised;
/* deserialization */
diff --unified --recursive --text --new-file --color glib-2.70.5.old/glib/gvarianttypeinfo.c glib-2.70.5.new/glib/gvarianttypeinfo.c
--- glib-2.70.5.old/glib/gvarianttypeinfo.c 2022-03-17 21:58:37.000000000 +0800
+++ glib-2.70.5.new/glib/gvarianttypeinfo.c 2023-04-14 10:51:26.494652410 +0800
@@ -245,8 +245,6 @@
guint *alignment,
gsize *fixed_size)
{
- g_variant_type_info_check (info, 0);
-
if (alignment)
*alignment = info->alignment;
diff --unified --recursive --text --new-file --color glib-2.70.5.old/glib/tests/gvariant.c glib-2.70.5.new/glib/tests/gvariant.c
--- glib-2.70.5.old/glib/tests/gvariant.c 2022-03-17 21:58:37.000000000 +0800
+++ glib-2.70.5.new/glib/tests/gvariant.c 2023-04-14 10:51:19.397984991 +0800
@@ -1,5 +1,7 @@
/*
* Copyright © 2010 Codethink Limited
+ * Copyright © 2020 William Manley
+ * Copyright © 2022 Endless OS Foundation, LLC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -1227,6 +1229,7 @@
GRand *rand;
gsize i;
+ g_assert_true (size == 0 || buffer != NULL);
g_assert_cmpint ((gsize) buffer & ALIGN_BITS & instance->alignment, ==, 0);
g_assert_cmpint (size, ==, instance->size);
@@ -1279,6 +1282,8 @@
serialised->size = instance->size;
serialised->depth = 0;
+ serialised->ordered_offsets_up_to = 0;
+ serialised->checked_offsets_up_to = 0;
g_assert_true (serialised->type_info == instance->type_info);
g_assert_cmpuint (serialised->size, ==, instance->size);
@@ -1438,21 +1443,26 @@
for (flavour = 0; flavour < 8; flavour += alignment)
{
- GVariantSerialised serialised;
+ GVariantSerialised serialised = { 0, };
GVariantSerialised child;
serialised.type_info = type_info;
serialised.data = flavoured_malloc (needed_size, flavour);
serialised.size = needed_size;
serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
+ serialised.checked_offsets_up_to = 0;
g_variant_serialiser_serialise (serialised,
random_instance_filler,
(gpointer *) &instance, 1);
+
child = g_variant_serialised_get_child (serialised, 0);
g_assert_true (child.type_info == instance->type_info);
- random_instance_assert (instance, child.data, child.size);
+ if (child.data != NULL) /* could be NULL if element is non-normal */
+ random_instance_assert (instance, child.data, child.size);
g_variant_type_info_unref (child.type_info);
+
flavoured_free (serialised.data, flavour);
}
}
@@ -1562,12 +1572,14 @@
for (flavour = 0; flavour < 8; flavour += alignment)
{
- GVariantSerialised serialised;
+ GVariantSerialised serialised = { 0, };
serialised.type_info = array_info;
serialised.data = flavoured_malloc (needed_size, flavour);
serialised.size = needed_size;
serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
+ serialised.checked_offsets_up_to = 0;
g_variant_serialiser_serialise (serialised, random_instance_filler,
(gpointer *) instances, n_children);
@@ -1583,7 +1595,8 @@
child = g_variant_serialised_get_child (serialised, i);
g_assert_true (child.type_info == instances[i]->type_info);
- random_instance_assert (instances[i], child.data, child.size);
+ if (child.data != NULL) /* could be NULL if element is non-normal */
+ random_instance_assert (instances[i], child.data, child.size);
g_variant_type_info_unref (child.type_info);
}
@@ -1726,12 +1739,14 @@
for (flavour = 0; flavour < 8; flavour += alignment)
{
- GVariantSerialised serialised;
+ GVariantSerialised serialised = { 0, };
serialised.type_info = type_info;
serialised.data = flavoured_malloc (needed_size, flavour);
serialised.size = needed_size;
serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
+ serialised.checked_offsets_up_to = 0;
g_variant_serialiser_serialise (serialised, random_instance_filler,
(gpointer *) instances, n_children);
@@ -1747,7 +1762,8 @@
child = g_variant_serialised_get_child (serialised, i);
g_assert_true (child.type_info == instances[i]->type_info);
- random_instance_assert (instances[i], child.data, child.size);
+ if (child.data != NULL) /* could be NULL if element is non-normal */
+ random_instance_assert (instances[i], child.data, child.size);
g_variant_type_info_unref (child.type_info);
}
@@ -1821,13 +1837,15 @@
for (flavour = 0; flavour < 8; flavour += alignment)
{
- GVariantSerialised serialised;
+ GVariantSerialised serialised = { 0, };
GVariantSerialised child;
serialised.type_info = type_info;
serialised.data = flavoured_malloc (needed_size, flavour);
serialised.size = needed_size;
serialised.depth = 0;
+ serialised.ordered_offsets_up_to = 0;
+ serialised.checked_offsets_up_to = 0;
g_variant_serialiser_serialise (serialised, random_instance_filler,
(gpointer *) &instance, 1);
@@ -2268,24 +2286,67 @@
static void
test_byteswap (void)
{
- GVariantSerialised one, two;
+ GVariantSerialised one = { 0, }, two = { 0, }, three = { 0, };
TreeInstance *tree;
+ GVariant *one_variant = NULL;
+ GVariant *two_variant = NULL;
+ GVariant *two_byteswapped = NULL;
+ GVariant *three_variant = NULL;
+ GVariant *three_byteswapped = NULL;
+ guint8 *three_data_copy = NULL;
+ gsize three_size_copy = 0;
+ /* Write a tree out twice, once normally and once byteswapped. */
tree = tree_instance_new (NULL, 3);
serialise_tree (tree, &one);
+ one_variant = g_variant_new_from_data (G_VARIANT_TYPE (g_variant_type_info_get_type_string (one.type_info)),
+ one.data, one.size, FALSE, NULL, NULL);
+
i_am_writing_byteswapped = TRUE;
serialise_tree (tree, &two);
+ serialise_tree (tree, &three);
i_am_writing_byteswapped = FALSE;
- g_variant_serialised_byteswap (two);
-
- g_assert_cmpmem (one.data, one.size, two.data, two.size);
- g_assert_cmpuint (one.depth, ==, two.depth);
-
+ /* Swap the first byteswapped one back using the function we want to test. */
+ two_variant = g_variant_new_from_data (G_VARIANT_TYPE (g_variant_type_info_get_type_string (two.type_info)),
+ two.data, two.size, FALSE, NULL, NULL);
+ two_byteswapped = g_variant_byteswap (two_variant);
+
+ /* Make the second byteswapped one non-normal (hopefully), and then byteswap
+ * it back using the function we want to test in its non-normal mode.
+ * This might not work because it’s not necessarily possible to make an
+ * arbitrary random variant non-normal. Adding a single zero byte to the end
+ * often makes something non-normal but still readable. */
+ three_size_copy = three.size + 1;
+ three_data_copy = g_malloc (three_size_copy);
+ memcpy (three_data_copy, three.data, three.size);
+ three_data_copy[three.size] = '\0';
+
+ three_variant = g_variant_new_from_data (G_VARIANT_TYPE (g_variant_type_info_get_type_string (three.type_info)),
+ three_data_copy, three_size_copy, FALSE, NULL, NULL);
+ three_byteswapped = g_variant_byteswap (three_variant);
+
+ /* Check they’re the same. We can always compare @one_variant and
+ * @two_byteswapped. We can only compare @two_byteswapped and
+ * @three_byteswapped if @two_variant and @three_variant are equal: in that
+ * case, the corruption to @three_variant was enough to make it non-normal but
+ * not enough to change its value. */
+ g_assert_cmpvariant (one_variant, two_byteswapped);
+
+ if (g_variant_equal (two_variant, three_variant))
+ g_assert_cmpvariant (two_byteswapped, three_byteswapped);
+
+ g_variant_unref (three_byteswapped);
+ g_variant_unref (three_variant);
+ g_variant_unref (two_byteswapped);
+ g_variant_unref (two_variant);
+ g_variant_unref (one_variant);
tree_instance_free (tree);
g_free (one.data);
g_free (two.data);
+ g_free (three.data);
+ g_free (three_data_copy);
}
static void
@@ -2342,7 +2403,7 @@
static void
test_fuzz (gdouble *fuzziness)
{
- GVariantSerialised serialised;
+ GVariantSerialised serialised = { 0, };
TreeInstance *tree;
/* make an instance */
@@ -3807,6 +3868,29 @@
}
static void
+test_gv_byteswap_non_normal_non_aligned (void)
+{
+ const guint8 data[] = { 0x02 };
+ GVariant *v = NULL;
+ GVariant *v_byteswapped = NULL;
+
+ g_test_summary ("Test that calling g_variant_byteswap() on a variant which "
+ "is in non-normal form and doesn’t need byteswapping returns "
+ "the same variant in normal form.");
+
+ v = g_variant_new_from_data (G_VARIANT_TYPE_BOOLEAN, data, sizeof (data), FALSE, NULL, NULL);
+ g_assert_false (g_variant_is_normal_form (v));
+
+ v_byteswapped = g_variant_byteswap (v);
+ g_assert_true (g_variant_is_normal_form (v_byteswapped));
+
+ g_assert_cmpvariant (v, v_byteswapped);
+
+ g_variant_unref (v);
+ g_variant_unref (v_byteswapped);
+}
+
+static void
test_parser (void)
{
TreeInstance *tree;
@@ -4999,6 +5083,38 @@
g_variant_unref (wrapper_variant);
}
+/* Test that a nested array with invalid values in its offset table (which point
+ * from the inner to the outer array) is normalised successfully without
+ * looping infinitely. */
+static void
+test_normal_checking_array_offsets_overlapped (void)
+{
+ const guint8 data[] = {
+ 0x01, 0x00,
+ };
+ gsize size = sizeof (data);
+ GVariant *variant = NULL;
+ GVariant *normal_variant = NULL;
+ GVariant *expected_variant = NULL;
+
+ variant = g_variant_new_from_data (G_VARIANT_TYPE ("aay"), data, size,
+ FALSE, NULL, NULL);
+ g_assert_nonnull (variant);
+
+ normal_variant = g_variant_get_normal_form (variant);
+ g_assert_nonnull (normal_variant);
+
+ expected_variant = g_variant_new_parsed ("[@ay [], []]");
+ g_assert_cmpvariant (normal_variant, expected_variant);
+
+ g_assert_cmpmem (g_variant_get_data (normal_variant), g_variant_get_size (normal_variant),
+ g_variant_get_data (expected_variant), g_variant_get_size (expected_variant));
+
+ g_variant_unref (expected_variant);
+ g_variant_unref (normal_variant);
+ g_variant_unref (variant);
+}
+
/* Test that an array with invalidly large values in its offset table is
* normalised successfully without looping infinitely. */
static void
@@ -5023,6 +5139,127 @@
g_variant_unref (variant);
}
+/* This is a regression test that we can't have non-normal values that take up
+ * significantly more space than the normal equivalent, by specifying the
+ * offset table entries so that array elements overlap.
+ *
+ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_832242 */
+static void
+test_normal_checking_array_offsets2 (void)
+{
+ const guint8 data[] = {
+ 'h', 'i', '\0',
+ 0x03, 0x00, 0x03,
+ 0x06, 0x00, 0x06,
+ 0x09, 0x00, 0x09,
+ 0x0c, 0x00, 0x0c,
+ 0x0f, 0x00, 0x0f,
+ 0x12, 0x00, 0x12,
+ 0x15, 0x00, 0x15,
+ };
+ gsize size = sizeof (data);
+ const GVariantType *aaaaaaas = G_VARIANT_TYPE ("aaaaaaas");
+ GVariant *variant = NULL;
+ GVariant *normal_variant = NULL;
+ GVariant *expected = NULL;
+
+ variant = g_variant_new_from_data (aaaaaaas, data, size, FALSE, NULL, NULL);
+ g_assert_nonnull (variant);
+
+ normal_variant = g_variant_get_normal_form (variant);
+ g_assert_nonnull (normal_variant);
+ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 2);
+
+ expected = g_variant_new_parsed (
+ "[[[[[[['hi', '', ''], [], []], [], []], [], []], [], []], [], []], [], []]");
+ g_assert_cmpvariant (expected, variant);
+ g_assert_cmpvariant (expected, normal_variant);
+
+ g_variant_unref (expected);
+ g_variant_unref (normal_variant);
+ g_variant_unref (variant);
+}
+
+/* Test that an otherwise-valid serialised GVariant is considered non-normal if
+ * its offset table entries are too wide.
+ *
+ * See §2.3.6 (Framing Offsets) of the GVariant specification. */
+static void
+test_normal_checking_array_offsets_minimal_sized (void)
+{
+ GVariantBuilder builder;
+ gsize i;
+ GVariant *aay_constructed = NULL;
+ const guint8 *data = NULL;
+ guint8 *data_owned = NULL;
+ GVariant *aay_deserialised = NULL;
+ GVariant *aay_normalised = NULL;
+
+ /* Construct an array of type aay, consisting of 128 elements which are each
+ * an empty array, i.e. `[[] * 128]`. This is chosen because the inner
+ * elements are variable sized (making the outer array variable sized, so it
+ * must have an offset table), but they are also zero-sized when serialised.
+ * So the serialised representation of @aay_constructed consists entirely of
+ * its offset table, which is entirely zeroes.
+ *
+ * The array is chosen to be 128 elements long because that means offset
+ * table entries which are 1 byte long. If the elements in the array were
+ * non-zero-sized (to the extent that the overall array is ≥256 bytes long),
+ * the offset table entries would end up being 2 bytes long. */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay"));
+
+ for (i = 0; i < 128; i++)
+ g_variant_builder_add_value (&builder, g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0));
+
+ aay_constructed = g_variant_builder_end (&builder);
+
+ /* Verify that the constructed array is in normal form, and its serialised
+ * form is `b'\0' * 128`. */
+ g_assert_true (g_variant_is_normal_form (aay_constructed));
+ g_assert_cmpuint (g_variant_n_children (aay_constructed), ==, 128);
+ g_assert_cmpuint (g_variant_get_size (aay_constructed), ==, 128);
+
+ data = g_variant_get_data (aay_constructed);
+ for (i = 0; i < g_variant_get_size (aay_constructed); i++)
+ g_assert_cmpuint (data[i], ==, 0);
+
+ /* Construct a serialised `aay` GVariant which is `b'\0' * 256`. This has to
+ * be a non-normal form of `[[] * 128]`, with 2-byte-long offset table
+ * entries, because each offset table entry has to be able to reference all of
+ * the byte boundaries in the container. All the entries in the offset table
+ * are zero, so all the elements of the array are zero-sized. */
+ data = data_owned = g_malloc0 (256);
+ aay_deserialised = g_variant_new_from_data (G_VARIANT_TYPE ("aay"),
+ data,
+ 256,
+ FALSE,
+ g_free,
+ g_steal_pointer (&data_owned));
+
+ g_assert_false (g_variant_is_normal_form (aay_deserialised));
+ g_assert_cmpuint (g_variant_n_children (aay_deserialised), ==, 128);
+ g_assert_cmpuint (g_variant_get_size (aay_deserialised), ==, 256);
+
+ data = g_variant_get_data (aay_deserialised);
+ for (i = 0; i < g_variant_get_size (aay_deserialised); i++)
+ g_assert_cmpuint (data[i], ==, 0);
+
+ /* Get its normal form. That should change the serialised size. */
+ aay_normalised = g_variant_get_normal_form (aay_deserialised);
+
+ g_assert_true (g_variant_is_normal_form (aay_normalised));
+ g_assert_cmpuint (g_variant_n_children (aay_normalised), ==, 128);
+ g_assert_cmpuint (g_variant_get_size (aay_normalised), ==, 128);
+
+ data = g_variant_get_data (aay_normalised);
+ for (i = 0; i < g_variant_get_size (aay_normalised); i++)
+ g_assert_cmpuint (data[i], ==, 0);
+
+ g_variant_unref (aay_normalised);
+ g_variant_unref (aay_deserialised);
+ g_variant_unref (aay_constructed);
+}
+
/* Test that a tuple with invalidly large values in its offset table is
* normalised successfully without looping infinitely. */
static void
@@ -5047,6 +5284,329 @@
g_variant_unref (variant);
}
+/* This is a regression test that we can't have non-normal values that take up
+ * significantly more space than the normal equivalent, by specifying the
+ * offset table entries so that tuple elements overlap.
+ *
+ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_838503 and
+ * https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_838513 */
+static void
+test_normal_checking_tuple_offsets2 (void)
+{
+ const GVariantType *data_type = G_VARIANT_TYPE ("(yyaiyyaiyy)");
+ const guint8 data[] = {
+ 0x12, 0x34, 0x56, 0x78, 0x01,
+ /*
+ ^───────────────────┘
+
+ ^^^^^^^^^^ 1st yy
+ ^^^^^^^^^^ 2nd yy
+ ^^^^^^^^^^ 3rd yy
+ ^^^^ Framing offsets
+ */
+
+ /* If this variant was encoded normally, it would be something like this:
+ * 0x12, 0x34, pad, pad, [array bytes], 0x56, 0x78, pad, pad, [array bytes], 0x9A, 0xBC, 0xXX
+ * ^─────────────────────────────────────────────────────┘
+ *
+ * ^^^^^^^^^^ 1st yy
+ * ^^^^^^^^^^ 2nd yy
+ * ^^^^^^^^^^ 3rd yy
+ * ^^^^ Framing offsets
+ */
+ };
+ gsize size = sizeof (data);
+ GVariant *variant = NULL;
+ GVariant *normal_variant = NULL;
+ GVariant *expected = NULL;
+
+ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
+ g_assert_nonnull (variant);
+
+ normal_variant = g_variant_get_normal_form (variant);
+ g_assert_nonnull (normal_variant);
+ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 3);
+
+ expected = g_variant_new_parsed (
+ "@(yyaiyyaiyy) (0x12, 0x34, [], 0x00, 0x00, [], 0x00, 0x00)");
+ g_assert_cmpvariant (expected, variant);
+ g_assert_cmpvariant (expected, normal_variant);
+
+ g_variant_unref (expected);
+ g_variant_unref (normal_variant);
+ g_variant_unref (variant);
+}
+
+/* This is a regression test that overlapping entries in the offset table are
+ * decoded consistently, even though they’re non-normal.
+ *
+ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_910935 */
+static void
+test_normal_checking_tuple_offsets3 (void)
+{
+ /* The expected decoding of this non-normal byte stream is complex. See
+ * section 2.7.3 (Handling Non-Normal Serialised Data) of the GVariant
+ * specification.
+ *
+ * The rule “Child Values Overlapping Framing Offsets” from the specification
+ * says that the first `ay` must be decoded as `[0x01]` even though it
+ * overlaps the first byte of the offset table. However, since commit
+ * 7eedcd76f7d5b8c98fa60013e1fe6e960bf19df3, GLib explicitly doesn’t allow
+ * this as it’s exploitable. So the first `ay` must be given a default value.
+ *
+ * The second and third `ay`s must be given default values because of rule
+ * “End Boundary Precedes Start Boundary”.
+ *
+ * The `i` must be given a default value because of rule “Start or End
+ * Boundary of a Child Falls Outside the Container”.
+ */
+ const GVariantType *data_type = G_VARIANT_TYPE ("(ayayiay)");
+ const guint8 data[] = {
+ 0x01, 0x00, 0x02,
+ /*
+ ^──┘
+
+ ^^^^^^^^^^ 1st ay, bytes 0-2 (but given a default value anyway, see above)
+ 2nd ay, bytes 2-0
+ i, bytes 0-4
+ 3rd ay, bytes 4-1
+ ^^^^^^^^^^ Framing offsets
+ */
+ };
+ gsize size = sizeof (data);
+ GVariant *variant = NULL;
+ GVariant *normal_variant = NULL;
+ GVariant *expected = NULL;
+
+ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
+ g_assert_nonnull (variant);
+
+ g_assert_false (g_variant_is_normal_form (variant));
+
+ normal_variant = g_variant_get_normal_form (variant);
+ g_assert_nonnull (normal_variant);
+ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 3);
+
+ expected = g_variant_new_parsed ("@(ayayiay) ([], [], 0, [])");
+ g_assert_cmpvariant (expected, variant);
+ g_assert_cmpvariant (expected, normal_variant);
+
+ g_variant_unref (expected);
+ g_variant_unref (normal_variant);
+ g_variant_unref (variant);
+}
+
+/* This is a regression test that overlapping entries in the offset table are
+ * decoded consistently, even though they’re non-normal.
+ *
+ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2121#note_910935 */
+static void
+test_normal_checking_tuple_offsets4 (void)
+{
+ /* The expected decoding of this non-normal byte stream is complex. See
+ * section 2.7.3 (Handling Non-Normal Serialised Data) of the GVariant
+ * specification.
+ *
+ * The rule “Child Values Overlapping Framing Offsets” from the specification
+ * says that the first `ay` must be decoded as `[0x01]` even though it
+ * overlaps the first byte of the offset table. However, since commit
+ * 7eedcd76f7d5b8c98fa60013e1fe6e960bf19df3, GLib explicitly doesn’t allow
+ * this as it’s exploitable. So the first `ay` must be given a default value.
+ *
+ * The second `ay` must be given a default value because of rule “End Boundary
+ * Precedes Start Boundary”.
+ *
+ * The third `ay` must be given a default value because its framing offsets
+ * overlap that of the first `ay`.
+ */
+ const GVariantType *data_type = G_VARIANT_TYPE ("(ayayay)");
+ const guint8 data[] = {
+ 0x01, 0x00, 0x02,
+ /*
+ ^──┘
+
+ ^^^^^^^^^^ 1st ay, bytes 0-2 (but given a default value anyway, see above)
+ 2nd ay, bytes 2-0
+ 3rd ay, bytes 0-1
+ ^^^^^^^^^^ Framing offsets
+ */
+ };
+ gsize size = sizeof (data);
+ GVariant *variant = NULL;
+ GVariant *normal_variant = NULL;
+ GVariant *expected = NULL;
+
+ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
+ g_assert_nonnull (variant);
+
+ g_assert_false (g_variant_is_normal_form (variant));
+
+ normal_variant = g_variant_get_normal_form (variant);
+ g_assert_nonnull (normal_variant);
+ g_assert_cmpuint (g_variant_get_size (normal_variant), <=, size * 3);
+
+ expected = g_variant_new_parsed ("@(ayayay) ([], [], [])");
+ g_assert_cmpvariant (expected, variant);
+ g_assert_cmpvariant (expected, normal_variant);
+
+ g_variant_unref (expected);
+ g_variant_unref (normal_variant);
+ g_variant_unref (variant);
+}
+
+/* This is a regression test that dereferencing the first element in the offset
+ * table doesn’t dereference memory before the start of the GVariant. The first
+ * element in the offset table gives the offset of the final member in the
+ * tuple (the offset table is stored in reverse), and the position of this final
+ * member is needed to check that none of the tuple members overlap with the
+ * offset table
+ *
+ * See https://gitlab.gnome.org/GNOME/glib/-/issues/2840 */
+static void
+test_normal_checking_tuple_offsets5 (void)
+{
+ /* A tuple of type (sss) in normal form would have an offset table with two
+ * entries:
+ * - The first entry (lowest index in the table) gives the offset of the
+ * third `s` in the tuple, as the offset table is reversed compared to the
+ * tuple members.
+ * - The second entry (highest index in the table) gives the offset of the
+ * second `s` in the tuple.
+ * - The offset of the first `s` in the tuple is always 0.
+ *
+ * See §2.5.4 (Structures) of the GVariant specification for details, noting
+ * that the table is only layed out this way because all three members of the
+ * tuple have non-fixed sizes.
+ *
+ * It’s not clear whether the 0xaa data of this variant is part of the strings
+ * in the tuple, or part of the offset table. It doesn’t really matter. This
+ * is a regression test to check that the code to validate the offset table
+ * doesn’t unconditionally try to access the first entry in the offset table
+ * by subtracting the table size from the end of the GVariant data.
+ *
+ * In this non-normal case, that would result in an address off the start of
+ * the GVariant data, and an out-of-bounds read, because the GVariant is one
+ * byte long, but the offset table is calculated as two bytes long (with 1B
+ * sized entries) from the tuple’s type.
+ */
+ const GVariantType *data_type = G_VARIANT_TYPE ("(sss)");
+ const guint8 data[] = { 0xaa };
+ gsize size = sizeof (data);
+ GVariant *variant = NULL;
+ GVariant *normal_variant = NULL;
+ GVariant *expected = NULL;
+
+ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2840");
+
+ variant = g_variant_new_from_data (data_type, data, size, FALSE, NULL, NULL);
+ g_assert_nonnull (variant);
+
+ g_assert_false (g_variant_is_normal_form (variant));
+
+ normal_variant = g_variant_get_normal_form (variant);
+ g_assert_nonnull (normal_variant);
+
+ expected = g_variant_new_parsed ("('', '', '')");
+ g_assert_cmpvariant (expected, variant);
+ g_assert_cmpvariant (expected, normal_variant);
+
+ g_variant_unref (expected);
+ g_variant_unref (normal_variant);
+ g_variant_unref (variant);
+}
+
+/* Test that an otherwise-valid serialised GVariant is considered non-normal if
+ * its offset table entries are too wide.
+ *
+ * See §2.3.6 (Framing Offsets) of the GVariant specification. */
+static void
+test_normal_checking_tuple_offsets_minimal_sized (void)
+{
+ GString *type_string = NULL;
+ GVariantBuilder builder;
+ gsize i;
+ GVariant *ray_constructed = NULL;
+ const guint8 *data = NULL;
+ guint8 *data_owned = NULL;
+ GVariant *ray_deserialised = NULL;
+ GVariant *ray_normalised = NULL;
+
+ /* Construct a tuple of type (ay…ay), consisting of 129 members which are each
+ * an empty array, i.e. `([] * 129)`. This is chosen because the inner
+ * members are variable sized, so the outer tuple must have an offset table,
+ * but they are also zero-sized when serialised. So the serialised
+ * representation of @ray_constructed consists entirely of its offset table,
+ * which is entirely zeroes.
+ *
+ * The tuple is chosen to be 129 members long because that means it has 128
+ * offset table entries which are 1 byte long each. If the members in the
+ * tuple were non-zero-sized (to the extent that the overall tuple is ≥256
+ * bytes long), the offset table entries would end up being 2 bytes long.
+ *
+ * 129 members are used unlike 128 array elements in
+ * test_normal_checking_array_offsets_minimal_sized(), because the last member
+ * in a tuple never needs an offset table entry. */
+ type_string = g_string_new ("");
+ g_string_append_c (type_string, '(');
+ for (i = 0; i < 129; i++)
+ g_string_append (type_string, "ay");
+ g_string_append_c (type_string, ')');
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE (type_string->str));
+
+ for (i = 0; i < 129; i++)
+ g_variant_builder_add_value (&builder, g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0));
+
+ ray_constructed = g_variant_builder_end (&builder);
+
+ /* Verify that the constructed tuple is in normal form, and its serialised
+ * form is `b'\0' * 128`. */
+ g_assert_true (g_variant_is_normal_form (ray_constructed));
+ g_assert_cmpuint (g_variant_n_children (ray_constructed), ==, 129);
+ g_assert_cmpuint (g_variant_get_size (ray_constructed), ==, 128);
+
+ data = g_variant_get_data (ray_constructed);
+ for (i = 0; i < g_variant_get_size (ray_constructed); i++)
+ g_assert_cmpuint (data[i], ==, 0);
+
+ /* Construct a serialised `(ay…ay)` GVariant which is `b'\0' * 256`. This has
+ * to be a non-normal form of `([] * 129)`, with 2-byte-long offset table
+ * entries, because each offset table entry has to be able to reference all of
+ * the byte boundaries in the container. All the entries in the offset table
+ * are zero, so all the members of the tuple are zero-sized. */
+ data = data_owned = g_malloc0 (256);
+ ray_deserialised = g_variant_new_from_data (G_VARIANT_TYPE (type_string->str),
+ data,
+ 256,
+ FALSE,
+ g_free,
+ g_steal_pointer (&data_owned));
+
+ g_assert_false (g_variant_is_normal_form (ray_deserialised));
+ g_assert_cmpuint (g_variant_n_children (ray_deserialised), ==, 129);
+ g_assert_cmpuint (g_variant_get_size (ray_deserialised), ==, 256);
+
+ data = g_variant_get_data (ray_deserialised);
+ for (i = 0; i < g_variant_get_size (ray_deserialised); i++)
+ g_assert_cmpuint (data[i], ==, 0);
+
+ /* Get its normal form. That should change the serialised size. */
+ ray_normalised = g_variant_get_normal_form (ray_deserialised);
+
+ g_assert_true (g_variant_is_normal_form (ray_normalised));
+ g_assert_cmpuint (g_variant_n_children (ray_normalised), ==, 129);
+ g_assert_cmpuint (g_variant_get_size (ray_normalised), ==, 128);
+
+ data = g_variant_get_data (ray_normalised);
+ for (i = 0; i < g_variant_get_size (ray_normalised); i++)
+ g_assert_cmpuint (data[i], ==, 0);
+
+ g_variant_unref (ray_normalised);
+ g_variant_unref (ray_deserialised);
+ g_variant_unref (ray_constructed);
+ g_string_free (type_string, TRUE);
+}
+
/* Test that an empty object path is normalised successfully to the base object
* path, ‘/’. */
static void
@@ -5158,6 +5718,7 @@
g_test_add_func ("/gvariant/builder-memory", test_builder_memory);
g_test_add_func ("/gvariant/hashing", test_hashing);
g_test_add_func ("/gvariant/byteswap", test_gv_byteswap);
+ g_test_add_func ("/gvariant/byteswap/non-normal-non-aligned", test_gv_byteswap_non_normal_non_aligned);
g_test_add_func ("/gvariant/parser", test_parses);
g_test_add_func ("/gvariant/parser/integer-bounds", test_parser_integer_bounds);
g_test_add_func ("/gvariant/parser/recursion", test_parser_recursion);
@@ -5187,10 +5748,26 @@
g_test_add_func ("/gvariant/normal-checking/tuples",
test_normal_checking_tuples);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets/overlapped",
+ test_normal_checking_array_offsets_overlapped);
g_test_add_func ("/gvariant/normal-checking/array-offsets",
test_normal_checking_array_offsets);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets2",
+ test_normal_checking_array_offsets2);
+ g_test_add_func ("/gvariant/normal-checking/array-offsets/minimal-sized",
+ test_normal_checking_array_offsets_minimal_sized);
g_test_add_func ("/gvariant/normal-checking/tuple-offsets",
test_normal_checking_tuple_offsets);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets2",
+ test_normal_checking_tuple_offsets2);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets3",
+ test_normal_checking_tuple_offsets3);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets4",
+ test_normal_checking_tuple_offsets4);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets5",
+ test_normal_checking_tuple_offsets5);
+ g_test_add_func ("/gvariant/normal-checking/tuple-offsets/minimal-sized",
+ test_normal_checking_tuple_offsets_minimal_sized);
g_test_add_func ("/gvariant/normal-checking/empty-object-path",
test_normal_checking_empty_object_path);