diff options
author | Philip Withnall <withnall@endlessm.com> | 2018-09-07 20:27:39 +0100 |
---|---|---|
committer | Philip Withnall <withnall@endlessm.com> | 2018-10-23 17:01:51 +1300 |
commit | 7c4e6e9fbe473de0401c778c6b0c4aad27d5145a (patch) | |
tree | 72c689dacbdd28cd1544f1cb768f0511fde00c4f /glib/gvarianttype.c | |
parent | eb7c9adc3b2570f6b82110b52a24609d124f38de (diff) | |
download | glib-7c4e6e9fbe473de0401c778c6b0c4aad27d5145a.tar.gz |
gvarianttype: Impose a recursion limit of 128 on variant types
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 128 levels (which should be more than enough for
anyone™), document it and add a test. This is, handily, also the limit
of 64 applied by the D-Bus specification (§(Valid Signatures)), plus a
bit to allow wrapping of D-Bus messages in additional layers of
variants.
oss-fuzz#9857
Signed-off-by: Philip Withnall <withnall@endlessm.com>
Diffstat (limited to 'glib/gvarianttype.c')
-rw-r--r-- | glib/gvarianttype.c | 137 |
1 files changed, 101 insertions, 36 deletions
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,11 +262,75 @@ 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 * |