Merge branch 'containers' into 'main'

Draft: Bring back the Containers interface

Closes #477 and #186

See merge request dbus/dbus!449
This commit is contained in:
Simon McVittie 2025-08-29 11:48:36 +00:00
commit fac71f6e8a
27 changed files with 1695 additions and 456 deletions

View file

@ -125,6 +125,7 @@ endif()
option(DBUS_DISABLE_ASSERT "Disable assertion checking" OFF)
option(DBUS_ENABLE_STATS "enable bus daemon usage statistics" OFF)
option(DBUS_ENABLE_CONTAINERS "enable restricted servers for app-containers" OFF)
option(ENABLE_TRADITIONAL_ACTIVATION "Enable traditional activation (without using systemd)" ON)
find_package(PkgConfig)

View file

@ -152,6 +152,9 @@ ENABLE_QT_HELP:STRING=AUTO
// enable bus daemon usage statistics
DBUS_ENABLE_STATS:BOOL=OFF
// enable restricted servers for app containers
DBUS_ENABLE_CONTAINERS:BOOL=OFF
// build with systemd at_console support
ENABLE_SYSTEMD:STRING=AUTO

View file

@ -595,7 +595,8 @@ cache_peer_loginfo_string (BusConnectionData *d,
dbus_bool_t prev_added;
const char *container = NULL;
const char *container_type = NULL;
const char *container_name = NULL;
const char *app_id = NULL;
const char *instance_id = NULL;
DBusCredentials *credentials;
if (!_dbus_string_init (&loginfo_buf))
@ -679,7 +680,8 @@ cache_peer_loginfo_string (BusConnectionData *d,
/* This does have to come from the connection, not the credentials */
if (bus_containers_connection_is_contained (connection, &container,
&container_type,
&container_name))
&app_id,
&instance_id))
{
dbus_bool_t did_append;
@ -690,10 +692,11 @@ cache_peer_loginfo_string (BusConnectionData *d,
}
did_append = _dbus_string_append_printf (&loginfo_buf,
"container=%s %s=\"%s\")",
"container=%s %s=\"%s\" inst=\"%s\")",
container,
container_type,
container_name);
app_id,
instance_id);
if (!did_append)
goto oom;
else
@ -2478,17 +2481,17 @@ bus_transaction_send (BusTransaction *transaction,
* until we know we have enough memory for the entire transaction,
* and that doesn't happen until we know all the recipients.
* So this is about the last possible time we could edit the header. */
if ((d->want_headers & BUS_EXTRA_HEADERS_CONTAINER_INSTANCE) &&
dbus_message_get_container_instance (message) == NULL)
if ((d->want_headers & BUS_EXTRA_HEADERS_CONTAINER_PATH) &&
dbus_message_get_container_path (message) == NULL)
{
const char *path;
if (sender == NULL ||
!bus_containers_connection_is_contained (sender, &path,
NULL, NULL))
NULL, NULL, NULL))
path = "/";
if (!dbus_message_set_container_instance (message, path))
if (!dbus_message_set_container_path (message, path))
return FALSE;
}

View file

@ -32,7 +32,7 @@
typedef enum
{
BUS_EXTRA_HEADERS_CONTAINER_INSTANCE = (1 << 0),
BUS_EXTRA_HEADERS_CONTAINER_PATH = (1 << 0),
BUS_EXTRA_HEADERS_NONE = 0
} BusExtraHeaders;

File diff suppressed because it is too large Load diff

View file

@ -38,7 +38,7 @@ dbus_bool_t bus_containers_handle_add_server (DBusConnection *connecti
BusTransaction *transaction,
DBusMessage *message,
DBusError *error);
dbus_bool_t bus_containers_handle_stop_instance (DBusConnection *connection,
dbus_bool_t bus_containers_handle_stop_server (DBusConnection *connection,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error);
@ -46,14 +46,14 @@ dbus_bool_t bus_containers_handle_stop_listening (DBusConnection *connecti
BusTransaction *transaction,
DBusMessage *message,
DBusError *error);
dbus_bool_t bus_containers_handle_get_instance_info (DBusConnection *connection,
dbus_bool_t bus_containers_handle_get_server_info (DBusConnection *connection,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error);
dbus_bool_t bus_containers_handle_get_connection_instance (DBusConnection *connection,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error);
dbus_bool_t bus_containers_handle_get_connection_info (DBusConnection *connection,
BusTransaction *transaction,
DBusMessage *message,
DBusError *error);
dbus_bool_t bus_containers_handle_request_header (DBusConnection *connection,
BusTransaction *transaction,
DBusMessage *message,
@ -66,7 +66,8 @@ void bus_containers_remove_connection (BusContainers *self,
dbus_bool_t bus_containers_connection_is_contained (DBusConnection *connection,
const char **path,
const char **type,
const char **name);
const char **app_id,
const char **instance_id);
static inline void
bus_clear_containers (BusContainers **containers_p)

View file

@ -293,7 +293,7 @@ bus_dispatch (DBusConnection *connection,
* don't understand (or validate), so that we can add header fields
* in future and clients can assume that we have checked them. */
if (!_dbus_message_remove_unknown_fields (message) ||
!dbus_message_set_container_instance (message, NULL))
!dbus_message_set_container_path (message, NULL))
{
BUS_SET_OOM (&error);
goto out;

View file

@ -118,7 +118,7 @@ bus_driver_check_caller_is_not_container (DBusConnection *connection,
DBusMessage *message,
DBusError *error)
{
if (bus_containers_connection_is_contained (connection, NULL, NULL, NULL))
if (bus_containers_connection_is_contained (connection, NULL, NULL, NULL, NULL))
{
const char *method = dbus_message_get_member (message);
@ -1970,9 +1970,7 @@ bus_driver_fill_connection_credentials (DBusCredentials *credentials,
dbus_pid_t pid = DBUS_PID_UNSET;
const char *windows_sid = NULL;
const char *linux_security_label = NULL;
#ifdef DBUS_ENABLE_CONTAINERS
const char *path;
#endif
#ifdef HAVE_UNIX_FD_PASSING
int pid_fd = -1; /* owned by credentials */
#endif
@ -2033,17 +2031,15 @@ bus_driver_fill_connection_credentials (DBusCredentials *credentials,
return FALSE;
}
#ifdef DBUS_ENABLE_CONTAINERS
/* This has to come from the connection, not the credentials */
if (peer_conn != NULL &&
bus_containers_connection_is_contained (peer_conn, &path, NULL, NULL))
bus_containers_connection_is_contained (peer_conn, &path, NULL, NULL, NULL))
{
if (!_dbus_asv_add_object_path (asv_iter,
DBUS_INTERFACE_CONTAINERS1 ".Instance",
DBUS_INTERFACE_CONTAINERS1 ".Path",
path))
return FALSE;
}
#endif
#ifdef HAVE_UNIX_FD_PASSING
if (caller_conn != NULL && pid_fd >= 0 &&
@ -2500,7 +2496,7 @@ typedef enum
* containers are never privileged. */
METHOD_FLAG_PRIVILEGED = (1 << 1),
/* If set, callers must not be associated with a container instance. */
/* If set, callers must not be associated with a container. */
METHOD_FLAG_NO_CONTAINERS = (1 << 2),
METHOD_FLAG_NONE = 0
@ -2651,16 +2647,16 @@ static const MessageHandler introspectable_message_handlers[] = {
#ifdef DBUS_ENABLE_CONTAINERS
static const MessageHandler containers_message_handlers[] = {
{ "AddServer", "ssa{sv}a{sv}", "oays", bus_containers_handle_add_server,
{ "AddServer", "sssa{sv}a{sv}", "oa{sv}", bus_containers_handle_add_server,
METHOD_FLAG_NO_CONTAINERS },
{ "StopInstance", "o", "", bus_containers_handle_stop_instance,
{ "StopServer", "o", "", bus_containers_handle_stop_server,
METHOD_FLAG_NO_CONTAINERS },
{ "StopListening", "o", "", bus_containers_handle_stop_listening,
METHOD_FLAG_NO_CONTAINERS },
{ "GetConnectionInstance", "s", "oa{sv}ssa{sv}",
bus_containers_handle_get_connection_instance,
{ "GetConnectionInfo", "s", "oa{sv}sssa{sv}",
bus_containers_handle_get_connection_info,
METHOD_FLAG_NONE },
{ "GetInstanceInfo", "o", "a{sv}ssa{sv}", bus_containers_handle_get_instance_info,
{ "GetServerInfo", "o", "a{sv}sssa{sv}", bus_containers_handle_get_server_info,
METHOD_FLAG_NONE },
{ "RequestHeader", "", "", bus_containers_handle_request_header,
METHOD_FLAG_NONE },
@ -2780,7 +2776,7 @@ static InterfaceHandler interface_handlers[] = {
#endif
#ifdef DBUS_ENABLE_CONTAINERS
{ DBUS_INTERFACE_CONTAINERS1, containers_message_handlers,
" <signal name=\"InstanceRemoved\">\n"
" <signal name=\"ServerRemoved\">\n"
" <arg type=\"o\" name=\"path\"/>\n"
" </signal>\n",
INTERFACE_FLAG_NONE, containers_property_handlers },

View file

@ -76,5 +76,11 @@
<limit name="max_names_per_connection">50000</limit>
<limit name="max_match_rules_per_connection">50000</limit>
<limit name="max_replies_per_connection">50000</limit>
<limit name="max_containers">10000</limit>
<limit name="max_containers_per_user">10000</limit>
<limit name="max_container_metadata_bytes">1000000000</limit>
<!-- This is relatively low so that app-containers (which we do not fully
trust) do not cause DoS. -->
<limit name="max_connections_per_container">16</limit>
</busconfig>

View file

@ -126,6 +126,10 @@
<!-- <limit name="max_names_per_connection">512</limit> -->
<!-- <limit name="max_match_rules_per_connection">512</limit> -->
<!-- <limit name="max_replies_per_connection">128</limit> -->
<!-- <limit name="max_containers">512</limit> -->
<!-- <limit name="max_containers_per_user">16</limit> -->
<!-- <limit name="max_container_metadata_bytes">4096</limit> -->
<!-- <limit name="max_connections_per_container">8</limit> -->
<!-- Config files are placed here that among other things, punch
holes in the above policy for specific services. -->

View file

@ -40,6 +40,7 @@
#cmakedefine DBUS_RUNSTATEDIR "@DBUS_RUNSTATEDIR@"
#cmakedefine DBUS_ENABLE_STATS
#cmakedefine DBUS_ENABLE_CONTAINERS
#cmakedefine ENABLE_TRADITIONAL_ACTIVATION
#define TEST_LISTEN "@TEST_LISTEN@"

View file

@ -86,7 +86,7 @@ _dbus_header_field_types[DBUS_HEADER_FIELD_LAST+1] = {
{ DBUS_HEADER_FIELD_SENDER, DBUS_TYPE_STRING },
{ DBUS_HEADER_FIELD_SIGNATURE, DBUS_TYPE_SIGNATURE },
{ DBUS_HEADER_FIELD_UNIX_FDS, DBUS_TYPE_UINT32 },
{ DBUS_HEADER_FIELD_CONTAINER_INSTANCE, DBUS_TYPE_OBJECT_PATH }
{ DBUS_HEADER_FIELD_CONTAINER_PATH, DBUS_TYPE_OBJECT_PATH }
};
/** Macro to look up the correct type for a field */
@ -922,7 +922,7 @@ load_and_validate_field (DBusHeader *header,
string_validation_func = NULL;
break;
case DBUS_HEADER_FIELD_CONTAINER_INSTANCE:
case DBUS_HEADER_FIELD_CONTAINER_PATH:
/* OBJECT_PATH was validated generically due to its type */
string_validation_func = NULL;
break;

View file

@ -4097,7 +4097,7 @@ dbus_message_contains_unix_fds(DBusMessage *message)
}
/**
* Sets the container instance this message was sent from.
* Sets the container context this message was sent from.
*
* The path must contain only valid characters for an object path
* as defined in the D-Bus specification.
@ -4107,8 +4107,8 @@ dbus_message_contains_unix_fds(DBusMessage *message)
* @returns #FALSE if not enough memory
*/
dbus_bool_t
dbus_message_set_container_instance (DBusMessage *message,
const char *object_path)
dbus_message_set_container_path (DBusMessage *message,
const char *object_path)
{
_dbus_return_val_if_fail (message != NULL, FALSE);
_dbus_return_val_if_fail (!message->locked, FALSE);
@ -4117,13 +4117,27 @@ dbus_message_set_container_instance (DBusMessage *message,
FALSE);
return set_or_delete_string_field (message,
DBUS_HEADER_FIELD_CONTAINER_INSTANCE,
DBUS_HEADER_FIELD_CONTAINER_PATH,
DBUS_TYPE_OBJECT_PATH,
object_path);
}
/**
* Gets the container instance this message was sent from, or #NULL
* Deprecated alias for dbus_message_set_container_path().
*
* @param message the message
* @param object_path the path or #NULL to unset
* @returns #FALSE if not enough memory
*/
dbus_bool_t
dbus_message_set_container_instance (DBusMessage *message,
const char *object_path)
{
return dbus_message_set_container_path (message, object_path);
}
/**
* Gets the container context this message was sent from, or #NULL
* if none.
*
* The returned string becomes invalid if the message is
@ -4133,7 +4147,7 @@ dbus_message_set_container_instance (DBusMessage *message,
* @returns the path (should not be freed) or #NULL
*/
const char *
dbus_message_get_container_instance (DBusMessage *message)
dbus_message_get_container_path (DBusMessage *message)
{
const char *v;
@ -4141,12 +4155,24 @@ dbus_message_get_container_instance (DBusMessage *message)
v = NULL; /* in case field doesn't exist */
_dbus_header_get_field_basic (&message->header,
DBUS_HEADER_FIELD_CONTAINER_INSTANCE,
DBUS_HEADER_FIELD_CONTAINER_PATH,
DBUS_TYPE_OBJECT_PATH,
(void *) &v);
return v;
}
/**
* Deprecated alias for dbus_message_set_container_instance().
*
* @param message the message
* @returns the path (should not be freed) or #NULL
*/
const char *
dbus_message_get_container_instance (DBusMessage *message)
{
return dbus_message_get_container_path (message);
}
/** @} */
/**

View file

@ -236,10 +236,17 @@ dbus_bool_t dbus_message_get_path_decomposed (DBusMessage *message,
char ***path);
DBUS_EXPORT
const char *dbus_message_get_container_instance (DBusMessage *message);
const char *dbus_message_get_container_path (DBusMessage *message);
DBUS_EXPORT
dbus_bool_t dbus_message_set_container_path (DBusMessage *message,
const char *object_path);
#ifndef DBUS_DISABLE_DEPRECATED
DBUS_EXPORT DBUS_DEPRECATED
const char *dbus_message_get_container_instance (DBusMessage *message);
DBUS_EXPORT DBUS_DEPRECATED
dbus_bool_t dbus_message_set_container_instance (DBusMessage *message,
const char *object_path);
#endif
DBUS_EXPORT
dbus_bool_t dbus_message_append_args (DBusMessage *message,

View file

@ -303,9 +303,15 @@ extern "C" {
*/
#define DBUS_HEADER_FIELD_UNIX_FDS 9
/**
* Header field code for the container instance that sent this message.
* Header field code for the container context that sent this message.
*/
#define DBUS_HEADER_FIELD_CONTAINER_PATH 10
/**
* Deprecated alias for #DBUS_HEADER_FIELD_CONTAINER_PATH
*/
#ifndef DBUS_DISABLE_DEPRECATED
#define DBUS_HEADER_FIELD_CONTAINER_INSTANCE 10
#endif
/**
@ -314,7 +320,7 @@ extern "C" {
* that unknown codes must be ignored, so check for that before
* indexing the array.
*/
#define DBUS_HEADER_FIELD_LAST DBUS_HEADER_FIELD_CONTAINER_INSTANCE
#define DBUS_HEADER_FIELD_LAST DBUS_HEADER_FIELD_CONTAINER_PATH
/** Header format is defined as a signature:
* byte byte order

View file

@ -88,6 +88,8 @@ typedef enum
*/
/** The interface exported by the object with #DBUS_SERVICE_DBUS and #DBUS_PATH_DBUS */
#define DBUS_INTERFACE_DBUS "org.freedesktop.DBus"
/** The restricted container interface exported by the dbus-daemon */
#define DBUS_INTERFACE_CONTAINERS1 "org.freedesktop.DBus.Containers1"
/** The monitoring interface exported by the dbus-daemon */
#define DBUS_INTERFACE_MONITORING "org.freedesktop.DBus.Monitoring"

View file

@ -922,48 +922,6 @@ close_and_invalidate (int *fd)
return ret;
}
static dbus_bool_t
make_pipe (int p[2],
DBusError *error)
{
int retval;
#ifdef HAVE_PIPE2
dbus_bool_t cloexec_done;
retval = pipe2 (p, O_CLOEXEC);
cloexec_done = retval >= 0;
/* Check if kernel seems to be too old to know pipe2(). We assume
that if pipe2 is available, O_CLOEXEC is too. */
if (retval < 0 && errno == ENOSYS)
#endif
{
retval = pipe(p);
}
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
if (retval < 0)
{
dbus_set_error (error,
DBUS_ERROR_SPAWN_FAILED,
"Failed to create pipe for communicating with child process (%s)",
_dbus_strerror (errno));
return FALSE;
}
#ifdef HAVE_PIPE2
if (!cloexec_done)
#endif
{
_dbus_fd_set_close_on_exec (p[0]);
_dbus_fd_set_close_on_exec (p[1]);
}
return TRUE;
}
static void
do_write (int fd, const void *buf, size_t count)
{
@ -1323,7 +1281,7 @@ _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p,
goto cleanup_and_fail;
}
if (!make_pipe (child_err_report_pipe, error))
if (!_dbus_unix_make_pipe (child_err_report_pipe, error))
goto cleanup_and_fail;
if (!_dbus_socketpair (&babysitter_pipe[0], &babysitter_pipe[1], TRUE, error))

View file

@ -5290,4 +5290,46 @@ _dbus_get_low_level_socket_errno (void)
return errno;
}
dbus_bool_t
_dbus_unix_make_pipe (int p[2],
DBusError *error)
{
int retval;
#ifdef HAVE_PIPE2
dbus_bool_t cloexec_done;
retval = pipe2 (p, O_CLOEXEC);
cloexec_done = retval >= 0;
/* Check if kernel seems to be too old to know pipe2(). We assume
that if pipe2 is available, O_CLOEXEC is too. */
if (retval < 0 && errno == ENOSYS)
#endif
{
retval = pipe(p);
}
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
if (retval < 0)
{
dbus_set_error (error,
_dbus_error_from_errno (errno),
"Failed to create pipe (%s)",
_dbus_strerror (errno));
return FALSE;
}
#ifdef HAVE_PIPE2
if (!cloexec_done)
#endif
{
_dbus_fd_set_close_on_exec (p[0]);
_dbus_fd_set_close_on_exec (p[1]);
}
return TRUE;
}
/* tests in dbus-sysdeps-util.c */

View file

@ -162,6 +162,10 @@ void _dbus_set_signal_handler (int sig,
dbus_bool_t _dbus_reset_oom_score_adj (const char **error_str_p);
DBUS_PRIVATE_EXPORT
dbus_bool_t _dbus_unix_make_pipe (int p[2],
DBusError *error);
/** @} */
DBUS_END_DECLS

View file

@ -856,6 +856,14 @@ Available limit names are:</para>
(number of calls-in-progress)
"reply_timeout" : milliseconds (thousandths)
until a method call times out
"max_containers" : max number of restricted servers for use
in app-containers, in total
"max_containers_per_user" : max number of app-containers per Unix uid
"max_container_metadata_bytes": max number of bytes of metadata to store
for each app-container
"max_connections_per_container": max number of (authenticated or
unauthenticated) connections to each
app-container
</literallayout> <!-- .fi -->

View file

@ -7044,6 +7044,19 @@
</entry>
</row>
<row>
<entry>org.freedesktop.DBus.Containers1.Path</entry>
<entry>OBJECT_PATH</entry>
<entry>
The object path of the container-specific server through
which this connection is connected, as returned by the
<link linkend="bus-messages-containers1-add-server"
>AddServer</link> method. Omitted from the
dictionary if this connection is not via a container's
server.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
@ -7452,6 +7465,764 @@
</para>
</sect2>
<sect2 id="message-bus-containers-interface">
<title>Containers Interface v1: <literal>org.freedesktop.DBus.Containers1</literal></title>
<para>
The special message bus name <literal>org.freedesktop.DBus</literal>
may optionally implement the
<literal>org.freedesktop.DBus.Containers1</literal> interface on
the object path <literal>/org/freedesktop/DBus</literal>.
</para>
<para>
This interface allows container managers and similar sandboxing
mechanisms to ask the message bus to create a special socket
for each sandboxed application,
which uniquely identifies the application to other message bus
clients without introducing race conditions.
For this mechanism to be useful, the sandboxed application must be
prevented from connecting to the message bus's usual socket.
This interface is a D-Bus equivalent of the
<ulink url="https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tree/main/staging/security-context">Wayland security-context extension</ulink>.
</para>
<para>
As currently implemented, this interface does not apply any
special filtering to the D-Bus messages sent and received by a
sandboxed application.
To limit what a sandboxed application can do on D-Bus, it is
likely to be necessary to impose restrictions,
perhaps by using a Linux security module such as AppArmor or a
filtering proxy such as
<ulink url="https://github.com/flatpak/xdg-dbus-proxy/">xdg-dbus-proxy</ulink>.
A future version of this specification might add a mechanism for
the creator of a confined socket to specify filtering rules.
</para>
<sect3 id="bus-messages-containers1-add-server">
<title>Method: <literal>org.freedesktop.DBus.Containers1.AddServer</literal></title>
<para>
As a method:
<programlisting>
AddServer (in STRING container_type,
in STRING app_id,
in STRING instance_id,
in DICT&lt;STRING,VARIANT&gt; metadata,
in DICT&lt;STRING,VARIANT&gt; named_arguments,
out OBJECT_PATH server_path,
out DICT&lt;STRING,VARIANT&gt; results)
</programlisting>
Message arguments:
<informaltable>
<tgroup cols="3">
<thead>
<row>
<entry>Argument</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>0</entry>
<entry>STRING</entry>
<entry>
Reversed domain name identifying a container manager
or container technology, such as
<literal>org.flatpak</literal> or
<literal>io.snapcraft</literal>. It must follow the
same syntactic rules as a
<link linkend="message-protocol-names-interface">D-Bus
interface name</link>.
Container managers should use the same identifier here
that they use for the
<literal>set_sandbox_engine</literal> request in the
<ulink url="https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tree/main/staging/security-context">Wayland security-context extension</ulink>.
</entry>
</row>
<row>
<entry>1</entry>
<entry>STRING</entry>
<entry>
Some unique identifier for an application or
container, whose meaning is defined by the
maintainers of the container type. If the container
type does not have a concept of identifying or
naming its applications or containers by a string,
using the empty string here is suggested.
Container managers should use the same identifier here
that they use for the
<literal>set_app_id</literal> request in the
<ulink url="https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tree/main/staging/security-context">Wayland security-context extension</ulink>.
</entry>
</row>
<row>
<entry>2</entry>
<entry>STRING</entry>
<entry>
Some unique identifier for a particular instance of an
application or container, whose meaning is defined by the
maintainers of the container type. If the container
type does not have a concept of identifying or
naming instances by a string,
using the empty string here is suggested.
Container managers should use the same identifier here
that they use for the
<literal>set_instance_id</literal> request in the
<ulink url="https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tree/main/staging/security-context">Wayland security-context extension</ulink>.
</entry>
</row>
<row>
<entry>3</entry>
<entry>DICT&lt;STRING,VARIANT&gt;</entry>
<entry>
Metadata describing the application or container.
All keys and values are reserved for future standardization,
either in this specification or in a separate
freedesktop.org specification referenced by this one.
</entry>
</row>
<row>
<entry>4</entry>
<entry>DICT&lt;STRING,VARIANT&gt;</entry>
<entry>
Additional arguments that extend this method.
The only named arguments that are allowed are the ones
listed in the
<literal>org.freedesktop.DBus.Containers1.SupportedArguments</literal>
property. All other named arguments are an error.
</entry>
</row>
<row>
<entry>5</entry>
<entry>OBJECT_PATH</entry>
<entry>
An opaque object path identifying the new container
context.
</entry>
</row>
<row>
<entry>6</entry>
<entry>DICT&lt;STRING,VARIANT&gt;</entry>
<entry>
Details of the container-specific server that was
created (see below).
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</para>
<para>
Set up a new server socket for use in an application container.
Clients can connect to that socket, and the result will be as
though they had connected to the message bus, with a few differences.
The intention is that a container manager will call this method
to get a new server socket, bind-mount the server socket
into a location that is accessible by the confined (sandboxed)
application, and ensure that the normal message bus socket is
<emphasis>not</emphasis> accessible by the confined application.
</para>
<para>
Each call to this method creates a new
<firstterm>container server</firstterm>, identified by an object
path. Even if the specified container type, app ID and instance ID
are the same as for a pre-existing container server, each call
creates a new server with a new unique object path, because
the new container server might represent a different
version of the confined application with different
characteristics and restrictions (although in practice it is
expected that container managers are most likely to create one
server per instance ID). The message bus may provide
an object at the given object path, but is not required to
do so.
</para>
<para>
Metadata from the first three arguments is stored by the message
bus, but not interpreted; software that interacts with the
container manager can use this metadata.
The method call may fail with the error
<literal>org.freedesktop.DBus.Error.LimitsExceeded</literal>
if the caller provides more metadata than the message bus
is willing to store.
</para>
<para>
The last argument (the <firstterm>named arguments</firstterm>)
can be used to modify
the behaviour of this method call; all keys in this dictionary not
containing <literal>.</literal> are reserved by this specification
for this purpose. It can also contain implementation-specific
arguments for a particular message bus implementation, which
should start with a reversed domain name in the same way as
interface names. For example, GLib's gdbus-daemon might use
<literal>org.gtk.GDBus.SomeArgument</literal> if it used this
extension point.
</para>
<para>
The only named arguments that are allowed are the ones
listed in the
<literal>org.freedesktop.DBus.Containers1.SupportedArguments</literal>
property. All other named arguments are an error.
The possible keys currently defined for the named arguments are:
<informaltable>
<tgroup cols="4">
<thead>
<row>
<entry>Key</entry>
<entry>Value type</entry>
<entry>Default value</entry>
<entry>Meaning</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>StopOnDisconnect</literal></entry>
<entry>BOOLEAN</entry>
<entry>true</entry>
<entry>
If true or omitted, the server socket will cease to
listen for new connections (in the same way as if
<literal>StopListening</literal> had been called
successfully) when the caller of this method
disconnects from the bus. Any confined connections
that are already active are unaffected.
If false, the server socket continues to listen after
the caller disconnects, until there is some other reason
for it to stop.
</entry>
</row>
<row>
<entry><literal>StopOnNotify</literal></entry>
<entry>UNIX_FD</entry>
<entry>(none)</entry>
<entry>
A file descriptor which will become readable (either
data available or end-of-file) when it is time to stop
listening for new connections. In practice, this is
expected to be the read end of a Unix pipe, for which
the corresponding write end will be closed (either
explicitly or when its owning process exits) when the
container manager detects that the application has
stopped running. This will often be combined with
<literal>{ "StopOnDisconnect": false }</literal>.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</para>
<para>
Keys in the returned result dictionary not containing "." are defined
by this specification. Bus daemon implementors wishing to emit
information not mentioned in this document should use keys
containing "." and starting with a reversed domain name.
Additional keys may be added to the result at any time, and
callers must accept and ignore unknown keys in this dictionary.
</para>
<para>
The defined keys for the result dictionary are:
<informaltable>
<tgroup cols="3">
<thead>
<row>
<entry>Key</entry>
<entry>Value type</entry>
<entry>Value</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>Address</literal></entry>
<entry>STRING</entry>
<entry>
A connectable D-Bus address, for example
<literal>unix:path=/run/user/1000/dbus-12345vwxyz</literal>.
</entry>
</row>
<row>
<entry><literal>SocketPath</literal></entry>
<entry>ARRAY of BYTE</entry>
<entry>
The absolute filesystem path of the resulting
<literal>AF_UNIX</literal> socket, followed by a single
zero (nul) byte, for example
<literal>/run/user/1000/dbus-12345vwxyz\x00</literal>
where \x00 represents the terminating zero byte.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</para>
<para>
In the most basic form of a call to this method, the new server
socket is an <literal>AF_UNIX</literal> socket at a path chosen
by the message bus and returned from the method.
When operating in this way, each successful call will return a
result dictionary containing both <literal>Address</literal> and
<literal>SocketPath</literal>.
Callers may use whichever of those members is more convenient.
If a future version of this specification adds a mechanism to
request that the server listens in some way that makes these two
members inapplicable, for example receiving a socket from the
caller in the named arguments dictionary via file descriptor passing
and arranging for it to listen for connections, then those members
will be omitted from the result dictionary.
</para>
<para>
Connections to the new server socket are said to be
<firstterm>confined</firstterm>. The message bus may prevent
confined connections from calling certain security-sensitive methods,
and may apply lower limits to these connections. However, container
managers and services must not rely on confined connections having
any particular filtering applied to them by the message bus.
</para>
<para>
The container server object path remains valid for as
long as one or more confined connection via the same server
socket remain open, or there is a way for the server socket
to produce new connections in future (in other words,
either it is preparing to listen for new connections, or
it is currently listening for new connections). When the
container server has ceased to listen for new connections
and no longer has any confined connections, the object path
becomes invalid, and API calls that specify it will fail with
the <literal>org.freedesktop.DBus.Error.NotContainer</literal>
error.
</para>
</sect3>
<sect3 id="bus-messages-containers1-stop-server">
<title>Method: <literal>org.freedesktop.DBus.Containers1.StopServer</literal></title>
<para>
As a method:
<programlisting>
StopServer (in OBJECT_PATH server_path)
</programlisting>
Message arguments:
<informaltable>
<tgroup cols="3">
<thead>
<row>
<entry>Argument</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>0</entry>
<entry>OBJECT_PATH</entry>
<entry>
The opaque object path that was returned from the
<literal>AddServer</literal> method, identifying a
per-container server.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</para>
<para>
Terminate the container server. The server will stop
listening for new connections, and any existing connections to
that server will be disconnected. When all connections have
been disconnected, the context will cease to exist.
</para>
<para>
If the given object path does not represent a valid container
context (see
<xref linkend="bus-messages-containers1-add-server"/>), the
<literal>org.freedesktop.DBus.Error.NotContainer</literal>
error is returned. In particular, if this method is called
twice, the second call will fail in this way.
</para>
<para>
If the given container server exists but the caller of this
method is not allowed to stop it (for example because the
caller is in a container server or because its user ID does
not match the user ID of the creator of the container
context),
the <literal>org.freedesktop.DBus.Error.AccessDenied</literal>
error is returned and nothing is stopped.
</para>
</sect3>
<sect3 id="bus-messages-containers1-stop-listening">
<title>Method: <literal>org.freedesktop.DBus.Containers1.StopListening</literal></title>
<para>
As a method:
<programlisting>
StopListening (in OBJECT_PATH server_path)
</programlisting>
Message arguments:
<informaltable>
<tgroup cols="3">
<thead>
<row>
<entry>Argument</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>0</entry>
<entry>OBJECT_PATH</entry>
<entry>
The opaque object path that was returned from the
<literal>AddServer</literal> method, identifying a
container server.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</para>
<para>
Stop listening for new connections to the given per-container
server, but do not disconnect any existing connections.
</para>
<para>
If this method is called more than once, while the container
context still exists because connections to it are still
open, the second and subsequent calls will be successful but
will have no practical effect.
</para>
<para>
If the given object path does not represent a valid container
context (see
<xref linkend="bus-messages-containers1-add-server"/>), the
<literal>org.freedesktop.DBus.Error.NotContainer</literal>
error is returned. In particular, this will happen if the
per-container server already stopped listening, and all
connections to it (if any) were already closed.
</para>
<para>
If the given per-container server exists but the caller of this
method is not allowed to stop it (for example because the
caller is itself in a container, or because its user ID does
not match the user ID of the creator of the container server),
the <literal>org.freedesktop.DBus.Error.AccessDenied</literal>
error is returned and nothing is stopped.
</para>
</sect3>
<sect3 id="bus-messages-containers1-get-connection-info">
<title>Method: <literal>org.freedesktop.DBus.Containers1.GetConnectionInfo</literal></title>
<para>
As a method:
<programlisting>
GetConnectionInfo (in STRING bus_name,
out OBJECT_PATH server_path,
out DICT&lt;STRING,VARIANT&gt; creator,
out STRING container_type,
out STRING app_id,
out STRING instance_id,
out DICT&lt;STRING,VARIANT&gt; metadata)
</programlisting>
Message arguments:
<informaltable>
<tgroup cols="3">
<thead>
<row>
<entry>Argument</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>0</entry>
<entry>STRING</entry>
<entry>
Unique or well-known bus name of the connection to
query, such as <literal>:12.34</literal> or
<literal>com.example.tea</literal>.
</entry>
</row>
<row>
<entry>1</entry>
<entry>OBJECT_PATH</entry>
<entry>
The opaque object path that was returned from the
<link linkend="bus-messages-containers1-add-server"
>AddServer</link> method, identifying a
per-container server.
This output parameter is produced by the message bus.
</entry>
</row>
<row>
<entry>2</entry>
<entry>DICT&lt;STRING,VARIANT&gt;</entry>
<entry>
Credentials of the connection that created this
per-container server.
The keys and values are the same as those that are
documented for
<link linkend="bus-messages-get-connection-credentials"
>GetConnectionCredentials</link>.
This output parameter is produced by the message bus
and is appropriate to use in trust decisions.
</entry>
</row>
<row>
<entry>3</entry>
<entry>STRING</entry>
<entry>
Reversed domain name identifying a container
manager or container technology, as passed to the
<literal>AddServer</literal> method, such as
<literal>org.flatpak</literal> or
<literal>io.snapcraft</literal>.
This output parameter is controlled by the creator
of the per-container server.
</entry>
</row>
<row>
<entry>4</entry>
<entry>STRING</entry>
<entry>
A unique identifier for an application or container,
whose meaning is defined by the maintainers of the
container type.
For example, Flatpak would use a freedesktop.org app ID
such as <literal>org.mozilla.firefox</literal>, but
Snap might use a Snap app ID such as
<literal>firefox</literal>.
This output parameter is controlled by the creator
of the per-container server.
Depending on the container type, it might be possible
to look up further metadata for the application in a
container-type-specific way.
</entry>
</row>
<row>
<entry>5</entry>
<entry>STRING</entry>
<entry>
A unique identifier for a particular instance of an
application or container, whose meaning is defined by
the maintainers of the container type.
For example, Flatpak would use the same numeric
instance ID that is shown by <literal>flatpak ps</literal>.
This output parameter is controlled by the creator
of the per-container server.
Depending on the container type, it might be possible
to look up further metadata for the instance in a
container-type-specific way, for example by reading
<filename>$XDG_RUNTIME_DIR/.flatpak/$INSTANCE/info</filename>
for a Flatpak app.
</entry>
</row>
<row>
<entry>6</entry>
<entry>DICT&lt;STRING,VARIANT&gt;</entry>
<entry>
Metadata describing the application or container.
All keys and values are reserved for future standardization,
either in this specification or in a separate
freedesktop.org specification referenced by this one.
This output parameter is controlled by the creator
of the per-container server.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</para>
<para>
If the given unique or well-known bus name is a connection to a
container server, return information about that per-container server.
If the bus name exists but is not confined (in other words, if it
is a direct connection to the main message bus socket), the
<literal>org.freedesktop.DBus.Error.NotContainer</literal> error
is returned instead. If the given bus name has no owner at all, the
<literal>org.freedesktop.DBus.Error.NameHasNoOwner</literal> error
is returned instead.
</para>
<para>
Several of the output parameters are controlled by the creator of
the per-container server.
On a bus that accepts connections from multiple clients at
different privilege levels, it is not appropriate to trust those
output parameters or use them in trust decisions unless the
process identified by the <literal>creator</literal> parameter
is trusted.
</para>
</sect3>
<sect3 id="bus-messages-containers1-get-server-info">
<title>Method: <literal>org.freedesktop.DBus.Containers1.GetServerInfo</literal></title>
<para>
As a method:
<programlisting>
GetServerInfo (in OBJECT_PATH server_path,
out DICT&lt;STRING,VARIANT&gt; creator,
out STRING container_type,
out STRING app_id,
out STRING instance_id,
out DICT&lt;STRING,VARIANT&gt; metadata)
</programlisting>
Message arguments:
<informaltable>
<tgroup cols="3">
<thead>
<row>
<entry>Argument</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>0</entry>
<entry>OBJECT_PATH</entry>
<entry>
The opaque object path that was returned from the
<literal>AddServer</literal> method, identifying a
per-container server.
</entry>
</row>
<row>
<entry>1</entry>
<entry>DICT&lt;STRING,VARIANT&gt;</entry>
<entry>
Credentials of the connection that created this
per-container server.
The keys and values are the same as those that are
documented for
<link linkend="bus-messages-get-connection-credentials"
>GetConnectionCredentials</link>.
This output parameter is produced by the message bus
and is appropriate to use in trust decisions.
</entry>
</row>
<row>
<entry>2</entry>
<entry>STRING</entry>
<entry>
Reversed domain name identifying a container
manager or container technology,
the same as the corresponding parameter in
<link linkend="bus-messages-containers1-get-connection-info"
>GetConnectionInfo</link>.
This output parameter is controlled by the creator
of the per-container server.
</entry>
</row>
<row>
<entry>3</entry>
<entry>STRING</entry>
<entry>
Some unique identifier for an application or container,
the same as the corresponding parameter in
<link linkend="bus-messages-containers1-get-connection-info"
>GetConnectionInfo</link>.
This output parameter is controlled by the creator
of the per-container server.
</entry>
</row>
<row>
<entry>4</entry>
<entry>STRING</entry>
<entry>
Some unique identifier for a particular instance of an
application or container,
the same as the corresponding parameter in
<link linkend="bus-messages-containers1-get-connection-info"
>GetConnectionInfo</link>.
This output parameter is controlled by the creator
of the per-container server.
</entry>
</row>
<row>
<entry>5</entry>
<entry>DICT&lt;STRING,VARIANT&gt;</entry>
<entry>
Metadata describing the application or container,
the same as the corresponding parameter in
<link linkend="bus-messages-containers1-get-connection-info"
>GetConnectionInfo</link>.
This output parameter is controlled by the creator
of the per-container server.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</para>
<para>
If the given object path represents a valid per-container server
(see <xref linkend="bus-messages-containers1-add-server"/>),
return information about it. Otherwise, the
<literal>org.freedesktop.DBus.Error.NotContainer</literal> error
is returned.
</para>
<para>
Several of the output parameters are controlled by the creator of
the per-container server.
See <xref linkend="bus-messages-containers1-get-connection-info"/>
for more details.
</para>
</sect3>
<sect3 id="bus-messages-containers1-server-removed">
<title>Signal: <literal>org.freedesktop.DBus.Containers1.ServerRemoved</literal></title>
<para>
As a signal emitted by the message bus:
<programlisting>
ServerRemoved (OBJECT_PATH server_path)
</programlisting>
Message arguments:
<informaltable>
<tgroup cols="3">
<thead>
<row>
<entry>Argument</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>0</entry>
<entry>OBJECT_PATH</entry>
<entry>
The opaque object path that was returned from the
<literal>AddServer</literal> method, identifying a
per-container server.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</para>
<para>
Emitted when a per-container server ceases to exist. A per-container
server continues to exist as long as it is listening for
new connections, or as long as connections to the server
are open, whichever is longer.
</para>
</sect3>
</sect2>
<sect2 id="message-bus-monitoring-interface">
<title>Monitoring Interface: <literal>org.freedesktop.DBus.Monitoring</literal></title>
<para>
@ -7502,7 +8273,12 @@
monitor connections<footnote>
<para>
In the reference implementation,
the default configuration is that each user (identified by
the default configuration is that connections to a
container server managed by
<link linkend="message-bus-containers-interface">the
Containers1 interface</link>
are not privileged and cannot become a monitor;
otherwise, each user (identified by
numeric user ID) may monitor their own session bus,
and the root user (user ID zero) may monitor the
system bus.

View file

@ -47,8 +47,6 @@
#include "test-utils-glib.h"
#define DBUS_INTERFACE_CONTAINERS1 "org.freedesktop.DBus.Containers1"
typedef struct {
TestMainContext *ctx;
gboolean skip;
@ -58,7 +56,7 @@ typedef struct {
GDBusProxy *proxy;
gchar *instance_path;
gchar *server_path;
gchar *socket_path;
gchar *socket_dbus_address;
GDBusConnection *unconfined_conn;
@ -67,10 +65,11 @@ typedef struct {
GDBusConnection *observer_conn;
GDBusProxy *observer_proxy;
GHashTable *containers_removed;
GHashTable *servers_removed;
guint removed_sub;
DBusConnection *libdbus_observer;
DBusMessage *latest_shout;
int stop_on_notify_pipe_write_end;
} Fixture;
typedef struct
@ -82,6 +81,8 @@ typedef struct
STOP_SERVER_DISCONNECT_FIRST,
STOP_SERVER_NEVER_CONNECTED,
STOP_SERVER_FORCE,
STOP_SERVER_VIA_NOTIFY,
STOP_SERVER_VIA_NOTIFY_NOT_MANAGER,
STOP_SERVER_WITH_MANAGER
}
stop_server;
@ -138,13 +139,13 @@ observe_shouting_cb (DBusConnection *observer,
}
static void
instance_removed_cb (GDBusConnection *observer,
const gchar *sender,
const gchar *path,
const gchar *iface,
const gchar *member,
GVariant *parameters,
gpointer user_data)
server_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;
@ -152,11 +153,11 @@ instance_removed_cb (GDBusConnection *observer,
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 (member, ==, "ServerRemoved");
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));
g_assert (!g_hash_table_contains (f->servers_removed, container));
g_hash_table_add (f->servers_removed, g_strdup (container));
}
static void
@ -177,6 +178,33 @@ fixture_disconnect_unconfined (Fixture *f)
g_clear_object (&f->unconfined_conn);
}
#ifdef DBUS_ENABLE_CONTAINERS
static void
fixture_disconnect_unconfined_and_wait (Fixture *f)
{
guint name_watch;
gboolean gone = FALSE;
/* 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);
}
#endif
static void
fixture_disconnect_observer (Fixture *f)
{
@ -233,15 +261,15 @@ setup (Fixture *f,
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->servers_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",
"ServerRemoved",
DBUS_PATH_DBUS, NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
instance_removed_cb,
server_removed_cb,
f, NULL);
/* We have to use libdbus for new header fields, because GDBus doesn't
@ -253,6 +281,8 @@ setup (Fixture *f,
if (!dbus_connection_add_filter (f->libdbus_observer, observe_shouting_cb, f,
NULL))
g_error ("OOM");
f->stop_on_notify_pipe_write_end = -1;
}
/*
@ -265,6 +295,7 @@ test_get_supported_arguments (Fixture *f,
GVariant *v;
#ifdef DBUS_ENABLE_CONTAINERS
const gchar **args;
gsize i;
gsize len;
#endif
@ -286,8 +317,11 @@ test_get_supported_arguments (Fixture *f,
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_assert_cmpuint (len, ==, 2);
i = 0;
g_assert_cmpstr (args[i++], ==, "StopOnDisconnect");
g_assert_cmpstr (args[i++], ==, "StopOnNotify");
g_assert_cmpstr (args[i++], ==, NULL);
g_free (args);
g_variant_unref (v);
@ -308,10 +342,15 @@ test_get_supported_arguments (Fixture *f,
*/
static gboolean
add_container_server (Fixture *f,
GVariant *parameters)
GVariant *parameters,
GUnixFDList *fds,
GUnixFDList **fds_out)
{
GVariant *tuple;
GVariant *named_results;
GStatBuf stat_buf;
gboolean found;
gchar *stringified_args;
f->proxy = g_dbus_proxy_new_sync (f->unconfined_conn,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
@ -320,9 +359,33 @@ add_container_server (Fixture *f,
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);
stringified_args = g_variant_print (parameters, TRUE);
g_test_message ("Calling AddServer%s...", stringified_args);
g_free (stringified_args);
if (fds != NULL || fds_out != NULL)
{
if (fds != NULL)
g_test_message ("Sending %d fds", g_unix_fd_list_get_length (fds));
if (fds_out != NULL)
g_test_message ("Expecting to receive fds in result");
tuple = g_dbus_proxy_call_with_unix_fd_list_sync (f->proxy,
"AddServer",
parameters,
G_DBUS_CALL_FLAGS_NONE,
-1,
fds,
fds_out,
NULL,
&f->error);
}
else
{
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
@ -341,22 +404,34 @@ add_container_server (Fixture *f,
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);
stringified_args = g_variant_print (tuple, TRUE);
g_test_message ("-> %s", stringified_args);
g_free (stringified_args);
g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(oa{sv})");
g_variant_get (tuple, "(o@a{sv})", &f->server_path, &named_results);
g_assert_nonnull (f->server_path);
g_assert_true (g_variant_is_object_path (f->server_path));
found = g_variant_lookup (named_results, "Address",
"s", &f->socket_dbus_address);
g_assert_true (found);
g_assert_nonnull (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));
found = g_variant_lookup (named_results, "SocketPath",
"^ay", &f->socket_path);
g_assert_true (found);
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);
g_clear_pointer (&named_results, g_variant_unref);
g_clear_pointer (&tuple, g_variant_unref);
return TRUE;
}
#endif
@ -380,7 +455,8 @@ test_basic (Fixture *f,
GVariantDict dict;
const gchar *confined_unique_name;
const gchar *path_from_query;
const gchar *name;
const gchar *app_id;
const gchar *instance_id;
const gchar *name_owner;
const gchar *type;
guint32 uid;
@ -389,16 +465,18 @@ test_basic (Fixture *f,
DBusMessage *libdbus_message = NULL;
DBusMessage *libdbus_reply = NULL;
DBusError libdbus_error = DBUS_ERROR_INIT;
gchar *stringified_result;
if (f->skip)
return;
parameters = g_variant_new ("(ssa{sv}a{sv})",
parameters = g_variant_new ("(sssa{sv}a{sv})",
"com.example.NotFlatpak",
"sample-app",
"sample-instance",
NULL, /* no metadata */
NULL); /* no named arguments */
if (!add_container_server (f, g_steal_pointer (&parameters)))
if (!add_container_server (f, g_steal_pointer (&parameters), NULL, NULL))
return;
g_test_message ("Connecting to %s...", f->socket_dbus_address);
@ -445,7 +523,7 @@ test_basic (Fixture *f,
while (f->latest_shout == NULL)
iterate_both_main_loops (f->ctx);
g_assert_cmpstr (dbus_message_get_container_instance (f->latest_shout), ==,
g_assert_cmpstr (dbus_message_get_container_path (f->latest_shout), ==,
NULL);
dbus_clear_message (&f->latest_shout);
@ -456,7 +534,7 @@ test_basic (Fixture *f,
while (f->latest_shout == NULL)
iterate_both_main_loops (f->ctx);
g_assert_cmpstr (dbus_message_get_container_instance (f->latest_shout), ==,
g_assert_cmpstr (dbus_message_get_container_path (f->latest_shout), ==,
NULL);
dbus_clear_message (&f->latest_shout);
@ -484,8 +562,8 @@ test_basic (Fixture *f,
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);
g_assert_cmpstr (dbus_message_get_container_path (f->latest_shout), ==,
f->server_path);
dbus_clear_message (&f->latest_shout);
g_dbus_connection_emit_signal (f->unconfined_conn, NULL, "/",
@ -495,7 +573,7 @@ test_basic (Fixture *f,
while (f->latest_shout == NULL)
iterate_both_main_loops (f->ctx);
g_assert_cmpstr (dbus_message_get_container_instance (f->latest_shout), ==,
g_assert_cmpstr (dbus_message_get_container_path (f->latest_shout), ==,
"/");
dbus_clear_message (&f->latest_shout);
@ -514,41 +592,50 @@ test_basic (Fixture *f,
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",
tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInfo",
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);
stringified_result = g_variant_print (tuple, TRUE);
g_test_message ("GetConnectionInfo() -> %s", stringified_result);
g_free (stringified_result);
g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(oa{sv}sssa{sv})");
g_variant_get (tuple, "(&o@a{sv}&s&s&s@a{sv})",
&path_from_query, &creator, &type, &app_id, &instance_id, &asv);
g_assert_cmpstr (path_from_query, ==, f->server_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");
g_assert_cmpstr (app_id, ==, "sample-app");
g_assert_cmpstr (instance_id, ==, "sample-instance");
/* 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_test_message ("Inspecting container server info");
tuple = g_dbus_proxy_call_sync (f->proxy, "GetServerInfo",
g_variant_new ("(o)", f->server_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);
stringified_result = g_variant_print (tuple, TRUE);
g_test_message ("GetServerInfo() -> %s", stringified_result);
g_free (stringified_result);
g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(a{sv}sssa{sv})");
g_variant_get (tuple, "(@a{sv}&s&s&s@a{sv})",
&creator, &type, &app_id, &instance_id, &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");
g_assert_cmpstr (app_id, ==, "sample-app");
g_assert_cmpstr (instance_id, ==, "sample-instance");
/* Trivial case: the metadata a{sv} is empty */
g_assert_cmpuint (g_variant_n_children (asv), ==, 0);
g_clear_pointer (&asv, g_variant_unref);
@ -584,12 +671,13 @@ test_wrong_uid (Fixture *f,
if (f->skip)
return;
parameters = g_variant_new ("(ssa{sv}a{sv})",
parameters = g_variant_new ("(sssa{sv}a{sv})",
"com.example.NotFlatpak",
"sample-app",
"instance1",
NULL, /* no metadata */
NULL); /* no named arguments */
if (!add_container_server (f, g_steal_pointer (&parameters)))
if (!add_container_server (f, g_steal_pointer (&parameters), NULL, NULL))
return;
g_test_message ("Connecting to %s...", f->socket_dbus_address);
@ -615,7 +703,8 @@ test_wrong_uid (Fixture *f,
/*
* 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.
* carried through correctly, and that the app/instance IDs are
* allowed to be empty.
*/
static void
test_metadata (Fixture *f,
@ -629,7 +718,8 @@ test_metadata (Fixture *f,
GVariantDict dict;
const gchar *confined_unique_name;
const gchar *path_from_query;
const gchar *name;
const gchar *app_id;
const gchar *instance_id;
const gchar *type;
guint32 uid;
guint u;
@ -644,13 +734,15 @@ test_metadata (Fixture *f,
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})",
parameters = g_variant_new ("(sss@a{sv}a{sv})",
"org.example.Springwatch",
/* Verify that empty app names are OK */
/* Verify that empty app IDs are OK */
"",
/* Verify that empty instance IDs are OK */
"",
g_variant_dict_end (&dict),
NULL); /* no named arguments */
if (!add_container_server (f, g_steal_pointer (&parameters)))
if (!add_container_server (f, g_steal_pointer (&parameters), NULL, NULL))
return;
g_test_message ("Connecting to %s...", f->socket_dbus_address);
@ -677,29 +769,30 @@ test_metadata (Fixture *f,
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",
DBUS_INTERFACE_CONTAINERS1 ".Path",
"&o", &path_from_query));
g_assert_cmpstr (path_from_query, ==, f->instance_path);
g_assert_cmpstr (path_from_query, ==, f->server_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",
tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInfo",
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_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(oa{sv}sssa{sv})");
g_variant_get (tuple, "(&o@a{sv}&s&s&s@a{sv})",
&path_from_query, &creator, &type, &app_id, &instance_id, &asv);
g_assert_cmpstr (path_from_query, ==, f->server_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_assert_cmpstr (app_id, ==, "");
g_assert_cmpstr (instance_id, ==, "");
g_variant_dict_init (&dict, asv);
g_assert_true (g_variant_dict_lookup (&dict, "NChildren", "u", &u));
g_assert_cmpuint (u, ==, 2);
@ -713,20 +806,22 @@ test_metadata (Fixture *f,
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_test_message ("Inspecting container server info");
tuple = g_dbus_proxy_call_sync (f->proxy, "GetServerInfo",
g_variant_new ("(o)", f->server_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_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(a{sv}sssa{sv})");
g_variant_get (tuple, "(@a{sv}&s&s&s@a{sv})",
&creator, &type, &app_id, &instance_id, &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_assert_cmpstr (app_id, ==, "");
g_assert_cmpstr (instance_id, ==, "");
g_variant_dict_init (&dict, asv);
g_assert_true (g_variant_dict_lookup (&dict, "NChildren", "u", &u));
g_assert_cmpuint (u, ==, 2);
@ -755,7 +850,7 @@ test_metadata (Fixture *f,
* Test StopListening(), which just closes the listening socket.
*
* With config->stop_server == STOP_SERVER_FORCE:
* Test StopInstance(), which closes the listening socket and
* Test StopServer(), which closes the listening socket and
* disconnects all its clients.
*/
static void
@ -769,14 +864,22 @@ test_stop_server (Fixture *f,
GDBusProxy *attacker_proxy;
GSocket *client_socket;
GSocketAddress *socket_address;
GUnixFDList *fds = NULL;
GVariant *tuple;
GVariant *creator;
GVariant *parameters;
GVariant *asv;
GVariantDict dict;
GVariantDict named_argument_builder;
gchar *error_name;
gchar *stringified_result;
const gchar *confined_unique_name;
const gchar *name_owner;
gboolean gone = FALSE;
guint name_watch;
const char *type;
const char *app_id;
const char *instance_id;
guint i;
guint32 uid;
g_assert_nonnull (config);
@ -791,12 +894,43 @@ test_stop_server (Fixture *f,
&f->error);
g_assert_no_error (f->error);
parameters = g_variant_new ("(ssa{sv}a{sv})",
g_variant_dict_init (&named_argument_builder, NULL);
fds = g_unix_fd_list_new ();
if (config->stop_server == STOP_SERVER_VIA_NOTIFY_NOT_MANAGER)
g_variant_dict_insert (&named_argument_builder, "StopOnDisconnect",
"b", FALSE);
if (config->stop_server == STOP_SERVER_VIA_NOTIFY ||
config->stop_server == STOP_SERVER_VIA_NOTIFY_NOT_MANAGER)
{
DBusError error = DBUS_ERROR_INIT;
int notify_pipe[2];
int fd_index;
_dbus_unix_make_pipe (notify_pipe, &error);
test_assert_no_error (&error);
/* transfer ownership */
f->stop_on_notify_pipe_write_end = notify_pipe[1];
/* duplicate into the fd list and close the original */
fd_index = g_unix_fd_list_append (fds, notify_pipe[0], &f->error);
g_assert_no_error (f->error);
g_assert_cmpint (fd_index, >=, 0);
close (notify_pipe[0]);
g_variant_dict_insert (&named_argument_builder, "StopOnNotify",
"h", fd_index);
}
parameters = g_variant_new ("(sssa{sv}@a{sv})",
"com.example.NotFlatpak",
"sample-app",
"",
NULL, /* no metadata */
NULL); /* no named arguments */
if (!add_container_server (f, g_steal_pointer (&parameters)))
g_variant_dict_end (&named_argument_builder));
if (!add_container_server (f, g_steal_pointer (&parameters), fds, NULL))
return;
socket_address = g_unix_socket_address_new (f->socket_path);
@ -813,6 +947,9 @@ test_stop_server (Fixture *f,
if (config->stop_server == STOP_SERVER_DISCONNECT_FIRST)
{
guint name_watch;
gboolean gone = FALSE;
g_test_message ("Disconnecting confined connection...");
gone = FALSE;
confined_unique_name = g_dbus_connection_get_unique_name (
@ -836,7 +973,7 @@ test_stop_server (Fixture *f,
}
/* 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 with a different uid cannot close our container servers. */
attacker = test_try_connect_gdbus_as_user (f->bus_address, TEST_USER_OTHER,
&f->error);
@ -851,15 +988,15 @@ test_stop_server (Fixture *f,
g_assert_no_error (f->error);
tuple = g_dbus_proxy_call_sync (attacker_proxy, "StopListening",
g_variant_new ("(o)", f->instance_path),
g_variant_new ("(o)", f->server_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),
tuple = g_dbus_proxy_call_sync (attacker_proxy, "StopServer",
g_variant_new ("(o)", f->server_path),
G_DBUS_CALL_FLAGS_NONE, -1, NULL,
&f->error);
g_assert_error (f->error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED);
@ -880,46 +1017,32 @@ test_stop_server (Fixture *f,
g_clear_error (&f->error);
}
g_assert_false (g_hash_table_contains (f->containers_removed,
f->instance_path));
g_assert_false (g_hash_table_contains (f->servers_removed,
f->server_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);
g_test_message ("Stopping server (but not confined connection) by "
"disconnecting container manager from bus");
fixture_disconnect_unconfined_and_wait (f);
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_variant_new ("(o)", f->server_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
/* The container server 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...");
g_test_message ("Checking we do not get ServerRemoved...");
tuple = g_dbus_proxy_call_sync (f->proxy, "NoSuchMethod", NULL,
G_DBUS_CALL_FLAGS_NONE, -1, NULL,
&f->error);
@ -928,33 +1051,49 @@ test_stop_server (Fixture *f,
g_clear_error (&f->error);
break;
case STOP_SERVER_VIA_NOTIFY_NOT_MANAGER:
fixture_disconnect_unconfined_and_wait (f);
/* Assert that the server is still functional at this point,
* because we configured it with StopOnDisconnect=FALSE */
g_assert_true (g_file_test (f->socket_path, G_FILE_TEST_EXISTS));
/* fall through */
case STOP_SERVER_VIA_NOTIFY:
g_test_message ("Stopping server (but not confined connection) by "
"closing pipe fd");
g_close (f->stop_on_notify_pipe_write_end, &f->error);
g_assert_no_error (f->error);
f->stop_on_notify_pipe_write_end = -1;
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_variant_new ("(o)", f->server_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_test_message ("Waiting for ServerRemoved...");
while (!g_hash_table_contains (f->servers_removed, f->server_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),
tuple = g_dbus_proxy_call_sync (f->proxy, "StopServer",
g_variant_new ("(o)", f->server_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_test_message ("Waiting for ServerRemoved...");
while (!g_hash_table_contains (f->servers_removed, f->server_path))
g_main_context_iteration (NULL, TRUE);
break;
@ -1034,6 +1173,8 @@ test_stop_server (Fixture *f,
break;
case STOP_SERVER_EXPLICITLY:
case STOP_SERVER_VIA_NOTIFY:
case STOP_SERVER_VIA_NOTIFY_NOT_MANAGER:
case STOP_SERVER_WITH_MANAGER:
g_test_message ("Checking that the confined app still works...");
tuple = g_dbus_connection_call_sync (f->confined_conn,
@ -1053,18 +1194,34 @@ test_stop_server (Fixture *f,
g_assert_cmpstr (name_owner, ==, DBUS_SERVICE_DBUS);
g_clear_pointer (&tuple, g_variant_unref);
/* The container instance will not disappear from the bus
/* The container server 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),
tuple = g_dbus_proxy_call_sync (f->observer_proxy, "GetServerInfo",
g_variant_new ("(o)", f->server_path),
G_DBUS_CALL_FLAGS_NONE, -1, NULL,
&f->error);
g_assert_no_error (f->error);
g_assert_nonnull (tuple);
stringified_result = g_variant_print (tuple, TRUE);
g_test_message ("GetServerInfo() -> %s", stringified_result);
g_free (stringified_result);
g_assert_cmpstr (g_variant_get_type_string (tuple), ==, "(a{sv}sssa{sv})");
g_variant_get (tuple, "(@a{sv}&s&s&s@a{sv})",
&creator, &type, &app_id, &instance_id, &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 (app_id, ==, "sample-app");
g_assert_cmpstr (instance_id, ==, "");
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);
/* Now disconnect the last confined connection, which will make the
* container instance go away */
* container server 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);
@ -1076,12 +1233,12 @@ test_stop_server (Fixture *f,
/* 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_test_message ("Waiting for ServerRemoved...");
while (!g_hash_table_contains (f->servers_removed, f->server_path))
g_main_context_iteration (NULL, TRUE);
tuple = g_dbus_proxy_call_sync (f->observer_proxy, "GetInstanceInfo",
g_variant_new ("(o)", f->instance_path),
tuple = g_dbus_proxy_call_sync (f->observer_proxy, "GetServerInfo",
g_variant_new ("(o)", f->server_path),
G_DBUS_CALL_FLAGS_NONE, -1, NULL,
&f->error);
g_assert_nonnull (f->error);
@ -1099,7 +1256,7 @@ test_stop_server (Fixture *f,
/*
* 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
* isn't a container server, or a bus name that isn't in a container
* or doesn't exist at all.
*/
static void
@ -1119,7 +1276,7 @@ test_invalid_metadata_getters (Fixture *f,
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",
tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInfo",
g_variant_new ("(s)", unique_name),
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
g_assert_nonnull (f->error);
@ -1135,7 +1292,7 @@ test_invalid_metadata_getters (Fixture *f,
g_clear_error (&f->error);
g_test_message ("Inspecting dbus-daemon");
tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInstance",
tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInfo",
g_variant_new ("(s)", DBUS_SERVICE_DBUS),
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
g_assert_nonnull (f->error);
@ -1152,7 +1309,7 @@ test_invalid_metadata_getters (Fixture *f,
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",
tuple = g_dbus_proxy_call_sync (f->proxy, "GetConnectionInfo",
g_variant_new ("(s)", "com.example.Nope"),
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
g_assert_nonnull (f->error);
@ -1168,8 +1325,8 @@ test_invalid_metadata_getters (Fixture *f,
g_clear_error (&f->error);
g_test_message ("Inspecting container instance info");
tuple = g_dbus_proxy_call_sync (f->proxy, "GetInstanceInfo",
g_test_message ("Inspecting container server info");
tuple = g_dbus_proxy_call_sync (f->proxy, "GetServerInfo",
g_variant_new ("(o)", "/nope"),
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &f->error);
g_assert_nonnull (f->error);
@ -1213,9 +1370,10 @@ test_unsupported_parameter (Fixture *f,
"ThisArgumentIsntImplemented",
"b", FALSE);
parameters = g_variant_new ("(ssa{sv}@a{sv})",
parameters = g_variant_new ("(sssa{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",
@ -1253,9 +1411,10 @@ test_invalid_type_name (Fixture *f,
NULL, &f->error);
g_assert_no_error (f->error);
parameters = g_variant_new ("(ssa{sv}a{sv})",
parameters = g_variant_new ("(sssa{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",
@ -1288,12 +1447,13 @@ test_invalid_nesting (Fixture *f,
if (f->skip)
return;
parameters = g_variant_new ("(ssa{sv}a{sv})",
parameters = g_variant_new ("(sssa{sv}a{sv})",
"com.example.NotFlatpak",
"sample-app",
"instance0",
NULL, /* no metadata */
NULL); /* no named arguments */
if (!add_container_server (f, g_steal_pointer (&parameters)))
if (!add_container_server (f, g_steal_pointer (&parameters), NULL, NULL))
return;
g_test_message ("Connecting to %s...", f->socket_dbus_address);
@ -1312,9 +1472,10 @@ test_invalid_nesting (Fixture *f,
&f->error);
g_assert_no_error (f->error);
parameters = g_variant_new ("(ssa{sv}a{sv})",
parameters = g_variant_new ("(sssa{sv}a{sv})",
"com.example.NotFlatpak",
"inner-app",
"instance1",
NULL, /* no metadata */
NULL); /* no named arguments */
tuple = g_dbus_proxy_call_sync (nested_proxy, "AddServer",
@ -1361,9 +1522,10 @@ test_max_containers (Fixture *f,
NULL, &f->error);
g_assert_no_error (f->error);
parameters = g_variant_new ("(ssa{sv}a{sv})",
parameters = g_variant_new ("(sssa{sv}a{sv})",
"com.example.NotFlatpak",
"sample-app",
"instance1",
NULL, /* no metadata */
NULL); /* no named arguments */
/* We will reuse this variant several times, so don't use floating refs */
@ -1377,7 +1539,7 @@ test_max_containers (Fixture *f,
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_get (tuple, "(o@a{sv})", &placeholders[i], NULL);
g_variant_unref (tuple);
g_test_message ("Placeholder server at %s", placeholders[i]);
}
@ -1458,7 +1620,6 @@ test_max_connections_per_container (Fixture *f,
* limit-containers.conf */
GSocket *placeholders[G_N_ELEMENTS (socket_paths) * 3] = { NULL };
GVariant *parameters;
GVariant *tuple;
guint i;
if (f->skip)
@ -1471,9 +1632,10 @@ test_max_connections_per_container (Fixture *f,
NULL, &f->error);
g_assert_no_error (f->error);
parameters = g_variant_new ("(ssa{sv}a{sv})",
parameters = g_variant_new ("(sssa{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 */
@ -1481,13 +1643,25 @@ test_max_connections_per_container (Fixture *f,
for (i = 0; i < G_N_ELEMENTS (socket_paths); i++)
{
GVariant *tuple;
GVariant *named_results;
gboolean found;
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_get (tuple, "(o@a{sv})", NULL, &named_results);
found = g_variant_lookup (named_results, "Address",
"s", &dbus_addresses[i]);
g_assert_true (found);
found = g_variant_lookup (named_results, "SocketPath",
"^ay", &socket_paths[i]);
g_assert_true (found);
g_variant_unref (named_results);
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]);
@ -1621,9 +1795,10 @@ test_max_container_metadata_bytes (Fixture *f,
1));
/* Floating reference, call_..._sync takes ownership */
parameters = g_variant_new ("(ss@a{sv}a{sv})",
parameters = g_variant_new ("(sss@a{sv}a{sv})",
"com.wasteheadquarters",
"Packt Like Sardines in a Crushd Tin Box",
"",
g_variant_dict_end (&dict),
NULL); /* no named arguments */
@ -1645,7 +1820,7 @@ teardown (Fixture *f,
g_clear_object (&f->proxy);
fixture_disconnect_observer (f);
g_clear_pointer (&f->containers_removed, g_hash_table_unref);
g_clear_pointer (&f->servers_removed, g_hash_table_unref);
if (f->libdbus_observer != NULL)
{
@ -1680,8 +1855,17 @@ teardown (Fixture *f,
f->daemon_pid = 0;
}
if (f->stop_on_notify_pipe_write_end >= 0)
{
GError *error = NULL;
g_close (f->stop_on_notify_pipe_write_end, &error);
g_assert_no_error (error);
f->stop_on_notify_pipe_write_end = -1;
}
dbus_clear_message (&f->latest_shout);
g_free (f->instance_path);
g_free (f->server_path);
g_free (f->socket_path);
g_free (f->socket_dbus_address);
g_free (f->bus_address);
@ -1710,6 +1894,16 @@ static const Config stop_server_force =
"valid-config-files/multi-user.conf",
STOP_SERVER_FORCE
};
static const Config stop_server_via_notify =
{
"valid-config-files/multi-user.conf",
STOP_SERVER_VIA_NOTIFY
};
static const Config stop_server_not_with_manager =
{
"valid-config-files/multi-user.conf",
STOP_SERVER_VIA_NOTIFY_NOT_MANAGER
};
static const Config stop_server_with_manager =
{
"valid-config-files/multi-user.conf",
@ -1769,6 +1963,10 @@ main (int argc,
&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/via-notify", Fixture,
&stop_server_via_notify, setup, test_stop_server, teardown);
g_test_add ("/containers/stop-server/not-with-manager", Fixture,
&stop_server_not_with_manager, 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,

View file

@ -13,4 +13,9 @@
<!-- Allow anyone to own anything -->
<allow own="*"/>
</policy>
<limit name="max_containers">5</limit>
<limit name="max_containers_per_user">3</limit>
<limit name="max_container_metadata_bytes">4096</limit>
<limit name="max_connections_per_container">3</limit>
</busconfig>

View file

@ -57,4 +57,11 @@
<limit name="max_names_per_connection">50000</limit>
<limit name="max_match_rules_per_connection">50000</limit>
<limit name="max_replies_per_connection">50000</limit>
<limit name="max_containers">10000</limit>
<limit name="max_containers_per_user">10000</limit>
<limit name="max_container_metadata_bytes">1000000000</limit>
<!-- This is relatively low so that app-containers (which we do not fully
trust) do not cause DoS. -->
<limit name="max_connections_per_container">16</limit>
</busconfig>

View file

@ -637,6 +637,10 @@ test_creds (Fixture *f,
g_assert_not_reached ();
#endif
}
else if (g_str_has_prefix (name, DBUS_INTERFACE_CONTAINERS1 "."))
{
g_assert_not_reached ();
}
dbus_message_iter_next (&arr_iter);
}

View file

@ -1170,9 +1170,9 @@ _dbus_message_test (const char *test_data_dir _DBUS_GNUC_UNUSED)
_dbus_assert (strcmp (dbus_message_get_path (message),
"/foo") == 0);
if (!dbus_message_set_container_instance (message, "/org/freedesktop/DBus/Containers1/c42"))
if (!dbus_message_set_container_path (message, "/org/freedesktop/DBus/Containers1/c42"))
_dbus_test_fatal ("out of memory");
_dbus_assert (strcmp (dbus_message_get_container_instance (message),
_dbus_assert (strcmp (dbus_message_get_container_path (message),
"/org/freedesktop/DBus/Containers1/c42") == 0);
if (!dbus_message_set_interface (message, "org.Foo"))

View file

@ -377,6 +377,7 @@ case "$ci_buildsys" in
fi
set -- -Db_pie=true "$@"
set -- -Dcontainers=true "$@"
set -- -Duser_session=true "$@"
;;
esac
@ -401,6 +402,7 @@ case "$ci_buildsys" in
set "$@" -Dlibaudit=disabled -Dvalgrind=disabled
# Disable optional features, some of which are on by
# default
set "$@" -Dcontainers=false
set "$@" -Dstats=false
set "$@" -Duser_session=false
shift
@ -462,6 +464,12 @@ case "$ci_buildsys" in
$meson_setup "$@" "$srcdir"
meson compile -v
if [ "$(id -u)" = 0 ]; then
# In production, this would be /run/dbus/containers and would
# have been set up by the init script or tmpfiles.d
mkdir -p /var/local/run/dbus/containers
fi
# This is too slow and verbose to keep enabled at the moment
export DBUS_TEST_MALLOC_FAILURES=0