+++ /dev/null
-/* containers.c - restricted bus servers for containers
- *
- * Copyright © 2017 Collabora Ltd.
- *
- * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later
- *
- * Licensed under the Academic Free License version 2.1
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- * 02110-1301 USA
- */
-
-#include <config.h>
-#include "containers.h"
-
-#include "dbus/dbus-internals.h"
-
-#ifdef DBUS_ENABLE_CONTAINERS
-
-#error This feature is not ready for production use
-
-#ifndef DBUS_UNIX
-# error DBUS_ENABLE_CONTAINERS requires DBUS_UNIX
-#endif
-
-#include <sys/types.h>
-
-#include "dbus/dbus-hash.h"
-#include "dbus/dbus-message-internal.h"
-#include "dbus/dbus-sysdeps-unix.h"
-
-#include "connection.h"
-#include "dispatch.h"
-#include "driver.h"
-#include "utils.h"
-
-/*
- * A container instance groups together a per-app-container server with
- * all the connections for which it is responsible.
- */
-typedef struct
-{
- int refcount;
- char *path;
- char *type;
- char *name;
- DBusVariant *metadata;
- BusContext *context;
- BusContainers *containers;
- DBusServer *server;
- DBusConnection *creator;
- /* List of owned DBusConnection, removed when the DBusConnection is
- * removed from the bus */
- DBusList *connections;
- unsigned long uid;
- unsigned announced:1;
-} BusContainerInstance;
-
-/* Data attached to a DBusConnection that has created container instances. */
-typedef struct
-{
- /* List of instances created by this connection; unowned.
- * The BusContainerInstance removes itself from here on destruction. */
- DBusList *instances;
-} BusContainerCreatorData;
-
-/* Data slot on DBusConnection, holding BusContainerCreatorData */
-static dbus_int32_t container_creator_data_slot = -1;
-
-/*
- * Singleton data structure encapsulating the container-related parts of
- * a BusContext.
- */
-struct BusContainers
-{
- int refcount;
- /* path borrowed from BusContainerInstance => unowned BusContainerInstance
- * The BusContainerInstance removes itself from here on destruction. */
- DBusHashTable *instances_by_path;
- /* uid => (void *) (uintptr_t) number of containers */
- DBusHashTable *n_containers_by_user;
- DBusString address_template;
- dbus_uint64_t next_container_id;
-};
-
-/* Data slot on DBusConnection, holding BusContainerInstance */
-static dbus_int32_t contained_data_slot = -1;
-
-BusContainers *
-bus_containers_new (void)
-{
- /* We allocate the hash table lazily, expecting that the common case will
- * be a connection where this feature is never used */
- BusContainers *self = NULL;
- DBusString invalid = _DBUS_STRING_INIT_INVALID;
-
- /* One reference per BusContainers, unless we ran out of memory the first
- * time we tried to allocate it, in which case it will be -1 when we
- * free the BusContainers */
- if (!dbus_connection_allocate_data_slot (&contained_data_slot))
- goto oom;
-
- /* Ditto */
- if (!dbus_connection_allocate_data_slot (&container_creator_data_slot))
- goto oom;
-
- self = dbus_new0 (BusContainers, 1);
-
- if (self == NULL)
- goto oom;
-
- self->refcount = 1;
- self->instances_by_path = NULL;
- self->next_container_id = DBUS_UINT64_CONSTANT (0);
- self->address_template = invalid;
-
- /* Make it mutable */
- if (!_dbus_string_init (&self->address_template))
- goto oom;
-
- if (_dbus_getuid () == 0)
- {
- DBusString dir;
-
- /* System bus (we haven't dropped privileges at this point), or
- * root's session bus. Use random socket paths resembling
- * /run/dbus/containers/dbus-abcdef, which is next to /run/dbus/pid
- * (if not using the Red Hat init scripts, which use a different
- * pid file for historical reasons).
- *
- * We rely on the tmpfiles.d snippet or an OS-specific init script to
- * have created this directory with the appropriate owner; if it hasn't,
- * creating container sockets will just fail. */
- _dbus_string_init_const (&dir, DBUS_RUNSTATEDIR "/dbus/containers");
-
- /* We specifically use paths, because an abstract socket that you can't
- * bind-mount is not particularly useful. */
- if (!_dbus_string_append (&self->address_template, "unix:dir=") ||
- !_dbus_address_append_escaped (&self->address_template, &dir))
- goto oom;
- }
- else
- {
- /* Otherwise defer creating the directory for sockets until we need it,
- * so that we can have better error behaviour */
- }
-
- return self;
-
-oom:
- if (self != NULL)
- {
- /* This will free the data slot too */
- bus_containers_unref (self);
- }
- else
- {
- if (contained_data_slot != -1)
- dbus_connection_free_data_slot (&contained_data_slot);
-
- if (container_creator_data_slot != -1)
- dbus_connection_free_data_slot (&container_creator_data_slot);
- }
-
- return NULL;
-}
-
-BusContainers *
-bus_containers_ref (BusContainers *self)
-{
- _dbus_assert (self->refcount > 0);
- _dbus_assert (self->refcount < _DBUS_INT_MAX);
-
- self->refcount++;
- return self;
-}
-
-void
-bus_containers_unref (BusContainers *self)
-{
- _dbus_assert (self != NULL);
- _dbus_assert (self->refcount > 0);
-
- if (--self->refcount == 0)
- {
- _dbus_clear_hash_table (&self->instances_by_path);
- _dbus_clear_hash_table (&self->n_containers_by_user);
- _dbus_string_free (&self->address_template);
- dbus_free (self);
-
- if (contained_data_slot != -1)
- dbus_connection_free_data_slot (&contained_data_slot);
-
- if (container_creator_data_slot != -1)
- dbus_connection_free_data_slot (&container_creator_data_slot);
- }
-}
-
-static BusContainerInstance *
-bus_container_instance_ref (BusContainerInstance *self)
-{
- _dbus_assert (self->refcount > 0);
- _dbus_assert (self->refcount < _DBUS_INT_MAX);
-
- self->refcount++;
- return self;
-}
-
-static dbus_bool_t
-bus_container_instance_emit_removed (BusContainerInstance *self)
-{
- BusTransaction *transaction = NULL;
- DBusMessage *message = NULL;
- DBusError error = DBUS_ERROR_INIT;
-
- transaction = bus_transaction_new (self->context);
-
- if (transaction == NULL)
- goto oom;
-
- message = dbus_message_new_signal (DBUS_PATH_DBUS,
- DBUS_INTERFACE_CONTAINERS1,
- "InstanceRemoved");
-
- if (message == NULL ||
- !dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
- !dbus_message_append_args (message,
- DBUS_TYPE_OBJECT_PATH, &self->path,
- DBUS_TYPE_INVALID) ||
- !bus_transaction_capture (transaction, NULL, NULL, message))
- goto oom;
-
- if (!bus_dispatch_matches (transaction, NULL, NULL, message, &error))
- {
- if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
- goto oom;
-
- /* This can't actually happen, because all of the error cases in
- * bus_dispatch_matches() are only if there is an addressed recipient
- * (a unicast message), which there is not in this case. But if it
- * somehow does happen, we don't want to stay in the OOM-retry loop,
- * because waiting for more memory will not help; so continue to
- * execute the transaction anyway. */
- _dbus_warn ("Failed to send InstanceRemoved for a reason "
- "other than OOM: %s: %s", error.name, error.message);
- dbus_error_free (&error);
- }
-
- bus_transaction_execute_and_free (transaction);
- dbus_message_unref (message);
- _DBUS_ASSERT_ERROR_IS_CLEAR (&error);
- return TRUE;
-
-oom:
- dbus_error_free (&error);
- dbus_clear_message (&message);
-
- if (transaction != NULL)
- bus_transaction_cancel_and_free (transaction);
-
- return FALSE;
-}
-
-static void
-bus_container_instance_unref (BusContainerInstance *self)
-{
- _dbus_assert (self->refcount > 0);
-
- if (--self->refcount == 0)
- {
- BusContainerCreatorData *creator_data;
-
- /* If we announced the container instance in a reply from
- * AddServer() (which is also the time at which it becomes
- * available for the querying methods), then we have to emit
- * InstanceRemoved for it.
- *
- * Similar to bus/connection.c dropping well-known name ownership,
- * this isn't really a situation where we can "fail", because
- * this last-unref is likely to be happening as a result of a
- * connection disconnecting; so we use a retry loop on OOM. */
- for (; self->announced; _dbus_wait_for_memory ())
- {
- if (bus_container_instance_emit_removed (self))
- self->announced = FALSE;
- }
-
- /* As long as the server is listening, the BusContainerInstance can't
- * be freed, because the DBusServer holds a reference to the
- * BusContainerInstance */
- _dbus_assert (self->server == NULL);
-
- /* Similarly, as long as there are connections, the BusContainerInstance
- * can't be freed, because each connection holds a reference to the
- * BusContainerInstance */
- _dbus_assert (self->connections == NULL);
-
- creator_data = dbus_connection_get_data (self->creator,
- container_creator_data_slot);
- _dbus_assert (creator_data != NULL);
- _dbus_list_remove (&creator_data->instances, self);
-
- /* It's OK to do this even if we were never added to instances_by_path,
- * because the paths are globally unique. */
- if (self->path != NULL && self->containers->instances_by_path != NULL &&
- _dbus_hash_table_remove_string (self->containers->instances_by_path,
- self->path))
- {
- DBusHashIter entry;
- uintptr_t n = 0;
-
- if (!_dbus_hash_iter_lookup (self->containers->n_containers_by_user,
- (void *) (uintptr_t) self->uid,
- FALSE, &entry))
- _dbus_assert_not_reached ("Container should not be placed in "
- "instances_by_path until its "
- "n_containers_by_user entry has "
- "been allocated");
-
- n = (uintptr_t) _dbus_hash_iter_get_value (&entry);
- _dbus_assert (n > 0);
- n -= 1;
- _dbus_hash_iter_set_value (&entry, (void *) n);
- }
-
- _dbus_clear_variant (&self->metadata);
- bus_context_unref (self->context);
- bus_containers_unref (self->containers);
- dbus_connection_unref (self->creator);
- dbus_free (self->path);
- dbus_free (self->type);
- dbus_free (self->name);
- dbus_free (self);
- }
-}
-
-static inline void
-bus_clear_container_instance (BusContainerInstance **instance_p)
-{
- _dbus_clear_pointer_impl (BusContainerInstance, instance_p,
- bus_container_instance_unref);
-}
-
-static void
-bus_container_instance_stop_listening (BusContainerInstance *self)
-{
- /* In case the DBusServer holds the last reference to self */
- bus_container_instance_ref (self);
-
- if (self->server != NULL)
- {
- dbus_server_set_new_connection_function (self->server, NULL, NULL, NULL);
- dbus_server_disconnect (self->server);
- dbus_clear_server (&self->server);
- }
-
- bus_container_instance_unref (self);
-}
-
-static BusContainerInstance *
-bus_container_instance_new (BusContext *context,
- BusContainers *containers,
- DBusConnection *creator,
- DBusError *error)
-{
- BusContainerInstance *self = NULL;
- DBusString path = _DBUS_STRING_INIT_INVALID;
-
- _dbus_assert (context != NULL);
- _dbus_assert (containers != NULL);
- _dbus_assert (creator != NULL);
- _DBUS_ASSERT_ERROR_IS_CLEAR (error);
-
- if (!_dbus_string_init (&path))
- {
- BUS_SET_OOM (error);
- goto fail;
- }
-
- self = dbus_new0 (BusContainerInstance, 1);
-
- if (self == NULL)
- {
- BUS_SET_OOM (error);
- goto fail;
- }
-
- self->refcount = 1;
- self->type = NULL;
- self->name = NULL;
- self->metadata = NULL;
- self->context = bus_context_ref (context);
- self->containers = bus_containers_ref (containers);
- self->server = NULL;
- self->creator = dbus_connection_ref (creator);
-
- if (containers->next_container_id >=
- DBUS_UINT64_CONSTANT (0xFFFFFFFFFFFFFFFF))
- {
- /* We can't increment it any further without wrapping around */
- dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
- "Too many containers created during the lifetime of "
- "this bus");
- goto fail;
- }
-
- if (!_dbus_string_append_printf (&path,
- "/org/freedesktop/DBus/Containers1/c%" DBUS_INT64_MODIFIER "u",
- containers->next_container_id++))
- {
- BUS_SET_OOM (error);
- goto fail;
- }
-
- if (!_dbus_string_steal_data (&path, &self->path))
- goto fail;
-
- _dbus_string_free (&path);
- return self;
-
-fail:
- _dbus_string_free (&path);
-
- if (self != NULL)
- bus_container_instance_unref (self);
-
- return NULL;
-}
-
-static void
-bus_container_creator_data_free (BusContainerCreatorData *self)
-{
- /* Each instance holds a ref to the creator, so there should be
- * nothing here */
- _dbus_assert (self->instances == NULL);
-
- dbus_free (self);
-}
-
-/* We only accept EXTERNAL authentication, because Unix platforms that are
- * sufficiently capable to have app-containers ought to have it. */
-static const char * const auth_mechanisms[] =
-{
- "EXTERNAL",
- NULL
-};
-
-/* Statically assert that we can store a uid in a void *, losslessly.
- *
- * In practice this is always true on Unix. For now we don't support this
- * feature on systems where it isn't. */
-_DBUS_STATIC_ASSERT (sizeof (uid_t) <= sizeof (uintptr_t));
-/* True by definition. */
-_DBUS_STATIC_ASSERT (sizeof (void *) == sizeof (uintptr_t));
-
-static dbus_bool_t
-allow_same_uid_only (DBusConnection *connection,
- unsigned long uid,
- void *data)
-{
- return (uid == (uintptr_t) data);
-}
-
-static void
-bus_container_instance_lost_connection (BusContainerInstance *instance,
- DBusConnection *connection)
-{
- bus_container_instance_ref (instance);
- dbus_connection_ref (connection);
-
- /* This is O(n), but we don't expect to have many connections per
- * container instance. */
- if (_dbus_list_remove (&instance->connections, connection))
- dbus_connection_unref (connection);
-
- /* We don't set connection's contained_data_slot to NULL, to make sure
- * that once we have marked a connection as belonging to a container,
- * there is no going back: even if we somehow keep a reference to it
- * around, it will never be treated as uncontained. The connection's
- * reference to the instance will be cleaned up on last-unref, and
- * the list removal above ensures that the instance does not hold a
- * circular ref to the connection, so the last-unref will happen. */
-
- dbus_connection_unref (connection);
- bus_container_instance_unref (instance);
-}
-
-static void
-new_connection_cb (DBusServer *server,
- DBusConnection *new_connection,
- void *data)
-{
- BusContainerInstance *instance = data;
- int limit = bus_context_get_max_connections_per_container (instance->context);
-
- /* This is O(n), but we assume n is small in practice. */
- if (_dbus_list_get_length (&instance->connections) >= limit)
- {
- /* We can't send this error to the new connection, so just log it */
- bus_context_log (instance->context, DBUS_SYSTEM_LOG_WARNING,
- "Closing connection to container server "
- "%s (%s \"%s\") because it would exceed resource limit "
- "(max_connections_per_container=%d)",
- instance->path, instance->type, instance->name, limit);
- return;
- }
-
- if (!dbus_connection_set_data (new_connection, contained_data_slot,
- bus_container_instance_ref (instance),
- (DBusFreeFunction) bus_container_instance_unref))
- {
- bus_container_instance_unref (instance);
- bus_container_instance_lost_connection (instance, new_connection);
- return;
- }
-
- if (_dbus_list_append (&instance->connections, new_connection))
- {
- dbus_connection_ref (new_connection);
- }
- else
- {
- bus_container_instance_lost_connection (instance, new_connection);
- return;
- }
-
- /* If this fails it logs a warning, so we don't need to do that.
- * We don't know how to undo this, so do it last (apart from things that
- * cannot fail) */
- if (!bus_context_add_incoming_connection (instance->context, new_connection))
- {
- bus_container_instance_lost_connection (instance, new_connection);
- return;
- }
-
- /* We'd like to check the uid here, but we can't yet. Instead clear the
- * BusContext's unix_user_function, which results in us getting the
- * default behaviour: only the user that owns the bus can connect.
- *
- * TODO: For the system bus we might want a way to opt-in to allowing
- * other uids, in which case we would refrain from overwriting the
- * BusContext's unix_user_function; but that isn't part of the
- * lowest-common-denominator implementation. */
- dbus_connection_set_unix_user_function (new_connection,
- allow_same_uid_only,
- /* The static assertion above
- * allow_same_uid_only ensures that
- * this cast does not lose
- * information */
- (void *) (uintptr_t) instance->uid,
- NULL);
-}
-
-static const char *
-bus_containers_ensure_address_template (BusContainers *self,
- DBusError *error)
-{
- DBusString dir = _DBUS_STRING_INIT_INVALID;
- const char *ret = NULL;
- const char *runtime_dir;
-
- /* Early-return if we already did this */
- if (_dbus_string_get_length (&self->address_template) > 0)
- return _dbus_string_get_const_data (&self->address_template);
-
- runtime_dir = _dbus_getenv ("XDG_RUNTIME_DIR");
-
- if (runtime_dir != NULL)
- {
- if (!_dbus_string_init (&dir))
- {
- BUS_SET_OOM (error);
- goto out;
- }
-
- /* We listen on a random socket path resembling
- * /run/user/1000/dbus-1/containers/dbus-abcdef, chosen to share
- * the dbus-1 directory with the dbus-1/services used for transient
- * session services. */
- if (!_dbus_string_append (&dir, runtime_dir) ||
- !_dbus_string_append (&dir, "/dbus-1"))
- {
- BUS_SET_OOM (error);
- goto out;
- }
-
- if (!_dbus_ensure_directory (&dir, error))
- goto out;
-
- if (!_dbus_string_append (&dir, "/containers"))
- {
- BUS_SET_OOM (error);
- goto out;
- }
-
- if (!_dbus_ensure_directory (&dir, error))
- goto out;
- }
- else
- {
- /* No XDG_RUNTIME_DIR, so don't do anything special or clever: just
- * use a random socket like /tmp/dbus-abcdef. */
- const char *tmpdir;
-
- tmpdir = _dbus_get_tmpdir ();
- _dbus_string_init_const (&dir, tmpdir);
- }
-
- /* We specifically use paths, even on Linux (unix:dir= not unix:tmpdir=),
- * because an abstract socket that you can't bind-mount is not useful
- * when you want something you can bind-mount into a container. */
- if (!_dbus_string_append (&self->address_template, "unix:dir=") ||
- !_dbus_address_append_escaped (&self->address_template, &dir))
- {
- _dbus_string_set_length (&self->address_template, 0);
- BUS_SET_OOM (error);
- goto out;
- }
-
- ret = _dbus_string_get_const_data (&self->address_template);
-
-out:
- _dbus_string_free (&dir);
- return ret;
-}
-
-static dbus_bool_t
-bus_container_instance_listen (BusContainerInstance *self,
- DBusError *error)
-{
- BusContainers *containers = bus_context_get_containers (self->context);
- const char *address;
-
- address = bus_containers_ensure_address_template (containers, error);
-
- if (address == NULL)
- return FALSE;
-
- self->server = dbus_server_listen (address, error);
-
- if (self->server == NULL)
- return FALSE;
-
- if (!bus_context_setup_server (self->context, self->server, error))
- return FALSE;
-
- if (!dbus_server_set_auth_mechanisms (self->server,
- (const char **) auth_mechanisms))
- {
- BUS_SET_OOM (error);
- return FALSE;
- }
-
- /* Cannot fail because the memory it uses was already allocated */
- dbus_server_set_new_connection_function (self->server, new_connection_cb,
- bus_container_instance_ref (self),
- (DBusFreeFunction) bus_container_instance_unref);
- return TRUE;
-}
-
-dbus_bool_t
-bus_containers_handle_add_server (DBusConnection *connection,
- BusTransaction *transaction,
- DBusMessage *message,
- DBusError *error)
-{
- BusContainerCreatorData *creator_data;
- DBusMessageIter iter;
- DBusMessageIter dict_iter;
- DBusMessageIter writer;
- DBusMessageIter array_writer;
- const char *type;
- const char *name;
- const char *path;
- BusContainerInstance *instance = NULL;
- BusContext *context;
- BusContainers *containers;
- char *address = NULL;
- DBusAddressEntry **entries = NULL;
- int n_entries;
- DBusMessage *reply = NULL;
- int metadata_size;
- int limit;
- DBusHashIter n_containers_by_user_entry;
- uintptr_t this_user_containers = 0;
-
- context = bus_transaction_get_context (transaction);
- containers = bus_context_get_containers (context);
-
- creator_data = dbus_connection_get_data (connection,
- container_creator_data_slot);
-
- if (creator_data == NULL)
- {
- creator_data = dbus_new0 (BusContainerCreatorData, 1);
-
- if (creator_data == NULL)
- goto oom;
-
- creator_data->instances = NULL;
-
- if (!dbus_connection_set_data (connection, container_creator_data_slot,
- creator_data,
- (DBusFreeFunction) bus_container_creator_data_free))
- {
- bus_container_creator_data_free (creator_data);
- goto oom;
- }
- }
-
- instance = bus_container_instance_new (context, containers, connection,
- error);
-
- if (instance == NULL)
- goto fail;
-
- if (!dbus_connection_get_unix_user (connection, &instance->uid))
- {
- dbus_set_error (error, DBUS_ERROR_FAILED,
- "Unable to determine user ID of caller");
- goto fail;
- }
-
- /* We already checked this in bus_driver_handle_message() */
- _dbus_assert (dbus_message_has_signature (message, "ssa{sv}a{sv}"));
-
- /* Argument 0: Container type */
- if (!dbus_message_iter_init (message, &iter))
- _dbus_assert_not_reached ("Message type was already checked");
-
- _dbus_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING);
- dbus_message_iter_get_basic (&iter, &type);
- instance->type = _dbus_strdup (type);
-
- if (instance->type == NULL)
- goto oom;
-
- if (!dbus_validate_interface (type, NULL))
- {
- dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
- "The container type identifier must have the "
- "syntax of an interface name");
- goto fail;
- }
-
- /* Argument 1: Name as defined by container manager */
- if (!dbus_message_iter_next (&iter))
- _dbus_assert_not_reached ("Message type was already checked");
-
- _dbus_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING);
- dbus_message_iter_get_basic (&iter, &name);
- instance->name = _dbus_strdup (name);
-
- if (instance->name == NULL)
- goto oom;
-
- /* Argument 2: Metadata as defined by container manager */
- if (!dbus_message_iter_next (&iter))
- _dbus_assert_not_reached ("Message type was already checked");
-
- _dbus_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_ARRAY);
- instance->metadata = _dbus_variant_read (&iter);
- _dbus_assert (strcmp (_dbus_variant_get_signature (instance->metadata),
- "a{sv}") == 0);
-
- /* For simplicity we don't count the size of the BusContainerInstance
- * itself, the object path, lengths, the non-payload parts of the DBusString,
- * NUL terminators and so on. That overhead is O(1) and relatively small.
- * This cannot overflow because all parts came from a message, and messages
- * are constrained to be orders of magnitude smaller than the maximum
- * int value. */
- metadata_size = _dbus_variant_get_length (instance->metadata) +
- (int) strlen (type) +
- (int) strlen (name);
- limit = bus_context_get_max_container_metadata_bytes (context);
-
- if (metadata_size > limit)
- {
- DBusError local_error = DBUS_ERROR_INIT;
-
- dbus_set_error (&local_error, DBUS_ERROR_LIMITS_EXCEEDED,
- "Connection \"%s\" (%s) is not allowed to set "
- "%d bytes of container metadata "
- "(max_container_metadata_bytes=%d)",
- bus_connection_get_name (connection),
- bus_connection_get_loginfo (connection),
- metadata_size, limit);
- bus_context_log_literal (context, DBUS_SYSTEM_LOG_WARNING,
- local_error.message);
- dbus_move_error (&local_error, error);
- goto fail;
- }
-
- /* Argument 3: Named parameters */
- if (!dbus_message_iter_next (&iter))
- _dbus_assert_not_reached ("Message type was already checked");
-
- _dbus_assert (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_ARRAY);
- dbus_message_iter_recurse (&iter, &dict_iter);
-
- while (dbus_message_iter_get_arg_type (&dict_iter) != DBUS_TYPE_INVALID)
- {
- DBusMessageIter pair_iter;
- const char *param_name;
-
- _dbus_assert (dbus_message_iter_get_arg_type (&dict_iter) ==
- DBUS_TYPE_DICT_ENTRY);
-
- dbus_message_iter_recurse (&dict_iter, &pair_iter);
- _dbus_assert (dbus_message_iter_get_arg_type (&pair_iter) ==
- DBUS_TYPE_STRING);
- dbus_message_iter_get_basic (&pair_iter, ¶m_name);
-
- /* If we supported any named parameters, we'd copy them into the data
- * structure here; but we don't, so fail instead. */
- dbus_set_error (error, DBUS_ERROR_INVALID_ARGS,
- "Named parameter %s is not understood", param_name);
- goto fail;
- }
-
- /* End of arguments */
- _dbus_assert (!dbus_message_iter_has_next (&iter));
-
- if (containers->instances_by_path == NULL)
- {
- containers->instances_by_path = _dbus_hash_table_new (DBUS_HASH_STRING,
- NULL, NULL);
-
- if (containers->instances_by_path == NULL)
- goto oom;
- }
-
- if (containers->n_containers_by_user == NULL)
- {
- containers->n_containers_by_user = _dbus_hash_table_new (DBUS_HASH_UINTPTR,
- NULL, NULL);
-
- if (containers->n_containers_by_user == NULL)
- goto oom;
- }
-
- limit = bus_context_get_max_containers (context);
-
- if (_dbus_hash_table_get_n_entries (containers->instances_by_path) >= limit)
- {
- DBusError local_error = DBUS_ERROR_INIT;
-
- dbus_set_error (&local_error, DBUS_ERROR_LIMITS_EXCEEDED,
- "Connection \"%s\" (%s) is not allowed to create more "
- "container servers (max_containers=%d)",
- bus_connection_get_name (connection),
- bus_connection_get_loginfo (connection),
- limit);
- bus_context_log_literal (context, DBUS_SYSTEM_LOG_WARNING,
- local_error.message);
- dbus_move_error (&local_error, error);
- goto fail;
- }
-
- if (!_dbus_hash_iter_lookup (containers->n_containers_by_user,
- /* We statically assert that a uid fits in a
- * uintptr_t, so this can't lose information */
- (void *) (uintptr_t) instance->uid, TRUE,
- &n_containers_by_user_entry))
- goto oom;
-
- this_user_containers = (uintptr_t) _dbus_hash_iter_get_value (&n_containers_by_user_entry);
- limit = bus_context_get_max_containers_per_user (context);
-
- /* We need to be careful with types here because this_user_containers is
- * unsigned. */
- if (limit <= 0 || this_user_containers >= (unsigned) limit)
- {
- DBusError local_error = DBUS_ERROR_INIT;
-
- dbus_set_error (&local_error, DBUS_ERROR_LIMITS_EXCEEDED,
- "Connection \"%s\" (%s) is not allowed to create more "
- "container servers for uid %lu "
- "(max_containers_per_user=%d)",
- bus_connection_get_name (connection),
- bus_connection_get_loginfo (connection),
- instance->uid, limit);
- bus_context_log_literal (context, DBUS_SYSTEM_LOG_WARNING,
- local_error.message);
- dbus_move_error (&local_error, error);
- goto fail;
- }
-
- if (!_dbus_hash_table_insert_string (containers->instances_by_path,
- instance->path, instance))
- goto oom;
-
- /* This cannot fail (we already allocated the memory) so we can do it after
- * we already succeeded in adding it to instances_by_path. The matching
- * decrement is done whenever we remove it from instances_by_path. */
- this_user_containers += 1;
- _dbus_hash_iter_set_value (&n_containers_by_user_entry,
- (void *) this_user_containers);
-
- if (!_dbus_list_append (&creator_data->instances, instance))
- goto oom;
-
- /* This part is separated out because we eventually want to be able to
- * accept a fd-passed server socket in the named parameters, instead of
- * creating our own server, and defer listening on it until later */
- if (!bus_container_instance_listen (instance, error))
- goto fail;
-
- address = dbus_server_get_address (instance->server);
-
- if (!dbus_parse_address (address, &entries, &n_entries, error))
- _dbus_assert_not_reached ("listening on unix:dir= should yield a valid address");
-
- _dbus_assert (n_entries == 1);
- _dbus_assert (strcmp (dbus_address_entry_get_method (entries[0]), "unix") == 0);
-
- path = dbus_address_entry_get_value (entries[0], "path");
- _dbus_assert (path != NULL);
-
- reply = dbus_message_new_method_return (message);
-
- if (!dbus_message_append_args (reply,
- DBUS_TYPE_OBJECT_PATH, &instance->path,
- DBUS_TYPE_INVALID))
- goto oom;
-
- dbus_message_iter_init_append (reply, &writer);
-
- if (!dbus_message_iter_open_container (&writer, DBUS_TYPE_ARRAY,
- DBUS_TYPE_BYTE_AS_STRING,
- &array_writer))
- goto oom;
-
- if (!dbus_message_iter_append_fixed_array (&array_writer, DBUS_TYPE_BYTE,
- &path, strlen (path) + 1))
- {
- dbus_message_iter_abandon_container (&writer, &array_writer);
- goto oom;
- }
-
- if (!dbus_message_iter_close_container (&writer, &array_writer))
- goto oom;
-
- if (!dbus_message_append_args (reply,
- DBUS_TYPE_STRING, &address,
- DBUS_TYPE_INVALID))
- goto oom;
-
- _dbus_assert (dbus_message_has_signature (reply, "oays"));
-
- if (! bus_transaction_send_from_driver (transaction, connection, reply))
- goto oom;
-
- instance->announced = TRUE;
- dbus_message_unref (reply);
- bus_container_instance_unref (instance);
- dbus_address_entries_free (entries);
- dbus_free (address);
- return TRUE;
-
-oom:
- BUS_SET_OOM (error);
- /* fall through */
-fail:
- if (instance != NULL)
- bus_container_instance_stop_listening (instance);
-
- dbus_clear_message (&reply);
- dbus_clear_address_entries (&entries);
- bus_clear_container_instance (&instance);
- dbus_free (address);
- return FALSE;
-}
-
-dbus_bool_t
-bus_containers_supported_arguments_getter (BusContext *context,
- DBusMessageIter *var_iter)
-{
- DBusMessageIter arr_iter;
-
- /* There are none so far */
- return dbus_message_iter_open_container (var_iter, DBUS_TYPE_ARRAY,
- DBUS_TYPE_STRING_AS_STRING,
- &arr_iter) &&
- dbus_message_iter_close_container (var_iter, &arr_iter);
-}
-
-dbus_bool_t
-bus_containers_handle_stop_instance (DBusConnection *connection,
- BusTransaction *transaction,
- DBusMessage *message,
- DBusError *error)
-{
- BusContext *context;
- BusContainers *containers;
- BusContainerInstance *instance = NULL;
- DBusList *iter;
- const char *path;
- unsigned long uid;
-
- if (!dbus_message_get_args (message, error,
- DBUS_TYPE_OBJECT_PATH, &path,
- DBUS_TYPE_INVALID))
- goto failed;
-
- context = bus_transaction_get_context (transaction);
- containers = bus_context_get_containers (context);
-
- if (containers->instances_by_path != NULL)
- {
- instance = _dbus_hash_table_lookup_string (containers->instances_by_path,
- path);
- }
-
- if (instance == NULL)
- {
- dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
- "There is no container with path '%s'", path);
- goto failed;
- }
-
- if (!dbus_connection_get_unix_user (connection, &uid))
- {
- dbus_set_error (error, DBUS_ERROR_FAILED,
- "Unable to determine user ID of caller");
- goto failed;
- }
-
- if (uid != instance->uid)
- {
- dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
- "User %lu cannot stop a container server started by "
- "user %lu", uid, instance->uid);
- goto failed;
- }
-
- bus_container_instance_ref (instance);
- bus_container_instance_stop_listening (instance);
-
- for (iter = _dbus_list_get_first_link (&instance->connections);
- iter != NULL;
- iter = _dbus_list_get_next_link (&instance->connections, iter))
- dbus_connection_close (iter->data);
-
- bus_container_instance_unref (instance);
-
- if (!bus_driver_send_ack_reply (connection, transaction, message, error))
- goto failed;
-
- return TRUE;
-
-failed:
- _DBUS_ASSERT_ERROR_IS_SET (error);
- return FALSE;
-}
-
-dbus_bool_t
-bus_containers_handle_stop_listening (DBusConnection *connection,
- BusTransaction *transaction,
- DBusMessage *message,
- DBusError *error)
-{
- BusContext *context;
- BusContainers *containers;
- BusContainerInstance *instance = NULL;
- const char *path;
- unsigned long uid;
-
- if (!dbus_message_get_args (message, error,
- DBUS_TYPE_OBJECT_PATH, &path,
- DBUS_TYPE_INVALID))
- goto failed;
-
- context = bus_transaction_get_context (transaction);
- containers = bus_context_get_containers (context);
-
- if (containers->instances_by_path != NULL)
- {
- instance = _dbus_hash_table_lookup_string (containers->instances_by_path,
- path);
- }
-
- if (instance == NULL)
- {
- dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
- "There is no container with path '%s'", path);
- goto failed;
- }
-
- if (!dbus_connection_get_unix_user (connection, &uid))
- {
- dbus_set_error (error, DBUS_ERROR_FAILED,
- "Unable to determine user ID of caller");
- goto failed;
- }
-
- if (uid != instance->uid)
- {
- dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
- "User %lu cannot stop a container server started by "
- "user %lu", uid, instance->uid);
- goto failed;
- }
-
- bus_container_instance_ref (instance);
- bus_container_instance_stop_listening (instance);
- bus_container_instance_unref (instance);
-
- if (!bus_driver_send_ack_reply (connection, transaction, message, error))
- goto failed;
-
- return TRUE;
-
-failed:
- _DBUS_ASSERT_ERROR_IS_SET (error);
- return FALSE;
-}
-
-/*
- * This accepts a NULL connection so that it can be used when checking
- * whether to allow sending or receiving a message, which might involve
- * the dbus-daemon itself as a message sender or recipient.
- */
-static BusContainerInstance *
-connection_get_instance (DBusConnection *connection)
-{
- if (connection == NULL)
- return NULL;
-
- if (contained_data_slot == -1)
- return NULL;
-
- return dbus_connection_get_data (connection, contained_data_slot);
-}
-
-dbus_bool_t
-bus_containers_handle_get_connection_instance (DBusConnection *caller,
- BusTransaction *transaction,
- DBusMessage *message,
- DBusError *error)
-{
- BusContainerInstance *instance;
- BusDriverFound found;
- DBusConnection *subject;
- DBusMessage *reply = NULL;
- DBusMessageIter writer;
- DBusMessageIter arr_writer;
- const char *bus_name;
-
- _DBUS_ASSERT_ERROR_IS_CLEAR (error);
-
- found = bus_driver_get_conn_helper (caller, message, "container instance",
- &bus_name, &subject, error);
-
- switch (found)
- {
- case BUS_DRIVER_FOUND_SELF:
- dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
- "The message bus is not in a container");
- goto failed;
-
- case BUS_DRIVER_FOUND_PEER:
- break;
-
- case BUS_DRIVER_FOUND_ERROR:
- /* fall through */
- default:
- goto failed;
- }
-
- instance = connection_get_instance (subject);
-
- if (instance == NULL)
- {
- dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
- "Connection '%s' is not in a container", bus_name);
- goto failed;
- }
-
- reply = dbus_message_new_method_return (message);
-
- if (reply == NULL)
- goto oom;
-
- if (!dbus_message_append_args (reply,
- DBUS_TYPE_OBJECT_PATH, &instance->path,
- DBUS_TYPE_INVALID))
- goto oom;
-
- dbus_message_iter_init_append (reply, &writer);
-
- if (!dbus_message_iter_open_container (&writer, DBUS_TYPE_ARRAY, "{sv}",
- &arr_writer))
- goto oom;
-
- if (!bus_driver_fill_connection_credentials (NULL, instance->creator,
- caller,
- &arr_writer))
- {
- dbus_message_iter_abandon_container (&writer, &arr_writer);
- goto oom;
- }
-
- if (!dbus_message_iter_close_container (&writer, &arr_writer))
- goto oom;
-
- if (!dbus_message_append_args (reply,
- DBUS_TYPE_STRING, &instance->type,
- DBUS_TYPE_STRING, &instance->name,
- DBUS_TYPE_INVALID))
- goto oom;
-
- dbus_message_iter_init_append (reply, &writer);
-
- if (!_dbus_variant_write (instance->metadata, &writer))
- goto oom;
-
- if (!bus_transaction_send_from_driver (transaction, caller, reply))
- goto oom;
-
- dbus_message_unref (reply);
- return TRUE;
-
-oom:
- BUS_SET_OOM (error);
- /* fall through */
-failed:
- _DBUS_ASSERT_ERROR_IS_SET (error);
-
- dbus_clear_message (&reply);
- return FALSE;
-}
-
-dbus_bool_t
-bus_containers_handle_get_instance_info (DBusConnection *connection,
- BusTransaction *transaction,
- DBusMessage *message,
- DBusError *error)
-{
- BusContext *context;
- BusContainers *containers;
- BusContainerInstance *instance = NULL;
- DBusMessage *reply = NULL;
- DBusMessageIter writer;
- DBusMessageIter arr_writer;
- const char *path;
-
- if (!dbus_message_get_args (message, error,
- DBUS_TYPE_OBJECT_PATH, &path,
- DBUS_TYPE_INVALID))
- goto failed;
-
- context = bus_transaction_get_context (transaction);
- containers = bus_context_get_containers (context);
-
- if (containers->instances_by_path != NULL)
- {
- instance = _dbus_hash_table_lookup_string (containers->instances_by_path,
- path);
- }
-
- if (instance == NULL)
- {
- dbus_set_error (error, DBUS_ERROR_NOT_CONTAINER,
- "There is no container with path '%s'", path);
- goto failed;
- }
-
- reply = dbus_message_new_method_return (message);
-
- if (reply == NULL)
- goto oom;
-
- dbus_message_iter_init_append (reply, &writer);
-
- if (!dbus_message_iter_open_container (&writer, DBUS_TYPE_ARRAY, "{sv}",
- &arr_writer))
- goto oom;
-
- if (!bus_driver_fill_connection_credentials (NULL, instance->creator,
- connection,
- &arr_writer))
- {
- dbus_message_iter_abandon_container (&writer, &arr_writer);
- goto oom;
- }
-
- if (!dbus_message_iter_close_container (&writer, &arr_writer))
- goto oom;
-
- if (!dbus_message_append_args (reply,
- DBUS_TYPE_STRING, &instance->type,
- DBUS_TYPE_STRING, &instance->name,
- DBUS_TYPE_INVALID))
- goto oom;
-
- dbus_message_iter_init_append (reply, &writer);
-
- if (!_dbus_variant_write (instance->metadata, &writer))
- goto oom;
-
- if (!bus_transaction_send_from_driver (transaction, connection, reply))
- goto oom;
-
- dbus_message_unref (reply);
- return TRUE;
-
-oom:
- BUS_SET_OOM (error);
- /* fall through */
-failed:
- _DBUS_ASSERT_ERROR_IS_SET (error);
-
- dbus_clear_message (&reply);
- return FALSE;
-}
-
-dbus_bool_t
-bus_containers_handle_request_header (DBusConnection *caller,
- BusTransaction *transaction,
- DBusMessage *message,
- DBusError *error)
-{
- DBusMessage *reply = NULL;
- dbus_bool_t ret = FALSE;
-
- reply = dbus_message_new_method_return (message);
-
- /* We prepare the transaction before carrying out its side-effects,
- * because otherwise it isn't transactional */
- if (reply == NULL ||
- !bus_transaction_send_from_driver (transaction, caller, reply))
- {
- BUS_SET_OOM (error);
- goto out;
- }
-
- bus_connection_request_headers (caller,
- BUS_EXTRA_HEADERS_CONTAINER_INSTANCE);
- ret = TRUE;
-
-out:
- dbus_clear_message (&reply);
- return ret;
-}
-
-void
-bus_containers_stop_listening (BusContainers *self)
-{
- if (self->instances_by_path != NULL)
- {
- DBusHashIter iter;
-
- _dbus_hash_iter_init (self->instances_by_path, &iter);
-
- while (_dbus_hash_iter_next (&iter))
- {
- BusContainerInstance *instance = _dbus_hash_iter_get_value (&iter);
-
- bus_container_instance_stop_listening (instance);
- }
- }
-}
-
-#else
-
-BusContainers *
-bus_containers_new (void)
-{
- /* Return an arbitrary non-NULL pointer just to indicate that we didn't
- * fail. There is no valid operation to do with it on this platform,
- * other than unreffing it, which does nothing. */
- return (BusContainers *) 1;
-}
-
-BusContainers *
-bus_containers_ref (BusContainers *self)
-{
- _dbus_assert (self == (BusContainers *) 1);
- return self;
-}
-
-void
-bus_containers_unref (BusContainers *self)
-{
- _dbus_assert (self == (BusContainers *) 1);
-}
-
-void
-bus_containers_stop_listening (BusContainers *self)
-{
- _dbus_assert (self == (BusContainers *) 1);
-}
-
-#endif /* DBUS_ENABLE_CONTAINERS */
-
-void
-bus_containers_remove_connection (BusContainers *self,
- DBusConnection *connection)
-{
-#ifdef DBUS_ENABLE_CONTAINERS
- BusContainerCreatorData *creator_data;
- BusContainerInstance *instance;
-
- dbus_connection_ref (connection);
- creator_data = dbus_connection_get_data (connection,
- container_creator_data_slot);
-
- if (creator_data != NULL)
- {
- DBusList *iter;
- DBusList *next;
-
- for (iter = _dbus_list_get_first_link (&creator_data->instances);
- iter != NULL;
- iter = next)
- {
- instance = iter->data;
-
- /* Remember where we got to before we do something that might free
- * iter and instance */
- next = _dbus_list_get_next_link (&creator_data->instances, iter);
-
- _dbus_assert (instance->creator == connection);
-
- /* This will invalidate iter and instance if there are no open
- * connections to this instance */
- bus_container_instance_stop_listening (instance);
- }
- }
-
- instance = connection_get_instance (connection);
-
- if (instance != NULL)
- bus_container_instance_lost_connection (instance, connection);
-
- dbus_connection_unref (connection);
-#endif /* DBUS_ENABLE_CONTAINERS */
-}
-
-dbus_bool_t
-bus_containers_connection_is_contained (DBusConnection *connection,
- const char **path,
- const char **type,
- const char **name)
-{
-#ifdef DBUS_ENABLE_CONTAINERS
- BusContainerInstance *instance;
-
- instance = connection_get_instance (connection);
-
- if (instance != NULL)
- {
- if (path != NULL)
- *path = instance->path;
-
- if (type != NULL)
- *type = instance->type;
-
- if (name != NULL)
- *name = instance->name;
-
- return TRUE;
- }
-#endif /* DBUS_ENABLE_CONTAINERS */
-
- return FALSE;
-}
+++ /dev/null
-/* Integration tests for restricted sockets for containers
- *
- * Copyright © 2017-2018 Collabora Ltd.
- * SPDX-License-Identifier: MIT
- *
- * 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 <errno.h>
-
-#include <dbus/dbus.h>
-
-#include <gio/gio.h>
-#include <glib.h>
-#include <glib/gstdio.h>
-
-#if defined(DBUS_ENABLE_CONTAINERS) && defined(HAVE_GIO_UNIX)
-
-#define HAVE_CONTAINERS_TEST
-
-#include <gio/gunixfdlist.h>
-#include <gio/gunixsocketaddress.h>
-
-#include "dbus/dbus-sysdeps-unix.h"
-
-#endif
-
-#include "test-utils-glib.h"
-
-#define DBUS_INTERFACE_CONTAINERS1 "org.freedesktop.DBus.Containers1"
-
-typedef struct {
- TestMainContext *ctx;
- gboolean skip;
- gchar *bus_address;
- GPid daemon_pid;
- GError *error;
-
- GDBusProxy *proxy;
-
- gchar *instance_path;
- gchar *socket_path;
- gchar *socket_dbus_address;
- GDBusConnection *unconfined_conn;
- gchar *unconfined_unique_name;
- GDBusConnection *confined_conn;
-
- GDBusConnection *observer_conn;
- GDBusProxy *observer_proxy;
- GHashTable *containers_removed;
- guint removed_sub;
- DBusConnection *libdbus_observer;
- DBusMessage *latest_shout;
-} Fixture;
-
-typedef struct
-{
- const gchar *config_file;
- enum
- {
- STOP_SERVER_EXPLICITLY,
- STOP_SERVER_DISCONNECT_FIRST,
- STOP_SERVER_NEVER_CONNECTED,
- STOP_SERVER_FORCE,
- STOP_SERVER_WITH_MANAGER
- }
- stop_server;
-} Config;
-
-static const Config default_config =
-{
- NULL,
- 0 /* not used, the stop-server test always uses non-default config */
-};
-
-#ifdef DBUS_ENABLE_CONTAINERS
-/* A GDBusNameVanishedCallback that sets a boolean flag. */
-static void
-name_gone_set_boolean_cb (GDBusConnection *conn,
- const gchar *name,
- gpointer user_data)
-{
- gboolean *gone_p = user_data;
-
- g_assert_nonnull (gone_p);
- g_assert_false (*gone_p);
- *gone_p = TRUE;
-}
-#endif
-
-#ifdef HAVE_CONTAINERS_TEST
-static void
-iterate_both_main_loops (TestMainContext *ctx)
-{
- /* TODO: Gluing these two main loops together so they can block would
- * be better than sleeping, but do we have enough API to do that without
- * reinventing dbus-glib? */
- g_usleep (G_USEC_PER_SEC / 100);
- test_main_context_iterate (ctx, FALSE);
- g_main_context_iteration (NULL, FALSE);
-}
-#endif
-
-static DBusHandlerResult
-observe_shouting_cb (DBusConnection *observer,
- DBusMessage *message,
- void *user_data)
-{
- Fixture *f = user_data;
-
- if (dbus_message_is_signal (message, "com.example.Shouting", "Shouted"))
- {
- dbus_clear_message (&f->latest_shout);
- f->latest_shout = dbus_message_ref (message);
- }
-
- return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-}
-
-static void
-instance_removed_cb (GDBusConnection *observer,
- const gchar *sender,
- const gchar *path,
- const gchar *iface,
- const gchar *member,
- GVariant *parameters,
- gpointer user_data)
-{
- Fixture *f = user_data;
- const gchar *container;
-
- g_assert_cmpstr (sender, ==, DBUS_SERVICE_DBUS);
- g_assert_cmpstr (path, ==, DBUS_PATH_DBUS);
- g_assert_cmpstr (iface, ==, DBUS_INTERFACE_CONTAINERS1);
- g_assert_cmpstr (member, ==, "InstanceRemoved");
- g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "(o)");
- g_variant_get (parameters, "(&o)", &container);
- g_assert (!g_hash_table_contains (f->containers_removed, container));
- g_hash_table_add (f->containers_removed, g_strdup (container));
-}
-
-static void
-fixture_disconnect_unconfined (Fixture *f)
-{
- if (f->unconfined_conn != NULL)
- {
- GError *error = NULL;
-
- g_dbus_connection_close_sync (f->unconfined_conn, NULL, &error);
-
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED))
- g_clear_error (&error);
- else
- g_assert_no_error (error);
- }
-
- g_clear_object (&f->unconfined_conn);
-}
-
-static void
-fixture_disconnect_observer (Fixture *f)
-{
- if (f->observer_conn != NULL)
- {
- GError *error = NULL;
-
- g_dbus_connection_signal_unsubscribe (f->observer_conn,
- f->removed_sub);
-
- g_dbus_connection_close_sync (f->observer_conn, NULL, &error);
-
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED))
- g_clear_error (&error);
- else
- g_assert_no_error (error);
- }
-
- g_clear_object (&f->observer_conn);
-}
-
-static void
-setup (Fixture *f,
- gconstpointer context)
-{
- const Config *config = context;
-
- if (config == NULL)
- config = &default_config;
-
- f->ctx = test_main_context_get ();
-
- f->bus_address = test_get_dbus_daemon (config->config_file, TEST_USER_ME,
- NULL, &f->daemon_pid);
-
- if (f->bus_address == NULL)
- {
- f->skip = TRUE;
- return;
- }
-
- f->unconfined_conn = g_dbus_connection_new_for_address_sync (f->bus_address,
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
- g_assert_no_error (f->error);
- f->unconfined_unique_name = g_strdup (
- g_dbus_connection_get_unique_name (f->unconfined_conn));
- g_test_message ("Unconfined connection: \"%s\"",
- f->unconfined_unique_name);
-
- f->observer_conn = g_dbus_connection_new_for_address_sync (f->bus_address,
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
- g_assert_no_error (f->error);
- f->containers_removed = g_hash_table_new_full (g_str_hash, g_str_equal,
- g_free, NULL);
- f->removed_sub = g_dbus_connection_signal_subscribe (f->observer_conn,
- DBUS_SERVICE_DBUS,
- DBUS_INTERFACE_CONTAINERS1,
- "InstanceRemoved",
- DBUS_PATH_DBUS, NULL,
- G_DBUS_SIGNAL_FLAGS_NONE,
- instance_removed_cb,
- f, NULL);
-
- /* We have to use libdbus for new header fields, because GDBus doesn't
- * yet have API for that. */
- f->libdbus_observer = test_connect_to_bus (f->ctx, f->bus_address);
- dbus_bus_add_match (f->libdbus_observer,
- "interface='com.example.Shouting'", NULL);
-
- if (!dbus_connection_add_filter (f->libdbus_observer, observe_shouting_cb, f,
- NULL))
- g_error ("OOM");
-}
-
-/*
- * Assert that Get(SupportedArguments) contains what we expect it to.
- */
-static void
-test_get_supported_arguments (Fixture *f,
- gconstpointer context)
-{
- GVariant *v;
-#ifdef DBUS_ENABLE_CONTAINERS
- const gchar **args;
- gsize len;
-#endif
-
- if (f->skip)
- return;
-
- f->proxy = g_dbus_proxy_new_sync (f->unconfined_conn, G_DBUS_PROXY_FLAGS_NONE,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_CONTAINERS1,
- NULL, &f->error);
-
- /* This one is DBUS_ENABLE_CONTAINERS rather than HAVE_CONTAINERS_TEST
- * because we can still test whether the interface appears or not, even
- * if we were not able to detect gio-unix-2.0 */
-#ifdef DBUS_ENABLE_CONTAINERS
- g_assert_no_error (f->error);
-
- v = g_dbus_proxy_get_cached_property (f->proxy, "SupportedArguments");
- g_assert_cmpstr (g_variant_get_type_string (v), ==, "as");
- args = g_variant_get_strv (v, &len);
-
- /* No arguments are defined yet */
- g_assert_cmpuint (len, ==, 0);
-
- g_free (args);
- g_variant_unref (v);
-#else /* !DBUS_ENABLE_CONTAINERS */
- g_assert_no_error (f->error);
- v = g_dbus_proxy_get_cached_property (f->proxy, "SupportedArguments");
- g_assert_null (v);
-#endif /* !DBUS_ENABLE_CONTAINERS */
-}
-
-#ifdef HAVE_CONTAINERS_TEST
-/*
- * Try to make an AddServer call that usually succeeds, but may fail and
- * be skipped if we are running as root and this version of dbus has not
- * been fully installed. Return TRUE if we can continue.
- *
- * parameters is sunk if it is a floating reference.
- */
-static gboolean
-add_container_server (Fixture *f,
- GVariant *parameters)
-{
- GVariant *tuple;
- GStatBuf stat_buf;
-
- f->proxy = g_dbus_proxy_new_sync (f->unconfined_conn,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_CONTAINERS1,
- NULL, &f->error);
- g_assert_no_error (f->error);
-
- g_test_message ("Calling AddServer...");
- tuple = g_dbus_proxy_call_sync (f->proxy, "AddServer", parameters,
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
-
- /* For root, the sockets go in /run/dbus/containers, which we rely on
- * system infrastructure to create; so it's OK for AddServer to fail
- * when uninstalled, although not OK if it fails as an installed-test. */
- if (f->error != NULL &&
- _dbus_getuid () == 0 &&
- _dbus_getenv ("DBUS_TEST_UNINSTALLED") != NULL)
- {
- g_test_message ("AddServer: %s", f->error->message);
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_FILE_NOT_FOUND);
- g_test_skip ("AddServer failed, probably because this dbus "
- "version is not fully installed");
- return FALSE;
- }
-
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
-
- g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(oays)");
- g_variant_get (tuple, "(o^ays)", &f->instance_path, &f->socket_path,
- &f->socket_dbus_address);
- g_assert_true (g_str_has_prefix (f->socket_dbus_address, "unix:"));
- g_assert_null (strchr (f->socket_dbus_address, ';'));
- g_assert_null (strchr (f->socket_dbus_address + strlen ("unix:"), ':'));
- g_clear_pointer (&tuple, g_variant_unref);
-
- g_assert_nonnull (f->instance_path);
- g_assert_true (g_variant_is_object_path (f->instance_path));
- g_assert_nonnull (f->socket_path);
- g_assert_true (g_path_is_absolute (f->socket_path));
- g_assert_nonnull (f->socket_dbus_address);
- g_assert_cmpstr (g_stat (f->socket_path, &stat_buf) == 0 ? NULL :
- g_strerror (errno), ==, NULL);
- g_assert_cmpuint ((stat_buf.st_mode & S_IFMT), ==, S_IFSOCK);
- return TRUE;
-}
-#endif
-
-/*
- * Assert that a simple AddServer() call succeeds and has the behaviour
- * we expect (we can connect a confined connection to it, the confined
- * connection can talk to the dbus-daemon and to an unconfined connection,
- * and the socket gets cleaned up when the dbus-daemon terminates).
- *
- * This also tests simple cases for metadata.
- */
-static void
-test_basic (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- GVariant *asv;
- GVariant *creator;
- GVariant *parameters;
- GVariantDict dict;
- const gchar *confined_unique_name;
- const gchar *path_from_query;
- const gchar *name;
- const gchar *name_owner;
- const gchar *type;
- guint32 uid;
- GStatBuf stat_buf;
- GVariant *tuple;
- DBusMessage *libdbus_message = NULL;
- DBusMessage *libdbus_reply = NULL;
- DBusError libdbus_error = DBUS_ERROR_INIT;
-
- if (f->skip)
- return;
-
- parameters = g_variant_new ("(ssa{sv}a{sv})",
- "com.example.NotFlatpak",
- "sample-app",
- NULL, /* no metadata */
- NULL); /* no named arguments */
- if (!add_container_server (f, g_steal_pointer (¶meters)))
- return;
-
- g_test_message ("Connecting to %s...", f->socket_dbus_address);
- f->confined_conn = g_dbus_connection_new_for_address_sync (
- f->socket_dbus_address,
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
- g_assert_no_error (f->error);
-
- g_test_message ("Making a method call from confined app...");
- tuple = g_dbus_connection_call_sync (f->confined_conn, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
- "GetNameOwner",
- g_variant_new ("(s)", DBUS_SERVICE_DBUS),
- G_VARIANT_TYPE ("(s)"),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(s)");
- g_variant_get (tuple, "(&s)", &name_owner);
- g_assert_cmpstr (name_owner, ==, DBUS_SERVICE_DBUS);
- g_clear_pointer (&tuple, g_variant_unref);
-
- g_test_message ("Making a method call from confined app to unconfined...");
- tuple = g_dbus_connection_call_sync (f->confined_conn,
- f->unconfined_unique_name,
- "/", DBUS_INTERFACE_PEER,
- "Ping",
- NULL, G_VARIANT_TYPE_UNIT,
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "()");
- g_clear_pointer (&tuple, g_variant_unref);
-
- g_test_message ("Receiving signals without requesting extra headers");
- g_dbus_connection_emit_signal (f->confined_conn, NULL, "/",
- "com.example.Shouting", "Shouted",
- NULL, NULL);
-
- while (f->latest_shout == NULL)
- iterate_both_main_loops (f->ctx);
-
- g_assert_cmpstr (dbus_message_get_container_instance (f->latest_shout), ==,
- NULL);
- dbus_clear_message (&f->latest_shout);
-
- g_dbus_connection_emit_signal (f->unconfined_conn, NULL, "/",
- "com.example.Shouting", "Shouted",
- NULL, NULL);
-
- while (f->latest_shout == NULL)
- iterate_both_main_loops (f->ctx);
-
- g_assert_cmpstr (dbus_message_get_container_instance (f->latest_shout), ==,
- NULL);
- dbus_clear_message (&f->latest_shout);
-
- g_test_message ("Receiving signals after requesting extra headers");
-
- libdbus_message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS,
- DBUS_INTERFACE_CONTAINERS1,
- "RequestHeader");
- libdbus_reply = test_main_context_call_and_wait (f->ctx,
- f->libdbus_observer,
- libdbus_message,
- DBUS_TIMEOUT_USE_DEFAULT);
-
- if (dbus_set_error_from_message (&libdbus_error, libdbus_reply))
- g_error ("%s", libdbus_error.message);
-
- dbus_clear_message (&libdbus_message);
- dbus_clear_message (&libdbus_reply);
-
- g_dbus_connection_emit_signal (f->confined_conn, NULL, "/",
- "com.example.Shouting", "Shouted",
- NULL, NULL);
-
- while (f->latest_shout == NULL)
- iterate_both_main_loops (f->ctx);
-
- g_assert_cmpstr (dbus_message_get_container_instance (f->latest_shout), ==,
- f->instance_path);
- dbus_clear_message (&f->latest_shout);
-
- g_dbus_connection_emit_signal (f->unconfined_conn, NULL, "/",
- "com.example.Shouting", "Shouted",
- NULL, NULL);
-
- while (f->latest_shout == NULL)
- iterate_both_main_loops (f->ctx);
-
- g_assert_cmpstr (dbus_message_get_container_instance (f->latest_shout), ==,
- "/");
- dbus_clear_message (&f->latest_shout);
-
- g_test_message ("Checking that confined app is not considered privileged...");
- tuple = g_dbus_connection_call_sync (f->confined_conn, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
- "UpdateActivationEnvironment",
- g_variant_new ("(a{ss})", NULL),
- G_VARIANT_TYPE_UNIT,
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED);
- g_test_message ("Access denied as expected: %s", f->error->message);
- g_clear_error (&f->error);
- g_assert_null (tuple);
-
- g_test_message ("Inspecting connection container info");
- confined_unique_name = g_dbus_connection_get_unique_name (f->confined_conn);
- tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInstance",
- g_variant_new ("(s)", confined_unique_name),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(oa{sv}ssa{sv})");
- g_variant_get (tuple, "(&o@a{sv}&s&s@a{sv})",
- &path_from_query, &creator, &type, &name, &asv);
- g_assert_cmpstr (path_from_query, ==, f->instance_path);
- g_variant_dict_init (&dict, creator);
- g_assert_true (g_variant_dict_lookup (&dict, "UnixUserID", "u", &uid));
- g_assert_cmpuint (uid, ==, _dbus_getuid ());
- g_variant_dict_clear (&dict);
- g_assert_cmpstr (type, ==, "com.example.NotFlatpak");
- g_assert_cmpstr (name, ==, "sample-app");
- /* Trivial case: the metadata a{sv} is empty */
- g_assert_cmpuint (g_variant_n_children (asv), ==, 0);
- g_clear_pointer (&asv, g_variant_unref);
- g_clear_pointer (&creator, g_variant_unref);
- g_clear_pointer (&tuple, g_variant_unref);
-
- g_test_message ("Inspecting container instance info");
- tuple = g_dbus_proxy_call_sync (f->proxy, "GetInstanceInfo",
- g_variant_new ("(o)", f->instance_path),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(a{sv}ssa{sv})");
- g_variant_get (tuple, "(@a{sv}&s&s@a{sv})", &creator, &type, &name, &asv);
- g_variant_dict_init (&dict, creator);
- g_assert_true (g_variant_dict_lookup (&dict, "UnixUserID", "u", &uid));
- g_assert_cmpuint (uid, ==, _dbus_getuid ());
- g_variant_dict_clear (&dict);
- g_assert_cmpstr (type, ==, "com.example.NotFlatpak");
- g_assert_cmpstr (name, ==, "sample-app");
- /* Trivial case: the metadata a{sv} is empty */
- g_assert_cmpuint (g_variant_n_children (asv), ==, 0);
- g_clear_pointer (&asv, g_variant_unref);
- g_clear_pointer (&creator, g_variant_unref);
- g_clear_pointer (&tuple, g_variant_unref);
-
- /* Check that the socket is cleaned up when the dbus-daemon is terminated */
- test_kill_pid (f->daemon_pid);
- g_spawn_close_pid (f->daemon_pid);
- f->daemon_pid = 0;
-
- while (g_stat (f->socket_path, &stat_buf) == 0)
- g_usleep (G_USEC_PER_SEC / 20);
-
- g_assert_cmpint (errno, ==, ENOENT);
-
-#else /* !HAVE_CONTAINERS_TEST */
- g_test_skip ("Containers or gio-unix-2.0 not supported");
-#endif /* !HAVE_CONTAINERS_TEST */
-}
-
-/*
- * If we are running as root, assert that when one uid (root) creates a
- * container server, another uid (TEST_USER_OTHER) cannot connect to it
- */
-static void
-test_wrong_uid (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- GVariant *parameters;
-
- if (f->skip)
- return;
-
- parameters = g_variant_new ("(ssa{sv}a{sv})",
- "com.example.NotFlatpak",
- "sample-app",
- NULL, /* no metadata */
- NULL); /* no named arguments */
- if (!add_container_server (f, g_steal_pointer (¶meters)))
- return;
-
- g_test_message ("Connecting to %s...", f->socket_dbus_address);
- f->confined_conn = test_try_connect_gdbus_as_user (f->socket_dbus_address,
- TEST_USER_OTHER,
- &f->error);
-
- /* That might be skipped if we can't become TEST_USER_OTHER */
- if (f->error != NULL &&
- g_error_matches (f->error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
- {
- g_test_skip (f->error->message);
- return;
- }
-
- /* The connection was unceremoniously closed */
- g_assert_error (f->error, G_IO_ERROR, G_IO_ERROR_CLOSED);
-
-#else /* !HAVE_CONTAINERS_TEST */
- g_test_skip ("Containers or gio-unix-2.0 not supported");
-#endif /* !HAVE_CONTAINERS_TEST */
-}
-
-/*
- * Test for non-trivial metadata: assert that the metadata a{sv} is
- * carried through correctly, and that the app name is allowed to be empty.
- */
-static void
-test_metadata (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- GVariant *asv;
- GVariant *creator;
- GVariant *tuple;
- GVariant *parameters;
- GVariantDict dict;
- const gchar *confined_unique_name;
- const gchar *path_from_query;
- const gchar *name;
- const gchar *type;
- guint32 uid;
- guint u;
- gboolean b;
- const gchar *s;
-
- if (f->skip)
- return;
-
- g_variant_dict_init (&dict, NULL);
- g_variant_dict_insert (&dict, "Species", "s", "Martes martes");
- g_variant_dict_insert (&dict, "IsCrepuscular", "b", TRUE);
- g_variant_dict_insert (&dict, "NChildren", "u", 2);
-
- parameters = g_variant_new ("(ss@a{sv}a{sv})",
- "org.example.Springwatch",
- /* Verify that empty app names are OK */
- "",
- g_variant_dict_end (&dict),
- NULL); /* no named arguments */
- if (!add_container_server (f, g_steal_pointer (¶meters)))
- return;
-
- g_test_message ("Connecting to %s...", f->socket_dbus_address);
- f->confined_conn = g_dbus_connection_new_for_address_sync (
- f->socket_dbus_address,
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
- g_assert_no_error (f->error);
-
- g_test_message ("Inspecting connection credentials...");
- confined_unique_name = g_dbus_connection_get_unique_name (f->confined_conn);
- tuple = g_dbus_connection_call_sync (f->confined_conn, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
- "GetConnectionCredentials",
- g_variant_new ("(s)",
- confined_unique_name),
- G_VARIANT_TYPE ("(a{sv})"),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(a{sv})");
- asv = g_variant_get_child_value (tuple, 0);
- g_variant_dict_init (&dict, asv);
- g_assert_true (g_variant_dict_lookup (&dict,
- DBUS_INTERFACE_CONTAINERS1 ".Instance",
- "&o", &path_from_query));
- g_assert_cmpstr (path_from_query, ==, f->instance_path);
- g_variant_dict_clear (&dict);
- g_clear_pointer (&asv, g_variant_unref);
- g_clear_pointer (&tuple, g_variant_unref);
-
- g_test_message ("Inspecting connection container info");
- tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInstance",
- g_variant_new ("(s)", confined_unique_name),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(oa{sv}ssa{sv})");
- g_variant_get (tuple, "(&o@a{sv}&s&s@a{sv})",
- &path_from_query, &creator, &type, &name, &asv);
- g_assert_cmpstr (path_from_query, ==, f->instance_path);
- g_variant_dict_init (&dict, creator);
- g_assert_true (g_variant_dict_lookup (&dict, "UnixUserID", "u", &uid));
- g_assert_cmpuint (uid, ==, _dbus_getuid ());
- g_variant_dict_clear (&dict);
- g_assert_cmpstr (type, ==, "org.example.Springwatch");
- g_assert_cmpstr (name, ==, "");
- g_variant_dict_init (&dict, asv);
- g_assert_true (g_variant_dict_lookup (&dict, "NChildren", "u", &u));
- g_assert_cmpuint (u, ==, 2);
- g_assert_true (g_variant_dict_lookup (&dict, "IsCrepuscular", "b", &b));
- g_assert_cmpint (b, ==, TRUE);
- g_assert_true (g_variant_dict_lookup (&dict, "Species", "&s", &s));
- g_assert_cmpstr (s, ==, "Martes martes");
- g_variant_dict_clear (&dict);
- g_assert_cmpuint (g_variant_n_children (asv), ==, 3);
- g_clear_pointer (&asv, g_variant_unref);
- g_clear_pointer (&creator, g_variant_unref);
- g_clear_pointer (&tuple, g_variant_unref);
-
- g_test_message ("Inspecting container instance info");
- tuple = g_dbus_proxy_call_sync (f->proxy, "GetInstanceInfo",
- g_variant_new ("(o)", f->instance_path),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(a{sv}ssa{sv})");
- g_variant_get (tuple, "(@a{sv}&s&s@a{sv})", &creator, &type, &name, &asv);
- g_variant_dict_init (&dict, creator);
- g_assert_true (g_variant_dict_lookup (&dict, "UnixUserID", "u", &uid));
- g_assert_cmpuint (uid, ==, _dbus_getuid ());
- g_variant_dict_clear (&dict);
- g_assert_cmpstr (type, ==, "org.example.Springwatch");
- g_assert_cmpstr (name, ==, "");
- g_variant_dict_init (&dict, asv);
- g_assert_true (g_variant_dict_lookup (&dict, "NChildren", "u", &u));
- g_assert_cmpuint (u, ==, 2);
- g_assert_true (g_variant_dict_lookup (&dict, "IsCrepuscular", "b", &b));
- g_assert_cmpint (b, ==, TRUE);
- g_assert_true (g_variant_dict_lookup (&dict, "Species", "&s", &s));
- g_assert_cmpstr (s, ==, "Martes martes");
- g_variant_dict_clear (&dict);
- g_assert_cmpuint (g_variant_n_children (asv), ==, 3);
- g_clear_pointer (&asv, g_variant_unref);
- g_clear_pointer (&creator, g_variant_unref);
- g_clear_pointer (&tuple, g_variant_unref);
-
-#else /* !HAVE_CONTAINERS_TEST */
- g_test_skip ("Containers or gio-unix-2.0 not supported");
-#endif /* !HAVE_CONTAINERS_TEST */
-}
-
-/*
- * With config->stop_server == STOP_SERVER_WITH_MANAGER:
- * Assert that without special parameters, when the container manager
- * disappears from the bus, so does the confined server.
- *
- * With config->stop_server == STOP_SERVER_EXPLICITLY or
- * config->stop_server == STOP_SERVER_DISCONNECT_FIRST:
- * Test StopListening(), which just closes the listening socket.
- *
- * With config->stop_server == STOP_SERVER_FORCE:
- * Test StopInstance(), which closes the listening socket and
- * disconnects all its clients.
- */
-static void
-test_stop_server (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- const Config *config = context;
- GDBusConnection *attacker;
- GDBusConnection *second_confined_conn;
- GDBusProxy *attacker_proxy;
- GSocket *client_socket;
- GSocketAddress *socket_address;
- GVariant *tuple;
- GVariant *parameters;
- gchar *error_name;
- const gchar *confined_unique_name;
- const gchar *name_owner;
- gboolean gone = FALSE;
- guint name_watch;
- guint i;
-
- g_assert_nonnull (config);
-
- if (f->skip)
- return;
-
- f->observer_proxy = g_dbus_proxy_new_sync (f->observer_conn,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS,
- DBUS_INTERFACE_CONTAINERS1, NULL,
- &f->error);
- g_assert_no_error (f->error);
-
- parameters = g_variant_new ("(ssa{sv}a{sv})",
- "com.example.NotFlatpak",
- "sample-app",
- NULL, /* no metadata */
- NULL); /* no named arguments */
- if (!add_container_server (f, g_steal_pointer (¶meters)))
- return;
-
- socket_address = g_unix_socket_address_new (f->socket_path);
-
- if (config->stop_server != STOP_SERVER_NEVER_CONNECTED)
- {
- g_test_message ("Connecting to %s...", f->socket_dbus_address);
- f->confined_conn = g_dbus_connection_new_for_address_sync (
- f->socket_dbus_address,
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
- g_assert_no_error (f->error);
-
- if (config->stop_server == STOP_SERVER_DISCONNECT_FIRST)
- {
- g_test_message ("Disconnecting confined connection...");
- gone = FALSE;
- confined_unique_name = g_dbus_connection_get_unique_name (
- f->confined_conn);
- name_watch = g_bus_watch_name_on_connection (f->observer_conn,
- confined_unique_name,
- G_BUS_NAME_WATCHER_FLAGS_NONE,
- NULL,
- name_gone_set_boolean_cb,
- &gone, NULL);
- g_dbus_connection_close_sync (f->confined_conn, NULL, &f->error);
- g_assert_no_error (f->error);
-
- g_test_message ("Waiting for confined app bus name to disappear...");
-
- while (!gone)
- g_main_context_iteration (NULL, TRUE);
-
- g_bus_unwatch_name (name_watch);
- }
- }
-
- /* If we are able to switch uid (i.e. we are root), check that a local
- * attacker with a different uid cannot close our container instances. */
- attacker = test_try_connect_gdbus_as_user (f->bus_address, TEST_USER_OTHER,
- &f->error);
-
- if (attacker != NULL)
- {
- attacker_proxy = g_dbus_proxy_new_sync (attacker,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS,
- DBUS_INTERFACE_CONTAINERS1, NULL,
- &f->error);
- g_assert_no_error (f->error);
-
- tuple = g_dbus_proxy_call_sync (attacker_proxy, "StopListening",
- g_variant_new ("(o)", f->instance_path),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED);
- g_assert_null (tuple);
- g_clear_error (&f->error);
-
- tuple = g_dbus_proxy_call_sync (attacker_proxy, "StopInstance",
- g_variant_new ("(o)", f->instance_path),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED);
- g_assert_null (tuple);
- g_clear_error (&f->error);
-
- g_clear_object (&attacker_proxy);
- g_dbus_connection_close_sync (attacker, NULL, &f->error);
- g_assert_no_error (f->error);
- g_clear_object (&attacker);
- }
- else
- {
- /* If we aren't running as root, it's OK to not be able to connect again
- * as some other user (usually 'nobody'). We don't g_test_skip() here
- * because this is just extra coverage */
- g_assert_error (f->error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED);
- g_clear_error (&f->error);
- }
-
- g_assert_false (g_hash_table_contains (f->containers_removed,
- f->instance_path));
-
- switch (config->stop_server)
- {
- case STOP_SERVER_WITH_MANAGER:
- /* Close the unconfined connection (the container manager) and wait
- * for it to go away */
- g_test_message ("Closing container manager...");
- name_watch = g_bus_watch_name_on_connection (f->confined_conn,
- f->unconfined_unique_name,
- G_BUS_NAME_WATCHER_FLAGS_NONE,
- NULL,
- name_gone_set_boolean_cb,
- &gone, NULL);
- fixture_disconnect_unconfined (f);
-
- g_test_message ("Waiting for container manager bus name to disappear...");
-
- while (!gone)
- g_main_context_iteration (NULL, TRUE);
-
- g_bus_unwatch_name (name_watch);
- break;
-
- case STOP_SERVER_EXPLICITLY:
- g_test_message ("Stopping server (but not confined connection)...");
- tuple = g_dbus_proxy_call_sync (f->proxy, "StopListening",
- g_variant_new ("(o)", f->instance_path),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_no_error (f->error);
- g_variant_unref (tuple);
-
- /* The container instance remains open, because the connection has
- * not gone away yet. Do another method call: if we were going to
- * get the signal, it would arrive before the reply to this second
- * method call. Any method will do here, even one that doesn't
- * exist. */
- g_test_message ("Checking we do not get InstanceRemoved...");
- tuple = g_dbus_proxy_call_sync (f->proxy, "NoSuchMethod", NULL,
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
- g_assert_null (tuple);
- g_clear_error (&f->error);
- break;
-
- case STOP_SERVER_DISCONNECT_FIRST:
- case STOP_SERVER_NEVER_CONNECTED:
- g_test_message ("Stopping server (with no confined connections)...");
- tuple = g_dbus_proxy_call_sync (f->proxy, "StopListening",
- g_variant_new ("(o)", f->instance_path),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_no_error (f->error);
- g_variant_unref (tuple);
-
- g_test_message ("Waiting for InstanceRemoved...");
- while (!g_hash_table_contains (f->containers_removed, f->instance_path))
- g_main_context_iteration (NULL, TRUE);
-
- break;
-
- case STOP_SERVER_FORCE:
- g_test_message ("Stopping server and all confined connections...");
- tuple = g_dbus_proxy_call_sync (f->proxy, "StopInstance",
- g_variant_new ("(o)", f->instance_path),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_no_error (f->error);
- g_variant_unref (tuple);
-
- g_test_message ("Waiting for InstanceRemoved...");
- while (!g_hash_table_contains (f->containers_removed, f->instance_path))
- g_main_context_iteration (NULL, TRUE);
-
- break;
-
- default:
- g_assert_not_reached ();
- }
-
- /* Now if we try to connect to the server again, it will fail (eventually -
- * closing the socket is not synchronous with respect to the name owner
- * change, so try a few times) */
- for (i = 0; i < 50; i++)
- {
- g_test_message ("Trying to connect to %s again...", f->socket_path);
- client_socket = g_socket_new (G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM,
- G_SOCKET_PROTOCOL_DEFAULT, &f->error);
- g_assert_no_error (f->error);
-
- if (!g_socket_connect (client_socket, socket_address, NULL, &f->error))
- {
- g_assert_cmpstr (g_quark_to_string (f->error->domain), ==,
- g_quark_to_string (G_IO_ERROR));
-
- if (f->error->code != G_IO_ERROR_CONNECTION_REFUSED &&
- f->error->code != G_IO_ERROR_NOT_FOUND)
- g_error ("Unexpected error code %d", f->error->code);
-
- g_clear_error (&f->error);
- g_clear_object (&client_socket);
- break;
- }
-
- g_clear_object (&client_socket);
- g_usleep (G_USEC_PER_SEC / 10);
- }
-
- /* The same thing happens for a D-Bus connection */
- g_test_message ("Trying to connect to %s again...", f->socket_dbus_address);
- second_confined_conn = g_dbus_connection_new_for_address_sync (
- f->socket_dbus_address,
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
- g_assert_cmpstr (g_quark_to_string (f->error->domain), ==,
- g_quark_to_string (G_IO_ERROR));
-
- if (f->error->code != G_IO_ERROR_CONNECTION_REFUSED &&
- f->error->code != G_IO_ERROR_NOT_FOUND)
- g_error ("Unexpected error code %d", f->error->code);
-
- g_clear_error (&f->error);
- g_assert_null (second_confined_conn);
-
- /* Deleting the socket is not synchronous with respect to stopping
- * listening on it, so again we are willing to wait a few seconds */
- for (i = 0; i < 50; i++)
- {
- if (g_file_test (f->socket_path, G_FILE_TEST_EXISTS))
- g_usleep (G_USEC_PER_SEC / 10);
- }
-
- /* The socket has been deleted */
- g_assert_false (g_file_test (f->socket_path, G_FILE_TEST_EXISTS));
-
- switch (config->stop_server)
- {
- case STOP_SERVER_FORCE:
- g_test_message ("Checking that the confined app gets disconnected...");
-
- while (!g_dbus_connection_is_closed (f->confined_conn))
- g_main_context_iteration (NULL, TRUE);
- break;
-
- case STOP_SERVER_DISCONNECT_FIRST:
- case STOP_SERVER_NEVER_CONNECTED:
- /* Nothing to be done here, no confined app is connected */
- break;
-
- case STOP_SERVER_EXPLICITLY:
- case STOP_SERVER_WITH_MANAGER:
- g_test_message ("Checking that the confined app still works...");
- tuple = g_dbus_connection_call_sync (f->confined_conn,
- DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS,
- DBUS_INTERFACE_DBUS,
- "GetNameOwner",
- g_variant_new ("(s)",
- DBUS_SERVICE_DBUS),
- G_VARIANT_TYPE ("(s)"),
- G_DBUS_CALL_FLAGS_NONE, -1,
- NULL, &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(s)");
- g_variant_get (tuple, "(&s)", &name_owner);
- g_assert_cmpstr (name_owner, ==, DBUS_SERVICE_DBUS);
- g_clear_pointer (&tuple, g_variant_unref);
-
- /* The container instance will not disappear from the bus
- * until the confined connection goes away */
- tuple = g_dbus_proxy_call_sync (f->observer_proxy, "GetInstanceInfo",
- g_variant_new ("(o)", f->instance_path),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_clear_pointer (&tuple, g_variant_unref);
-
- /* Now disconnect the last confined connection, which will make the
- * container instance go away */
- g_test_message ("Closing confined connection...");
- g_dbus_connection_close_sync (f->confined_conn, NULL, &f->error);
- g_assert_no_error (f->error);
- break;
-
- default:
- g_assert_not_reached ();
- }
-
- /* Whatever happened above, by now it has gone away */
-
- g_test_message ("Waiting for InstanceRemoved...");
- while (!g_hash_table_contains (f->containers_removed, f->instance_path))
- g_main_context_iteration (NULL, TRUE);
-
- tuple = g_dbus_proxy_call_sync (f->observer_proxy, "GetInstanceInfo",
- g_variant_new ("(o)", f->instance_path),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_nonnull (f->error);
- error_name = g_dbus_error_get_remote_error (f->error);
- g_assert_cmpstr (error_name, ==, DBUS_ERROR_NOT_CONTAINER);
- g_free (error_name);
- g_assert_null (tuple);
- g_clear_error (&f->error);
- g_clear_object (&socket_address);
-
-#else /* !HAVE_CONTAINERS_TEST */
- g_test_skip ("Containers or gio-unix-2.0 not supported");
-#endif /* !HAVE_CONTAINERS_TEST */
-}
-
-/*
- * Assert that we cannot get the container metadata for a path that
- * isn't a container instance, or a bus name that isn't in a container
- * or doesn't exist at all.
- */
-static void
-test_invalid_metadata_getters (Fixture *f,
- gconstpointer context)
-{
- const gchar *unique_name;
- GVariant *tuple;
- gchar *error_name;
-
- f->proxy = g_dbus_proxy_new_sync (f->unconfined_conn,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_CONTAINERS1,
- NULL, &f->error);
- g_assert_no_error (f->error);
-
- g_test_message ("Inspecting unconfined connection");
- unique_name = g_dbus_connection_get_unique_name (f->unconfined_conn);
- tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInstance",
- g_variant_new ("(s)", unique_name),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
- g_assert_nonnull (f->error);
- g_assert_null (tuple);
- error_name = g_dbus_error_get_remote_error (f->error);
-#ifdef DBUS_ENABLE_CONTAINERS
- g_assert_cmpstr (error_name, ==, DBUS_ERROR_NOT_CONTAINER);
-#else
- /* TODO: We can use g_assert_error for this when we depend on GLib 2.42 */
- g_assert_cmpstr (error_name, ==, DBUS_ERROR_UNKNOWN_INTERFACE);
-#endif
- g_free (error_name);
- g_clear_error (&f->error);
-
- g_test_message ("Inspecting dbus-daemon");
- tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInstance",
- g_variant_new ("(s)", DBUS_SERVICE_DBUS),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
- g_assert_nonnull (f->error);
- g_assert_null (tuple);
- error_name = g_dbus_error_get_remote_error (f->error);
-#ifdef DBUS_ENABLE_CONTAINERS
- g_assert_cmpstr (error_name, ==, DBUS_ERROR_NOT_CONTAINER);
-#else
- /* TODO: We can use g_assert_error for this when we depend on GLib 2.42 */
- g_assert_cmpstr (error_name, ==, DBUS_ERROR_UNKNOWN_INTERFACE);
-#endif
- g_free (error_name);
- g_clear_error (&f->error);
-
- g_test_message ("Inspecting a non-connection");
- unique_name = g_dbus_connection_get_unique_name (f->unconfined_conn);
- tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInstance",
- g_variant_new ("(s)", "com.example.Nope"),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
- g_assert_nonnull (f->error);
- g_assert_null (tuple);
-#ifdef DBUS_ENABLE_CONTAINERS
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER);
-#else
- /* TODO: We can use g_assert_error for this when we depend on GLib 2.42 */
- error_name = g_dbus_error_get_remote_error (f->error);
- g_assert_cmpstr (error_name, ==, DBUS_ERROR_UNKNOWN_INTERFACE);
- g_free (error_name);
-#endif
- g_clear_error (&f->error);
-
-
- g_test_message ("Inspecting container instance info");
- tuple = g_dbus_proxy_call_sync (f->proxy, "GetInstanceInfo",
- g_variant_new ("(o)", "/nope"),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
- g_assert_nonnull (f->error);
- g_assert_null (tuple);
- error_name = g_dbus_error_get_remote_error (f->error);
-#ifdef DBUS_ENABLE_CONTAINERS
- g_assert_cmpstr (error_name, ==, DBUS_ERROR_NOT_CONTAINER);
-#else
- /* TODO: We can use g_assert_error for this when we depend on GLib 2.42 */
- g_assert_cmpstr (error_name, ==, DBUS_ERROR_UNKNOWN_INTERFACE);
-#endif
- g_free (error_name);
- g_clear_error (&f->error);
-}
-
-/*
- * Assert that named arguments are validated: passing an unsupported
- * named argument causes an error.
- */
-static void
-test_unsupported_parameter (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- GVariant *tuple;
- GVariant *parameters;
- GVariantDict named_argument_builder;
-
- if (f->skip)
- return;
-
- f->proxy = g_dbus_proxy_new_sync (f->unconfined_conn,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_CONTAINERS1,
- NULL, &f->error);
- g_assert_no_error (f->error);
-
- g_variant_dict_init (&named_argument_builder, NULL);
- g_variant_dict_insert (&named_argument_builder,
- "ThisArgumentIsntImplemented",
- "b", FALSE);
-
- parameters = g_variant_new ("(ssa{sv}@a{sv})",
- "com.example.NotFlatpak",
- "sample-app",
- NULL, /* no metadata */
- g_variant_dict_end (&named_argument_builder));
- tuple = g_dbus_proxy_call_sync (f->proxy, "AddServer",
- g_steal_pointer (¶meters),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
-
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
- g_assert_null (tuple);
- g_clear_error (&f->error);
-#else /* !HAVE_CONTAINERS_TEST */
- g_test_skip ("Containers or gio-unix-2.0 not supported");
-#endif /* !HAVE_CONTAINERS_TEST */
-}
-
-/*
- * Assert that container types are validated: a container type (container
- * technology) that is not a syntactically valid D-Bus interface name
- * causes an error.
- */
-static void
-test_invalid_type_name (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- GVariant *tuple;
- GVariant *parameters;
-
- if (f->skip)
- return;
-
- f->proxy = g_dbus_proxy_new_sync (f->unconfined_conn,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_CONTAINERS1,
- NULL, &f->error);
- g_assert_no_error (f->error);
-
- parameters = g_variant_new ("(ssa{sv}a{sv})",
- "this is not a valid container type name",
- "sample-app",
- NULL, /* no metadata */
- NULL); /* no named arguments */
- tuple = g_dbus_proxy_call_sync (f->proxy, "AddServer",
- g_steal_pointer (¶meters),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
-
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
- g_assert_null (tuple);
- g_clear_error (&f->error);
-#else /* !HAVE_CONTAINERS_TEST */
- g_test_skip ("Containers or gio-unix-2.0 not supported");
-#endif /* !HAVE_CONTAINERS_TEST */
-}
-
-/*
- * Assert that a request to create a container server cannot come from a
- * connection to an existing container server.
- * (You cannot put containers in your container so you can sandbox while
- * you sandbox.)
- */
-static void
-test_invalid_nesting (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- GDBusProxy *nested_proxy;
- GVariant *tuple;
- GVariant *parameters;
-
- if (f->skip)
- return;
-
- parameters = g_variant_new ("(ssa{sv}a{sv})",
- "com.example.NotFlatpak",
- "sample-app",
- NULL, /* no metadata */
- NULL); /* no named arguments */
- if (!add_container_server (f, g_steal_pointer (¶meters)))
- return;
-
- g_test_message ("Connecting to %s...", f->socket_dbus_address);
- f->confined_conn = g_dbus_connection_new_for_address_sync (
- f->socket_dbus_address,
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
- g_assert_no_error (f->error);
-
- g_test_message ("Checking that confined app cannot nest containers...");
- nested_proxy = g_dbus_proxy_new_sync (f->confined_conn,
- G_DBUS_PROXY_FLAGS_NONE, NULL,
- DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
- DBUS_INTERFACE_CONTAINERS1, NULL,
- &f->error);
- g_assert_no_error (f->error);
-
- parameters = g_variant_new ("(ssa{sv}a{sv})",
- "com.example.NotFlatpak",
- "inner-app",
- NULL, /* no metadata */
- NULL); /* no named arguments */
- tuple = g_dbus_proxy_call_sync (nested_proxy, "AddServer",
- g_steal_pointer (¶meters),
- G_DBUS_CALL_FLAGS_NONE,
- -1, NULL, &f->error);
-
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED);
- g_assert_null (tuple);
- g_clear_error (&f->error);
-
- g_clear_object (&nested_proxy);
-
-#else /* !HAVE_CONTAINERS_TEST */
- g_test_skip ("Containers or gio-unix-2.0 not supported");
-#endif /* !HAVE_CONTAINERS_TEST */
-}
-
-/*
- * Assert that we can have up to 3 containers, but no more than that,
- * either because max-containers.conf imposes max_containers=3
- * or because limit-containers.conf imposes max_containers_per_user=3
- * (and we only have one uid).
- */
-static void
-test_max_containers (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- GVariant *parameters;
- GVariant *tuple;
- /* Length must match max_containers in max-containers.conf, and also
- * max_containers_per_user in limit-containers.conf */
- gchar *placeholders[3] = { NULL };
- guint i;
-
- if (f->skip)
- return;
-
- f->proxy = g_dbus_proxy_new_sync (f->unconfined_conn,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_CONTAINERS1,
- NULL, &f->error);
- g_assert_no_error (f->error);
-
- parameters = g_variant_new ("(ssa{sv}a{sv})",
- "com.example.NotFlatpak",
- "sample-app",
- NULL, /* no metadata */
- NULL); /* no named arguments */
- /* We will reuse this variant several times, so don't use floating refs */
- g_variant_ref_sink (parameters);
-
- /* We can go up to the limit without exceeding it */
- for (i = 0; i < G_N_ELEMENTS (placeholders); i++)
- {
- tuple = g_dbus_proxy_call_sync (f->proxy, "AddServer",
- parameters, G_DBUS_CALL_FLAGS_NONE, -1,
- NULL, &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_variant_get (tuple, "(o^ays)", &placeholders[i], NULL, NULL);
- g_variant_unref (tuple);
- g_test_message ("Placeholder server at %s", placeholders[i]);
- }
-
- /* We cannot exceed the limit */
- tuple = g_dbus_proxy_call_sync (f->proxy, "AddServer",
- parameters, G_DBUS_CALL_FLAGS_NONE, -1,
- NULL, &f->error);
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_LIMITS_EXCEEDED);
- g_clear_error (&f->error);
- g_assert_null (tuple);
-
- /* Stop one of the placeholders */
- tuple = g_dbus_proxy_call_sync (f->proxy, "StopListening",
- g_variant_new ("(o)", placeholders[1]),
- G_DBUS_CALL_FLAGS_NONE, -1, NULL,
- &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_variant_unref (tuple);
-
- /* We can have another container server now that we are back below the
- * limit */
- tuple = g_dbus_proxy_call_sync (f->proxy, "AddServer",
- parameters, G_DBUS_CALL_FLAGS_NONE, -1,
- NULL, &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_variant_unref (tuple);
-
- g_variant_unref (parameters);
-
- for (i = 0; i < G_N_ELEMENTS (placeholders); i++)
- g_free (placeholders[i]);
-
-#else /* !HAVE_CONTAINERS_TEST */
- g_test_skip ("Containers or gio-unix-2.0 not supported");
-#endif /* !HAVE_CONTAINERS_TEST */
-}
-
-#ifdef HAVE_CONTAINERS_TEST
-static void
-assert_connection_closed (GError *error)
-{
- /* "before 2.44 some "connection closed" errors returned
- * G_IO_ERROR_BROKEN_PIPE, but others returned G_IO_ERROR_FAILED"
- * —GIO documentation */
- if (error->code == G_IO_ERROR_BROKEN_PIPE)
- {
- g_assert_error (error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE);
- }
- else
- {
- g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
- g_test_message ("Old GLib: %s", error->message);
- /* This is wrong and bad, but it's the only way to detect this, and
- * the older GLib versions that raised FAILED are no longer a moving
- * target */
- g_assert_true (strstr (error->message, g_strerror (ECONNRESET)) != NULL);
- }
-}
-#endif
-
-/*
- * Test that if we have multiple app-containers,
- * max_connections_per_container applies to each one individually.
- */
-static void
-test_max_connections_per_container (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- /* Length is arbitrary */
- gchar *socket_paths[2] = { NULL };
- gchar *dbus_addresses[G_N_ELEMENTS (socket_paths)] = { NULL };
- GSocketAddress *socket_addresses[G_N_ELEMENTS (socket_paths)] = { NULL };
- /* Length must be length of socket_paths * max_connections_per_container in
- * limit-containers.conf */
- GSocket *placeholders[G_N_ELEMENTS (socket_paths) * 3] = { NULL };
- GVariant *parameters;
- GVariant *tuple;
- guint i;
-
- if (f->skip)
- return;
-
- f->proxy = g_dbus_proxy_new_sync (f->unconfined_conn,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_CONTAINERS1,
- NULL, &f->error);
- g_assert_no_error (f->error);
-
- parameters = g_variant_new ("(ssa{sv}a{sv})",
- "com.example.NotFlatpak",
- "sample-app",
- NULL, /* no metadata */
- NULL); /* no named arguments */
- /* We will reuse this variant several times, so don't use floating refs */
- g_variant_ref_sink (parameters);
-
- for (i = 0; i < G_N_ELEMENTS (socket_paths); i++)
- {
- tuple = g_dbus_proxy_call_sync (f->proxy, "AddServer",
- parameters, G_DBUS_CALL_FLAGS_NONE, -1,
- NULL, &f->error);
- g_assert_no_error (f->error);
- g_assert_nonnull (tuple);
- g_variant_get (tuple, "(o^ays)", NULL, &socket_paths[i],
- &dbus_addresses[i]);
- g_variant_unref (tuple);
- socket_addresses[i] = g_unix_socket_address_new (socket_paths[i]);
- g_test_message ("Server #%u at %s", i, socket_paths[i]);
- }
-
- for (i = 0; i < G_N_ELEMENTS (placeholders); i++)
- {
- /* We enforce the resource limit for any connection to the socket,
- * not just D-Bus connections that have done the handshake */
- placeholders[i] = g_socket_new (G_SOCKET_FAMILY_UNIX,
- G_SOCKET_TYPE_STREAM,
- G_SOCKET_PROTOCOL_DEFAULT, &f->error);
- g_assert_no_error (f->error);
-
- g_socket_connect (placeholders[i],
- socket_addresses[i % G_N_ELEMENTS (socket_paths)],
- NULL, &f->error);
- g_assert_no_error (f->error);
- g_test_message ("Placeholder connection #%u to %s", i,
- socket_paths[i % G_N_ELEMENTS (socket_paths)]);
- }
-
- /* An extra connection to either of the sockets fails: they are both at
- * capacity now */
- for (i = 0; i < G_N_ELEMENTS (socket_paths); i++)
- {
- f->confined_conn = g_dbus_connection_new_for_address_sync (
- dbus_addresses[i],
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
- assert_connection_closed (f->error);
-
- g_clear_error (&f->error);
- g_assert_null (f->confined_conn);
- }
-
- /* Free up one slot (this happens to be connected to socket_paths[0]) */
- g_socket_close (placeholders[2], &f->error);
- g_assert_no_error (f->error);
-
- /* Now we can connect, but only once. Use a retry loop since the dbus-daemon
- * won't necessarily notice our socket closing synchronously. */
- while (f->confined_conn == NULL)
- {
- g_test_message ("Trying to use the slot we just freed up...");
- f->confined_conn = g_dbus_connection_new_for_address_sync (
- dbus_addresses[0],
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
-
- if (f->confined_conn == NULL)
- {
- assert_connection_closed (f->error);
- g_clear_error (&f->error);
- g_assert_nonnull (f->confined_conn);
- }
- else
- {
- g_assert_no_error (f->error);
- }
- }
-
- /* An extra connection to either of the sockets fails: they are both at
- * capacity again */
- for (i = 0; i < G_N_ELEMENTS (socket_paths); i++)
- {
- GDBusConnection *another = g_dbus_connection_new_for_address_sync (
- dbus_addresses[i],
- (G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
- G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT),
- NULL, NULL, &f->error);
-
- assert_connection_closed (f->error);
- g_clear_error (&f->error);
- g_assert_null (another);
- }
-
- g_variant_unref (parameters);
-
- for (i = 0; i < G_N_ELEMENTS (socket_paths); i++)
- {
- g_free (socket_paths[i]);
- g_free (dbus_addresses[i]);
- g_clear_object (&socket_addresses[i]);
- }
-
- for (i = 0; i < G_N_ELEMENTS (placeholders); i++)
- g_clear_object (&placeholders[i]);
-
-#undef LIMIT
-#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
- * configuration.
- */
-static void
-test_max_container_metadata_bytes (Fixture *f,
- gconstpointer context)
-{
-#ifdef HAVE_CONTAINERS_TEST
- /* Must be >= max_container_metadata_bytes in limit-containers.conf, so that
- * when the serialization overhead, app-container type and app name are
- * added, it is too much for the limit */
- guchar waste_of_space[4096] = { 0 };
- GVariant *tuple;
- GVariant *parameters;
- GVariantDict dict;
-
- if (f->skip)
- return;
-
- f->proxy = g_dbus_proxy_new_sync (f->unconfined_conn,
- G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
- NULL, DBUS_SERVICE_DBUS,
- DBUS_PATH_DBUS, DBUS_INTERFACE_CONTAINERS1,
- NULL, &f->error);
- g_assert_no_error (f->error);
-
- g_variant_dict_init (&dict, NULL);
- g_variant_dict_insert (&dict, "waste of space", "@ay",
- g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
- waste_of_space,
- sizeof (waste_of_space),
- 1));
-
- /* Floating reference, call_..._sync takes ownership */
- parameters = g_variant_new ("(ss@a{sv}a{sv})",
- "com.wasteheadquarters",
- "Packt Like Sardines in a Crushd Tin Box",
- g_variant_dict_end (&dict),
- NULL); /* no named arguments */
-
- tuple = g_dbus_proxy_call_sync (f->proxy, "AddServer", parameters,
- G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
- g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_LIMITS_EXCEEDED);
- g_assert_null (tuple);
- g_clear_error (&f->error);
-
-#else /* !HAVE_CONTAINERS_TEST */
- g_test_skip ("Containers or gio-unix-2.0 not supported");
-#endif /* !HAVE_CONTAINERS_TEST */
-}
-
-static void
-teardown (Fixture *f,
- gconstpointer context G_GNUC_UNUSED)
-{
- g_clear_object (&f->proxy);
-
- fixture_disconnect_observer (f);
- g_clear_pointer (&f->containers_removed, g_hash_table_unref);
-
- if (f->libdbus_observer != NULL)
- {
- dbus_connection_remove_filter (f->libdbus_observer,
- observe_shouting_cb, f);
- test_connection_shutdown (f->ctx, f->libdbus_observer);
- dbus_connection_close (f->libdbus_observer);
- }
-
- dbus_clear_connection (&f->libdbus_observer);
-
- fixture_disconnect_unconfined (f);
-
- if (f->confined_conn != NULL)
- {
- GError *error = NULL;
-
- g_dbus_connection_close_sync (f->confined_conn, NULL, &error);
-
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED))
- g_clear_error (&error);
- else
- g_assert_no_error (error);
- }
-
- g_clear_object (&f->confined_conn);
-
- if (f->daemon_pid != 0)
- {
- test_kill_pid (f->daemon_pid);
- g_spawn_close_pid (f->daemon_pid);
- f->daemon_pid = 0;
- }
-
- dbus_clear_message (&f->latest_shout);
- g_free (f->instance_path);
- g_free (f->socket_path);
- g_free (f->socket_dbus_address);
- g_free (f->bus_address);
- g_clear_error (&f->error);
- test_main_context_unref (f->ctx);
- g_free (f->unconfined_unique_name);
-}
-
-static const Config stop_server_explicitly =
-{
- "valid-config-files/multi-user.conf",
- STOP_SERVER_EXPLICITLY
-};
-static const Config stop_server_disconnect_first =
-{
- "valid-config-files/multi-user.conf",
- STOP_SERVER_DISCONNECT_FIRST
-};
-static const Config stop_server_never_connected =
-{
- "valid-config-files/multi-user.conf",
- STOP_SERVER_NEVER_CONNECTED
-};
-static const Config stop_server_force =
-{
- "valid-config-files/multi-user.conf",
- STOP_SERVER_FORCE
-};
-static const Config stop_server_with_manager =
-{
- "valid-config-files/multi-user.conf",
- STOP_SERVER_WITH_MANAGER
-};
-static const Config limit_containers =
-{
- "valid-config-files/limit-containers.conf",
- 0 /* not relevant for this test */
-};
-static const Config max_containers =
-{
- "valid-config-files/max-containers.conf",
- 0 /* not relevant for this test */
-};
-
-int
-main (int argc,
- char **argv)
-{
- GError *error = NULL;
- gchar *runtime_dir;
- gchar *runtime_dbus_dir;
- gchar *runtime_containers_dir;
- gchar *runtime_services_dir;
- int ret;
-
- runtime_dir = g_dir_make_tmp ("dbus-test-containers.XXXXXX", &error);
-
- if (runtime_dir == NULL)
- {
- g_print ("Bail out! %s\n", error->message);
- g_clear_error (&error);
- return 1;
- }
-
- g_setenv ("XDG_RUNTIME_DIR", runtime_dir, TRUE);
- runtime_dbus_dir = g_build_filename (runtime_dir, "dbus-1", NULL);
- runtime_containers_dir = g_build_filename (runtime_dir, "dbus-1",
- "containers", NULL);
- runtime_services_dir = g_build_filename (runtime_dir, "dbus-1",
- "services", NULL);
-
- test_init (&argc, &argv);
-
- g_test_add ("/containers/get-supported-arguments", Fixture, NULL,
- setup, test_get_supported_arguments, teardown);
- g_test_add ("/containers/basic", Fixture, NULL,
- setup, test_basic, teardown);
- g_test_add ("/containers/wrong-uid", Fixture, NULL,
- setup, test_wrong_uid, teardown);
- g_test_add ("/containers/stop-server/explicitly", Fixture,
- &stop_server_explicitly, setup, test_stop_server, teardown);
- g_test_add ("/containers/stop-server/disconnect-first", Fixture,
- &stop_server_disconnect_first, setup, test_stop_server, teardown);
- g_test_add ("/containers/stop-server/never-connected", Fixture,
- &stop_server_never_connected, setup, test_stop_server, teardown);
- g_test_add ("/containers/stop-server/force", Fixture,
- &stop_server_force, setup, test_stop_server, teardown);
- g_test_add ("/containers/stop-server/with-manager", Fixture,
- &stop_server_with_manager, setup, test_stop_server, teardown);
- g_test_add ("/containers/metadata", Fixture, &limit_containers,
- setup, test_metadata, teardown);
- g_test_add ("/containers/invalid-metadata-getters", Fixture, NULL,
- setup, test_invalid_metadata_getters, teardown);
- g_test_add ("/containers/unsupported-parameter", Fixture, NULL,
- setup, test_unsupported_parameter, teardown);
- g_test_add ("/containers/invalid-type-name", Fixture, NULL,
- setup, test_invalid_type_name, teardown);
- g_test_add ("/containers/invalid-nesting", Fixture, NULL,
- setup, test_invalid_nesting, teardown);
- g_test_add ("/containers/max-containers", Fixture, &max_containers,
- setup, test_max_containers, teardown);
- g_test_add ("/containers/max-containers-per-user", Fixture, &limit_containers,
- setup, test_max_containers, teardown);
- g_test_add ("/containers/max-connections-per-container", Fixture,
- &limit_containers,
- setup, test_max_connections_per_container, teardown);
- g_test_add ("/containers/max-container-metadata-bytes", Fixture,
- &limit_containers,
- setup, test_max_container_metadata_bytes, teardown);
-
- ret = g_test_run ();
-
- test_rmdir_if_exists (runtime_containers_dir);
- test_rmdir_if_exists (runtime_services_dir);
- test_rmdir_if_exists (runtime_dbus_dir);
- test_rmdir_must_exist (runtime_dir);
- g_free (runtime_containers_dir);
- g_free (runtime_services_dir);
- g_free (runtime_dbus_dir);
- g_free (runtime_dir);
- dbus_shutdown ();
- return ret;
-}