Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Step:15-SP2
glib2
glib2-fix-normal-form-handling-in-gvariant.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File glib2-fix-normal-form-handling-in-gvariant.patch of Package glib2
diff --unified --recursive --text --new-file --color glib-2.62.6.old/glib/gvariant.c glib-2.62.6.new/glib/gvariant.c --- glib-2.62.6.old/glib/gvariant.c 2023-04-18 15:10:28.399376215 +0800 +++ glib-2.62.6.new/glib/gvariant.c 2023-04-18 15:11:49.029951224 +0800 @@ -2202,20 +2202,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: * @@ -2229,19 +2231,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"); @@ -2254,7 +2273,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; @@ -2296,7 +2315,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 = ""; @@ -2305,8 +2324,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; } @@ -2342,8 +2360,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; } @@ -5788,32 +5805,96 @@ /* Serialised 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)); @@ -5821,28 +5902,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)); @@ -5872,7 +5988,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 serialised @@ -5897,7 +6015,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); @@ -5917,6 +6035,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 @@ -5934,31 +6057,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); - 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.62.6.old/glib/gvariant-core.c glib-2.62.6.new/glib/gvariant-core.c --- glib-2.62.6.old/glib/gvariant-core.c 2020-03-18 21:16:11.000000000 +0800 +++ glib-2.62.6.new/glib/gvariant-core.c 2023-04-18 15:11:49.029951224 +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)) { @@ -589,6 +668,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; @@ -665,6 +747,21 @@ return (value->state & STATE_TRUSTED) != 0; } +/* < internal > + * g_variant_get_depth: + * @value: a #GVariant + * + * Gets the nesting depth of a #GVariant. This is 0 for a #GVariant with no + * children. + * + * Returns: nesting depth of @value + */ +gsize +g_variant_get_depth (GVariant *value) +{ + return value->depth; +} + /* -- public -- */ /** @@ -991,16 +1088,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; @@ -1040,11 +1129,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) @@ -1061,12 +1152,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; @@ -1075,6 +1161,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 @@ -1086,6 +1176,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); } @@ -1100,12 +1191,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 serialised data at @@ -1179,14 +1342,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.62.6.old/glib/gvariant-core.h glib-2.62.6.new/glib/gvariant-core.h --- glib-2.62.6.old/glib/gvariant-core.h 2020-03-18 21:16:11.000000000 +0800 +++ glib-2.62.6.new/glib/gvariant-core.h 2023-04-18 15:11:29.802390598 +0800 @@ -34,4 +34,9 @@ GVariantTypeInfo * g_variant_get_type_info (GVariant *value); +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.62.6.old/glib/gvariant-serialiser.c glib-2.62.6.new/glib/gvariant-serialiser.c --- glib-2.62.6.old/glib/gvariant-serialiser.c 2020-03-18 21:16:11.000000000 +0800 +++ glib-2.62.6.new/glib/gvariant-serialiser.c 2023-04-18 15:11:42.829665425 +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 serialised #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.62.6.old/glib/gvariant-serialiser.h glib-2.62.6.new/glib/gvariant-serialiser.h --- glib-2.62.6.old/glib/gvariant-serialiser.h 2020-03-18 21:16:11.000000000 +0800 +++ glib-2.62.6.new/glib/gvariant-serialiser.h 2023-04-18 15:11:29.802390598 +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; /* deserialisation */ diff --unified --recursive --text --new-file --color glib-2.62.6.old/glib/gvarianttypeinfo.c glib-2.62.6.new/glib/gvarianttypeinfo.c --- glib-2.62.6.old/glib/gvarianttypeinfo.c 2020-03-18 21:16:11.000000000 +0800 +++ glib-2.62.6.new/glib/gvarianttypeinfo.c 2023-04-18 15:11:49.029951224 +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.62.6.old/glib/tests/gvariant.c glib-2.62.6.new/glib/tests/gvariant.c --- glib-2.62.6.old/glib/tests/gvariant.c 2020-03-18 21:16:11.000000000 +0800 +++ glib-2.62.6.new/glib/tests/gvariant.c 2023-04-18 15:11:42.829665425 +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 @@ -1231,6 +1233,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); @@ -1283,6 +1286,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); @@ -1442,21 +1447,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); } } @@ -1566,12 +1576,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); @@ -1587,7 +1599,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); } @@ -1730,12 +1743,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); @@ -1751,7 +1766,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); } @@ -1825,13 +1841,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); @@ -2272,24 +2290,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 @@ -2346,7 +2407,7 @@ static void test_fuzz (gdouble *fuzziness) { - GVariantSerialised serialised; + GVariantSerialised serialised = { 0, }; TreeInstance *tree; /* make an instance */ @@ -3823,6 +3884,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; @@ -5015,6 +5099,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 @@ -5039,6 +5155,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 @@ -5063,6 +5300,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 @@ -5175,6 +5735,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); @@ -5204,10 +5765,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);
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor