mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-24 03:02:10 +01:00
Merge branch '1582-backport-411-422-ossfuzz-fixes-glib-2-56' into 'glib-2-56'
Resolve "Backport GMarkup/GVariant/GDBus fixes to glib-2-58 and glib-2-56" (glib-2-56) See merge request GNOME/glib!448
This commit is contained in:
commit
fe4fd7319f
@ -1,4 +1,4 @@
|
||||
image: registry.gitlab.gnome.org/gnome/glib/master:v3
|
||||
image: registry.gitlab.gnome.org/gnome/glib/2-58:v1
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
@ -3420,6 +3420,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>
|
||||
|
||||
|
||||
|
@ -983,7 +983,10 @@ g_dbus_message_set_serial (GDBusMessage *message,
|
||||
*
|
||||
* Gets a header field on @message.
|
||||
*
|
||||
* Returns: A #GVariant with the value if the header was found, %NULL
|
||||
* The caller is responsible for checking the type of the returned #GVariant
|
||||
* matches what is expected.
|
||||
*
|
||||
* Returns: (transfer none) (nullable): A #GVariant with the value if the header was found, %NULL
|
||||
* otherwise. Do not free, it is owned by @message.
|
||||
*
|
||||
* Since: 2.26
|
||||
@ -1841,8 +1844,11 @@ parse_value_from_blob (GMemoryBuffer *buf,
|
||||
sig = read_string (buf, (gsize) siglen, &local_error);
|
||||
if (sig == NULL)
|
||||
goto fail;
|
||||
if (!g_variant_is_signature (sig))
|
||||
if (!g_variant_is_signature (sig) ||
|
||||
!g_variant_type_string_is_valid (sig))
|
||||
{
|
||||
/* A D-Bus signature can contain zero or more complete types,
|
||||
* but a GVariant has to be exactly one complete type. */
|
||||
g_set_error (&local_error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_ARGUMENT,
|
||||
@ -1924,7 +1930,7 @@ parse_value_from_blob (GMemoryBuffer *buf,
|
||||
|
||||
/**
|
||||
* g_dbus_message_bytes_needed:
|
||||
* @blob: (array length=blob_len) (element-type guint8): A blob represent a binary D-Bus message.
|
||||
* @blob: (array length=blob_len) (element-type guint8): A blob representing a binary D-Bus message.
|
||||
* @blob_len: The length of @blob (must be at least 16).
|
||||
* @error: Return location for error or %NULL.
|
||||
*
|
||||
@ -2001,6 +2007,9 @@ g_dbus_message_bytes_needed (guchar *blob,
|
||||
* order that the message was in can be retrieved using
|
||||
* g_dbus_message_get_byte_order().
|
||||
*
|
||||
* If the @blob cannot be parsed, contains invalid fields, or contains invalid
|
||||
* headers, %G_IO_ERROR_INVALID_ARGUMENT will be returned.
|
||||
*
|
||||
* Returns: A new #GDBusMessage or %NULL if @error is set. Free with
|
||||
* g_object_unref().
|
||||
*
|
||||
@ -2113,6 +2122,15 @@ g_dbus_message_new_from_blob (guchar *blob,
|
||||
const gchar *signature_str;
|
||||
gsize signature_str_len;
|
||||
|
||||
if (!g_variant_is_of_type (signature, G_VARIANT_TYPE_SIGNATURE))
|
||||
{
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_ARGUMENT,
|
||||
_("Signature header found but is not of type signature"));
|
||||
goto out;
|
||||
}
|
||||
|
||||
signature_str = g_variant_get_string (signature, &signature_str_len);
|
||||
|
||||
/* signature but no body */
|
||||
@ -2693,6 +2711,16 @@ g_dbus_message_to_blob (GDBusMessage *message,
|
||||
body_start_offset = mbuf.valid_len;
|
||||
|
||||
signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE);
|
||||
|
||||
if (signature != NULL && !g_variant_is_of_type (signature, G_VARIANT_TYPE_SIGNATURE))
|
||||
{
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_ARGUMENT,
|
||||
_("Signature header found but is not of type signature"));
|
||||
goto out;
|
||||
}
|
||||
|
||||
signature_str = NULL;
|
||||
if (signature != NULL)
|
||||
signature_str = g_variant_get_string (signature, NULL);
|
||||
|
@ -1067,7 +1067,7 @@ make_valid_utf8 (const char *name)
|
||||
|
||||
while (remaining_bytes != 0)
|
||||
{
|
||||
if (g_utf8_validate (remainder, remaining_bytes, &invalid))
|
||||
if (g_utf8_validate (remainder, remaining_bytes, &invalid))
|
||||
break;
|
||||
valid_bytes = invalid - remainder;
|
||||
|
||||
|
@ -855,7 +855,7 @@ message_serialize_header_checks (void)
|
||||
{
|
||||
GDBusMessage *message;
|
||||
GDBusMessage *reply;
|
||||
GError *error;
|
||||
GError *error = NULL;
|
||||
guchar *blob;
|
||||
gsize blob_size;
|
||||
|
||||
@ -863,14 +863,27 @@ message_serialize_header_checks (void)
|
||||
* check we can't serialize messages with INVALID type
|
||||
*/
|
||||
message = g_dbus_message_new ();
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: type is INVALID");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
g_object_unref (message);
|
||||
|
||||
/*
|
||||
* check that we can't serialize messages with SIGNATURE set to a non-signature-typed value
|
||||
*/
|
||||
message = g_dbus_message_new_signal ("/the/path", "The.Interface", "TheMember");
|
||||
g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, g_variant_new_boolean (FALSE));
|
||||
blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Signature header found but is not of type signature");
|
||||
g_assert_null (blob);
|
||||
|
||||
g_clear_error (&error);
|
||||
g_clear_object (&message);
|
||||
|
||||
/*
|
||||
* check we can't serialize signal messages with INTERFACE, PATH or MEMBER unset / set to reserved value
|
||||
*/
|
||||
@ -878,50 +891,45 @@ message_serialize_header_checks (void)
|
||||
/* ----- */
|
||||
/* interface NULL => error */
|
||||
g_dbus_message_set_interface (message, NULL);
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: SIGNAL message: PATH, INTERFACE or MEMBER header field is missing");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
/* interface reserved value => error */
|
||||
g_dbus_message_set_interface (message, "org.freedesktop.DBus.Local");
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: SIGNAL message: The INTERFACE header field is using the reserved value org.freedesktop.DBus.Local");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
/* reset interface */
|
||||
g_dbus_message_set_interface (message, "The.Interface");
|
||||
/* ----- */
|
||||
/* path NULL => error */
|
||||
g_dbus_message_set_path (message, NULL);
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: SIGNAL message: PATH, INTERFACE or MEMBER header field is missing");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
/* path reserved value => error */
|
||||
g_dbus_message_set_path (message, "/org/freedesktop/DBus/Local");
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: SIGNAL message: The PATH header field is using the reserved value /org/freedesktop/DBus/Local");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
/* reset path */
|
||||
g_dbus_message_set_path (message, "/the/path");
|
||||
/* ----- */
|
||||
/* member NULL => error */
|
||||
g_dbus_message_set_member (message, NULL);
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: SIGNAL message: PATH, INTERFACE or MEMBER header field is missing");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
/* reset member */
|
||||
g_dbus_message_set_member (message, "TheMember");
|
||||
/* ----- */
|
||||
@ -935,23 +943,21 @@ message_serialize_header_checks (void)
|
||||
/* ----- */
|
||||
/* path NULL => error */
|
||||
g_dbus_message_set_path (message, NULL);
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: METHOD_CALL message: PATH or MEMBER header field is missing");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
/* reset path */
|
||||
g_dbus_message_set_path (message, "/the/path");
|
||||
/* ----- */
|
||||
/* member NULL => error */
|
||||
g_dbus_message_set_member (message, NULL);
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (message, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: METHOD_CALL message: PATH or MEMBER header field is missing");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
/* reset member */
|
||||
g_dbus_message_set_member (message, "TheMember");
|
||||
/* ----- */
|
||||
@ -967,34 +973,31 @@ message_serialize_header_checks (void)
|
||||
reply = g_dbus_message_new_method_reply (message);
|
||||
g_assert_cmpint (g_dbus_message_get_reply_serial (reply), ==, 42);
|
||||
g_dbus_message_set_header (reply, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, NULL);
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (reply, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: METHOD_RETURN message: REPLY_SERIAL header field is missing");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
g_object_unref (reply);
|
||||
/* method error - first nuke ERROR_NAME, then REPLY_SERIAL */
|
||||
reply = g_dbus_message_new_method_error (message, "Some.Error.Name", "the message");
|
||||
g_assert_cmpint (g_dbus_message_get_reply_serial (reply), ==, 42);
|
||||
/* nuke ERROR_NAME */
|
||||
g_dbus_message_set_error_name (reply, NULL);
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (reply, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: ERROR message: REPLY_SERIAL or ERROR_NAME header field is missing");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
/* reset ERROR_NAME */
|
||||
g_dbus_message_set_error_name (reply, "Some.Error.Name");
|
||||
/* nuke REPLY_SERIAL */
|
||||
g_dbus_message_set_header (reply, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, NULL);
|
||||
error = NULL;
|
||||
blob = g_dbus_message_to_blob (reply, &blob_size, G_DBUS_CAPABILITY_FLAGS_NONE, &error);
|
||||
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||
g_assert_cmpstr (error->message, ==, "Cannot serialize message: ERROR message: REPLY_SERIAL or ERROR_NAME header field is missing");
|
||||
g_error_free (error);
|
||||
g_assert (blob == NULL);
|
||||
g_clear_error (&error);
|
||||
g_assert_null (blob);
|
||||
g_object_unref (reply);
|
||||
g_object_unref (message);
|
||||
}
|
||||
@ -1081,6 +1084,125 @@ test_double_array (void)
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
/* Test that an invalid header in a D-Bus message (specifically, with a type
|
||||
* which doesn’t match what’s expected for the given header) is gracefully
|
||||
* handled with an error rather than a crash.
|
||||
* The set of bytes here come directly from fuzzer output. */
|
||||
static void
|
||||
test_message_parse_non_signature_header (void)
|
||||
{
|
||||
const guint8 data[] = {
|
||||
'l', /* little-endian byte order */
|
||||
0x04, /* message type */
|
||||
0x0f, /* message flags */
|
||||
0x01, /* major protocol version */
|
||||
0x00, 0x00, 0x00, 0x00, /* body length */
|
||||
0x00, 0x00, 0x00, 0xbc, /* message serial */
|
||||
/* a{yv} of header fields:
|
||||
* (things start to be invalid below here) */
|
||||
0x02, 0x00, 0x00, 0x00, /* array length (in bytes) */
|
||||
G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, /* array key */
|
||||
/* Variant array value: */
|
||||
0x04, /* signature length */
|
||||
'd', 0x00, 0x00, 'F', /* signature (invalid) */
|
||||
0x00, /* nul terminator */
|
||||
/* (Variant array value payload missing) */
|
||||
/* (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 header in a D-Bus message (specifically, containing a
|
||||
* variant with an empty type signature) is gracefully handled with an error
|
||||
* rather than a crash. The set of bytes here come directly from fuzzer
|
||||
* output. */
|
||||
static void
|
||||
test_message_parse_empty_signature_header (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 */
|
||||
/* Variant array value: */
|
||||
0x00, /* signature length */
|
||||
0x00, /* nul terminator */
|
||||
/* (Variant array value payload missing) */
|
||||
/* (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 header in a D-Bus message (specifically, containing a
|
||||
* variant with a type signature containing multiple complete types) is
|
||||
* gracefully handled with an error rather than a crash. The set of bytes here
|
||||
* come directly from fuzzer output. */
|
||||
static void
|
||||
test_message_parse_multiple_signature_header (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 */
|
||||
/* Variant array value: */
|
||||
0x02, /* signature length */
|
||||
'b', 'b', /* two complete types */
|
||||
0x00, /* nul terminator */
|
||||
/* (Variant array value payload missing) */
|
||||
/* (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);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
@ -1099,6 +1221,12 @@ main (int argc,
|
||||
message_parse_empty_arrays_of_arrays);
|
||||
|
||||
g_test_add_func ("/gdbus/message-serialize/double-array", test_double_array);
|
||||
g_test_add_func ("/gdbus/message-parse/non-signature-header",
|
||||
test_message_parse_non_signature_header);
|
||||
g_test_add_func ("/gdbus/message-parse/empty-signature-header",
|
||||
test_message_parse_empty_signature_header);
|
||||
g_test_add_func ("/gdbus/message-parse/multiple-signature-header",
|
||||
test_message_parse_multiple_signature_header);
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "gstrfuncs.h"
|
||||
#include "gtestutils.h"
|
||||
#include "glibintl.h"
|
||||
#include "gunicodeprivate.h"
|
||||
|
||||
|
||||
/**
|
||||
@ -2323,7 +2324,7 @@ reconvert:
|
||||
|
||||
/* UTF-8, just validate, emulate g_iconv */
|
||||
|
||||
if (!g_utf8_validate (from_buf, try_len, &badchar))
|
||||
if (!_g_utf8_validate_len (from_buf, try_len, &badchar))
|
||||
{
|
||||
gunichar try_char;
|
||||
gsize incomplete_len = from_buf + try_len - badchar;
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "gtestutils.h"
|
||||
#include "glibintl.h"
|
||||
#include "gthread.h"
|
||||
#include "gunicodeprivate.h"
|
||||
|
||||
/**
|
||||
* SECTION:markup
|
||||
@ -455,7 +456,7 @@ slow_name_validate (GMarkupParseContext *context,
|
||||
{
|
||||
const gchar *p = name;
|
||||
|
||||
if (!g_utf8_validate (name, strlen (name), NULL))
|
||||
if (!g_utf8_validate (name, -1, NULL))
|
||||
{
|
||||
set_error (context, error, G_MARKUP_ERROR_BAD_UTF8,
|
||||
_("Invalid UTF-8 encoded text in name - not valid '%s'"), name);
|
||||
@ -538,7 +539,7 @@ text_validate (GMarkupParseContext *context,
|
||||
gint len,
|
||||
GError **error)
|
||||
{
|
||||
if (!g_utf8_validate (p, len, NULL))
|
||||
if (!_g_utf8_validate_len (p, len, NULL))
|
||||
{
|
||||
set_error (context, error, G_MARKUP_ERROR_BAD_UTF8,
|
||||
_("Invalid UTF-8 encoded text in name - not valid '%s'"), p);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define __G_UNICODE_PRIVATE_H__
|
||||
|
||||
#include "gtypes.h"
|
||||
#include "gunicode.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@ -27,6 +28,10 @@ gunichar *_g_utf8_normalize_wc (const gchar *str,
|
||||
gssize max_len,
|
||||
GNormalizeMode mode);
|
||||
|
||||
gboolean _g_utf8_validate_len (const gchar *str,
|
||||
gsize max_len,
|
||||
const gchar **end);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __G_UNICODE_PRIVATE_H__ */
|
||||
|
45
glib/gutf8.c
45
glib/gutf8.c
@ -39,6 +39,7 @@
|
||||
#include "gtypes.h"
|
||||
#include "gthread.h"
|
||||
#include "glibintl.h"
|
||||
#include "gunicodeprivate.h"
|
||||
|
||||
#define UTF8_COMPUTE(Char, Mask, Len) \
|
||||
if (Char < 128) \
|
||||
@ -1669,16 +1670,48 @@ g_utf8_validate (const char *str,
|
||||
{
|
||||
const gchar *p;
|
||||
|
||||
if (max_len < 0)
|
||||
p = fast_validate (str);
|
||||
else
|
||||
p = fast_validate_len (str, max_len);
|
||||
if (max_len >= 0)
|
||||
return _g_utf8_validate_len (str, max_len, end);
|
||||
|
||||
p = fast_validate (str);
|
||||
|
||||
if (end)
|
||||
*end = p;
|
||||
|
||||
if ((max_len >= 0 && p != str + max_len) ||
|
||||
(max_len < 0 && *p != '\0'))
|
||||
if (*p != '\0')
|
||||
return FALSE;
|
||||
else
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* _g_utf8_validate_len:
|
||||
* @str: (array length=max_len) (element-type guint8): a pointer to character data
|
||||
* @max_len: max bytes to validate
|
||||
* @end: (out) (optional) (transfer none): return location for end of valid data
|
||||
*
|
||||
* Validates UTF-8 encoded text.
|
||||
*
|
||||
* As with g_utf8_validate(), but @max_len must be set, and hence this function
|
||||
* will always return %FALSE if any of the bytes of @str are nul.
|
||||
*
|
||||
* Returns: %TRUE if the text was valid UTF-8
|
||||
* Since: 2.60 (backported to 2.56)
|
||||
*/
|
||||
gboolean
|
||||
_g_utf8_validate_len (const char *str,
|
||||
gsize max_len,
|
||||
const gchar **end)
|
||||
|
||||
{
|
||||
const gchar *p;
|
||||
|
||||
p = fast_validate_len (str, max_len);
|
||||
|
||||
if (end)
|
||||
*end = p;
|
||||
|
||||
if (p != str + max_len)
|
||||
return FALSE;
|
||||
else
|
||||
return TRUE;
|
||||
|
@ -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))
|
||||
|
@ -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__ */
|
||||
|
@ -22,7 +22,9 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "gvariant-serialiser.h"
|
||||
#include "gunicodeprivate.h"
|
||||
|
||||
#include <glib/gvariant-internal.h>
|
||||
#include <glib/gtestutils.h>
|
||||
#include <glib/gstrfuncs.h>
|
||||
#include <glib/gtypes.h>
|
||||
@ -81,7 +83,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 +117,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 +262,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 +294,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 +315,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 +356,8 @@ gvs_variable_sized_maybe_get_child (GVariantSerialised value,
|
||||
if (value.size == 0)
|
||||
value.data = NULL;
|
||||
|
||||
value.depth++;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -376,7 +387,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 +406,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 +457,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 +487,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 +503,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 +670,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);
|
||||
|
||||
@ -679,7 +695,7 @@ gvs_variable_sized_array_get_child (GVariantSerialised value,
|
||||
(offset_size * index_),
|
||||
offset_size);
|
||||
|
||||
if (start < end && end <= value.size)
|
||||
if (start < end && end <= value.size && end <= last_end)
|
||||
{
|
||||
child.data = value.data + start;
|
||||
child.size = end - start;
|
||||
@ -783,6 +799,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++)
|
||||
@ -854,10 +871,11 @@ gvs_tuple_get_child (GVariantSerialised value,
|
||||
const GVariantMemberInfo *member_info;
|
||||
GVariantSerialised child = { 0, };
|
||||
gsize offset_size;
|
||||
gsize start, end;
|
||||
gsize start, end, last_end;
|
||||
|
||||
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
|
||||
@ -923,7 +941,19 @@ gvs_tuple_get_child (GVariantSerialised value,
|
||||
offset_size * (member_info->i + 2),
|
||||
offset_size);
|
||||
|
||||
if (start < end && end <= value.size)
|
||||
/* The child should not extend into the offset table. */
|
||||
if (index_ != g_variant_type_info_n_members (value.type_info) - 1)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
last_end = end;
|
||||
|
||||
if (start < end && end <= value.size && end <= last_end)
|
||||
{
|
||||
child.data = value.data + start;
|
||||
child.size = end - start;
|
||||
@ -1042,6 +1072,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);
|
||||
|
||||
@ -1065,6 +1096,9 @@ gvs_tuple_is_normal (GVariantSerialised value)
|
||||
break;
|
||||
|
||||
case G_VARIANT_MEMBER_ENDING_OFFSET:
|
||||
if (offset_ptr < offset_size)
|
||||
return FALSE;
|
||||
|
||||
offset_ptr -= offset_size;
|
||||
|
||||
if (offset_ptr < offset)
|
||||
@ -1168,8 +1202,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 */
|
||||
@ -1177,8 +1213,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);
|
||||
@ -1190,6 +1228,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;
|
||||
}
|
||||
@ -1231,10 +1270,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);
|
||||
@ -1552,6 +1594,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))
|
||||
@ -1600,6 +1644,7 @@ g_variant_serialiser_is_string (gconstpointer data,
|
||||
const gchar *expected_end;
|
||||
const gchar *end;
|
||||
|
||||
/* Strings must end with a nul terminator. */
|
||||
if (size == 0)
|
||||
return FALSE;
|
||||
|
||||
@ -1608,7 +1653,7 @@ g_variant_serialiser_is_string (gconstpointer data,
|
||||
if (*expected_end != '\0')
|
||||
return FALSE;
|
||||
|
||||
g_utf8_validate (data, size, &end);
|
||||
_g_utf8_validate_len (data, size, &end);
|
||||
|
||||
return end == expected_end;
|
||||
}
|
||||
@ -1673,7 +1718,9 @@ g_variant_serialiser_is_object_path (gconstpointer data,
|
||||
* Performs the checks for being a valid string.
|
||||
*
|
||||
* Also, ensures that @data is a valid D-Bus type signature, as per the
|
||||
* D-Bus specification.
|
||||
* D-Bus specification. Note that this means the empty string is valid, as the
|
||||
* D-Bus specification defines a signature as “zero or more single complete
|
||||
* types”.
|
||||
*/
|
||||
gboolean
|
||||
g_variant_serialiser_is_signature (gconstpointer data,
|
||||
|
@ -28,6 +28,7 @@ typedef struct
|
||||
GVariantTypeInfo *type_info;
|
||||
guchar *data;
|
||||
gsize size;
|
||||
gsize depth; /* same semantics as GVariant.depth */
|
||||
} GVariantSerialised;
|
||||
|
||||
/* deserialisation */
|
||||
|
@ -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,6 +198,76 @@ g_variant_type_check (const GVariantType *type)
|
||||
#endif
|
||||
}
|
||||
|
||||
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')
|
||||
return FALSE;
|
||||
|
||||
switch (*string++)
|
||||
{
|
||||
case '(':
|
||||
while (string == limit || *string != ')')
|
||||
{
|
||||
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 (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':
|
||||
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:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (endptr != NULL)
|
||||
*endptr = string;
|
||||
if (depth != NULL)
|
||||
*depth = max_depth;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_variant_type_string_scan:
|
||||
* @string: a pointer to any string
|
||||
@ -223,46 +297,37 @@ g_variant_type_string_scan (const gchar *string,
|
||||
const gchar *limit,
|
||||
const gchar **endptr)
|
||||
{
|
||||
g_return_val_if_fail (string != NULL, FALSE);
|
||||
return variant_type_string_scan_internal (string, limit, endptr, NULL,
|
||||
G_VARIANT_MAX_RECURSION_DEPTH);
|
||||
}
|
||||
|
||||
if (string == limit || *string == '\0')
|
||||
return FALSE;
|
||||
/* < 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 (backported to 2.56)
|
||||
*/
|
||||
gsize
|
||||
g_variant_type_string_get_depth_ (const gchar *type_string)
|
||||
{
|
||||
const gchar *endptr;
|
||||
gsize depth = 0;
|
||||
|
||||
switch (*string++)
|
||||
{
|
||||
case '(':
|
||||
while (string == limit || *string != ')')
|
||||
if (!g_variant_type_string_scan (string, limit, &string))
|
||||
return FALSE;
|
||||
g_return_val_if_fail (type_string != NULL, 0);
|
||||
|
||||
string++;
|
||||
break;
|
||||
if (!variant_type_string_scan_internal (type_string, NULL, &endptr, &depth,
|
||||
G_VARIANT_MAX_RECURSION_DEPTH) ||
|
||||
*endptr != '\0')
|
||||
return 0;
|
||||
|
||||
case '{':
|
||||
if (string == limit || *string == '\0' || /* { */
|
||||
!strchr ("bynqihuxtdsog?", *string++) || /* key */
|
||||
!g_variant_type_string_scan (string, limit, &string) || /* value */
|
||||
string == limit || *string++ != '}') /* } */
|
||||
return FALSE;
|
||||
|
||||
break;
|
||||
|
||||
case 'm': case 'a':
|
||||
return g_variant_type_string_scan (string, limit, endptr);
|
||||
|
||||
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':
|
||||
break;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (endptr != NULL)
|
||||
*endptr = string;
|
||||
|
||||
return TRUE;
|
||||
return depth;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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_56
|
||||
gsize g_variant_type_string_get_depth_ (const gchar *type_string);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@ -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 (backported to 2.56)
|
||||
*/
|
||||
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 *
|
||||
|
@ -130,6 +130,8 @@ GLIB_AVAILABLE_IN_ALL
|
||||
void g_variant_type_info_query (GVariantTypeInfo *typeinfo,
|
||||
guint *alignment,
|
||||
gsize *size);
|
||||
GLIB_AVAILABLE_IN_2_56
|
||||
gsize g_variant_type_info_query_depth (GVariantTypeInfo *typeinfo);
|
||||
|
||||
/* array */
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
|
@ -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);
|
||||
@ -4599,6 +4659,204 @@ test_stack_dict_init (void)
|
||||
g_variant_unref (variant);
|
||||
}
|
||||
|
||||
/* Test checking arbitrary binary data for normal form. This time, it’s a tuple
|
||||
* with invalid element ends. */
|
||||
static void
|
||||
test_normal_checking_tuples (void)
|
||||
{
|
||||
const guint8 data[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||
'a', '(', 'a', 'o', 'a', 'o', 'a', 'a', 'o', 'a', 'a', 'o', ')'
|
||||
};
|
||||
gsize size = sizeof (data);
|
||||
GVariant *variant = NULL;
|
||||
GVariant *normal_variant = NULL;
|
||||
|
||||
variant = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT, data, 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);
|
||||
}
|
||||
|
||||
/* Check that deeply nested variants are not considered in normal form when
|
||||
* deserialised from untrusted data.*/
|
||||
static void
|
||||
test_recursion_limits_variant_in_variant (void)
|
||||
{
|
||||
GVariant *wrapper_variant = NULL;
|
||||
gsize i;
|
||||
GBytes *bytes = NULL;
|
||||
GVariant *deserialised_variant = NULL;
|
||||
|
||||
/* Construct a hierarchy of variants, containing a single string. This is just
|
||||
* below the maximum recursion level, as a series of nested variant types. */
|
||||
wrapper_variant = g_variant_new_string ("hello");
|
||||
|
||||
for (i = 0; i < G_VARIANT_MAX_RECURSION_DEPTH - 1; i++)
|
||||
wrapper_variant = g_variant_new_variant (g_steal_pointer (&wrapper_variant));
|
||||
|
||||
/* Serialise and deserialise it as untrusted data, to force normalisation. */
|
||||
bytes = g_variant_get_data_as_bytes (wrapper_variant);
|
||||
deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT,
|
||||
bytes, FALSE);
|
||||
g_assert_nonnull (deserialised_variant);
|
||||
g_assert_true (g_variant_is_normal_form (deserialised_variant));
|
||||
|
||||
g_bytes_unref (bytes);
|
||||
g_variant_unref (deserialised_variant);
|
||||
|
||||
/* Wrap it once more. Normalisation should now fail. */
|
||||
wrapper_variant = g_variant_new_variant (g_steal_pointer (&wrapper_variant));
|
||||
|
||||
bytes = g_variant_get_data_as_bytes (wrapper_variant);
|
||||
deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT,
|
||||
bytes, FALSE);
|
||||
g_assert_nonnull (deserialised_variant);
|
||||
g_assert_false (g_variant_is_normal_form (deserialised_variant));
|
||||
|
||||
g_variant_unref (deserialised_variant);
|
||||
|
||||
/* Deserialise it again, but trusted this time. This should succeed. */
|
||||
deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT,
|
||||
bytes, TRUE);
|
||||
g_assert_nonnull (deserialised_variant);
|
||||
g_assert_true (g_variant_is_normal_form (deserialised_variant));
|
||||
|
||||
g_bytes_unref (bytes);
|
||||
g_variant_unref (deserialised_variant);
|
||||
g_variant_unref (wrapper_variant);
|
||||
}
|
||||
|
||||
/* Check that deeply nested arrays are not considered in normal form when
|
||||
* deserialised from untrusted data after being wrapped in a variant. This is
|
||||
* worth testing, because neither the deeply nested array, nor the variant,
|
||||
* have a static #GVariantType which is too deep — only when nested together do
|
||||
* they become too deep. */
|
||||
static void
|
||||
test_recursion_limits_array_in_variant (void)
|
||||
{
|
||||
GVariant *child_variant = NULL;
|
||||
GVariant *wrapper_variant = NULL;
|
||||
gsize i;
|
||||
GBytes *bytes = NULL;
|
||||
GVariant *deserialised_variant = NULL;
|
||||
|
||||
/* Construct a hierarchy of arrays, containing a single string. This is just
|
||||
* below the maximum recursion level, all in a single definite type. */
|
||||
child_variant = g_variant_new_string ("hello");
|
||||
|
||||
for (i = 0; i < G_VARIANT_MAX_RECURSION_DEPTH - 1; i++)
|
||||
child_variant = g_variant_new_array (NULL, &child_variant, 1);
|
||||
|
||||
/* Serialise and deserialise it as untrusted data, to force normalisation. */
|
||||
bytes = g_variant_get_data_as_bytes (child_variant);
|
||||
deserialised_variant = g_variant_new_from_bytes (g_variant_get_type (child_variant),
|
||||
bytes, FALSE);
|
||||
g_assert_nonnull (deserialised_variant);
|
||||
g_assert_true (g_variant_is_normal_form (deserialised_variant));
|
||||
|
||||
g_bytes_unref (bytes);
|
||||
g_variant_unref (deserialised_variant);
|
||||
|
||||
/* Wrap it in a variant. Normalisation should now fail. */
|
||||
wrapper_variant = g_variant_new_variant (g_steal_pointer (&child_variant));
|
||||
|
||||
bytes = g_variant_get_data_as_bytes (wrapper_variant);
|
||||
deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT,
|
||||
bytes, FALSE);
|
||||
g_assert_nonnull (deserialised_variant);
|
||||
g_assert_false (g_variant_is_normal_form (deserialised_variant));
|
||||
|
||||
g_variant_unref (deserialised_variant);
|
||||
|
||||
/* Deserialise it again, but trusted this time. This should succeed. */
|
||||
deserialised_variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT,
|
||||
bytes, TRUE);
|
||||
g_assert_nonnull (deserialised_variant);
|
||||
g_assert_true (g_variant_is_normal_form (deserialised_variant));
|
||||
|
||||
g_bytes_unref (bytes);
|
||||
g_variant_unref (deserialised_variant);
|
||||
g_variant_unref (wrapper_variant);
|
||||
}
|
||||
|
||||
/* Test that an array with invalidly large values in its offset table is
|
||||
* normalised successfully without looping infinitely. */
|
||||
static void
|
||||
test_normal_checking_array_offsets (void)
|
||||
{
|
||||
const guint8 data[] = {
|
||||
0x07, 0xe5, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00,
|
||||
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'g',
|
||||
};
|
||||
gsize size = sizeof (data);
|
||||
GVariant *variant = NULL;
|
||||
GVariant *normal_variant = NULL;
|
||||
|
||||
variant = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT, data, 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);
|
||||
}
|
||||
|
||||
/* Test that a tuple with invalidly large values in its offset table is
|
||||
* normalised successfully without looping infinitely. */
|
||||
static void
|
||||
test_normal_checking_tuple_offsets (void)
|
||||
{
|
||||
const guint8 data[] = {
|
||||
0x07, 0xe5, 0x00, 0x07, 0x00, 0x07,
|
||||
'(', 'a', 's', 'a', 's', 'a', 's', 'a', 's', 'a', 's', 'a', 's', ')',
|
||||
};
|
||||
gsize size = sizeof (data);
|
||||
GVariant *variant = NULL;
|
||||
GVariant *normal_variant = NULL;
|
||||
|
||||
variant = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT, data, 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);
|
||||
}
|
||||
|
||||
/* Test that an empty object path is normalised successfully to the base object
|
||||
* path, ‘/’. */
|
||||
static void
|
||||
test_normal_checking_empty_object_path (void)
|
||||
{
|
||||
const guint8 data[] = {
|
||||
0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
'(', 'h', '(', 'a', 'i', 'a', 'b', 'i', 'o', ')', ')',
|
||||
};
|
||||
gsize size = sizeof (data);
|
||||
GVariant *variant = NULL;
|
||||
GVariant *normal_variant = NULL;
|
||||
|
||||
variant = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT, data, 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)
|
||||
{
|
||||
@ -4607,6 +4865,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);
|
||||
@ -4660,5 +4922,20 @@ main (int argc, char **argv)
|
||||
|
||||
g_test_add_func ("/gvariant/stack-builder-init", test_stack_builder_init);
|
||||
g_test_add_func ("/gvariant/stack-dict-init", test_stack_dict_init);
|
||||
|
||||
g_test_add_func ("/gvariant/normal-checking/tuples",
|
||||
test_normal_checking_tuples);
|
||||
g_test_add_func ("/gvariant/normal-checking/array-offsets",
|
||||
test_normal_checking_array_offsets);
|
||||
g_test_add_func ("/gvariant/normal-checking/tuple-offsets",
|
||||
test_normal_checking_tuple_offsets);
|
||||
g_test_add_func ("/gvariant/normal-checking/empty-object-path",
|
||||
test_normal_checking_empty_object_path);
|
||||
|
||||
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 ();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user