From 6dbd100c9a618484536a8f09dcca228f423e6263 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 8 Sep 2014 16:33:50 +0100 Subject: [PATCH] New test for fd-passing --- configure.ac | 2 +- test/Makefile.am | 5 + test/fdpass.c | 698 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 704 insertions(+), 1 deletion(-) create mode 100644 test/fdpass.c diff --git a/configure.ac b/configure.ac index 1aaa1cb..1958586 100644 --- a/configure.ac +++ b/configure.ac @@ -208,7 +208,7 @@ fi # or binaries. AC_DEFINE([GLIB_VERSION_MIN_REQUIRED], [GLIB_VERSION_2_26], [Ignore post-2.26 deprecations]) -AC_DEFINE([GLIB_VERSION_MAX_ALLOWED], [GLIB_VERSION_2_32], [Prevent post-2.32 APIs]) +AC_DEFINE([GLIB_VERSION_MAX_ALLOWED], [GLIB_VERSION_2_38], [Prevent post-2.38 APIs]) with_glib=yes diff --git a/test/Makefile.am b/test/Makefile.am index 5d4701d..f81c303 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -119,6 +119,10 @@ test_syslog_SOURCES = internals/syslog.c test_syslog_CPPFLAGS = $(static_cppflags) test_syslog_LDADD = libdbus-testutils-internal.la $(GLIB_LIBS) +test_fdpass_SOURCES = fdpass.c +test_fdpass_CPPFLAGS = $(static_cppflags) +test_fdpass_LDADD = libdbus-testutils-internal.la $(GLIB_LIBS) + manual_dir_iter_SOURCES = manual-dir-iter.c manual_dir_iter_CPPFLAGS = $(static_cppflags) manual_dir_iter_LDADD = $(top_builddir)/dbus/libdbus-internal.la @@ -130,6 +134,7 @@ testexecdir = $(libdir)/dbus-1.0/test testexec_PROGRAMS = installable_tests = \ + test-fdpass \ test-shell \ test-printf \ $(NULL) diff --git a/test/fdpass.c b/test/fdpass.c new file mode 100644 index 0000000..cdbdcaf --- /dev/null +++ b/test/fdpass.c @@ -0,0 +1,698 @@ +/* + * Copyright © 2010-2012 Nokia Corporation + * Copyright © 2014 Collabora Ltd. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +#include + +#include +#include + +#include + +#ifdef G_OS_UNIX +# include +# include +# include +# include +# include +#endif + +#include "test-utils.h" + +/* Arbitrary; included here to avoid relying on the default */ +#define MAX_MESSAGE_UNIX_FDS 20 + +/* Arbitrary; included here to avoid relying on the default. */ +#define MAX_INCOMING_UNIX_FDS (MAX_MESSAGE_UNIX_FDS * 4) + +/* Arbitrary, except that MAX_MESSAGE_UNIX_FDS * SOME_MESSAGES must be + * less than the process's file descriptor limit. */ +#define SOME_MESSAGES 50 + +/* As in test/relay.c, this is a miniature dbus-daemon: we relay messages + * from the client on the left to the client on the right. + * + * left socket left dispatch right socket right + * client ===========> server --------------> server ===========> client + * conn conn conn conn + */ + +#ifdef HAVE_UNIX_FD_PASSING +typedef struct { + TestMainContext *ctx; + DBusError e; + + DBusServer *server; + + DBusConnection *left_client_conn; + DBusConnection *left_server_conn; + + DBusConnection *right_server_conn; + DBusConnection *right_client_conn; + /* queue of DBusMessage received by right_client_conn */ + GQueue messages; + + int fd_before; +} Fixture; + +#if !GLIB_CHECK_VERSION (2, 38, 0) +#define g_test_skip(s) my_test_skip (s) +static void my_test_skip (const gchar *s) +{ + g_message ("SKIP: %s", s); +} +#endif + +static void oom (const gchar *doing) G_GNUC_NORETURN; + +static void +oom (const gchar *doing) +{ + g_error ("out of memory (%s)", doing); +} + +static void +assert_no_error (const DBusError *e) +{ + if (G_UNLIKELY (dbus_error_is_set (e))) + g_error ("expected success but got error: %s: %s", e->name, e->message); +} + +static DBusHandlerResult +server_message_cb (DBusConnection *server_conn, + DBusMessage *message, + void *data) +{ + Fixture *f = data; + + g_assert (server_conn == f->left_server_conn); + g_assert (f->right_server_conn != NULL); + + dbus_connection_send (f->right_server_conn, message, NULL); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult +right_client_message_cb (DBusConnection *client_conn, + DBusMessage *message, + void *data) +{ + Fixture *f = data; + + g_assert (client_conn == f->right_client_conn); + g_queue_push_tail (&f->messages, dbus_message_ref (message)); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void +new_conn_cb (DBusServer *server, + DBusConnection *server_conn, + void *data) +{ + Fixture *f = data; + + dbus_connection_set_max_message_unix_fds (server_conn, + MAX_MESSAGE_UNIX_FDS); + dbus_connection_set_max_received_unix_fds (server_conn, + MAX_INCOMING_UNIX_FDS); + + if (f->left_server_conn == NULL) + { + f->left_server_conn = dbus_connection_ref (server_conn); + + if (!dbus_connection_add_filter (server_conn, + server_message_cb, f, NULL)) + oom ("adding filter"); + } + else + { + g_assert (f->right_server_conn == NULL); + f->right_server_conn = dbus_connection_ref (server_conn); + } + + test_connection_setup (f->ctx, server_conn); +} + +static void +test_connect (Fixture *f, + gconstpointer data G_GNUC_UNUSED) +{ + char *address; + + g_assert (f->left_server_conn == NULL); + g_assert (f->right_server_conn == NULL); + + address = dbus_server_get_address (f->server); + g_assert (address != NULL); + + f->left_client_conn = dbus_connection_open_private (address, &f->e); + assert_no_error (&f->e); + g_assert (f->left_client_conn != NULL); + test_connection_setup (f->ctx, f->left_client_conn); + + /* The left client connection is allowed to behave abusively. */ + dbus_connection_set_max_message_unix_fds (f->left_client_conn, 1000); + dbus_connection_set_max_received_unix_fds (f->left_client_conn, 1000000); + + while (f->left_server_conn == NULL) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + f->right_client_conn = dbus_connection_open_private (address, &f->e); + assert_no_error (&f->e); + g_assert (f->right_client_conn != NULL); + test_connection_setup (f->ctx, f->right_client_conn); + + dbus_free (address); + + while (f->right_server_conn == NULL) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + if (!dbus_connection_add_filter (f->right_client_conn, + right_client_message_cb, f, NULL)) + oom ("adding filter"); + + /* The right client connection is allowed to queue all the messages. */ + dbus_connection_set_max_message_unix_fds (f->right_client_conn, 1000); + dbus_connection_set_max_received_unix_fds (f->right_client_conn, 1000000); + + while (!dbus_connection_get_is_authenticated (f->left_client_conn) || + !dbus_connection_get_is_authenticated (f->right_client_conn) || + !dbus_connection_get_is_authenticated (f->left_server_conn) || + !dbus_connection_get_is_authenticated (f->right_server_conn)) + { + g_printerr ("*"); + test_main_context_iterate (f->ctx, TRUE); + } + + if (!dbus_connection_can_send_type (f->left_client_conn, + DBUS_TYPE_UNIX_FD)) + g_error ("left client connection cannot send Unix fds"); + + if (!dbus_connection_can_send_type (f->left_server_conn, + DBUS_TYPE_UNIX_FD)) + g_error ("left server connection cannot send Unix fds"); + + if (!dbus_connection_can_send_type (f->right_client_conn, + DBUS_TYPE_UNIX_FD)) + g_error ("right client connection cannot send Unix fds"); + + if (!dbus_connection_can_send_type (f->right_server_conn, + DBUS_TYPE_UNIX_FD)) + g_error ("right server connection cannot send Unix fds"); +} +#endif + +static void +setup (Fixture *f, + gconstpointer data G_GNUC_UNUSED) +{ +#ifdef HAVE_UNIX_FD_PASSING + /* We assume that anything with fd-passing supports the unix: transport */ + + f->ctx = test_main_context_get (); + dbus_error_init (&f->e); + g_queue_init (&f->messages); + + f->server = dbus_server_listen ("unix:tmpdir=/tmp", &f->e); + assert_no_error (&f->e); + g_assert (f->server != NULL); + + dbus_server_set_new_connection_function (f->server, + new_conn_cb, f, NULL); + test_server_setup (f->ctx, f->server); + + f->fd_before = open ("/dev/zero", O_RDONLY); + + if (f->fd_before >= 0) + _dbus_fd_set_close_on_exec (f->fd_before); +#endif +} + +static void +test_relay (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + /* We assume that any platform with working fd-passing is POSIX, + * and therefore has open() and fstat() */ + dbus_uint32_t serial; + DBusMessage *outgoing, *incoming; + int fd_after; + struct stat stat_before; + struct stat stat_after; + + if (f->fd_before < 0) + { + g_test_skip ("cannot open /dev/zero"); + return; + } + + test_connect (f, data); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + + if (!dbus_connection_send (f->left_client_conn, outgoing, &serial)) + oom ("sending message"); + + dbus_message_unref (outgoing); + + while (g_queue_get_length (&f->messages) < 1) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 1); + + incoming = g_queue_pop_head (&f->messages); + + g_assert (dbus_message_contains_unix_fds (incoming)); + g_assert_cmpstr (dbus_message_get_destination (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_error_name (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_interface (incoming), ==, + "com.example.Hello"); + g_assert_cmpstr (dbus_message_get_member (incoming), ==, "Greeting"); + g_assert_cmpstr (dbus_message_get_sender (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_signature (incoming), ==, + DBUS_TYPE_UNIX_FD_AS_STRING); + g_assert_cmpstr (dbus_message_get_path (incoming), ==, "/com/example/Hello"); + g_assert_cmpuint (dbus_message_get_serial (incoming), ==, serial); + + if (!dbus_message_get_args (incoming, + &f->e, + DBUS_TYPE_UNIX_FD, &fd_after, + DBUS_TYPE_INVALID)) + g_error ("%s: %s", f->e.name, f->e.message); + + assert_no_error (&f->e); + + if (fstat (f->fd_before, &stat_before) < 0) + g_error ("%s", g_strerror (errno)); + + if (fstat (fd_after, &stat_after) < 0) + g_error ("%s", g_strerror (errno)); + + /* this seems like enough to say "it's the same file" */ + g_assert_cmpint (stat_before.st_dev, ==, stat_after.st_dev); + g_assert_cmpint (stat_before.st_ino, ==, stat_after.st_ino); + g_assert_cmpint (stat_before.st_rdev, ==, stat_after.st_rdev); + + dbus_message_unref (incoming); + + if (close (fd_after) < 0) + g_error ("%s", g_strerror (errno)); +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_limit (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + dbus_uint32_t serial; + DBusMessage *outgoing, *incoming; + int i; + + if (f->fd_before < 0) + { + g_test_skip ("cannot open /dev/zero"); + return; + } + + test_connect (f, data); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + for (i = 0; i < MAX_MESSAGE_UNIX_FDS; i++) + { + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + + if (!dbus_connection_send (f->left_client_conn, outgoing, &serial)) + oom ("sending message"); + + dbus_message_unref (outgoing); + + while (g_queue_get_length (&f->messages) < 1) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 1); + + incoming = g_queue_pop_head (&f->messages); + + g_assert (dbus_message_contains_unix_fds (incoming)); + g_assert_cmpstr (dbus_message_get_destination (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_error_name (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_interface (incoming), ==, + "com.example.Hello"); + g_assert_cmpstr (dbus_message_get_member (incoming), ==, "Greeting"); + g_assert_cmpstr (dbus_message_get_sender (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_path (incoming), ==, "/com/example/Hello"); + g_assert_cmpuint (dbus_message_get_serial (incoming), ==, serial); + + dbus_message_unref (incoming); +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_too_many (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + DBusMessage *outgoing; + int i; + + if (f->fd_before < 0) + { + g_test_skip ("cannot open /dev/zero"); + return; + } + + test_connect (f, data); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + for (i = 0; i < MAX_MESSAGE_UNIX_FDS + GPOINTER_TO_UINT (data); i++) + { + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + + if (!dbus_connection_send (f->left_client_conn, outgoing, NULL)) + oom ("sending message"); + + dbus_message_unref (outgoing); + + /* The sender is unceremoniously disconnected. */ + while (dbus_connection_get_is_connected (f->left_client_conn)) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + /* The message didn't get through without its fds. */ + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 0); +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_flood (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + int i, j; + DBusMessage *outgoing[SOME_MESSAGES]; + dbus_uint32_t serial; + + if (f->fd_before < 0) + { + g_test_skip ("cannot open /dev/zero"); + return; + } + + test_connect (f, data); + + for (j = 0; j < SOME_MESSAGES; j++) + { + outgoing[j] = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing[j] != NULL); + + for (i = 0; i < GPOINTER_TO_UINT (data); i++) + { + if (!dbus_message_append_args (outgoing[j], + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + } + + /* This is in its own loop so we do it as fast as possible */ + for (j = 0; j < SOME_MESSAGES; j++) + { + if (!dbus_connection_send (f->left_client_conn, outgoing[j], &serial)) + oom ("sending message"); + } + + for (j = 0; j < SOME_MESSAGES; j++) + { + dbus_message_unref (outgoing[j]); + } + + while (g_queue_get_length (&f->messages) < SOME_MESSAGES) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, SOME_MESSAGES); + + for (j = 0; j < SOME_MESSAGES; j++) + { + DBusMessage *incoming; + + incoming = g_queue_pop_head (&f->messages); + + g_assert (dbus_message_contains_unix_fds (incoming)); + g_assert_cmpstr (dbus_message_get_destination (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_error_name (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_interface (incoming), ==, + "com.example.Hello"); + g_assert_cmpstr (dbus_message_get_member (incoming), ==, "Greeting"); + g_assert_cmpstr (dbus_message_get_sender (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_path (incoming), ==, "/com/example/Hello"); + + dbus_message_unref (incoming); + } +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_odd_limit (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + DBusMessage *outgoing; + int i; + + if (f->fd_before < 0) + { + g_test_skip ("cannot open /dev/zero"); + return; + } + + test_connect (f, data); + dbus_connection_set_max_message_unix_fds (f->left_server_conn, 7); + dbus_connection_set_max_message_unix_fds (f->right_server_conn, 7); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + for (i = 0; i < 7 + GPOINTER_TO_INT (data); i++) + { + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + + if (!dbus_connection_send (f->left_client_conn, outgoing, NULL)) + oom ("sending message"); + + dbus_message_unref (outgoing); + + if (GPOINTER_TO_INT (data) > 0) + { + /* The sender is unceremoniously disconnected. */ + while (dbus_connection_get_is_connected (f->left_client_conn)) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + /* The message didn't get through without its fds. */ + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 0); + } + else + { + DBusMessage *incoming; + + /* We're at or under the limit. The message gets through intact. */ + while (g_queue_get_length (&f->messages) < 1) + { + g_print ("."); + test_main_context_iterate (f->ctx, TRUE); + } + + g_assert_cmpuint (g_queue_get_length (&f->messages), ==, 1); + + incoming = g_queue_pop_head (&f->messages); + + g_assert (dbus_message_contains_unix_fds (incoming)); + g_assert_cmpstr (dbus_message_get_destination (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_error_name (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_interface (incoming), ==, + "com.example.Hello"); + g_assert_cmpstr (dbus_message_get_member (incoming), ==, "Greeting"); + g_assert_cmpstr (dbus_message_get_sender (incoming), ==, NULL); + g_assert_cmpstr (dbus_message_get_path (incoming), ==, + "/com/example/Hello"); + + dbus_message_unref (incoming); + } +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +teardown (Fixture *f, + gconstpointer data G_GNUC_UNUSED) +{ +#ifdef HAVE_UNIX_FD_PASSING + if (f->left_client_conn != NULL) + { + dbus_connection_close (f->left_client_conn); + dbus_connection_unref (f->left_client_conn); + f->left_client_conn = NULL; + } + + if (f->right_client_conn != NULL) + { + dbus_connection_close (f->right_client_conn); + dbus_connection_unref (f->right_client_conn); + f->right_client_conn = NULL; + } + + if (f->left_server_conn != NULL) + { + dbus_connection_close (f->left_server_conn); + dbus_connection_unref (f->left_server_conn); + f->left_server_conn = NULL; + } + + if (f->right_server_conn != NULL) + { + dbus_connection_close (f->right_server_conn); + dbus_connection_unref (f->right_server_conn); + f->right_server_conn = NULL; + } + + if (f->server != NULL) + { + dbus_server_disconnect (f->server); + dbus_server_unref (f->server); + f->server = NULL; + } + + test_main_context_unref (f->ctx); + + if (f->fd_before >= 0 && close (f->fd_before) < 0) + g_error ("%s", g_strerror (errno)); +#endif +} + +int +main (int argc, + char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + + g_test_add ("/relay", Fixture, NULL, setup, + test_relay, teardown); + g_test_add ("/limit", Fixture, NULL, setup, + test_limit, teardown); + + g_test_add ("/too-many", Fixture, GUINT_TO_POINTER (1), setup, + test_too_many, teardown); + g_test_add ("/too-many", Fixture, GUINT_TO_POINTER (2), setup, + test_too_many, teardown); + g_test_add ("/too-many", Fixture, GUINT_TO_POINTER (17), setup, + test_too_many, teardown); + + g_test_add ("/flood/1", Fixture, GUINT_TO_POINTER (1), + setup, test_flood, teardown); +#if MAX_MESSAGE_UNIX_FDS >= 2 + g_test_add ("/flood/half-limit", Fixture, + GUINT_TO_POINTER (MAX_MESSAGE_UNIX_FDS / 2), + setup, test_flood, teardown); +#endif +#if MAX_MESSAGE_UNIX_FDS >= 4 + g_test_add ("/flood/over-half-limit", Fixture, + GUINT_TO_POINTER (3 * MAX_MESSAGE_UNIX_FDS / 4), + setup, test_flood, teardown); +#endif + g_test_add ("/flood/limit", Fixture, + GUINT_TO_POINTER (MAX_MESSAGE_UNIX_FDS), setup, test_flood, teardown); + + g_test_add ("/odd-limit/minus1", Fixture, GINT_TO_POINTER (-1), setup, + test_odd_limit, teardown); + g_test_add ("/odd-limit/at", Fixture, GINT_TO_POINTER (0), setup, + test_odd_limit, teardown); + g_test_add ("/odd-limit/plus1", Fixture, GINT_TO_POINTER (+1), setup, + test_odd_limit, teardown); + g_test_add ("/odd-limit/plus2", Fixture, GINT_TO_POINTER (+2), setup, + test_odd_limit, teardown); + + return g_test_run (); +} -- 2.1.0