glib/gio/tests/gdbus-method-invocation.c
Alex Richardson 994f96fb2b tests/gdbus-method-invocation: Fix missing g_variant_new() argument
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.
2022-12-16 10:28:50 +00:00

409 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 dont 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);
/* Dont 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
* doesnt expect a result first; message ordering should ensure that
* its completed by the time the second call completes, so we dont
* have to account for it separately.
*
* Thats 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 ();
}