mirror of
https://gitlab.freedesktop.org/dbus/dbus.git
synced 2025-12-26 06:20:14 +01:00
The /odd-limit/at test passes on 13.1 and 14.0 images, but fails on 13.1. Debugging has not given me any useful hints why this may be the case, so disable this test on 13.0 for now. This allows us to drop the ci_test_fatal: "no" override which will ensure that any FreeBSD regressions are caught.
969 lines
29 KiB
C
969 lines
29 KiB
C
/*
|
|
* 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 <config.h>
|
|
|
|
#include <dbus/dbus.h>
|
|
#include <dbus/dbus-internals.h>
|
|
#include <dbus/dbus-sysdeps.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef G_OS_UNIX
|
|
# include <dbus/dbus-sysdeps-unix.h>
|
|
|
|
# include <errno.h>
|
|
# include <fcntl.h>
|
|
# ifdef HAVE_SYS_RESOURCE_H
|
|
# include <sys/resource.h>
|
|
# endif
|
|
# include <sys/stat.h>
|
|
# include <sys/time.h>
|
|
# include <sys/types.h>
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#include "test-utils-glib.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 should be
|
|
* less than the process's file descriptor limit. */
|
|
#define SOME_MESSAGES 20
|
|
/* To cover some situations on Linux we want this to be true. */
|
|
_DBUS_STATIC_ASSERT (MAX_MESSAGE_UNIX_FDS * SOME_MESSAGES > 256);
|
|
|
|
/* 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;
|
|
gboolean skip;
|
|
|
|
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;
|
|
DBusInitialFDs *initial_fds;
|
|
} Fixture;
|
|
|
|
static void oom (const gchar *doing) G_GNUC_NORETURN;
|
|
|
|
static void
|
|
oom (const gchar *doing)
|
|
{
|
|
g_error ("out of memory (%s)", doing);
|
|
abort ();
|
|
}
|
|
|
|
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,
|
|
gboolean should_support_fds)
|
|
{
|
|
char *address;
|
|
|
|
if (f->skip)
|
|
return;
|
|
|
|
f->initial_fds = _dbus_check_fdleaks_enter ();
|
|
|
|
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)
|
|
{
|
|
test_progress ('.');
|
|
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)
|
|
{
|
|
test_progress ('.');
|
|
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))
|
|
{
|
|
test_progress ('*');
|
|
test_main_context_iterate (f->ctx, TRUE);
|
|
}
|
|
|
|
if (!should_support_fds)
|
|
return;
|
|
|
|
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");
|
|
}
|
|
|
|
static void
|
|
setup_common (Fixture *f,
|
|
const char *address)
|
|
{
|
|
f->ctx = test_main_context_get ();
|
|
dbus_error_init (&f->e);
|
|
g_queue_init (&f->messages);
|
|
|
|
if ((g_str_has_prefix (address, "tcp:") ||
|
|
g_str_has_prefix (address, "nonce-tcp:")) &&
|
|
!test_check_tcp_works ())
|
|
{
|
|
f->skip = TRUE;
|
|
return;
|
|
}
|
|
|
|
f->server = dbus_server_listen (address, &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);
|
|
}
|
|
|
|
static void
|
|
setup_unsupported (Fixture *f,
|
|
gconstpointer data G_GNUC_UNUSED)
|
|
{
|
|
setup_common (f, "tcp:host=127.0.0.1");
|
|
}
|
|
|
|
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 */
|
|
setup_common (f, "unix:tmpdir=/tmp");
|
|
|
|
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_unsupported (Fixture *f,
|
|
gconstpointer data)
|
|
{
|
|
if (f->skip)
|
|
return;
|
|
|
|
test_connect (f, FALSE);
|
|
|
|
if (dbus_connection_can_send_type (f->left_client_conn,
|
|
DBUS_TYPE_UNIX_FD))
|
|
g_error ("left client connection claims it can send Unix fds");
|
|
|
|
if (dbus_connection_can_send_type (f->left_server_conn,
|
|
DBUS_TYPE_UNIX_FD))
|
|
g_error ("left server connection claims it can send Unix fds");
|
|
|
|
if (dbus_connection_can_send_type (f->right_client_conn,
|
|
DBUS_TYPE_UNIX_FD))
|
|
g_error ("right client connection claims it can send Unix fds");
|
|
|
|
if (dbus_connection_can_send_type (f->right_server_conn,
|
|
DBUS_TYPE_UNIX_FD))
|
|
g_error ("right server connection claims it can send Unix fds");
|
|
}
|
|
|
|
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->skip)
|
|
return;
|
|
|
|
test_connect (f, TRUE);
|
|
|
|
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)
|
|
{
|
|
test_progress ('.');
|
|
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_set_error_from_message (&f->e, incoming))
|
|
g_error ("%s: %s", f->e.name, f->e.message);
|
|
else 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;
|
|
|
|
if (f->skip)
|
|
return;
|
|
|
|
test_connect (f, TRUE);
|
|
|
|
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)
|
|
{
|
|
test_progress ('.');
|
|
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;
|
|
unsigned int i;
|
|
|
|
if (f->skip)
|
|
return;
|
|
|
|
test_connect (f, TRUE);
|
|
|
|
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))
|
|
{
|
|
test_progress ('.');
|
|
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;
|
|
DBusSocket left_client_socket;
|
|
char *payload;
|
|
int payload_len;
|
|
DBusString buffer;
|
|
int fds[TOO_MANY_FDS];
|
|
int done;
|
|
#ifdef HAVE_GETRLIMIT
|
|
struct rlimit lim;
|
|
#endif
|
|
|
|
if (f->skip)
|
|
return;
|
|
|
|
/* 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
|
|
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, TRUE);
|
|
|
|
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.fd))
|
|
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))
|
|
{
|
|
test_progress ('.');
|
|
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
|
|
unsigned int i, j;
|
|
DBusMessage *outgoing[SOME_MESSAGES];
|
|
dbus_uint32_t serial;
|
|
|
|
if (f->skip)
|
|
return;
|
|
|
|
test_connect (f, TRUE);
|
|
|
|
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)
|
|
{
|
|
test_progress ('.');
|
|
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;
|
|
|
|
if (f->skip)
|
|
return;
|
|
|
|
#ifdef __FreeBSD__
|
|
g_test_message ("Running test on FreeBSD %d...", getosreldate ());
|
|
if (GPOINTER_TO_INT (data) == 0 && getosreldate () < 1301000)
|
|
{
|
|
g_test_skip ("This test fails on FreeBSD < 13.1");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
test_connect (f, TRUE);
|
|
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))
|
|
{
|
|
test_progress ('.');
|
|
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)
|
|
{
|
|
test_progress ('.');
|
|
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)
|
|
{
|
|
if (f->left_client_conn != NULL)
|
|
{
|
|
test_connection_shutdown (f->ctx, f->left_client_conn);
|
|
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)
|
|
{
|
|
test_connection_shutdown (f->ctx, f->right_client_conn);
|
|
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)
|
|
{
|
|
test_connection_shutdown (f->ctx, f->left_server_conn);
|
|
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)
|
|
{
|
|
test_connection_shutdown (f->ctx, f->right_server_conn);
|
|
dbus_connection_close (f->right_server_conn);
|
|
dbus_connection_unref (f->right_server_conn);
|
|
f->right_server_conn = NULL;
|
|
}
|
|
|
|
if (f->server != NULL)
|
|
{
|
|
test_server_shutdown (f->ctx, f->server);
|
|
dbus_server_unref (f->server);
|
|
f->server = NULL;
|
|
}
|
|
|
|
if (f->ctx != NULL)
|
|
test_main_context_unref (f->ctx);
|
|
|
|
#ifdef HAVE_UNIX_FD_PASSING
|
|
if (f->fd_before >= 0 && close (f->fd_before) < 0)
|
|
g_error ("%s", g_strerror (errno));
|
|
#endif
|
|
|
|
/* TODO: It would be nice if we could ask GLib which test-case
|
|
* we're currently in */
|
|
if (f->initial_fds != NULL)
|
|
_dbus_check_fdleaks_leave (f->initial_fds, "next test-case");
|
|
}
|
|
|
|
int
|
|
main (int argc,
|
|
char **argv)
|
|
{
|
|
int ret;
|
|
|
|
test_init (&argc, &argv);
|
|
|
|
#ifdef HAVE_GETRLIMIT
|
|
{
|
|
struct rlimit lim;
|
|
|
|
if (getrlimit (RLIMIT_NOFILE, &lim) < 0)
|
|
g_error ("Failed to get RLIMIT_NOFILE limit: %s", g_strerror (errno));
|
|
|
|
if (lim.rlim_cur != RLIM_INFINITY &&
|
|
/* only run if we have a fairly generous margin of error
|
|
* for stdout, stderr, duplicates, the D-Bus connection, etc. */
|
|
lim.rlim_cur < 2 * MAX_MESSAGE_UNIX_FDS * SOME_MESSAGES)
|
|
{
|
|
g_message ("not enough RLIMIT_NOFILE to run this test");
|
|
/* Autotools exit code for "all skipped" */
|
|
return 77;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
g_test_add ("/unsupported", Fixture, NULL, setup_unsupported,
|
|
test_unsupported, teardown);
|
|
|
|
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);
|
|
|
|
ret = g_test_run ();
|
|
dbus_shutdown ();
|
|
return ret;
|
|
}
|