From 50588093eed6506f162c10dd9786002b5a790fca Mon Sep 17 00:00:00 2001
From: Philip Withnall <withnall@endlessm.com>
Date: Fri, 7 Sep 2018 20:27:39 +0100
Subject: [PATCH 02/15] gvarianttype: Impose a recursion limit of 64 on variant
 types
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Previously, GVariant has allowed ‘arbitrary’ recursion on GVariantTypes,
but this isn’t really feasible. We have to deal with GVariants from
untrusted sources, and the nature of GVariantType means that another
level of recursion (and hence, for example, another stack frame in your
application) can be added with a single byte in a variant type signature
in the input. This gives malicious input sources far too much leverage
to cause deep stack recursion or massive memory allocations which can
DoS an application.

Limit recursion to 64 levels (which should be more than enough for
anyone™), document it and add a test. This is, handily, also the limit
applied by the D-Bus specification (§(Valid Signatures)).

oss-fuzz#9857

Signed-off-by: Philip Withnall <withnall@endlessm.com>
---
 docs/reference/glib/glib-sections.txt |   1 +
 glib/gvariant-core.c                  |  46 +++++++++++-
 glib/gvariant-internal.h              |  18 +++++
 glib/gvariant-serialiser.c            |  38 ++++++++--
 glib/gvariant-serialiser.h            |   1 +
 glib/gvarianttype.c                   | 137 +++++++++++++++++++++++++---------
 glib/gvarianttype.h                   |   2 +
 glib/gvarianttypeinfo.c               |  25 +++++++
 glib/gvarianttypeinfo.h               |   2 +
 glib/tests/gvariant.c                 |  69 +++++++++++++++++
 10 files changed, 295 insertions(+), 44 deletions(-)

diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 5f049cfcb..f88d6b53b 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -3431,6 +3431,7 @@ g_variant_parse_error_print_context
 g_variant_parse_error_quark
 g_variant_parser_get_error_quark
 g_variant_type_checked_
+g_variant_type_string_get_depth_
 </SECTION>
 
 
diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
index bdf09696f..815bdf9e0 100644
--- a/glib/gvariant-core.c
+++ b/glib/gvariant-core.c
@@ -20,6 +20,7 @@
 
 #include <glib/gvariant-core.h>
 
+#include <glib/gvariant-internal.h>
 #include <glib/gvariant-serialiser.h>
 #include <glib/gtestutils.h>
 #include <glib/gbitlock.h>
@@ -74,6 +75,7 @@ struct _GVariant
 
   gint state;
   gint ref_count;
+  gsize depth;
 };
 
 /* struct GVariant:
@@ -202,6 +204,11 @@ struct _GVariant
  *                    reference.  See g_variant_ref_sink().
  *
  * ref_count: the reference count of the instance
+ *
+ * depth: the depth of the GVariant in a hierarchy of nested containers,
+ *        increasing with the level of nesting. The top-most GVariant has depth
+ *        zero.  This is used to avoid recursing too deeply and overflowing the
+ *        stack when handling deeply nested untrusted serialised GVariants.
  */
 #define STATE_LOCKED     1
 #define STATE_SERIALISED 2
@@ -366,6 +373,7 @@ g_variant_serialise (GVariant *value,
   serialised.type_info = value->type_info;
   serialised.size = value->size;
   serialised.data = data;
+  serialised.depth = value->depth;
 
   children = (gpointer *) value->contents.tree.children;
   n_children = value->contents.tree.n_children;
@@ -407,6 +415,7 @@ g_variant_fill_gvs (GVariantSerialised *serialised,
   if (serialised->size == 0)
     serialised->size = value->size;
   g_assert (serialised->size == value->size);
+  serialised->depth = value->depth;
 
   if (serialised->data)
     /* g_variant_store() is a public API, so it
@@ -480,6 +489,7 @@ g_variant_alloc (const GVariantType *type,
                  STATE_FLOATING;
   value->size = (gssize) -1;
   value->ref_count = 1;
+  value->depth = 0;
 
   return value;
 }
@@ -933,7 +943,8 @@ g_variant_n_children (GVariant *value)
       GVariantSerialised serialised = {
         value->type_info,
         (gpointer) value->contents.serialised.data,
-        value->size
+        value->size,
+        value->depth,
       };
 
       n_children = g_variant_serialised_n_children (serialised);
@@ -962,6 +973,11 @@ g_variant_n_children (GVariant *value)
  * The returned value is never floating.  You should free it with
  * g_variant_unref() when you're done with it.
  *
+ * There may be implementation specific restrictions on deeply nested values,
+ * which would result in the unit tuple being returned as the child value,
+ * instead of further nested children. #GVariant is guaranteed to handle
+ * nesting up to at least 64 levels.
+ *
  * This function is O(1).
  *
  * Returns: (transfer full): the child at the specified index
@@ -973,6 +989,7 @@ g_variant_get_child_value (GVariant *value,
                            gsize     index_)
 {
   g_return_val_if_fail (index_ < g_variant_n_children (value), NULL);
+  g_return_val_if_fail (value->depth < G_MAXSIZE, NULL);
 
   if (~g_atomic_int_get (&value->state) & STATE_SERIALISED)
     {
@@ -995,7 +1012,8 @@ g_variant_get_child_value (GVariant *value,
     GVariantSerialised serialised = {
       value->type_info,
       (gpointer) value->contents.serialised.data,
-      value->size
+      value->size,
+      value->depth,
     };
     GVariantSerialised s_child;
     GVariant *child;
@@ -1005,6 +1023,20 @@ g_variant_get_child_value (GVariant *value,
      */
     s_child = g_variant_serialised_get_child (serialised, index_);
 
+    /* Check whether this would cause nesting too deep. If so, return a fake
+     * child. The only situation we expect this to happen in is with a variant,
+     * as all other deeply-nested types have a static type, and hence should
+     * have been rejected earlier. In the case of a variant whose nesting plus
+     * the depth of its child is too great, return a unit variant () instead of
+     * the real child. */
+    if (!(value->state & STATE_TRUSTED) &&
+        g_variant_type_info_query_depth (s_child.type_info) >=
+        G_VARIANT_MAX_RECURSION_DEPTH - value->depth)
+      {
+        g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_VARIANT));
+        return g_variant_new_tuple (NULL, 0);
+      }
+
     /* create a new serialised instance out of it */
     child = g_slice_new (GVariant);
     child->type_info = s_child.type_info;
@@ -1012,6 +1044,7 @@ g_variant_get_child_value (GVariant *value,
                    STATE_SERIALISED;
     child->size = s_child.size;
     child->ref_count = 1;
+    child->depth = value->depth + 1;
     child->contents.serialised.bytes =
       g_bytes_ref (value->contents.serialised.bytes);
     child->contents.serialised.data = s_child.data;
@@ -1074,6 +1107,9 @@ g_variant_store (GVariant *value,
  * being trusted.  If the value was already marked as being trusted then
  * this function will immediately return %TRUE.
  *
+ * There may be implementation specific restrictions on deeply nested values.
+ * GVariant is guaranteed to handle nesting up to at least 64 levels.
+ *
  * Returns: %TRUE if @value is in normal form
  *
  * Since: 2.24
@@ -1086,12 +1122,16 @@ g_variant_is_normal_form (GVariant *value)
 
   g_variant_lock (value);
 
+  if (value->depth >= G_VARIANT_MAX_RECURSION_DEPTH)
+    return FALSE;
+
   if (value->state & STATE_SERIALISED)
     {
       GVariantSerialised serialised = {
         value->type_info,
         (gpointer) value->contents.serialised.data,
-        value->size
+        value->size,
+        value->depth
       };
 
       if (g_variant_serialised_is_normal (serialised))
diff --git a/glib/gvariant-internal.h b/glib/gvariant-internal.h
index 600be1c1a..f7536da71 100644
--- a/glib/gvariant-internal.h
+++ b/glib/gvariant-internal.h
@@ -47,4 +47,22 @@ GVariantType *                  g_variant_format_string_scan_type       (const g
                                                                          const gchar          *limit,
                                                                          const gchar         **endptr);
 
+/* The maximum number of levels of nested container which this implementation
+ * of #GVariant will handle.
+ *
+ * The limit must be at least 64 + 1, to allow D-Bus messages to be wrapped in
+ * a top-level #GVariant. This comes from the D-Bus specification (§(Valid
+ * Signatures)), but also seems generally reasonable. #GDBusMessage wraps its
+ * payload in a top-level tuple.
+ *
+ * The limit is actually set to be a lot greater than 64, to allow much greater
+ * nesting of values off D-Bus. It cannot be set over around 80000, or the risk
+ * of overflowing the stack when parsing type strings becomes too great.
+ *
+ * Aside from those constraints, the choice of this value is arbitrary. The
+ * only restrictions on it from the API are that it has to be greater than 64
+ * (due to D-Bus).
+*/
+#define G_VARIANT_MAX_RECURSION_DEPTH ((gsize) 128)
+
 #endif /* __G_VARIANT_INTERNAL_H__ */
diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
index 96df54e23..fe0bcf0aa 100644
--- a/glib/gvariant-serialiser.c
+++ b/glib/gvariant-serialiser.c
@@ -23,6 +23,7 @@
 
 #include "gvariant-serialiser.h"
 
+#include <glib/gvariant-internal.h>
 #include <glib/gtestutils.h>
 #include <glib/gstrfuncs.h>
 #include <glib/gtypes.h>
@@ -81,7 +82,9 @@
  * values is permitted (eg: 0 to 255 is a valid byte).  Special checks
  * need to be performed for booleans (only 0 or 1 allowed), strings
  * (properly nul-terminated) and object paths and signature strings
- * (meeting the D-Bus specification requirements).
+ * (meeting the D-Bus specification requirements).  Depth checks need to be
+ * performed for nested types (arrays, tuples, and variants), to avoid massive
+ * recursion which could exhaust our stack when handling untrusted input.
  */
 
 /* < private >
@@ -113,6 +116,9 @@
  * fixed-sized type, yet @size must be non-zero.  The effect of this
  * combination should be as if @data were a pointer to an
  * appropriately-sized zero-filled region.
+ *
+ * @depth has no restrictions; the depth of a top-level serialised #GVariant is
+ * zero, and it increases for each level of nested child.
  */
 
 /* < private >
@@ -255,6 +261,7 @@ gvs_fixed_sized_maybe_get_child (GVariantSerialised value,
    */
   value.type_info = g_variant_type_info_element (value.type_info);
   g_variant_type_info_ref (value.type_info);
+  value.depth++;
 
   return value;
 }
@@ -286,7 +293,7 @@ gvs_fixed_sized_maybe_serialise (GVariantSerialised        value,
 {
   if (n_children)
     {
-      GVariantSerialised child = { NULL, value.data, value.size };
+      GVariantSerialised child = { NULL, value.data, value.size, value.depth + 1 };
 
       gvs_filler (&child, children[0]);
     }
@@ -307,6 +314,7 @@ gvs_fixed_sized_maybe_is_normal (GVariantSerialised value)
 
       /* proper element size: "Just".  recurse to the child. */
       value.type_info = g_variant_type_info_element (value.type_info);
+      value.depth++;
 
       return g_variant_serialised_is_normal (value);
     }
@@ -347,6 +355,8 @@ gvs_variable_sized_maybe_get_child (GVariantSerialised value,
   if (value.size == 0)
     value.data = NULL;
 
+  value.depth++;
+
   return value;
 }
 
@@ -376,7 +386,7 @@ gvs_variable_sized_maybe_serialise (GVariantSerialised        value,
 {
   if (n_children)
     {
-      GVariantSerialised child = { NULL, value.data, value.size - 1 };
+      GVariantSerialised child = { NULL, value.data, value.size - 1, value.depth + 1 };
 
       /* write the data for the child.  */
       gvs_filler (&child, children[0]);
@@ -395,6 +405,7 @@ gvs_variable_sized_maybe_is_normal (GVariantSerialised value)
 
   value.type_info = g_variant_type_info_element (value.type_info);
   value.size--;
+  value.depth++;
 
   return g_variant_serialised_is_normal (value);
 }
@@ -445,6 +456,7 @@ gvs_fixed_sized_array_get_child (GVariantSerialised value,
   g_variant_type_info_query (child.type_info, NULL, &child.size);
   child.data = value.data + (child.size * index_);
   g_variant_type_info_ref (child.type_info);
+  child.depth = value.depth + 1;
 
   return child;
 }
@@ -474,6 +486,7 @@ gvs_fixed_sized_array_serialise (GVariantSerialised        value,
   child.type_info = g_variant_type_info_element (value.type_info);
   g_variant_type_info_query (child.type_info, NULL, &child.size);
   child.data = value.data;
+  child.depth = value.depth + 1;
 
   for (i = 0; i < n_children; i++)
     {
@@ -489,6 +502,7 @@ gvs_fixed_sized_array_is_normal (GVariantSerialised value)
 
   child.type_info = g_variant_type_info_element (value.type_info);
   g_variant_type_info_query (child.type_info, NULL, &child.size);
+  child.depth = value.depth + 1;
 
   if (value.size % child.size != 0)
     return FALSE;
@@ -655,6 +669,7 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
 
   child.type_info = g_variant_type_info_element (value.type_info);
   g_variant_type_info_ref (child.type_info);
+  child.depth = value.depth + 1;
 
   offset_size = gvs_get_offset_size (value.size);
 
@@ -783,6 +798,7 @@ gvs_variable_sized_array_is_normal (GVariantSerialised value)
 
   child.type_info = g_variant_type_info_element (value.type_info);
   g_variant_type_info_query (child.type_info, &alignment, NULL);
+  child.depth = value.depth + 1;
   offset = 0;
 
   for (i = 0; i < length; i++)
@@ -858,6 +874,7 @@ gvs_tuple_get_child (GVariantSerialised value,
 
   member_info = g_variant_type_info_member_info (value.type_info, index_);
   child.type_info = g_variant_type_info_ref (member_info->type_info);
+  child.depth = value.depth + 1;
   offset_size = gvs_get_offset_size (value.size);
 
   /* tuples are the only (potentially) fixed-sized containers, so the
@@ -1042,6 +1059,7 @@ gvs_tuple_is_normal (GVariantSerialised value)
 
       member_info = g_variant_type_info_member_info (value.type_info, i);
       child.type_info = member_info->type_info;
+      child.depth = value.depth + 1;
 
       g_variant_type_info_query (child.type_info, &alignment, &fixed_size);
 
@@ -1171,8 +1189,10 @@ gvs_variant_get_child (GVariantSerialised value,
               if (g_variant_type_is_definite (type))
                 {
                   gsize fixed_size;
+                  gsize child_type_depth;
 
                   child.type_info = g_variant_type_info_get (type);
+                  child.depth = value.depth + 1;
 
                   if (child.size != 0)
                     /* only set to non-%NULL if size > 0 */
@@ -1180,8 +1200,10 @@ gvs_variant_get_child (GVariantSerialised value,
 
                   g_variant_type_info_query (child.type_info,
                                              NULL, &fixed_size);
+                  child_type_depth = g_variant_type_info_query_depth (child.type_info);
 
-                  if (!fixed_size || fixed_size == child.size)
+                  if ((!fixed_size || fixed_size == child.size) &&
+                      value.depth < G_VARIANT_MAX_RECURSION_DEPTH - child_type_depth)
                     return child;
 
                   g_variant_type_info_unref (child.type_info);
@@ -1193,6 +1215,7 @@ gvs_variant_get_child (GVariantSerialised value,
   child.type_info = g_variant_type_info_get (G_VARIANT_TYPE_UNIT);
   child.data = NULL;
   child.size = 1;
+  child.depth = value.depth + 1;
 
   return child;
 }
@@ -1234,10 +1257,13 @@ gvs_variant_is_normal (GVariantSerialised value)
 {
   GVariantSerialised child;
   gboolean normal;
+  gsize child_type_depth;
 
   child = gvs_variant_get_child (value, 0);
+  child_type_depth = g_variant_type_info_query_depth (child.type_info);
 
-  normal = (child.data != NULL || child.size == 0) &&
+  normal = (value.depth < G_VARIANT_MAX_RECURSION_DEPTH - child_type_depth) &&
+           (child.data != NULL || child.size == 0) &&
            g_variant_serialised_is_normal (child);
 
   g_variant_type_info_unref (child.type_info);
@@ -1555,6 +1581,8 @@ g_variant_serialised_is_normal (GVariantSerialised serialised)
 
   if (serialised.data == NULL)
     return FALSE;
+  if (serialised.depth >= G_VARIANT_MAX_RECURSION_DEPTH)
+    return FALSE;
 
   /* some hard-coded terminal cases */
   switch (g_variant_type_info_get_type_char (serialised.type_info))
diff --git a/glib/gvariant-serialiser.h b/glib/gvariant-serialiser.h
index c49708662..fa4252d36 100644
--- a/glib/gvariant-serialiser.h
+++ b/glib/gvariant-serialiser.h
@@ -28,6 +28,7 @@ typedef struct
   GVariantTypeInfo *type_info;
   guchar           *data;
   gsize             size;
+  gsize             depth;  /* same semantics as GVariant.depth */
 } GVariantSerialised;
 
 /* deserialisation */
diff --git a/glib/gvarianttype.c b/glib/gvarianttype.c
index f473ad008..b8c6cc195 100644
--- a/glib/gvarianttype.c
+++ b/glib/gvarianttype.c
@@ -24,6 +24,7 @@
 
 #include <glib/gtestutils.h>
 #include <glib/gstrfuncs.h>
+#include <glib/gvariant-internal.h>
 
 #include <string.h>
 
@@ -114,7 +115,10 @@
  *
  * The above definition is recursive to arbitrary depth. "aaaaai" and
  * "(ui(nq((y)))s)" are both valid type strings, as is
- * "a(aa(ui)(qna{ya(yd)}))".
+ * "a(aa(ui)(qna{ya(yd)}))". In order to not hit memory limits, #GVariant
+ * imposes a limit on recursion depth of 65 nested containers. This is the
+ * limit in the D-Bus specification (64) plus one to allow a #GDBusMessage to
+ * be nested in a top-level tuple.
  *
  * The meaning of each of the characters is as follows:
  * - `b`: the type string of %G_VARIANT_TYPE_BOOLEAN; a boolean value.
@@ -194,35 +198,15 @@ g_variant_type_check (const GVariantType *type)
 #endif
 }
 
-/**
- * g_variant_type_string_scan:
- * @string: a pointer to any string
- * @limit: (nullable): the end of @string, or %NULL
- * @endptr: (out) (optional): location to store the end pointer, or %NULL
- *
- * Scan for a single complete and valid GVariant type string in @string.
- * The memory pointed to by @limit (or bytes beyond it) is never
- * accessed.
- *
- * If a valid type string is found, @endptr is updated to point to the
- * first character past the end of the string that was found and %TRUE
- * is returned.
- *
- * If there is no valid type string starting at @string, or if the type
- * string does not end before @limit then %FALSE is returned.
- *
- * For the simple case of checking if a string is a valid type string,
- * see g_variant_type_string_is_valid().
- *
- * Returns: %TRUE if a valid type string was found
- *
- * Since: 2.24
- **/
-gboolean
-g_variant_type_string_scan (const gchar  *string,
-                            const gchar  *limit,
-                            const gchar **endptr)
+static gboolean
+variant_type_string_scan_internal (const gchar  *string,
+                                   const gchar  *limit,
+                                   const gchar **endptr,
+                                   gsize        *depth,
+                                   gsize         depth_limit)
 {
+  gsize max_depth = 0, child_depth;
+
   g_return_val_if_fail (string != NULL, FALSE);
 
   if (string == limit || *string == '\0')
@@ -232,27 +216,44 @@ g_variant_type_string_scan (const gchar  *string,
     {
     case '(':
       while (string == limit || *string != ')')
-        if (!g_variant_type_string_scan (string, limit, &string))
-          return FALSE;
+        {
+          if (depth_limit == 0 ||
+              !variant_type_string_scan_internal (string, limit, &string,
+                                                  &child_depth,
+                                                  depth_limit - 1))
+            return FALSE;
+
+          max_depth = MAX (max_depth, child_depth + 1);
+        }
 
       string++;
       break;
 
     case '{':
-      if (string == limit || *string == '\0' ||                    /* { */
-          !strchr ("bynqihuxtdsog?", *string++) ||                 /* key */
-          !g_variant_type_string_scan (string, limit, &string) ||  /* value */
-          string == limit || *string++ != '}')                     /* } */
+      if (depth_limit == 0 ||
+          string == limit || *string == '\0' ||                                  /* { */
+          !strchr ("bynqihuxtdsog?", *string++) ||                               /* key */
+          !variant_type_string_scan_internal (string, limit, &string,
+                                              &child_depth, depth_limit - 1) ||  /* value */
+          string == limit || *string++ != '}')                                   /* } */
         return FALSE;
 
+      max_depth = MAX (max_depth, child_depth + 1);
       break;
 
     case 'm': case 'a':
-      return g_variant_type_string_scan (string, limit, endptr);
+      if (depth_limit == 0 ||
+          !variant_type_string_scan_internal (string, limit, &string,
+                                              &child_depth, depth_limit - 1))
+        return FALSE;
+
+      max_depth = MAX (max_depth, child_depth + 1);
+      break;
 
     case 'b': case 'y': case 'n': case 'q': case 'i': case 'u':
     case 'x': case 't': case 'd': case 's': case 'o': case 'g':
     case 'v': case 'r': case '*': case '?': case 'h':
+      max_depth = MAX (max_depth, 1);
       break;
 
     default:
@@ -261,10 +262,74 @@ g_variant_type_string_scan (const gchar  *string,
 
   if (endptr != NULL)
     *endptr = string;
+  if (depth != NULL)
+    *depth = max_depth;
 
   return TRUE;
 }
 
+/**
+ * g_variant_type_string_scan:
+ * @string: a pointer to any string
+ * @limit: (nullable): the end of @string, or %NULL
+ * @endptr: (out) (optional): location to store the end pointer, or %NULL
+ *
+ * Scan for a single complete and valid GVariant type string in @string.
+ * The memory pointed to by @limit (or bytes beyond it) is never
+ * accessed.
+ *
+ * If a valid type string is found, @endptr is updated to point to the
+ * first character past the end of the string that was found and %TRUE
+ * is returned.
+ *
+ * If there is no valid type string starting at @string, or if the type
+ * string does not end before @limit then %FALSE is returned.
+ *
+ * For the simple case of checking if a string is a valid type string,
+ * see g_variant_type_string_is_valid().
+ *
+ * Returns: %TRUE if a valid type string was found
+ *
+ * Since: 2.24
+ **/
+gboolean
+g_variant_type_string_scan (const gchar  *string,
+                            const gchar  *limit,
+                            const gchar **endptr)
+{
+  return variant_type_string_scan_internal (string, limit, endptr, NULL,
+                                            G_VARIANT_MAX_RECURSION_DEPTH);
+}
+
+/* < private >
+ * g_variant_type_string_get_depth_:
+ * @type_string: a pointer to any string
+ *
+ * Get the maximum depth of the nested types in @type_string. A basic type will
+ * return depth 1, and a container type will return a greater value. The depth
+ * of a tuple is 1 plus the depth of its deepest child type.
+ *
+ * If @type_string is not a valid #GVariant type string, 0 will be returned.
+ *
+ * Returns: depth of @type_string, or 0 on error
+ * Since: 2.60
+ */
+gsize
+g_variant_type_string_get_depth_ (const gchar *type_string)
+{
+  const gchar *endptr;
+  gsize depth = 0;
+
+  g_return_val_if_fail (type_string != NULL, 0);
+
+  if (!variant_type_string_scan_internal (type_string, NULL, &endptr, &depth,
+                                          G_VARIANT_MAX_RECURSION_DEPTH) ||
+      *endptr != '\0')
+    return 0;
+
+  return depth;
+}
+
 /**
  * g_variant_type_string_is_valid:
  * @type_string: a pointer to any string
diff --git a/glib/gvarianttype.h b/glib/gvarianttype.h
index 6bb5e07b5..fdc364166 100644
--- a/glib/gvarianttype.h
+++ b/glib/gvarianttype.h
@@ -374,6 +374,8 @@ GVariantType *                  g_variant_type_new_dict_entry           (const G
 /*< private >*/
 GLIB_AVAILABLE_IN_ALL
 const GVariantType *            g_variant_type_checked_                 (const gchar *);
+GLIB_AVAILABLE_IN_2_54
+gsize                           g_variant_type_string_get_depth_        (const gchar *type_string);
 
 G_END_DECLS
 
diff --git a/glib/gvarianttypeinfo.c b/glib/gvarianttypeinfo.c
index 9dade7064..78d029425 100644
--- a/glib/gvarianttypeinfo.c
+++ b/glib/gvarianttypeinfo.c
@@ -251,6 +251,31 @@ g_variant_type_info_query (GVariantTypeInfo *info,
     *fixed_size = info->fixed_size;
 }
 
+/* < private >
+ * g_variant_type_info_query_depth:
+ * @info: a #GVariantTypeInfo
+ *
+ * Queries @info to determine the depth of the type.
+ *
+ * See g_variant_type_string_get_depth_() for more details.
+ *
+ * Returns: depth of @info
+ * Since: 2.60
+ */
+gsize
+g_variant_type_info_query_depth (GVariantTypeInfo *info)
+{
+  g_variant_type_info_check (info, 0);
+
+  if (info->container_class)
+    {
+      ContainerInfo *container = (ContainerInfo *) info;
+      return g_variant_type_string_get_depth_ (container->type_string);
+    }
+
+  return 1;
+}
+
 /* == array == */
 #define GV_ARRAY_INFO_CLASS 'a'
 static ArrayInfo *
diff --git a/glib/gvarianttypeinfo.h b/glib/gvarianttypeinfo.h
index cc60f36e2..6ecd1d4bb 100644
--- a/glib/gvarianttypeinfo.h
+++ b/glib/gvarianttypeinfo.h
@@ -130,6 +130,8 @@ GLIB_AVAILABLE_IN_ALL
 void                            g_variant_type_info_query               (GVariantTypeInfo   *typeinfo,
                                                                          guint              *alignment,
                                                                          gsize              *size);
+GLIB_AVAILABLE_IN_2_54
+gsize                           g_variant_type_info_query_depth         (GVariantTypeInfo   *typeinfo);
 
 /* array */
 GLIB_AVAILABLE_IN_ALL
diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
index a5095a380..51fa0f02b 100644
--- a/glib/tests/gvariant.c
+++ b/glib/tests/gvariant.c
@@ -661,6 +661,57 @@ test_gvarianttype (void)
     }
 }
 
+/* Test that scanning a deeply recursive type string doesn’t exhaust our
+ * stack space (which it would if the type string scanner was recursive). */
+static void
+test_gvarianttype_string_scan_recursion_tuple (void)
+{
+  gchar *type_string = NULL;
+  gsize type_string_len = 1000001;  /* not including nul terminator */
+  gsize i;
+
+  /* Build a long type string of ‘((…u…))’. */
+  type_string = g_new0 (gchar, type_string_len + 1);
+  for (i = 0; i < type_string_len; i++)
+    {
+      if (i < type_string_len / 2)
+        type_string[i] = '(';
+      else if (i == type_string_len / 2)
+        type_string[i] = 'u';
+      else
+        type_string[i] = ')';
+    }
+
+  /* Goes (way) over allowed recursion limit. */
+  g_assert_false (g_variant_type_string_is_valid (type_string));
+
+  g_free (type_string);
+}
+
+/* Same as above, except with an array rather than a tuple. */
+static void
+test_gvarianttype_string_scan_recursion_array (void)
+{
+  gchar *type_string = NULL;
+  gsize type_string_len = 1000001;  /* not including nul terminator */
+  gsize i;
+
+  /* Build a long type string of ‘aaa…aau’. */
+  type_string = g_new0 (gchar, type_string_len + 1);
+  for (i = 0; i < type_string_len; i++)
+    {
+      if (i < type_string_len - 1)
+        type_string[i] = 'a';
+      else
+        type_string[i] = 'u';
+    }
+
+  /* Goes (way) over allowed recursion limit. */
+  g_assert_false (g_variant_type_string_is_valid (type_string));
+
+  g_free (type_string);
+}
+
 #define ALIGNED(x, y)   (((x + (y - 1)) / y) * y)
 
 /* do our own calculation of the fixed_size and alignment of a type
@@ -1230,6 +1281,8 @@ random_instance_filler (GVariantSerialised *serialised,
   if (serialised->size == 0)
     serialised->size = instance->size;
 
+  serialised->depth = 0;
+
   g_assert (serialised->type_info == instance->type_info);
   g_assert (serialised->size == instance->size);
 
@@ -1394,6 +1447,7 @@ test_maybe (void)
         serialised.type_info = type_info;
         serialised.data = flavoured_malloc (needed_size, flavour);
         serialised.size = needed_size;
+        serialised.depth = 0;
 
         g_variant_serialiser_serialise (serialised,
                                         random_instance_filler,
@@ -1516,6 +1570,7 @@ test_array (void)
         serialised.type_info = array_info;
         serialised.data = flavoured_malloc (needed_size, flavour);
         serialised.size = needed_size;
+        serialised.depth = 0;
 
         g_variant_serialiser_serialise (serialised, random_instance_filler,
                                         (gpointer *) instances, n_children);
@@ -1679,6 +1734,7 @@ test_tuple (void)
         serialised.type_info = type_info;
         serialised.data = flavoured_malloc (needed_size, flavour);
         serialised.size = needed_size;
+        serialised.depth = 0;
 
         g_variant_serialiser_serialise (serialised, random_instance_filler,
                                         (gpointer *) instances, n_children);
@@ -1774,6 +1830,7 @@ test_variant (void)
         serialised.type_info = type_info;
         serialised.data = flavoured_malloc (needed_size, flavour);
         serialised.size = needed_size;
+        serialised.depth = 0;
 
         g_variant_serialiser_serialise (serialised, random_instance_filler,
                                         (gpointer *) &instance, 1);
@@ -2055,6 +2112,8 @@ tree_filler (GVariantSerialised *serialised,
   if (serialised->type_info == NULL)
     serialised->type_info = instance->info;
 
+  serialised->depth = 0;
+
   if (instance->data_size == 0)
     /* is a container */
     {
@@ -2225,6 +2284,7 @@ test_byteswap (void)
   g_variant_serialised_byteswap (two);
 
   g_assert_cmpmem (one.data, one.size, two.data, two.size);
+  g_assert_cmpuint (one.depth, ==, two.depth);
 
   tree_instance_free (tree);
   g_free (one.data);
@@ -4663,6 +4723,10 @@ main (int argc, char **argv)
   g_test_init (&argc, &argv, NULL);
 
   g_test_add_func ("/gvariant/type", test_gvarianttype);
+  g_test_add_func ("/gvariant/type/string-scan/recursion/tuple",
+                   test_gvarianttype_string_scan_recursion_tuple);
+  g_test_add_func ("/gvariant/type/string-scan/recursion/array",
+                   test_gvarianttype_string_scan_recursion_array);
   g_test_add_func ("/gvariant/typeinfo", test_gvarianttypeinfo);
   g_test_add_func ("/gvariant/serialiser/maybe", test_maybes);
   g_test_add_func ("/gvariant/serialiser/array", test_arrays);
@@ -4720,5 +4784,10 @@ main (int argc, char **argv)
   g_test_add_func ("/gvariant/normal-checking/tuples",
                    test_normal_checking_tuples);
 
+  g_test_add_func ("/gvariant/recursion-limits/variant-in-variant",
+                   test_recursion_limits_variant_in_variant);
+  g_test_add_func ("/gvariant/recursion-limits/array-in-variant",
+                   test_recursion_limits_array_in_variant);
+
   return g_test_run ();
 }
-- 
2.14.4

