/* Unit tests for systemd activation. * * Copyright © 2010-2011 Nokia Corporation * Copyright © 2015 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 "test-utils-glib.h" typedef struct { TestMainContext *ctx; DBusError e; GError *ge; gchar *address; GPid daemon_pid; DBusConnection *caller; const char *caller_name; DBusConnection *systemd; const char *systemd_name; DBusMessage *systemd_message; DBusConnection *activated; const char *activated_name; DBusMessage *activated_message; } Fixture; /* this is a macro so it gets the right line number */ #define assert_signal(m, \ sender, path, iface, member, signature, \ destination) \ do { \ g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \ ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_SIGNAL)); \ g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \ g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \ g_assert_cmpstr (dbus_message_get_path (m), ==, path); \ g_assert_cmpstr (dbus_message_get_interface (m), ==, iface); \ g_assert_cmpstr (dbus_message_get_member (m), ==, member); \ g_assert_cmpstr (dbus_message_get_signature (m), ==, signature); \ g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \ g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \ } while (0) static DBusHandlerResult systemd_filter (DBusConnection *connection, DBusMessage *message, void *user_data) { Fixture *f = user_data; if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameAcquired") || dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameLost")) { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } g_assert (f->systemd_message == NULL); f->systemd_message = dbus_message_ref (message); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusHandlerResult activated_filter (DBusConnection *connection, DBusMessage *message, void *user_data) { Fixture *f = user_data; if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameAcquired") || dbus_message_is_signal (message, DBUS_INTERFACE_DBUS, "NameLost")) { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } g_assert (f->activated_message == NULL); f->activated_message = dbus_message_ref (message); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static void setup (Fixture *f, gconstpointer context G_GNUC_UNUSED) { f->ctx = test_main_context_get (); f->ge = NULL; dbus_error_init (&f->e); f->address = test_get_dbus_daemon ( "valid-config-files/systemd-activation.conf", TEST_USER_ME, &f->daemon_pid); if (f->address == NULL) return; f->caller = test_connect_to_bus (f->ctx, f->address); f->caller_name = dbus_bus_get_unique_name (f->caller); } static void take_well_known_name (Fixture *f, DBusConnection *connection, const char *name) { int ret; ret = dbus_bus_request_name (connection, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e); test_assert_no_error (&f->e); g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER); } static void test_activation (Fixture *f, gconstpointer context) { DBusMessage *m; if (f->address == NULL) return; /* The sender sends a message to an activatable service. */ m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal1"); if (!dbus_message_set_destination (m, "com.example.SystemdActivatable1")) g_error ("OOM"); dbus_connection_send (f->caller, m, NULL); dbus_message_unref (m); /* The fake systemd connects to the bus. */ f->systemd = test_connect_to_bus (f->ctx, f->address); if (!dbus_connection_add_filter (f->systemd, systemd_filter, f, NULL)) g_error ("OOM"); f->systemd_name = dbus_bus_get_unique_name (f->systemd); take_well_known_name (f, f->systemd, "org.freedesktop.systemd1"); /* It gets its activation request. */ while (f->systemd_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->systemd_message; f->systemd_message = NULL; assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, "org.freedesktop.systemd1.Activator", "ActivationRequest", "s", "org.freedesktop.systemd1"); dbus_message_unref (m); /* systemd starts the activatable service. */ f->activated = test_connect_to_bus (f->ctx, f->address); if (!dbus_connection_add_filter (f->activated, activated_filter, f, NULL)) g_error ("OOM"); f->activated_name = dbus_bus_get_unique_name (f->activated); take_well_known_name (f, f->activated, "com.example.SystemdActivatable1"); /* The message is delivered to the activatable service. */ while (f->activated_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->activated_message; f->activated_message = NULL; assert_signal (m, f->caller_name, "/foo", "com.example.bar", "UnicastSignal1", "", "com.example.SystemdActivatable1"); dbus_message_unref (m); /* The sender sends a message to a different activatable service. */ m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal2"); if (!dbus_message_set_destination (m, "com.example.SystemdActivatable2")) g_error ("OOM"); dbus_connection_send (f->caller, m, NULL); dbus_message_unref (m); /* This time systemd is already ready for it. */ while (f->systemd_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->systemd_message; f->systemd_message = NULL; assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, "org.freedesktop.systemd1.Activator", "ActivationRequest", "s", "org.freedesktop.systemd1"); dbus_message_unref (m); /* A malicious process tries to disrupt the activation. * In a more realistic scenario this would be another parallel * connection. */ m = dbus_message_new_signal ("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"); if (!dbus_message_set_destination (m, "org.freedesktop.DBus")) g_error ("OOM"); do { const char *unit = "dbus-com.example.SystemdActivatable2.service"; const char *error_name = "com.example.Malice"; const char *error_message = "I'm on yr bus, making yr activations fail"; if (!dbus_message_append_args (m, DBUS_TYPE_STRING, &unit, DBUS_TYPE_STRING, &error_name, DBUS_TYPE_STRING, &error_message, DBUS_TYPE_INVALID)) g_error ("OOM"); } while (0); dbus_connection_send (f->caller, m, NULL); dbus_message_unref (m); /* This is just to make sure that the malicious message has arrived and * been processed by the dbus-daemon, i.e. @caller won the race * with @activated. */ take_well_known_name (f, f->caller, "com.example.Sync"); /* The activatable service takes its name. Here I'm faking it by using * an existing connection; in real life it would be yet another * connection. */ take_well_known_name (f, f->activated, "com.example.SystemdActivatable2"); /* The message is delivered to the activatable service. */ while (f->activated_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->activated_message; f->activated_message = NULL; assert_signal (m, f->caller_name, "/foo", "com.example.bar", "UnicastSignal2", "", "com.example.SystemdActivatable2"); dbus_message_unref (m); /* A third activation. */ m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal3"); if (!dbus_message_set_destination (m, "com.example.SystemdActivatable3")) g_error ("OOM"); dbus_connection_send (f->caller, m, NULL); dbus_message_unref (m); while (f->systemd_message == NULL) test_main_context_iterate (f->ctx, TRUE); m = f->systemd_message; f->systemd_message = NULL; assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, "org.freedesktop.systemd1.Activator", "ActivationRequest", "s", "org.freedesktop.systemd1"); dbus_message_unref (m); /* This time activation fails */ m = dbus_message_new_signal ("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"); do { const char *unit = "dbus-com.example.SystemdActivatable3.service"; const char *error_name = "com.example.Nope"; const char *error_message = "Computer says no"; if (!dbus_message_append_args (m, DBUS_TYPE_STRING, &unit, DBUS_TYPE_STRING, &error_name, DBUS_TYPE_STRING, &error_message, DBUS_TYPE_INVALID)) g_error ("OOM"); } while (0); if (!dbus_message_set_destination (m, "org.freedesktop.DBus")) g_error ("OOM"); dbus_connection_send (f->systemd, m, NULL); dbus_message_unref (m); } static void teardown (Fixture *f, gconstpointer context G_GNUC_UNUSED) { dbus_error_free (&f->e); g_clear_error (&f->ge); if (f->caller != NULL) { dbus_connection_close (f->caller); dbus_connection_unref (f->caller); f->caller = NULL; } if (f->systemd != NULL) { dbus_connection_remove_filter (f->systemd, systemd_filter, f); dbus_connection_close (f->systemd); dbus_connection_unref (f->systemd); f->systemd = NULL; } if (f->activated != NULL) { dbus_connection_remove_filter (f->activated, activated_filter, f); dbus_connection_close (f->activated); dbus_connection_unref (f->activated); f->activated = NULL; } test_kill_pid (f->daemon_pid); g_spawn_close_pid (f->daemon_pid); test_main_context_unref (f->ctx); g_free (f->address); } int main (int argc, char **argv) { test_init (&argc, &argv); g_test_add ("/sd-activation", Fixture, NULL, setup, test_activation, teardown); return g_test_run (); }