diff --unified --recursive --text --new-file --color glib-2.48.2.old/glib/gvariant.c glib-2.48.2.new/glib/gvariant.c
--- glib-2.48.2.old/glib/gvariant.c	2023-03-28 11:02:00.981989398 +0800
+++ glib-2.48.2.new/glib/gvariant.c	2023-03-29 15:29:34.379681950 +0800
@@ -308,6 +308,11 @@
  * Constructs a new trusted #GVariant instance from the provided data.
  * This is used to implement g_variant_new_* for all the basic types.
  *
+ * Note: @data must be backed by memory that is aligned appropriately for the
+ * @type being loaded. Otherwise this function will internally create a copy of
+ * the memory (since GLib 2.60) or (in older versions) fail and exit the
+ * process.
+ *
  * Returns: a new floating #GVariant
  */
 static GVariant *
@@ -5682,32 +5687,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));
 
@@ -5715,28 +5784,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));
@@ -5766,7 +5870,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
@@ -5784,7 +5890,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);
@@ -5804,6 +5910,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
@@ -5821,31 +5932,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);
 
-  return g_variant_ref_sink (new);
+  g_assert (g_variant_is_trusted (new));
+
+  return g_steal_pointer (&new);
 }
 
 /**
@@ -5882,6 +6000,11 @@
  * needed.  The exact time of this call is unspecified and might even be
  * before this function returns.
  *
+ * Note: @data must be backed by memory that is aligned appropriately for the
+ * @type being loaded. Otherwise this function will internally create a copy of
+ * the memory (since GLib 2.60) or (in older versions) fail and exit the
+ * process.
+ *
  * Returns: (transfer none): a new floating #GVariant of type @type
  *
  * Since: 2.24
diff --unified --recursive --text --new-file --color glib-2.48.2.old/glib/gvariant-core.c glib-2.48.2.new/glib/gvariant-core.c
--- glib-2.48.2.old/glib/gvariant-core.c	2023-03-28 10:53:40.922086481 +0800
+++ glib-2.48.2.new/glib/gvariant-core.c	2023-03-29 17:27:50.423819486 +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
@@ -22,6 +23,7 @@
 
 #include <glib/gvariant-internal.h>
 #include <glib/gvariant-serialiser.h>
+#include "gvariantprivate.h"
 #include <glib/gtestutils.h>
 #include <glib/gbitlock.h>
 #include <glib/gatomic.h>
@@ -29,6 +31,7 @@
 #include <glib/gslice.h>
 #include <glib/gmem.h>
 #include <string.h>
+#include <stdlib.h>
 
 
 /*
@@ -64,6 +67,8 @@
     {
       GBytes *bytes;
       gconstpointer data;
+      gsize ordered_offsets_up_to;
+      gsize checked_offsets_up_to;
     } serialised;
 
     struct
@@ -161,6 +166,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
@@ -349,6 +390,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
@@ -374,6 +438,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;
@@ -417,6 +483,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.
@@ -459,6 +536,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;
     }
 }
@@ -506,6 +585,10 @@
  *
  * A reference is taken on @bytes.
  *
+ * The data in @bytes must be aligned appropriately for the @type being loaded.
+ * Otherwise this function will internally create a copy of the memory (since
+ * GLib 2.60) or (in older versions) fail and exit the process.
+ *
  * Returns: (transfer none): a new #GVariant with a floating reference
  *
  * Since: 2.36
@@ -518,14 +601,57 @@
   GVariant *value;
   guint alignment;
   gsize size;
+  GBytes *owned_bytes = NULL;
+  GVariantSerialised serialised;
 
   value = g_variant_alloc (type, TRUE, trusted);
 
-  value->contents.serialised.bytes = g_bytes_ref (bytes);
-
   g_variant_type_info_query (value->type_info,
                              &alignment, &size);
 
+  /* Ensure the alignment is correct. This is a huge performance hit if it’s
+   * not correct, but that’s better than aborting if a caller provides data
+   * with the wrong alignment (which is likely to happen very occasionally, and
+   * only cause an abort on some architectures — so is unlikely to be caught
+   * in testing). Callers can always actively ensure they use the correct
+   * alignment to avoid the performance hit. */
+  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))
+    {
+#ifdef HAVE_POSIX_MEMALIGN
+      gpointer aligned_data = NULL;
+      gsize aligned_size = g_bytes_get_size (bytes);
+
+      /* posix_memalign() requires the alignment to be a multiple of
+       * sizeof(void*), and a power of 2. See g_variant_type_info_query() for
+       * details on the alignment format. */
+      if (posix_memalign (&aligned_data, MAX (sizeof (void *), alignment + 1),
+                          aligned_size) != 0)
+        g_error ("posix_memalign failed");
+
+      memcpy (aligned_data, g_bytes_get_data (bytes, NULL), aligned_size);
+
+      bytes = owned_bytes = g_bytes_new_with_free_func (aligned_data,
+                                                        aligned_size,
+                                                        free, aligned_data);
+      aligned_data = NULL;
+#else
+      /* NOTE: there may be platforms that lack posix_memalign() and also
+       * have malloc() that returns non-8-aligned.  if so, we need to try
+       * harder here.
+       */
+      bytes = owned_bytes = g_bytes_new (g_bytes_get_data (bytes, NULL),
+                                         g_bytes_get_size (bytes));
+#endif
+    }
+
+  value->contents.serialised.bytes = g_bytes_ref (bytes);
+
   if (size && g_bytes_get_size (bytes) != size)
     {
       /* Creating a fixed-sized GVariant with a bytes of the wrong
@@ -543,6 +669,11 @@
       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;
 }
 
@@ -617,6 +748,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 -- */
 
 /**
@@ -939,16 +1085,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;
 
@@ -1009,12 +1147,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;
 
@@ -1023,6 +1156,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
@@ -1034,6 +1171,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);
       }
 
@@ -1048,12 +1186,82 @@
     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 = s_child.ordered_offsets_up_to;
+    child->contents.serialised.checked_offsets_up_to = 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 (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_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
@@ -1127,14 +1335,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.48.2.old/glib/gvariant-core.h glib-2.48.2.new/glib/gvariant-core.h
--- glib-2.48.2.old/glib/gvariant-core.h	2016-08-17 23:20:47.000000000 +0800
+++ glib-2.48.2.new/glib/gvariant-core.h	2023-03-29 16:44:15.856443641 +0800
@@ -34,4 +34,9 @@
 
 GVariantTypeInfo *      g_variant_get_type_info                         (GVariant            *value);
 
+GVariant *              g_variant_maybe_get_child_value                 (GVariant            *value,
+                                                                         gsize                index_);
+
+gsize                   g_variant_get_depth                             (GVariant            *value);
+
 #endif /* __G_VARIANT_CORE_H__ */
diff --unified --recursive --text --new-file --color glib-2.48.2.old/glib/gvariantprivate.h glib-2.48.2.new/glib/gvariantprivate.h
--- glib-2.48.2.old/glib/gvariantprivate.h	1970-01-01 08:00:00.000000000 +0800
+++ glib-2.48.2.new/glib/gvariantprivate.h	2023-03-29 16:22:26.287681505 +0800
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2023 SUSE, LLC
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __G_VARIANTPRIVATE_H__
+#define __G_VARIANTPRIVATE_H__
+
+#include "gvariant-serialiser.h"
+
+/*
+ * This becomes a public API since GLib 2.60, but we are backporting, so make it
+ * project-private.
+ */
+gboolean                        g_variant_serialised_check              (GVariantSerialised        serialised);
+
+#endif
diff --unified --recursive --text --new-file --color glib-2.48.2.old/glib/gvariant-serialiser.c glib-2.48.2.new/glib/gvariant-serialiser.c
--- glib-2.48.2.old/glib/gvariant-serialiser.c	2023-03-28 10:53:40.932086357 +0800
+++ glib-2.48.2.new/glib/gvariant-serialiser.c	2023-03-29 16:20:54.587594523 +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
@@ -22,6 +23,7 @@
 #include "config.h"
 
 #include "gvariant-serialiser.h"
+#include "gvariantprivate.h"
 
 #include <glib/gvariant-internal.h>
 #include <glib/gtestutils.h>
@@ -119,6 +121,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 >
@@ -127,20 +131,27 @@
  *
  * Checks @serialised for validity according to the invariants described
  * above.
+ *
+ * Returns: %TRUE if @serialised is valid; %FALSE otherwise
  */
-static void
+gboolean
 g_variant_serialised_check (GVariantSerialised serialised)
 {
   gsize fixed_size;
   guint alignment;
 
-  g_assert (serialised.type_info != NULL);
+  if (serialised.type_info == NULL)
+    return FALSE;
   g_variant_type_info_query (serialised.type_info, &alignment, &fixed_size);
 
-  if (fixed_size)
-    g_assert_cmpint (serialised.size, ==, fixed_size);
-  else
-    g_assert (serialised.size == 0 || serialised.data != NULL);
+  if (fixed_size != 0 && serialised.size != fixed_size)
+    return FALSE;
+  else if (fixed_size == 0 &&
+           !(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.
@@ -167,10 +178,8 @@
    * Check if this is a small allocation and return without enforcing
    * the alignment assertion if this is the case.
    */
-  if (serialised.size <= alignment)
-    return;
-
-  g_assert_cmpint (alignment & (gsize) serialised.data, ==, 0);
+  return (serialised.size <= alignment ||
+          (alignment & (gsize) serialised.data) == 0);
 }
 
 /* < private >
@@ -262,6 +271,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;
 }
@@ -293,7 +304,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]);
     }
@@ -315,6 +326,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);
     }
@@ -356,6 +369,8 @@
     value.data = NULL;
 
   value.depth++;
+  value.ordered_offsets_up_to = 0;
+  value.checked_offsets_up_to = 0;
 
   return value;
 }
@@ -386,7 +401,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]);
@@ -406,6 +421,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);
 }
@@ -631,39 +648,102 @@
   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 */
 
-  return offsets_array_size / offset_size;
+  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 */
+
+  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) \
+  static gsize \
+  find_unordered_##type (const guint8 *data, gsize start, gsize len) \
+  { \
+    gsize off; \
+    type current, previous; \
+    \
+    memcpy (&previous, data + start * sizeof (current), sizeof (current)); \
+    for (off = (start + 1) * sizeof (current); off < len * sizeof (current); off += sizeof (current)) \
+      { \
+        memcpy (&current, data + off, sizeof (current)); \
+        if (current < previous) \
+          break; \
+        previous = current; \
+      } \
+    return off / sizeof (current) - 1; \
+  }
+
+DEFINE_FIND_UNORDERED (guint8);
+DEFINE_FIND_UNORDERED (guint16);
+DEFINE_FIND_UNORDERED (guint32);
+DEFINE_FIND_UNORDERED (guint64);
+
 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;
 
@@ -671,18 +751,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;
@@ -690,11 +813,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;
@@ -766,34 +887,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);
-
-  if (last_end > value.size)
-    return FALSE;
+  struct Offsets offsets = gvs_variable_sized_array_get_frame_offsets (value);
 
-  offsets_array_size = value.size - last_end;
-
-  if (offsets_array_size % offset_size)
+  if (!offsets.is_normal)
     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);
@@ -801,14 +904,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)
@@ -830,7 +933,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;
 }
@@ -857,6 +964,55 @@
  * 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)
+    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)
+    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 /* G_VARIANT_MEMBER_ENDING_OFFSET */
+    member_end = gvs_read_unaligned_le (value.data + value.size -
+                                        offset_size * (member_info->i + 2),
+                                        offset_size);
+
+  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)
 {
@@ -877,14 +1033,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.
        */
@@ -894,63 +1054,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)
     {
@@ -1051,6 +1206,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)
@@ -1064,7 +1220,7 @@
   for (i = 0; i < length; i++)
     {
       const GVariantMemberInfo *member_info;
-      GVariantSerialised child;
+      GVariantSerialised child = { 0, };
       gsize fixed_size;
       guint alignment;
       gsize end;
@@ -1124,6 +1280,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;
@@ -1151,7 +1311,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
@@ -1355,7 +1527,7 @@
 gsize
 g_variant_serialised_n_children (GVariantSerialised serialised)
 {
-  g_variant_serialised_check (serialised);
+  g_assert (g_variant_serialised_check (serialised));
 
   DISPATCH_CASES (serialised.type_info,
 
@@ -1392,7 +1564,7 @@
 {
   GVariantSerialised child;
 
-  g_variant_serialised_check (serialised);
+  g_assert (g_variant_serialised_check (serialised));
 
   if G_LIKELY (index_ < g_variant_serialised_n_children (serialised))
     {
@@ -1400,7 +1572,7 @@
 
                       child = gvs_/**/,/**/_get_child (serialised, index_);
                       g_assert (child.size || child.data == NULL);
-                      g_variant_serialised_check (child);
+                      g_assert (g_variant_serialised_check (child));
                       return child;
 
                      )
@@ -1441,7 +1613,7 @@
                                 const gpointer           *children,
                                 gsize                     n_children)
 {
-  g_variant_serialised_check (serialised);
+  g_assert (g_variant_serialised_check (serialised));
 
   DISPATCH_CASES (serialised.type_info,
 
@@ -1496,7 +1668,7 @@
   gsize fixed_size;
   guint alignment;
 
-  g_variant_serialised_check (serialised);
+  g_assert (g_variant_serialised_check (serialised));
 
   if (!serialised.data)
     return;
diff --unified --recursive --text --new-file --color glib-2.48.2.old/glib/gvariant-serialiser.h glib-2.48.2.new/glib/gvariant-serialiser.h
--- glib-2.48.2.old/glib/gvariant-serialiser.h	2023-03-28 10:53:40.922086481 +0800
+++ glib-2.48.2.new/glib/gvariant-serialiser.h	2023-03-29 16:09:13.560288264 +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.48.2.old/glib/Makefile.am glib-2.48.2.new/glib/Makefile.am
--- glib-2.48.2.old/glib/Makefile.am	2023-03-28 11:02:00.985322809 +0800
+++ glib-2.48.2.new/glib/Makefile.am	2023-03-29 16:30:03.414787019 +0800
@@ -191,6 +191,7 @@
 	gutils.c		\
 	gvariant.h		\
 	gvariant.c		\
+	gvariantprivate.h       \
 	gvariant-core.h		\
 	gvariant-core.c		\
 	gvariant-internal.h	\
diff --unified --recursive --text --new-file --color glib-2.48.2.old/glib/Makefile.in glib-2.48.2.new/glib/Makefile.in
--- glib-2.48.2.old/glib/Makefile.in	2023-03-28 11:02:00.985322809 +0800
+++ glib-2.48.2.new/glib/Makefile.in	2023-03-29 16:23:23.024402240 +0800
@@ -253,8 +253,8 @@
 	gtranslit.c gtranslit-data.h gtrashstack.c gtree.c guniprop.c \
 	gutf8.c gunibreak.h gunibreak.c gunichartables.h gunicollate.c \
 	gunicomp.h gunidecomp.h gunidecomp.c gunicodeprivate.h \
-	gurifuncs.c gutils.c gvariant.h gvariant.c gvariant-core.h \
-	gvariant-core.c gvariant-internal.h gvariant-parser.c \
+	gurifuncs.c gutils.c gvariant.h gvariant.c gvariantprivate.h \
+	gvariant-core.h gvariant-core.c gvariant-internal.h gvariant-parser.c \
 	gvariant-serialiser.h gvariant-serialiser.c gvarianttypeinfo.h \
 	gvarianttypeinfo.c gvarianttype.c gversion.c gwakeup.h \
 	gwakeup.c gprintf.c gprintfint.h valgrind.h glib-unix.c \
diff --unified --recursive --text --new-file --color glib-2.48.2.old/glib/tests/gvariant.c glib-2.48.2.new/glib/tests/gvariant.c
--- glib-2.48.2.old/glib/tests/gvariant.c	2023-03-28 10:53:40.928753065 +0800
+++ glib-2.48.2.new/glib/tests/gvariant.c	2023-03-29 15:32:00.163112135 +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
@@ -1230,6 +1232,7 @@
   GRand *rand;
   gint i;
 
+  g_assert_true (size == 0 || buffer != NULL);
   g_assert_cmpint ((gsize) buffer & ALIGN_BITS & instance->alignment, ==, 0);
   g_assert_cmpint (size, ==, instance->size);
 
@@ -1282,6 +1285,8 @@
     serialised->size = instance->size;
 
   serialised->depth = 0;
+  serialised->ordered_offsets_up_to = 0;
+  serialised->checked_offsets_up_to = 0;
 
   g_assert (serialised->type_info == instance->type_info);
   g_assert (serialised->size == instance->size);
@@ -1441,20 +1446,23 @@
 
     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 (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);
       }
@@ -1565,12 +1573,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);
@@ -1586,7 +1596,8 @@
 
             child = g_variant_serialised_get_child (serialised, i);
             g_assert (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);
           }
 
@@ -1729,12 +1740,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);
@@ -1750,7 +1763,8 @@
 
             child = g_variant_serialised_get_child (serialised, i);
             g_assert (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);
           }
 
@@ -1824,13 +1838,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);
@@ -2271,24 +2287,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
@@ -2305,7 +2364,7 @@
 static void
 test_fuzz (gdouble *fuzziness)
 {
-  GVariantSerialised serialised;
+  GVariantSerialised serialised = { 0, };
   TreeInstance *tree;
 
   /* make an instance */
@@ -3707,6 +3766,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;
@@ -4617,6 +4699,38 @@
   g_variant_unref (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
@@ -4641,6 +4755,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
@@ -4665,6 +4900,268 @@
   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);
+}
+
+/* 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
@@ -4689,6 +5186,51 @@
   g_variant_unref (variant);
 }
 
+/* Test that constructing a #GVariant from data which is not correctly aligned
+ * for the variant type is OK, by loading a variant from data at various offsets
+ * which are aligned and unaligned. When unaligned, a slow construction path
+ * should be taken. */
+static void
+test_unaligned_construction (void)
+{
+  const guint8 data[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+  };
+  GVariant *variant = NULL;
+  GVariant *normal_variant = NULL;
+  gsize i, offset;
+  const struct {
+    const GVariantType *type;
+    gsize size;
+    gsize max_offset;
+  } vectors[] = {
+    { G_VARIANT_TYPE_UINT64, sizeof (guint64), sizeof (guint64) },
+    { G_VARIANT_TYPE_UINT32, sizeof (guint32), sizeof (guint32) },
+    { G_VARIANT_TYPE_UINT16, sizeof (guint16), sizeof (guint16) },
+    { G_VARIANT_TYPE_BYTE, sizeof (guint8), 3 },
+  };
+
+  G_STATIC_ASSERT (sizeof (guint64) * 2 <= sizeof (data));
+
+  for (i = 0; i < G_N_ELEMENTS (vectors); i++)
+    {
+      for (offset = 0; offset < vectors[i].max_offset; offset++)
+        {
+          variant = g_variant_new_from_data (vectors[i].type, data + offset,
+                                             vectors[i].size,
+                                             FALSE, NULL, NULL);
+          g_assert_nonnull (variant);
+
+          normal_variant = g_variant_get_normal_form (variant);
+          g_assert_nonnull (normal_variant);
+
+          g_variant_unref (normal_variant);
+          g_variant_unref (variant);
+        }
+    }
+}
+
 int
 main (int argc, char **argv)
 {
@@ -4730,6 +5272,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/parse-failures", test_parse_failures);
   g_test_add_func ("/gvariant/parse-positional", test_parse_positional);
@@ -4754,10 +5297,24 @@
 
   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-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);
 
@@ -4766,5 +5323,8 @@
   g_test_add_func ("/gvariant/recursion-limits/array-in-variant",
                    test_recursion_limits_array_in_variant);
 
+  g_test_add_func ("/gvariant/unaligned-construction",
+                   test_unaligned_construction);
+
   return g_test_run ();
 }
