From 628439482af4a82ea55ad08c825f272ce4ef34f9 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 1 Jun 2018 15:12:25 +0100 Subject: [PATCH 33/33] containers test: Exercise allowing/denying signals Signed-off-by: Simon McVittie Bug: https://bugs.freedesktop.org/show_bug.cgi?id=105656 --- test/containers.c | 408 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) diff --git a/test/containers.c b/test/containers.c index 203c2991..d5f5e8f3 100644 --- a/test/containers.c +++ b/test/containers.c @@ -676,6 +676,28 @@ allow_tests_message_filter (GDBusConnection *connection, return NULL; } +/* + * Helper for Allow tests: GDBusSignalCallback that counts how many + * times we got a particular signal. + */ +static void +count_signal_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + guint *counter = user_data; + + g_test_message ("Connection %p received %s.%s from %s:%s", + connection, interface_name, signal_name, + sender_name, object_path); + + *counter += 1; +} + static gboolean add_container_server (Fixture *f, GVariant *parameters); #endif @@ -780,6 +802,45 @@ typedef struct AllowMessageFlags flags; } AllowMethodCall; +/* + * The result of a signal that we should or shouldn't be able to receive + */ +typedef enum +{ + /* Signal received outside the container (by the observer if we are + * sending it from the unconfined connection, or by the unconfined + * connection if we are sending it from the confined connection) */ + SIGNAL_RECEIVED_OUTSIDE = (1 << 0), + /* Signal received inside the container (by the first confined + * connection if we are sending it from the unconfined connection, or + * by the second confined connection if we are sending it from the + * first) */ + SIGNAL_RECEIVED_INSIDE = (1 << 1), + /* Signal not received by anyone */ + SIGNAL_NOT_RECEIVED = 0 +} AllowSignalResult; + +/* + * A signal that we should or shouldn't be able to send + */ +typedef struct +{ + AllowSignalResult result; + /* The destination, or NULL for a broadcast signal */ + const char *bus_name; + /* The source object path */ + const char *object_path; + /* The interface */ + const char *iface; + /* The member name. Some member names are given special meaning + * as a short-cut for defining test cases, for example AddMatch + * gets a valid match rule. */ + const char *member; + /* A string argument or NULL */ + const char *argument; + AllowMessageFlags flags; +} AllowSignal; + /* * Flags affecting an entire test-case */ @@ -811,6 +872,8 @@ typedef struct const char * const cannot_see_names[16]; /* Can be terminated early by an entry with result INVALID */ const AllowMethodCall method_calls[64]; + /* Can be terminated early by an entry with path NULL */ + const AllowSignal signals[64]; } AllowRulesTest; static const AllowRulesTest allow_rules_tests[] = @@ -1040,6 +1103,50 @@ static const AllowRulesTest allow_rules_tests[] = ALLOW_MESSAGE_FLAGS_NONE }, { METHOD_INVALID } /* sentinel */ + }, + { /* signals: */ + + /* Peers inside the container may communicate among themselves */ + { SIGNAL_RECEIVED_INSIDE, + REPLACE_WITH_CONFINED_1_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_NONE }, + + /* May receive unicast signals from outside */ + { SIGNAL_RECEIVED_INSIDE, + REPLACE_WITH_CONFINED_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_INITIATOR_OUTSIDE }, + + /* May send fds to outside */ + { SIGNAL_RECEIVED_OUTSIDE, + REPLACE_WITH_UNCONFINED_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_SEND_FD }, + + /* May receive fds from outside */ + { SIGNAL_RECEIVED_INSIDE, + REPLACE_WITH_CONFINED_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_INITIATOR_OUTSIDE | ALLOW_MESSAGE_FLAGS_SEND_FD }, + + /* May receive broadcasts from outside */ + { SIGNAL_RECEIVED_INSIDE | SIGNAL_RECEIVED_OUTSIDE, + NULL, "/", "com.example.Foo", "BroadcastSignal", NULL, + ALLOW_MESSAGE_FLAGS_INITIATOR_OUTSIDE }, + + /* May send unicast signals to outside */ + { SIGNAL_RECEIVED_OUTSIDE, + REPLACE_WITH_UNCONFINED_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_NONE }, + + /* May send broadcasts due to being unrestricted */ + { SIGNAL_RECEIVED_INSIDE | SIGNAL_RECEIVED_OUTSIDE, + NULL, "/", "com.example.Foo", "BroadcastSignal", NULL, + ALLOW_MESSAGE_FLAGS_NONE }, + + { SIGNAL_NOT_RECEIVED, NULL, NULL } /* sentinel */ } }, @@ -1338,6 +1445,55 @@ static const AllowRulesTest allow_rules_tests[] = #endif { METHOD_INVALID } /* sentinel */ + }, + { /* signals: */ + + /* Peers inside the container may communicate among themselves */ + { SIGNAL_RECEIVED_INSIDE, + REPLACE_WITH_CONFINED_1_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_NONE }, + + /* May receive unicast signals from outside */ + { SIGNAL_RECEIVED_INSIDE, + REPLACE_WITH_CONFINED_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_INITIATOR_OUTSIDE }, + + /* We can't test whether sending fds to dbus-daemon in a + * signal is allowed (but it's academic, because it's going to + * receive them whether it wants to or not) */ + + /* Must not send fds to outside */ + { SIGNAL_NOT_RECEIVED, + REPLACE_WITH_UNCONFINED_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_SEND_FD }, + + /* Must not receive fds from outside */ + { SIGNAL_NOT_RECEIVED, + REPLACE_WITH_CONFINED_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_INITIATOR_OUTSIDE | ALLOW_MESSAGE_FLAGS_SEND_FD }, + + /* Must not receive broadcasts from outside */ + { SIGNAL_RECEIVED_OUTSIDE, + NULL, "/", "com.example.Foo", "BroadcastSignal", NULL, + ALLOW_MESSAGE_FLAGS_INITIATOR_OUTSIDE }, + + /* Must not send unicast signals to outside */ + { SIGNAL_NOT_RECEIVED, + REPLACE_WITH_UNCONFINED_UNIQUE_NAME, "/", + "com.example.Foo", "UnicastSignal", NULL, + ALLOW_MESSAGE_FLAGS_NONE }, + + /* Must not send broadcasts to outside, but can still broadcast + * withing the container */ + { SIGNAL_RECEIVED_INSIDE, + NULL, "/", "com.example.Foo", "BroadcastSignal", NULL, + ALLOW_MESSAGE_FLAGS_NONE }, + + { SIGNAL_NOT_RECEIVED, NULL, NULL } /* sentinel */ } } }; @@ -3843,6 +3999,253 @@ test_allow_methods (Fixture *f, #endif /* !HAVE_CONTAINERS_TEST */ } +/* + * Assert that the given Allow rules work as intended for signals. + */ +static void +test_allow_signals (Fixture *f, + gconstpointer context) +{ +#ifdef HAVE_CONTAINERS_TEST + const AllowRulesTest *test = context; + guint i; + + if (f->skip) + return; + + /* This is the data-driven part of the test: try sending a lot of + * messages and see what happens. */ + for (i = 0; i < G_N_ELEMENTS (test->signals); i++) + { + const AllowSignal *signal = &test->signals[i]; + const gchar *bus_name = signal->bus_name; + GDBusConnection *initiator; + const gchar *initiator_description; + GDBusConnection *confined_signal_receiver = NULL; + GDBusConnection *unconfined_signal_receiver = NULL; + const gchar *confined_signal_receiver_name = NULL; + const gchar *unconfined_signal_receiver_name = NULL; + guint unconfined_subscription = 0; + guint confined_subscription = 0; + guint received_confined = 0; + guint received_unconfined = 0; + + if (signal->object_path == NULL) + break; + + g_test_message ("%s %s #%d", G_STRFUNC, test->name, i); + + /* do not test g_dbus_is_name() until after we have substituted + * special strings like REPLACE_WITH_CONFINED_UNIQUE_NAME */ + g_assert (signal->object_path != NULL); + g_assert (g_variant_is_object_path (signal->object_path)); + g_assert (signal->iface != NULL); + g_assert (g_dbus_is_interface_name (signal->iface)); + g_assert (signal->member != NULL); + g_assert (g_dbus_is_member_name (signal->member)); + /* this flag would be meaningless here */ + g_assert (!(signal->flags & ALLOW_MESSAGE_FLAGS_FD_IN_REPLY)); + + if (signal->flags & ALLOW_MESSAGE_FLAGS_INITIATOR_OUTSIDE) + { + initiator = f->unconfined_conn; + initiator_description = "unconfined connection"; + } + else + { + initiator = f->confined_conns[0]; + initiator_description = "confined connection"; + } + + if (g_strcmp0 (bus_name, REPLACE_WITH_CONFINED_UNIQUE_NAME) == 0) + bus_name = f->confined_unique_names[0]; + else if (g_strcmp0 (bus_name, REPLACE_WITH_CONFINED_1_UNIQUE_NAME) == 0) + bus_name = f->confined_unique_names[1]; + else if (g_strcmp0 (bus_name, REPLACE_WITH_UNCONFINED_UNIQUE_NAME) == 0) + bus_name = f->unconfined_unique_name; + else if (g_strcmp0 (bus_name, REPLACE_WITH_OBSERVER_UNIQUE_NAME) == 0) + bus_name = f->observer_unique_name; + else + g_assert (bus_name == NULL || bus_name[0] != ':'); + + g_assert (bus_name == NULL || g_dbus_is_name (bus_name)); + + if (signal->flags & ALLOW_MESSAGE_FLAGS_INITIATOR_OUTSIDE) + { + /* We only send messages from the unconfined connection if + * they are going to the first confined connection; there's + * no point in distinguishing between confined connections, + * and we aren't testing communication that doesn't involve + * at least one confined connection. */ + g_assert_true (bus_name == NULL || + g_strcmp0 (signal->bus_name, + REPLACE_WITH_CONFINED_UNIQUE_NAME) == 0); + confined_signal_receiver = f->confined_conns[0]; + confined_signal_receiver_name = "confined connection 0"; + unconfined_signal_receiver = f->observer_conn; + unconfined_signal_receiver_name = "observer"; + } + else if (bus_name == NULL) + { + /* It's a broadcast to unconfined and confined connections. + * Use the other confined connection to see who + * receives it. */ + confined_signal_receiver = f->confined_conns[1]; + confined_signal_receiver_name = "confined connection 1"; + unconfined_signal_receiver = f->observer_conn; + unconfined_signal_receiver_name = "observer"; + } + else if (g_strcmp0 (signal->bus_name, + REPLACE_WITH_CONFINED_1_UNIQUE_NAME) == 0) + { + /* It's unicast to the other confined connection */ + g_assert ((signal->result & SIGNAL_RECEIVED_OUTSIDE) == 0); + confined_signal_receiver = f->confined_conns[1]; + confined_signal_receiver_name = "confined connection 1"; + unconfined_signal_receiver = NULL; + } + else + { + /* It's unicast to the unconfined connection */ + g_assert ((signal->result & SIGNAL_RECEIVED_INSIDE) == 0); + confined_signal_receiver = NULL; + g_assert_cmpstr (signal->bus_name, ==, + REPLACE_WITH_UNCONFINED_UNIQUE_NAME); + unconfined_signal_receiver = f->unconfined_conn; + unconfined_signal_receiver_name = "unconfined connection"; + } + + if (confined_signal_receiver != NULL) + confined_subscription = g_dbus_connection_signal_subscribe ( + confined_signal_receiver, + g_dbus_connection_get_unique_name (initiator), + signal->iface, + signal->member, + signal->object_path, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + count_signal_cb, + &received_confined, + NULL); + + if (unconfined_signal_receiver != NULL) + unconfined_subscription = g_dbus_connection_signal_subscribe ( + unconfined_signal_receiver, + g_dbus_connection_get_unique_name (initiator), + signal->iface, + signal->member, + signal->object_path, + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + count_signal_cb, + &received_unconfined, + NULL); + + g_test_message ("%s sending signal", initiator_description); + + if (bus_name == NULL) + g_test_message ("... broadcast"); + else if (g_strcmp0 (signal->bus_name, bus_name) != 0) + g_test_message ("... unicast to %s (%s)", signal->bus_name, + bus_name); + else + g_test_message ("... unicast to %s", bus_name); + + g_test_message ("... path %s", signal->object_path); + g_test_message ("... %s.%s", signal->iface, signal->member); + + if (signal->flags & ALLOW_MESSAGE_FLAGS_SEND_FD) + { + GDBusMessage *s; + + g_test_message ("... with attached file descriptor"); + s = g_dbus_message_new_signal (signal->object_path, + signal->iface, + signal->member); + + if (bus_name != NULL) + g_dbus_message_set_destination (s, bus_name); + + message_set_body_to_unix_fd (s); + g_dbus_connection_send_message (initiator, s, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + NULL, &f->error); + g_object_unref (s); + } + else + { + g_dbus_connection_emit_signal (initiator, + bus_name, + signal->object_path, + signal->iface, + signal->member, + NULL, + &f->error); + } + + g_assert_no_error (f->error); + + /* Sync up the connections. Because messages are delivered + * in-order, if the signal hasn't been received by the time a + * ping involving the same two connections has completed, + * then it never will be. */ + if (initiator == f->unconfined_conn) + { + if (unconfined_signal_receiver != NULL) + test_sync_gdbus_connections (initiator, + unconfined_signal_receiver); + + if (confined_signal_receiver != NULL) + test_sync_gdbus_connections (initiator, + confined_signal_receiver); + } + else + { + g_assert (initiator == f->confined_conns[0]); + + /* If the initiator was the confined connection, it might + * not be allowed to contact the unconfined connection, + * so we do this backwards, using the reply rather than + * the call to enforce ordering. */ + if (unconfined_signal_receiver != NULL) + test_sync_gdbus_connections (unconfined_signal_receiver, + initiator); + + if (confined_signal_receiver != NULL) + test_sync_gdbus_connections (initiator, + confined_signal_receiver); + } + + if (unconfined_signal_receiver != NULL) + { + g_dbus_connection_signal_unsubscribe (unconfined_signal_receiver, + unconfined_subscription); + g_test_message ("-> %u signal(s) received by %s <%p>", + received_unconfined, + unconfined_signal_receiver_name, + unconfined_signal_receiver); + } + + if (confined_signal_receiver != NULL) + { + g_dbus_connection_signal_unsubscribe (confined_signal_receiver, + confined_subscription); + g_test_message ("-> %u signal(s) received by %s <%p>", + received_confined, + confined_signal_receiver_name, + confined_signal_receiver); + } + + g_assert_cmpuint (received_confined, ==, + (signal->result & SIGNAL_RECEIVED_INSIDE) != 0); + g_assert_cmpuint (received_unconfined, ==, + (signal->result & SIGNAL_RECEIVED_OUTSIDE) != 0); + } +#else /* !HAVE_CONTAINERS_TEST */ + g_test_skip ("Containers or gio-unix-2.0 not supported"); +#endif /* !HAVE_CONTAINERS_TEST */ +} + /* * Test what happens when we exceed max_container_metadata_bytes. * test_metadata() exercises the non-excessive case with the same @@ -4100,6 +4503,11 @@ main (int argc, g_test_add (path, Fixture, test, set_up_allow_test, test_allow_methods, teardown); g_free (path); + + path = g_strdup_printf ("/containers/allow/%s/signals", test->name); + g_test_add (path, Fixture, test, + set_up_allow_test, test_allow_signals, teardown); + g_free (path); } ret = g_test_run (); -- 2.19.0.rc1