From 8ddb6793f01fd5b456c5d357082768e4ac6cfb69 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 12 Sep 2014 15:23:07 +0100 Subject: [PATCH 11/12] New test for fd-passing Bug: https://bugs.freedesktop.org/show_bug.cgi?id=83622 Reviewed-by: Alban Crequy --- configure.ac | 4 +- test/Makefile.am | 5 + test/fdpass.c | 851 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 858 insertions(+), 2 deletions(-) create mode 100644 test/fdpass.c diff --git a/configure.ac b/configure.ac index ed19394..ca00283 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 @@ -591,7 +591,7 @@ AC_DEFINE_UNQUOTED([DBUS_USE_SYNC], [$have_sync], [Use the gcc __sync extension] AC_SEARCH_LIBS(socket,[socket network]) AC_CHECK_FUNC(gethostbyname,,[AC_CHECK_LIB(nsl,gethostbyname)]) -AC_CHECK_FUNCS(vsnprintf vasprintf nanosleep usleep setenv clearenv unsetenv socketpair getgrouplist fpathconf setrlimit poll setlocale localeconv strtoll strtoull issetugid getresuid) +AC_CHECK_FUNCS([vsnprintf vasprintf nanosleep usleep setenv clearenv unsetenv socketpair getgrouplist fpathconf setrlimit poll setlocale localeconv strtoll strtoull issetugid getresuid getrlimit]) AC_CHECK_HEADERS([syslog.h]) if test "x$ac_cv_header_syslog_h" = "xyes"; then diff --git a/test/Makefile.am b/test/Makefile.am index cec5cda..ec7bb3d 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) + EXTRA_DIST = dbus-test-runner testexecdir = $(libdir)/dbus-1.0/test @@ -126,6 +130,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..e674a9b --- /dev/null +++ b/test/fdpass.c @@ -0,0 +1,851 @@ +/* + * 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 + +#include + +#ifdef G_OS_UNIX +# include +# include +# ifdef HAVE_SYS_RESOURCE_H +# include +# endif +# include +# include +# include +# include +#endif + +#include "test-utils.h" + +/* Arbitrary; included here to avoid relying on the default */ +#define MAX_MESSAGE_UNIX_FDS 20 +/* This test won't work on Linux unless this is true. */ +_DBUS_STATIC_ASSERT (MAX_MESSAGE_UNIX_FDS <= 253); + +/* 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 + +/* Linux won't allow more than 253 fds per sendmsg(). */ +#define TOO_MANY_FDS 255 +_DBUS_STATIC_ASSERT (MAX_MESSAGE_UNIX_FDS < TOO_MANY_FDS); + +/* 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 + */ + +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 inline void my_test_skip (const gchar *s) +{ + g_message ("SKIP: %s", s); +} +#endif + +#ifdef HAVE_UNIX_FD_PASSING + +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 +left_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, + left_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/null", O_RDONLY); + + /* this should succeed on any reasonable Unix */ + if (f->fd_before < 0) + g_error ("cannot open /dev/null for reading: %s", g_strerror (errno)); + + _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; + + 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)); + + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + g_assert (dbus_connection_get_is_connected (f->left_client_conn)); + g_assert (dbus_connection_get_is_connected (f->left_server_conn)); +#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; + + 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); + + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + g_assert (dbus_connection_get_is_connected (f->left_client_conn)); + g_assert (dbus_connection_get_is_connected (f->left_server_conn)); +#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; + + 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) || + dbus_connection_get_is_connected (f->left_server_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); + + /* The intended victim is unaffected by the left connection's + * misbehaviour. */ + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); +#else + g_test_skip ("fd-passing not supported on this platform"); +#endif +} + +static void +test_too_many_split (Fixture *f, + gconstpointer data) +{ +#ifdef HAVE_UNIX_FD_PASSING + DBusMessage *outgoing; + int i; + int left_client_socket; + char *payload; + int payload_len; + DBusString buffer; + int fds[TOO_MANY_FDS]; + int done; + + /* This test deliberately pushes up against OS limits, so skip it + * if we don't have enough fds. 4 times the maximum per message + * ought to be enough: that will cover the message, the dup'd fds + * we actually send, the copy that we potentially receive, and some + * spare capacity for everything else. */ +#ifdef HAVE_GETRLIMIT + struct rlimit lim; + + if (getrlimit (RLIMIT_NOFILE, &lim) == 0) + { + if (lim.rlim_cur != RLIM_INFINITY && + lim.rlim_cur < 4 * TOO_MANY_FDS) + { + g_test_skip ("not enough RLIMIT_NOFILE"); + return; + } + } +#endif + + test_connect (f, data); + + outgoing = dbus_message_new_signal ("/com/example/Hello", + "com.example.Hello", "Greeting"); + g_assert (outgoing != NULL); + + /* TOO_MANY_FDS fds are far too many: in particular, Linux doesn't allow + * sending this many in a single sendmsg(). libdbus never splits + * a message between two sendmsg() calls if it can help it, and + * in particular it always sends all the fds with the first sendmsg(), + * but malicious senders might not be so considerate. */ + for (i = 0; i < TOO_MANY_FDS; i++) + { + if (!dbus_message_append_args (outgoing, + DBUS_TYPE_UNIX_FD, &f->fd_before, + DBUS_TYPE_INVALID)) + oom ("appending fd"); + } + + /* This probably shouldn't work for messages with fds, but it does, + * which is convenient for this sort of trickery. */ + if (!dbus_message_marshal (outgoing, &payload, &payload_len)) + oom ("marshalling message"); + + _dbus_string_init_const_len (&buffer, payload, payload_len); + + for (i = 0; i < TOO_MANY_FDS; i++) + { + fds[i] = dup (f->fd_before); + + if (fds[i] < 0) + g_error ("could not dup fd: %s", g_strerror (errno)); + } + + /* This is blatant cheating, and the API documentation specifically + * tells you not use this function in this way. Never do this + * in application code. */ + if (!dbus_connection_get_socket (f->left_client_conn, &left_client_socket)) + g_error ("'unix:' DBusConnection should have had a socket"); + + /* Just to be sure that we're at a message boundary. */ + dbus_connection_flush (f->left_client_conn); + + /* We have too many fds for one sendmsg(), so send the first half + * (rounding down if odd) with the first byte... */ + done = _dbus_write_socket_with_unix_fds (left_client_socket, &buffer, 0, 1, + &fds[0], TOO_MANY_FDS / 2); + + if (done < 0) + g_error ("could not send first byte and first batch of fds: %s", + g_strerror (errno)); + + /* ... and the second half (rounding up if odd) with the rest of + * the message */ + done = _dbus_write_socket_with_unix_fds (left_client_socket, &buffer, 1, + payload_len - 1, &fds[TOO_MANY_FDS / 2], + TOO_MANY_FDS - (TOO_MANY_FDS / 2)); + + if (done < 0) + { + g_error ("could not send rest of message and rest of fds: %s", + g_strerror (errno)); + } + else if (done < payload_len - 1) + { + /* For simplicity, assume the socket buffer is big enough for the + * whole message, which should be < 2 KiB. If this fails on some + * OS, redo this test code to use a proper loop like the real + * libdbus does. */ + g_error ("short write in sendmsg(), fix this test: %d/%d", + done, payload_len - 1); + } + + dbus_free (payload); + + for (i = 0; i < TOO_MANY_FDS; i++) + close (fds[i]); + + dbus_message_unref (outgoing); + + /* The sender is unceremoniously disconnected. */ + while (dbus_connection_get_is_connected (f->left_client_conn) || + dbus_connection_get_is_connected (f->left_server_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); + + /* The intended victim is unaffected by the left connection's + * misbehaviour. */ + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); +#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; + + 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); + } + + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + g_assert (dbus_connection_get_is_connected (f->left_client_conn)); + g_assert (dbus_connection_get_is_connected (f->left_server_conn)); +#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; + + 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) || + dbus_connection_get_is_connected (f->left_server_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); + + /* The intended victim is unaffected by the left connection's + * misbehaviour. */ + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + } + 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); + + g_assert (dbus_connection_get_is_connected (f->right_client_conn)); + g_assert (dbus_connection_get_is_connected (f->right_server_conn)); + g_assert (dbus_connection_get_is_connected (f->left_client_conn)); + g_assert (dbus_connection_get_is_connected (f->left_server_conn)); + } +#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/plus1", Fixture, GUINT_TO_POINTER (1), setup, + test_too_many, teardown); + g_test_add ("/too-many/plus2", Fixture, GUINT_TO_POINTER (2), setup, + test_too_many, teardown); + g_test_add ("/too-many/plus17", Fixture, GUINT_TO_POINTER (17), setup, + test_too_many, teardown); + + g_test_add ("/too-many/split", Fixture, NULL, setup, + test_too_many_split, 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