mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-26 07:26:15 +01:00
994f96fb2b
I noticed this when running the test on an Arm Morello system where varargs have bounds. g_variant_new() was trying to read an integer using va_arg(), but since there was no argument it resulted in a bounds errors there. On most other architectures this will just read whatever value is contained in the next argument register and is not something that ASan can detect, so it never resulted in test failures.
409 lines
14 KiB
C
409 lines
14 KiB
C
/* GIO - GLib Input, Output and Streaming Library
|
||
*
|
||
* Copyright © 2022 Endless OS Foundation, LLC
|
||
*
|
||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU Lesser General Public
|
||
* License as published by the Free Software Foundation; either
|
||
* version 2.1 of the License, or (at your option) any later version.
|
||
*
|
||
* This library is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
* Lesser General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Lesser General
|
||
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||
*
|
||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||
*/
|
||
|
||
#include <gio/gio.h>
|
||
#include <gio/gunixfdlist.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
|
||
#include "gdbus-tests.h"
|
||
|
||
static const GDBusArgInfo foo_get_fds_in_args =
|
||
{
|
||
-1,
|
||
"type",
|
||
"s",
|
||
NULL
|
||
};
|
||
static const GDBusArgInfo * const foo_get_fds_in_arg_pointers[] = {&foo_get_fds_in_args, NULL};
|
||
|
||
static const GDBusArgInfo foo_get_fds_out_args =
|
||
{
|
||
-1,
|
||
"some_fd",
|
||
"h",
|
||
NULL
|
||
};
|
||
static const GDBusArgInfo * const foo_get_fds_out_arg_pointers[] = {&foo_get_fds_out_args, NULL};
|
||
|
||
static const GDBusMethodInfo foo_method_info_wrong_return_type =
|
||
{
|
||
-1,
|
||
"WrongReturnType",
|
||
NULL, /* in args */
|
||
NULL, /* out args */
|
||
NULL /* annotations */
|
||
};
|
||
static const GDBusMethodInfo foo_method_info_close_before_returning =
|
||
{
|
||
-1,
|
||
"CloseBeforeReturning",
|
||
NULL, /* in args */
|
||
NULL, /* out args */
|
||
NULL /* annotations */
|
||
};
|
||
static const GDBusMethodInfo foo_method_info_get_fds =
|
||
{
|
||
-1,
|
||
"GetFDs",
|
||
(GDBusArgInfo **) foo_get_fds_in_arg_pointers,
|
||
(GDBusArgInfo **) foo_get_fds_out_arg_pointers,
|
||
NULL /* annotations */
|
||
};
|
||
static const GDBusMethodInfo foo_method_info_return_error =
|
||
{
|
||
-1,
|
||
"ReturnError",
|
||
NULL, /* in args */
|
||
NULL, /* out args */
|
||
NULL /* annotations */
|
||
};
|
||
static const GDBusMethodInfo * const foo_method_info_pointers[] = {
|
||
&foo_method_info_wrong_return_type,
|
||
&foo_method_info_close_before_returning,
|
||
&foo_method_info_get_fds,
|
||
&foo_method_info_return_error,
|
||
NULL
|
||
};
|
||
|
||
static const GDBusPropertyInfo foo_property_info[] =
|
||
{
|
||
{
|
||
-1,
|
||
"InvalidType",
|
||
"s",
|
||
G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
|
||
NULL
|
||
},
|
||
{
|
||
-1,
|
||
"InvalidTypeNull",
|
||
"s",
|
||
G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
|
||
NULL
|
||
},
|
||
{
|
||
-1,
|
||
"InvalidValueType",
|
||
"s",
|
||
G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
|
||
NULL
|
||
},
|
||
};
|
||
static const GDBusPropertyInfo * const foo_property_info_pointers[] =
|
||
{
|
||
&foo_property_info[0],
|
||
&foo_property_info[1],
|
||
&foo_property_info[2],
|
||
NULL
|
||
};
|
||
|
||
static const GDBusInterfaceInfo foo_interface_info =
|
||
{
|
||
-1,
|
||
"org.example.Foo",
|
||
(GDBusMethodInfo **) &foo_method_info_pointers,
|
||
NULL, /* signals */
|
||
(GDBusPropertyInfo **) &foo_property_info_pointers,
|
||
NULL, /* annotations */
|
||
};
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
test_method_invocation_return_method_call (GDBusConnection *connection,
|
||
const gchar *sender,
|
||
const gchar *object_path,
|
||
const gchar *interface_name,
|
||
const gchar *method_name,
|
||
GVariant *parameters,
|
||
GDBusMethodInvocation *invocation,
|
||
gpointer user_data)
|
||
{
|
||
gboolean no_reply = g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED;
|
||
|
||
if (g_str_equal (interface_name, "org.freedesktop.DBus.Properties") &&
|
||
g_str_equal (method_name, "Get"))
|
||
{
|
||
const gchar *iface_name, *prop_name;
|
||
|
||
g_variant_get (parameters, "(&s&s)", &iface_name, &prop_name);
|
||
g_assert_cmpstr (iface_name, ==, "org.example.Foo");
|
||
|
||
/* Do different things depending on the property name. */
|
||
if (g_str_equal (prop_name, "InvalidType"))
|
||
{
|
||
if (!no_reply)
|
||
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
|
||
"Type of return value for property 'Get' call should be '(v)' but got '(s)'");
|
||
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "this type is invalid"));
|
||
}
|
||
else if (g_str_equal (prop_name, "InvalidTypeNull"))
|
||
{
|
||
if (!no_reply)
|
||
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
|
||
"Type of return value for property 'Get' call should be '(v)' but got '()'");
|
||
g_dbus_method_invocation_return_value (invocation, NULL);
|
||
}
|
||
else if (g_str_equal (prop_name, "InvalidValueType"))
|
||
{
|
||
if (!no_reply)
|
||
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
|
||
"Value returned from property 'Get' call for 'InvalidValueType' should be 's' but is 'u'");
|
||
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(v)", g_variant_new_uint32 (123)));
|
||
}
|
||
else
|
||
{
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
g_test_assert_expected_messages ();
|
||
}
|
||
else if (g_str_equal (interface_name, "org.freedesktop.DBus.Properties") &&
|
||
g_str_equal (method_name, "Set"))
|
||
{
|
||
const gchar *iface_name, *prop_name;
|
||
GVariant *value;
|
||
|
||
g_variant_get (parameters, "(&s&sv)", &iface_name, &prop_name, &value);
|
||
g_assert_cmpstr (iface_name, ==, "org.example.Foo");
|
||
|
||
if (g_str_equal (prop_name, "InvalidType"))
|
||
{
|
||
if (!no_reply)
|
||
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
|
||
"Type of return value for property 'Set' call should be '()' but got '(s)'");
|
||
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be unit"));
|
||
}
|
||
else
|
||
{
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
g_test_assert_expected_messages ();
|
||
g_variant_unref (value);
|
||
}
|
||
else if (g_str_equal (interface_name, "org.freedesktop.DBus.Properties") &&
|
||
g_str_equal (method_name, "GetAll"))
|
||
{
|
||
const gchar *iface_name;
|
||
|
||
g_variant_get (parameters, "(&s)", &iface_name);
|
||
g_assert_cmpstr (iface_name, ==, "org.example.Foo");
|
||
|
||
if (!no_reply)
|
||
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
|
||
"Type of return value for property 'GetAll' call should be '(a{sv})' but got '(s)'");
|
||
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type"));
|
||
}
|
||
else if (g_str_equal (interface_name, "org.example.Foo") &&
|
||
g_str_equal (method_name, "WrongReturnType"))
|
||
{
|
||
if (!no_reply)
|
||
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
|
||
"Type of return value is incorrect: expected '()', got '(s)'");
|
||
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type"));
|
||
}
|
||
else if (g_str_equal (interface_name, "org.example.Foo") &&
|
||
g_str_equal (method_name, "CloseBeforeReturning"))
|
||
{
|
||
g_dbus_connection_close (connection, NULL, NULL, NULL);
|
||
|
||
g_dbus_method_invocation_return_value (invocation, NULL);
|
||
}
|
||
else if (g_str_equal (interface_name, "org.example.Foo") &&
|
||
g_str_equal (method_name, "GetFDs"))
|
||
{
|
||
const gchar *action;
|
||
GUnixFDList *list = NULL;
|
||
GError *local_error = NULL;
|
||
|
||
g_variant_get (parameters, "(&s)", &action);
|
||
|
||
list = g_unix_fd_list_new ();
|
||
g_unix_fd_list_append (list, 1, &local_error);
|
||
g_assert_no_error (local_error);
|
||
|
||
if (g_str_equal (action, "WrongNumber"))
|
||
{
|
||
g_unix_fd_list_append (list, 1, &local_error);
|
||
g_assert_no_error (local_error);
|
||
}
|
||
|
||
if (g_str_equal (action, "Valid") ||
|
||
g_str_equal (action, "WrongNumber"))
|
||
g_dbus_method_invocation_return_value_with_unix_fd_list (invocation, g_variant_new ("(h)", 0), list);
|
||
else
|
||
g_assert_not_reached ();
|
||
|
||
g_object_unref (list);
|
||
}
|
||
else if (g_str_equal (interface_name, "org.example.Foo") &&
|
||
g_str_equal (method_name, "ReturnError"))
|
||
{
|
||
g_dbus_method_invocation_return_dbus_error (invocation, "org.example.Foo", "SomeError");
|
||
}
|
||
else
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
static void
|
||
ensure_result_cb (GObject *source,
|
||
GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
GDBusConnection *connection = G_DBUS_CONNECTION (source);
|
||
GVariant *reply;
|
||
guint *n_outstanding_calls = user_data;
|
||
|
||
reply = g_dbus_connection_call_finish (connection, result, NULL);
|
||
|
||
/* We don’t care what the reply is. */
|
||
g_clear_pointer (&reply, g_variant_unref);
|
||
|
||
g_assert_cmpint (*n_outstanding_calls, >, 0);
|
||
*n_outstanding_calls = *n_outstanding_calls - 1;
|
||
}
|
||
|
||
static void
|
||
test_method_invocation_return (void)
|
||
{
|
||
GDBusConnection *connection = NULL;
|
||
GError *local_error = NULL;
|
||
guint registration_id;
|
||
const GDBusInterfaceVTable vtable = {
|
||
test_method_invocation_return_method_call, NULL, NULL, { 0 }
|
||
};
|
||
guint n_outstanding_calls = 0;
|
||
|
||
g_test_summary ("Test calling g_dbus_method_invocation_return_*() in various ways");
|
||
|
||
/* Connect to the bus. */
|
||
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error);
|
||
g_assert_no_error (local_error);
|
||
g_assert_nonnull (connection);
|
||
|
||
/* Don’t exit the test when the server closes the connection in
|
||
* CloseBeforeReturning(). */
|
||
g_dbus_connection_set_exit_on_close (connection, FALSE);
|
||
|
||
/* Register an object which we can call methods on. */
|
||
registration_id = g_dbus_connection_register_object (connection,
|
||
"/foo",
|
||
(GDBusInterfaceInfo *) &foo_interface_info,
|
||
&vtable, NULL, NULL, &local_error);
|
||
g_assert_no_error (local_error);
|
||
g_assert_cmpint (registration_id, !=, 0);
|
||
|
||
/* Test a variety of error cases */
|
||
{
|
||
const struct
|
||
{
|
||
const gchar *interface_name;
|
||
const gchar *method_name;
|
||
const gchar *parameters_string;
|
||
gboolean tests_undefined_behaviour;
|
||
}
|
||
calls[] =
|
||
{
|
||
{ "org.freedesktop.DBus.Properties", "Get", "('org.example.Foo', 'InvalidType')", TRUE },
|
||
{ "org.freedesktop.DBus.Properties", "Get", "('org.example.Foo', 'InvalidTypeNull')", TRUE },
|
||
{ "org.freedesktop.DBus.Properties", "Get", "('org.example.Foo', 'InvalidValueType')", TRUE },
|
||
{ "org.freedesktop.DBus.Properties", "Set", "('org.example.Foo', 'InvalidType', <'irrelevant'>)", TRUE },
|
||
{ "org.freedesktop.DBus.Properties", "GetAll", "('org.example.Foo',)", TRUE },
|
||
{ "org.example.Foo", "WrongReturnType", "()", TRUE },
|
||
{ "org.example.Foo", "GetFDs", "('Valid',)", FALSE },
|
||
{ "org.example.Foo", "GetFDs", "('WrongNumber',)", TRUE },
|
||
{ "org.example.Foo", "ReturnError", "()", FALSE },
|
||
{ "org.example.Foo", "CloseBeforeReturning", "()", FALSE },
|
||
};
|
||
gsize i;
|
||
|
||
for (i = 0; i < G_N_ELEMENTS (calls); i++)
|
||
{
|
||
if (calls[i].tests_undefined_behaviour && !g_test_undefined ())
|
||
{
|
||
g_test_message ("Skipping %s.%s", calls[i].interface_name, calls[i].method_name);
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
g_test_message ("Calling %s.%s", calls[i].interface_name, calls[i].method_name);
|
||
}
|
||
|
||
/* Call twice, once expecting a result and once not. Do the call which
|
||
* doesn’t expect a result first; message ordering should ensure that
|
||
* it’s completed by the time the second call completes, so we don’t
|
||
* have to account for it separately.
|
||
*
|
||
* That’s good, because the only way to get g_dbus_connection_call()
|
||
* to set %G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED is to not provide
|
||
* a callback function. */
|
||
n_outstanding_calls++;
|
||
|
||
g_dbus_connection_call (connection,
|
||
g_dbus_connection_get_unique_name (connection),
|
||
"/foo",
|
||
calls[i].interface_name,
|
||
calls[i].method_name,
|
||
g_variant_new_parsed (calls[i].parameters_string),
|
||
NULL,
|
||
G_DBUS_CALL_FLAGS_NONE,
|
||
-1,
|
||
NULL,
|
||
NULL, /* no callback */
|
||
NULL);
|
||
|
||
g_dbus_connection_call (connection,
|
||
g_dbus_connection_get_unique_name (connection),
|
||
"/foo",
|
||
calls[i].interface_name,
|
||
calls[i].method_name,
|
||
g_variant_new_parsed (calls[i].parameters_string),
|
||
NULL,
|
||
G_DBUS_CALL_FLAGS_NONE,
|
||
-1,
|
||
NULL,
|
||
ensure_result_cb,
|
||
&n_outstanding_calls);
|
||
}
|
||
}
|
||
|
||
/* Wait until all the calls are complete. */
|
||
while (n_outstanding_calls > 0)
|
||
g_main_context_iteration (NULL, TRUE);
|
||
|
||
g_dbus_connection_unregister_object (connection, registration_id);
|
||
g_object_unref (connection);
|
||
}
|
||
|
||
int
|
||
main (int argc,
|
||
char *argv[])
|
||
{
|
||
g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
|
||
|
||
g_test_add_func ("/gdbus/method-invocation/return", test_method_invocation_return);
|
||
|
||
return session_bus_run ();
|
||
}
|