[glib: 3/4] gdbusmessage: Limit recursion of variants in D-Bus messages



commit 5054b48b7ccf7e040a0770427645d67bf2b44f2d
Author: Philip Withnall <withnall endlessm com>
Date:   Tue Oct 29 18:47:02 2019 +0000

    gdbusmessage: Limit recursion of variants in D-Bus messages
    
    This is the analogue of commit 7c4e6e9fbe, but applied to the
    `GDBusMessage` parser, which does its own top-level parsing of the
    variant format in D-Bus messages.
    
    Previously, this code allowed arbitrary recursion of variant containers,
    which could lead to a stack overflow. Now, that recursion is limited to
    64 levels, as per the D-Bus specification:
    
    https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-signature
    
    This includes a new unit test.
    
    oss-fuzz#14870
    
    Signed-off-by: Philip Withnall <withnall endlessm com>

 gio/gdbusmessage.c              |  49 +++++++++++
 gio/tests/gdbus-serialization.c | 179 ++++++++++++++++++++++++++++++++++++++--
 2 files changed, 221 insertions(+), 7 deletions(-)
---
diff --git a/gio/gdbusmessage.c b/gio/gdbusmessage.c
index 9914caa51..6e3bd8bf4 100644
--- a/gio/gdbusmessage.c
+++ b/gio/gdbusmessage.c
@@ -58,6 +58,10 @@
 
 #include "glibintl.h"
 
+/* See https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-signature
+ * This is 64 containers plus 1 value within them. */
+#define G_DBUS_MAX_TYPE_DEPTH (64 + 1)
+
 typedef struct _GMemoryBuffer GMemoryBuffer;
 struct _GMemoryBuffer
 {
@@ -1439,6 +1443,7 @@ read_bytes (GMemoryBuffer  *mbuf,
 static GVariant *
 parse_value_from_blob (GMemoryBuffer       *buf,
                        const GVariantType  *type,
+                       guint                max_depth,
                        gboolean             just_align,
                        guint                indent,
                        GError             **error)
@@ -1450,6 +1455,15 @@ parse_value_from_blob (GMemoryBuffer       *buf,
 #endif /* DEBUG_SERIALIZER */
   const gchar *type_string;
 
+  if (max_depth == 0)
+    {
+      g_set_error_literal (&local_error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_INVALID_ARGUMENT,
+                           _("Value nested too deeply"));
+      goto fail;
+    }
+
   type_string = g_variant_type_peek_string (type);
 
 #ifdef DEBUG_SERIALIZER
@@ -1687,6 +1701,17 @@ parse_value_from_blob (GMemoryBuffer       *buf,
                   goto fail;
                 }
 
+              if (max_depth == 1)
+                {
+                  /* If we had recursed into parse_value_from_blob() again to
+                   * parse the array values, this would have been emitted. */
+                  g_set_error_literal (&local_error,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_INVALID_ARGUMENT,
+                                       _("Value nested too deeply"));
+                  goto fail;
+                }
+
               ensure_input_padding (buf, fixed_size);
               array_data = read_bytes (buf, array_len, &local_error);
               if (array_data == NULL)
@@ -1714,6 +1739,7 @@ parse_value_from_blob (GMemoryBuffer       *buf,
                   GVariant *item G_GNUC_UNUSED  /* when compiling with G_DISABLE_ASSERT */;
                   item = parse_value_from_blob (buf,
                                                 element_type,
+                                                max_depth - 1,
                                                 TRUE,
                                                 indent + 2,
                                                 NULL);
@@ -1728,6 +1754,7 @@ parse_value_from_blob (GMemoryBuffer       *buf,
                       GVariant *item;
                       item = parse_value_from_blob (buf,
                                                     element_type,
+                                                    max_depth - 1,
                                                     FALSE,
                                                     indent + 2,
                                                     &local_error);
@@ -1767,6 +1794,7 @@ parse_value_from_blob (GMemoryBuffer       *buf,
               key_type = g_variant_type_key (type);
               key = parse_value_from_blob (buf,
                                            key_type,
+                                           max_depth - 1,
                                            FALSE,
                                            indent + 2,
                                            &local_error);
@@ -1775,6 +1803,7 @@ parse_value_from_blob (GMemoryBuffer       *buf,
               value_type = g_variant_type_value (type);
               value = parse_value_from_blob (buf,
                                              value_type,
+                                             max_depth - 1,
                                              FALSE,
                                              indent + 2,
                                              &local_error);
@@ -1809,6 +1838,7 @@ parse_value_from_blob (GMemoryBuffer       *buf,
                   GVariant *item;
                   item = parse_value_from_blob (buf,
                                                 element_type,
+                                                max_depth - 1,
                                                 FALSE,
                                                 indent + 2,
                                                 &local_error);
@@ -1855,9 +1885,26 @@ parse_value_from_blob (GMemoryBuffer       *buf,
                                sig);
                   goto fail;
                 }
+
+              if (max_depth <= g_variant_type_string_get_depth_ (sig))
+                {
+                  /* Catch the type nesting being too deep without having to
+                   * parse the data. We don’t have to check this for static
+                   * container types (like arrays and tuples, above) because
+                   * the g_variant_type_string_is_valid() check performed before
+                   * the initial parse_value_from_blob() call should check the
+                   * static type nesting. */
+                  g_set_error_literal (&local_error,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_INVALID_ARGUMENT,
+                                       _("Value nested too deeply"));
+                  goto fail;
+                }
+
               variant_type = g_variant_type_new (sig);
               value = parse_value_from_blob (buf,
                                              variant_type,
+                                             max_depth - 1,
                                              FALSE,
                                              indent + 2,
                                              &local_error);
@@ -2095,6 +2142,7 @@ g_dbus_message_new_from_blob (guchar                *blob,
 #endif /* DEBUG_SERIALIZER */
   headers = parse_value_from_blob (&mbuf,
                                    G_VARIANT_TYPE ("a{yv}"),
+                                   G_DBUS_MAX_TYPE_DEPTH + 2 /* for the a{yv} */,
                                    FALSE,
                                    2,
                                    error);
@@ -2166,6 +2214,7 @@ g_dbus_message_new_from_blob (guchar                *blob,
 #endif /* DEBUG_SERIALIZER */
           message->body = parse_value_from_blob (&mbuf,
                                                  variant_type,
+                                                 G_DBUS_MAX_TYPE_DEPTH + 1 /* for the surrounding tuple */,
                                                  FALSE,
                                                  2,
                                                  error);
diff --git a/gio/tests/gdbus-serialization.c b/gio/tests/gdbus-serialization.c
index ae6514b0f..2ca28d99b 100644
--- a/gio/tests/gdbus-serialization.c
+++ b/gio/tests/gdbus-serialization.c
@@ -514,9 +514,8 @@ get_body_signature (GVariant *value)
 }
 
 /* If @value is floating, this assumes ownership. */
-static void
-check_serialization (GVariant *value,
-                     const gchar *expected_dbus_1_output)
+static gchar *
+get_and_check_serialization (GVariant *value)
 {
   guchar *blob;
   gsize blob_size;
@@ -525,7 +524,7 @@ check_serialization (GVariant *value,
   GDBusMessage *recovered_message;
   GError *error;
   DBusError dbus_error;
-  gchar *s;
+  gchar *s = NULL;
   guint n;
 
   message = g_dbus_message_new ();
@@ -597,9 +596,6 @@ check_serialization (GVariant *value,
       s = dbus_1_message_print (dbus_1_message);
       dbus_message_unref (dbus_1_message);
 
-      g_assert_cmpstr (s, ==, expected_dbus_1_output);
-      g_free (s);
-
       /* Then serialize back and check that the body is identical */
 
       error = NULL;
@@ -624,6 +620,18 @@ check_serialization (GVariant *value,
     }
 
   g_object_unref (message);
+
+  return g_steal_pointer (&s);
+}
+
+/* If @value is floating, this assumes ownership. */
+static void
+check_serialization (GVariant *value,
+                     const gchar *expected_dbus_1_output)
+{
+  gchar *s = get_and_check_serialization (value);
+  g_assert_cmpstr (s, ==, expected_dbus_1_output);
+  g_free (s);
 }
 
 static void
@@ -665,6 +673,8 @@ test_message_serialize_complex (void)
 {
   GError *error;
   GVariant *value;
+  guint i;
+  gchar *serialization = NULL;
 
   error = NULL;
 
@@ -724,6 +734,37 @@ test_message_serialize_complex (void)
                        "    unix-fd: (not extracted)\n");
   g_variant_unref (value);
 #endif
+
+  /* Deep nesting of variants (just below the recursion limit). */
+  value = g_variant_new_string ("buried");
+  for (i = 0; i < 64; i++)
+    value = g_variant_new_variant (value);
+  value = g_variant_new_tuple (&value, 1);
+
+  serialization = get_and_check_serialization (value);
+  g_assert_nonnull (serialization);
+  g_assert_true (g_str_has_prefix (serialization,
+                                   "value 0:   variant:\n"
+                                   "    variant:\n"
+                                   "      variant:\n"));
+  g_free (serialization);
+
+  /* Deep nesting of arrays and structs (just below the recursion limit).
+   * See https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-signature */
+  value = g_variant_new_string ("hello");
+  for (i = 0; i < 32; i++)
+    value = g_variant_new_tuple (&value, 1);
+  for (i = 0; i < 32; i++)
+    value = g_variant_new_array (NULL, &value, 1);
+  value = g_variant_new_tuple (&value, 1);
+
+  serialization = get_and_check_serialization (value);
+  g_assert_nonnull (serialization);
+  g_assert_true (g_str_has_prefix (serialization,
+                                   "value 0:   array:\n"
+                                   "    array:\n"
+                                   "      array:\n"));
+  g_free (serialization);
 }
 
 
@@ -1252,6 +1293,126 @@ test_message_parse_over_long_signature_header (void)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+/* Test that an invalid header in a D-Bus message (specifically, containing too
+ * many levels of nested variant) is gracefully handled with an error rather
+ * than a crash. The set of bytes here come almost directly from fuzzer output. */
+static void
+test_message_parse_deep_header_nesting (void)
+{
+  const guint8 data[] = {
+    'l',  /* little-endian byte order */
+    0x20,  /* message type */
+    0x20,  /* message flags */
+    0x01,  /* major protocol version */
+    0x20, 0x20, 0x20, 0x00,  /* body length (invalid) */
+    0x20, 0x20, 0x20, 0x20,  /* message serial */
+    /* a{yv} of header fields:
+     * (things start to be even more invalid below here) */
+    0x20, 0x20, 0x20, 0x00,  /* array length (in bytes) */
+      0x20,  /* array key (this is not currently a valid header field) */
+      /* Variant array value: */
+      0x01,  /* signature length */
+      'v',  /* one complete type */
+      0x00,  /* nul terminator */
+      /* (Variant array value payload) */
+      /* Critically, this contains 64 nested variants (minus two for the
+       * ‘arbitrary valid content’ below, but ignoring two for the `a{yv}`
+       * above), which in total exceeds %G_DBUS_MAX_TYPE_DEPTH. */
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      0x01, 'v', 0x00, 0x01, 'v', 0x00,
+      /* Some arbitrary valid content inside the innermost variant: */
+      0x01, 'y', 0x00, 0xcc,
+    /* (message body length missing) */
+  };
+  gsize size = sizeof (data);
+  GDBusMessage *message = NULL;
+  GError *local_error = NULL;
+
+  message = g_dbus_message_new_from_blob ((guchar *) data, size,
+                                          G_DBUS_CAPABILITY_FLAGS_NONE,
+                                          &local_error);
+  g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
+  g_assert_null (message);
+
+  g_clear_error (&local_error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* Test that an invalid body in a D-Bus message (specifically, containing too
+ * many levels of nested variant) is gracefully handled with an error rather
+ * than a crash. The set of bytes here are a modified version of the bytes from
+ * test_message_parse_deep_header_nesting(). */
+static void
+test_message_parse_deep_body_nesting (void)
+{
+  const guint8 data[] = {
+    'l',  /* little-endian byte order */
+    0x20,  /* message type */
+    0x20,  /* message flags */
+    0x01,  /* major protocol version */
+    0x20, 0x20, 0x20, 0x00,  /* body length (invalid) */
+    0x20, 0x20, 0x20, 0x20,  /* message serial */
+    /* a{yv} of header fields: */
+    0x07, 0x00, 0x00, 0x00,  /* array length (in bytes) */
+      0x08,  /* array key (signature field) */
+      /* Variant array value: */
+      0x01,  /* signature length */
+      'g',  /* one complete type */
+      0x00,  /* nul terminator */
+      /* (Variant array value payload) */
+      0x01, 'v', 0x00,
+    /* End-of-header padding to reach an 8-byte boundary: */
+    0x00,
+    /* Message body: over 64 levels of nested variant, which is not valid: */
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00, 0x01, 'v', 0x00,
+    /* Some arbitrary valid content inside the innermost variant: */
+    0x01, 'y', 0x00, 0xcc,
+  };
+  gsize size = sizeof (data);
+  GDBusMessage *message = NULL;
+  GError *local_error = NULL;
+
+  message = g_dbus_message_new_from_blob ((guchar *) data, size,
+                                          G_DBUS_CAPABILITY_FLAGS_NONE,
+                                          &local_error);
+  g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
+  g_assert_null (message);
+
+  g_clear_error (&local_error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 int
 main (int   argc,
       char *argv[])
@@ -1283,6 +1444,10 @@ main (int   argc,
                    test_message_parse_multiple_signature_header);
   g_test_add_func ("/gdbus/message-parse/over-long-signature-header",
                    test_message_parse_over_long_signature_header);
+  g_test_add_func ("/gdbus/message-parse/deep-header-nesting",
+                   test_message_parse_deep_header_nesting);
+  g_test_add_func ("/gdbus/message-parse/deep-body-nesting",
+                   test_message_parse_deep_body_nesting);
 
   return g_test_run();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]