Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package xdg-dbus-proxy for openSUSE:Factory
checked in at 2026-09-28 10:35:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/xdg-dbus-proxy (Old)
and /work/SRC/openSUSE:Factory/.xdg-dbus-proxy.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "xdg-dbus-proxy"
Mon Sep 28 10:35:18 2026 rev:10 rq:1380187 version:0.1.9
Changes:
--------
--- /work/SRC/openSUSE:Factory/xdg-dbus-proxy/xdg-dbus-proxy.changes
2026-08-22 21:34:54.754564082 +0200
+++
/work/SRC/openSUSE:Factory/.xdg-dbus-proxy.new.383539/xdg-dbus-proxy.changes
2026-09-28 10:35:59.051643721 +0200
@@ -1,0 +2,13 @@
+Wed Sep 23 19:28:11 UTC 2026 - Dominique Leuenberger <[email protected]>
+
+- Update to version 0.1.9:
+ + Fix message filtering bypass vulnerabilities (CVE-2026-94422,
+ GHSA-2cgv-pwcq-wvpq):
+ - Don't allow method calls and signals to be treated as
+ requested replies, even if they specify a reply serial number
+ - Only allow replies that were sent to the appropriate
+ destination
+ + Improve automated tests to include attempts to exploit
+ CVE-2026-94422
+
+-------------------------------------------------------------------
@@ -5,0 +19 @@
+ (CVE-2026-93676, GHSA-r7hp-698j-2h6c)
@@ -24 +38 @@
- + Fix CVE-2026-34080
+ (CVE-2026-34080, GHSA-vjp5-hjfm-7677)
Old:
----
xdg-dbus-proxy-0.1.8.tar.xz
New:
----
xdg-dbus-proxy-0.1.9.tar.xz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ xdg-dbus-proxy.spec ++++++
--- /var/tmp/diff_new_pack.7MTrGn/_old 2026-09-28 10:35:59.967682118 +0200
+++ /var/tmp/diff_new_pack.7MTrGn/_new 2026-09-28 10:35:59.968682160 +0200
@@ -18,7 +18,7 @@
Name: xdg-dbus-proxy
-Version: 0.1.8
+Version: 0.1.9
Release: 0
Summary: Filtering proxy for D-Bus connections
License: LGPL-2.1-or-later
++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.7MTrGn/_old 2026-09-28 10:36:00.001683543 +0200
+++ /var/tmp/diff_new_pack.7MTrGn/_new 2026-09-28 10:36:00.006683753 +0200
@@ -1,6 +1,7 @@
-mtime: 1787214355
-commit: 3ce2c8449f3e5125299f57cce14b06e0b1ff1ab847c7748a3a3c082758f1e11d
+mtime: 1790249885
+commit: a81bb3de071015979779f2ac1e28471db7ad76fb72287c3e849990b28c4058b8
url: https://src.opensuse.org/GNOME/xdg-dbus-proxy
-revision: 3ce2c8449f3e5125299f57cce14b06e0b1ff1ab847c7748a3a3c082758f1e11d
+revision: a81bb3de071015979779f2ac1e28471db7ad76fb72287c3e849990b28c4058b8
+trackingbranch: factory
projectscmsync: https://src.opensuse.org/GNOME/_ObsPrj
++++++ build.specials.obscpio ++++++
--- old/.gitignore 2026-08-20 10:25:55.000000000 +0200
+++ new/.gitignore 2026-09-24 13:38:05.000000000 +0200
@@ -2,3 +2,4 @@
*.osc
_build.*
.pbuild
+osc-collab.*
++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore 1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore 2026-09-24 13:38:05.000000000 +0200
@@ -0,0 +1,5 @@
+*.obscpio
+*.osc
+_build.*
+.pbuild
+osc-collab.*
++++++ xdg-dbus-proxy-0.1.8.tar.xz -> xdg-dbus-proxy-0.1.9.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xdg-dbus-proxy-0.1.8/NEWS
new/xdg-dbus-proxy-0.1.9/NEWS
--- old/xdg-dbus-proxy-0.1.8/NEWS 2026-08-11 14:05:09.000000000 +0200
+++ new/xdg-dbus-proxy-0.1.9/NEWS 2026-09-23 15:42:04.000000000 +0200
@@ -1,9 +1,22 @@
+Changes in 0.1.9
+================
+
+Released: 2026-09-23
+
+ * Fix message filtering bypass vulnerabilities
+ (CVE-2026-94422, GHSA-2cgv-pwcq-wvpq)
+ - Don't allow method calls and signals to be treated as requested
+ replies, even if they specify a reply serial number
+ - Only allow replies that were sent to the appropriate destination
+ * Improve automated tests to include attempts to exploit CVE-2026-94422
+
Changes in 0.1.8
================
Released 2026-08-11
* Fix broadcast messages bypassing path/interface/member checks
+ (CVE-2026-93676, GHSA-r7hp-698j-2h6c)
* Improvements to the existing testing infrastructure
* Add tests for owning names, issuing method calls, receiving messages
@@ -17,6 +30,7 @@
* Prevent a crash on disconnect
* Fix building with glibc >= 2.43
* Fix the eavesdrop filtering to prevent message interception
+ (CVE-2026-34080, GHSA-vjp5-hjfm-7677)
Changes in 0.1.6
================
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xdg-dbus-proxy-0.1.8/flatpak-proxy.c
new/xdg-dbus-proxy-0.1.9/flatpak-proxy.c
--- old/xdg-dbus-proxy-0.1.8/flatpak-proxy.c 2026-08-11 14:05:09.000000000
+0200
+++ new/xdg-dbus-proxy-0.1.9/flatpak-proxy.c 2026-09-23 15:42:04.000000000
+0200
@@ -123,9 +123,7 @@
* Once authenticated we receive incoming messages one at a time,
* and then we demarshal the message headers to make routing decisions.
* This means we trust the bus to do message format validation, etc.
- * (because we don't parse the body). Also we assume that the bus verifies
- * reply_serials, i.e. that a reply can only be sent once and by the real
- * recipient of an previously sent method call.
+ * (because we don't parse the body).
*
* Serial numbers larger than MAX_CLIENT_SERIAL reserved for messages created
by the
* proxy itself (fake messages). This limits the possible values of serials
@@ -252,7 +250,6 @@
const char *destination;
const char *sender;
const char *signature;
- gboolean has_reply_serial;
guint32 reply_serial;
guint32 unix_fds;
} Header;
@@ -281,6 +278,61 @@
static void header_free (Header *header);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (Header, header_free)
+/*
+ * A pending method call from @caller on the bus to the client,
+ * for which we expect a reply from the client back to @caller,
+ * represented as a single memory block.
+ */
+typedef struct
+{
+ guint32 serial;
+ char caller[];
+} ExpectedReplyFromClient;
+
+static ExpectedReplyFromClient *
+expected_reply_from_client_new (guint32 serial,
+ const char *caller)
+{
+ ExpectedReplyFromClient *ret;
+ size_t len;
+
+ /* A missing destination on the method call means a method call from
+ * the message bus itself (unlikely, but possible).
+ * We represent this as the empty string (which is not a valid bus name)
+ * to avoid needing a separate representation for NULL. */
+ if (caller == NULL)
+ caller = "";
+
+ len = strlen (caller) + 1;
+
+ ret = g_malloc0 (sizeof (*ret) + len);
+ ret->serial = serial;
+ memcpy (&ret->caller, caller, len);
+ return ret;
+}
+
+/* Because it's a single memory block, we can just g_free() it. */
+#define expected_reply_from_client_free g_free
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (ExpectedReplyFromClient,
expected_reply_from_client_free)
+
+static gboolean
+expected_reply_from_client_equal (const void *v1,
+ const void *v2)
+{
+ const ExpectedReplyFromClient *left = v1;
+ const ExpectedReplyFromClient *right = v2;
+
+ return left->serial == right->serial && g_str_equal (&left->caller,
&right->caller);
+}
+
+static guint
+expected_reply_from_client_hash (const void *v)
+{
+ const ExpectedReplyFromClient *self = v;
+
+ return self->serial ^ g_str_hash (&self->caller);
+}
+
typedef struct
{
gboolean got_first_byte; /* always true on bus side */
@@ -297,8 +349,6 @@
GList *buffers; /* to be sent */
GList *control_messages;
-
- GHashTable *expected_replies;
} ProxySide;
struct FlatpakProxyClient
@@ -315,6 +365,31 @@
ProxySide client_side;
ProxySide bus_side;
+ /* (element-type ExpectedReplyFromClient ExpectedReplyType)
+ * Map from message serial number and caller to expected replies.
+ * If this map contains
+ * { { serial, caller }: EXPECTED_REPLY_FOO },
+ * then it means that @caller on the bus sent a method call to the
+ * sandboxed client with SERIAL=serial.
+ * This means that in future we're expecting the sandboxed client to
+ * respond with a message back to @caller with REPLY_SERIAL=serial. */
+ GHashTable *expected_replies_from_client;
+
+ /* (element-type guint ExpectedReplyType)
+ * Map from message serial number to expected reply type.
+ * If this map contains
+ * { serial: EXPECTED_REPLY_FOO },
+ * then it means that the sandboxed client sent a method call to the
+ * bus with SERIAL=serial.
+ * This means that in future we're expecting something on the bus to
+ * respond with a message that has REPLY_SERIAL=serial.
+ * This direction is simpler than the other way round because we don't
+ * need to distinguish between destinations: we trust the bus,
+ * and there is only one possible destination on the sandboxed client
+ * side anyway.
+ */
+ GHashTable *expected_replies_from_bus;
+
/* Filtering data: */
guint32 hello_serial;
guint32 last_fake_serial;
@@ -416,8 +491,6 @@
g_source_destroy (side->in_source);
if (side->out_source)
g_source_destroy (side->out_source);
-
- g_hash_table_destroy (side->expected_replies);
}
static void
@@ -433,6 +506,8 @@
g_hash_table_destroy (client->get_owner_reply);
g_hash_table_destroy (client->unique_id_policy);
g_hash_table_destroy (client->unique_id_owned_names);
+ g_hash_table_destroy (client->expected_replies_from_bus);
+ g_hash_table_destroy (client->expected_replies_from_client);
free_side (&client->client_side);
free_side (&client->bus_side);
@@ -456,7 +531,6 @@
side->header_buffer.size = 16;
side->header_buffer.pos = 0;
side->current_read_buffer = &side->header_buffer;
- side->expected_replies = g_hash_table_new (g_direct_hash, g_direct_equal);
}
static void
@@ -471,6 +545,11 @@
client->get_owner_reply = g_hash_table_new_full (g_direct_hash,
g_direct_equal, NULL, g_free);
client->unique_id_policy = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
client->unique_id_owned_names = g_hash_table_new_full (g_str_hash,
g_str_equal, g_free, (GDestroyNotify) string_list_free);
+ client->expected_replies_from_client = g_hash_table_new_full
(expected_reply_from_client_hash,
+
expected_reply_from_client_equal,
+
expected_reply_from_client_free,
+ NULL);
+ client->expected_replies_from_bus = g_hash_table_new (g_direct_hash,
g_direct_equal);
}
static FlatpakProxyClient *
@@ -1066,27 +1145,102 @@
}
}
+/*
+ * A method call was received from the sandboxed client,
+ * with serial number @serial.
+ * We now expect a reply from the bus side, of type @type.
+ * We do not need to track the expected destination,
+ * because there is only one destination on the client side
+ * (and we don't know its name until we have delivered the Hello reply).
+ */
static void
-queue_expected_reply (ProxySide *side, guint32 serial, ExpectedReplyType type)
+queue_expected_reply_from_bus (FlatpakProxyClient *client,
+ guint32 serial,
+ ExpectedReplyType type)
{
- g_hash_table_replace (side->expected_replies,
+ g_return_if_fail (serial != 0);
+ g_return_if_fail (type != EXPECTED_REPLY_NONE);
+
+ g_hash_table_replace (client->expected_replies_from_bus,
GUINT_TO_POINTER (serial),
GUINT_TO_POINTER (type));
}
+/*
+ * A method call was received from sender @caller on the bus,
+ * with serial number @serial.
+ * We now expect a reply from the sandboxed client of type @type.
+ */
+static void
+queue_expected_reply_from_client (FlatpakProxyClient *client,
+ guint32 serial,
+ const char *caller,
+ ExpectedReplyType type)
+{
+ g_return_if_fail (serial != 0);
+ g_return_if_fail (caller == NULL || caller[0] != '\0');
+ g_return_if_fail (type != EXPECTED_REPLY_NONE);
+
+ g_hash_table_replace (client->expected_replies_from_client,
+ expected_reply_from_client_new (serial, caller),
+ GUINT_TO_POINTER (type));
+}
+
+
+/*
+ * @client: The client
+ * @serial: The reply_serial field of the reply
+ *
+ * Returns: The type of the matching reply, or %EXPECTED_REPLY_NONE
+ * if no match was found.
+ */
static ExpectedReplyType
-steal_expected_reply (ProxySide *side, guint32 serial)
+steal_expected_reply_from_bus (FlatpakProxyClient *client,
+ guint32 serial)
{
ExpectedReplyType type;
- type = GPOINTER_TO_UINT (g_hash_table_lookup (side->expected_replies,
+ g_return_val_if_fail (serial != 0, EXPECTED_REPLY_NONE);
+
+ type = GPOINTER_TO_UINT (g_hash_table_lookup
(client->expected_replies_from_bus,
GUINT_TO_POINTER (serial)));
if (type)
- g_hash_table_remove (side->expected_replies,
+ g_hash_table_remove (client->expected_replies_from_bus,
GUINT_TO_POINTER (serial));
return type;
}
+/*
+ * @client: The client
+ * @serial: The reply_serial field of the reply
+ * @caller: Only match pending method calls that came from @caller
+ * (and therefore the reply should be sent to @caller).
+ *
+ * Returns: The type of the matching reply, or %EXPECTED_REPLY_NONE
+ * if no match was found.
+ */
+static ExpectedReplyType
+steal_expected_reply_from_client (FlatpakProxyClient *client,
+ guint32 serial,
+ const char *caller)
+{
+ g_autoptr(ExpectedReplyFromClient) key = NULL;
+ void *type;
+
+ g_return_val_if_fail (serial != 0, EXPECTED_REPLY_NONE);
+ g_return_val_if_fail (caller == NULL || caller[0] != '\0',
EXPECTED_REPLY_NONE);
+
+ key = expected_reply_from_client_new (serial, caller);
+
+ if (g_hash_table_lookup_extended (client->expected_replies_from_client, key,
+ NULL, &type))
+ {
+ g_hash_table_remove (client->expected_replies_from_client, key);
+ return GPOINTER_TO_UINT (type);
+ }
+
+ return EXPECTED_REPLY_NONE;
+}
static void
queue_outgoing_buffer (ProxySide *side, Buffer *buffer)
@@ -1221,6 +1375,22 @@
return s->str;
}
+static gboolean
+is_reply (Header *header)
+{
+ switch (header->type)
+ {
+ case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ case G_DBUS_MESSAGE_TYPE_ERROR:
+ return TRUE;
+
+ case G_DBUS_MESSAGE_TYPE_METHOD_CALL:
+ case G_DBUS_MESSAGE_TYPE_SIGNAL:
+ default:
+ return FALSE;
+ }
+}
+
static Header *
parse_header (Buffer *buffer, GError **error)
{
@@ -1228,6 +1398,7 @@
guint32 offset, end_offset;
guint8 header_type;
const char *signature;
+ gboolean has_reply_serial = FALSE;
g_autoptr(GError) str_error = NULL;
g_autoptr(GString) header_str = NULL;
@@ -1452,7 +1623,7 @@
return NULL;
}
- header->has_reply_serial = TRUE;
+ has_reply_serial = TRUE;
header->reply_serial = read_uint32 (header, &buffer->data[offset]);
offset += 4;
break;
@@ -1570,7 +1741,7 @@
break;
case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
- if (!header->has_reply_serial)
+ if (!has_reply_serial)
{
g_set_error (error,
G_IO_ERROR,
@@ -1582,7 +1753,7 @@
break;
case G_DBUS_MESSAGE_TYPE_ERROR:
- if (header->error_name == NULL || !header->has_reply_serial)
+ if (header->error_name == NULL || !has_reply_serial)
{
g_set_error (error,
G_IO_ERROR,
@@ -1628,6 +1799,12 @@
return NULL;
}
+ /* Invariant: every reply has a reply serial.
+ * (Note that the converse is not true: it is technically possible to
+ * send a method call or signal that claims to be a reply.) */
+ if (is_reply (header))
+ g_assert (has_reply_serial);
+
return g_steal_pointer (&header);
}
@@ -1972,6 +2149,9 @@
g_strcmp0 (header->interface, "org.freedesktop.DBus.Introspectable") == 0;
}
+/*
+ * @header: an outgoing message from the sandboxed client to the bus
+ */
static BusHandler
get_dbus_method_handler (FlatpakProxyClient *client, Header *header)
{
@@ -1980,11 +2160,17 @@
g_autoptr(GList) filters = NULL;
- if (header->has_reply_serial)
+ /* If the message claims to be a reply to a method call with serial number n,
+ * we allow if and only if there is indeed a pending method call from
+ * the reply's destination, with serial number n, and it has not yet had
+ * its reply */
+ if (is_reply (header))
{
ExpectedReplyType expected_reply =
- steal_expected_reply (&client->bus_side,
- header->reply_serial);
+ steal_expected_reply_from_client (client,
+ header->reply_serial,
+ header->destination);
+
if (expected_reply == EXPECTED_REPLY_NONE)
return HANDLE_DENY;
@@ -2348,7 +2534,7 @@
g_object_unref (message);
queue_outgoing_buffer (&client->bus_side, buffer);
- queue_expected_reply (&client->client_side, client->last_fake_serial,
reply_type);
+ queue_expected_reply_from_bus (client, client->last_fake_serial, reply_type);
}
/* After the first Hello message we need to synthesize a bunch of messages to
synchronize the
@@ -2473,12 +2659,17 @@
}
}
-
+/*
+ * Called when we received @buffer from the client side of @client:
+ * it's an outgoing message from the sandboxed client to the bus.
+ */
static void
got_buffer_from_client (FlatpakProxyClient *client, ProxySide *side, Buffer
*buffer)
{
ExpectedReplyType expecting_reply = EXPECTED_REPLY_NONE;
+ g_assert (side == &client->client_side);
+
if (client->auth_state == AUTH_COMPLETE && client->proxy->filter)
{
g_autoptr(Header) header = NULL;
@@ -2492,18 +2683,18 @@
{
g_warning ("Invalid message header format from client: %s",
error->message);
- side_closed (side);
+ side_closed (&client->client_side);
buffer_unref (buffer);
return;
}
- if (!update_socket_messages (side, buffer, header))
+ if (!update_socket_messages (&client->client_side, buffer, header))
return;
if (header->serial > MAX_CLIENT_SERIAL)
{
g_warning ("Invalid client serial: Exceeds maximum value of %u",
MAX_CLIENT_SERIAL);
- side_closed (side);
+ side_closed (&client->client_side);
buffer_unref (buffer);
return;
}
@@ -2633,7 +2824,7 @@
}
if (buffer != NULL && expecting_reply != EXPECTED_REPLY_NONE)
- queue_expected_reply (side, header->serial, expecting_reply);
+ queue_expected_reply_from_bus (client, header->serial,
expecting_reply);
}
if (buffer)
@@ -2643,9 +2834,15 @@
queue_initial_name_ops (client);
}
+/*
+ * Called when we received @buffer from the bus side of @client:
+ * it's an incoming message from the bus to the sandboxed client.
+ */
static void
got_buffer_from_bus (FlatpakProxyClient *client, ProxySide *side, Buffer
*buffer)
{
+ g_assert (side == &client->bus_side);
+
if (client->auth_state == AUTH_COMPLETE && client->proxy->filter)
{
g_autoptr(Header) header = NULL;
@@ -2662,19 +2859,26 @@
g_warning ("Invalid message header format from bus: %s",
error->message);
buffer_unref (buffer);
- side_closed (side);
+ side_closed (&client->bus_side);
return;
}
- if (!update_socket_messages (side, buffer, header))
+ if (!update_socket_messages (&client->bus_side, buffer, header))
return;
if (client->proxy->log_messages)
print_incoming_header (header);
- if (header->has_reply_serial)
+ if (is_reply (header))
{
- expected_reply = steal_expected_reply (get_other_side (side),
header->reply_serial);
+ /* If the client previously made a method call out to the bus
+ * with serial number n, we allow one reply to come from
+ * the bus "in reply to: n" back to the client.
+ * We allow any destination - this is OK, because we trust
+ * the bus, and in any case there is only one valid destination
+ * on the client side (it's the sandboxed client). */
+ expected_reply = steal_expected_reply_from_bus (client,
+
header->reply_serial);
switch (expected_reply)
{
@@ -2781,15 +2985,8 @@
}
else /* Not reply */
{
-
- /* Don't allow reply types with no reply_serial */
- if (header->type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN ||
- header->type == G_DBUS_MESSAGE_TYPE_ERROR)
- {
- if (client->proxy->log_messages)
- g_print ("*Invalid reply*\n");
- g_clear_pointer (&buffer, buffer_unref);
- }
+ g_assert (header->type != G_DBUS_MESSAGE_TYPE_METHOD_RETURN);
+ g_assert (header->type != G_DBUS_MESSAGE_TYPE_ERROR);
/* We filter all NameOwnerChanged signal according to the policy */
if (message_is_name_owner_changed (client, header))
@@ -2828,8 +3025,10 @@
if (buffer && header->sender && header->sender[0] == ':')
flatpak_proxy_client_update_unique_id_policy (client, header->sender,
FLATPAK_POLICY_SEE);
+ /* Remember that the sandboxed client is allowed to send a single
+ * reply back to header->sender, marked "in reply to" header->serial. */
if (buffer && client_message_generates_reply (header))
- queue_expected_reply (side, header->serial, EXPECTED_REPLY_NORMAL);
+ queue_expected_reply_from_client (client, header->serial,
header->sender, EXPECTED_REPLY_NORMAL);
}
if (buffer)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xdg-dbus-proxy-0.1.8/meson.build
new/xdg-dbus-proxy-0.1.9/meson.build
--- old/xdg-dbus-proxy-0.1.8/meson.build 2026-08-11 14:05:09.000000000
+0200
+++ new/xdg-dbus-proxy-0.1.9/meson.build 2026-09-23 15:42:04.000000000
+0200
@@ -1,7 +1,7 @@
project(
'xdg-dbus-proxy',
'c',
- version : '0.1.8',
+ version : '0.1.9',
meson_version : '>=0.49.0',
default_options : [
'warning_level=2',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xdg-dbus-proxy-0.1.8/tests/test-proxy.c
new/xdg-dbus-proxy-0.1.9/tests/test-proxy.c
--- old/xdg-dbus-proxy-0.1.8/tests/test-proxy.c 2026-08-11 14:05:09.000000000
+0200
+++ new/xdg-dbus-proxy-0.1.9/tests/test-proxy.c 2026-09-23 15:42:04.000000000
+0200
@@ -40,6 +40,7 @@
#define DBUS_REQUEST_NAME_REPLY_EXISTS 3
#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4
+#define CALLER_NAME "com.example.Caller"
#define CANNOT_ACCESS_NAME "com.example.CannotAccess"
#define CAN_SEE_NAME "com.example.CanSee"
#define CAN_TALK_NAME "com.example.CanTalk"
@@ -60,6 +61,13 @@
#define CAN_RECEIVE_SOME_SIGNAL "JustThisSignal"
#define CAN_RECEIVE_SOME_PATH "/just/this/path"
+#define IGNORE_IFACE "com.example.Ignore"
+#define IGNORE_METHOD "Ignore"
+
+#define MALICIOUS_IFACE "com.example.Malice"
+#define MALICIOUS_MEMBER "ShouldNotBeAllowed"
+#define MALICIOUS_BODY "Message containing this body should have been blocked"
+
static void
ready_cb (GObject *source_object,
GAsyncResult *result,
@@ -109,6 +117,7 @@
typedef struct
{
Connection proxied;
+ Connection caller_conn;
Connection cannot_access_conn;
Connection can_see_conn;
Connection can_talk_conn;
@@ -167,6 +176,16 @@
case G_DBUS_MESSAGE_TYPE_METHOD_CALL:
g_test_message ("%s got method call", conn_info->label);
g_atomic_int_inc (&conn_info->n_method_calls);
+
+ if (g_strcmp0 (iface, IGNORE_IFACE) == 0 &&
+ g_strcmp0 (member, IGNORE_METHOD) == 0)
+ {
+ g_test_message ("method call is %s.%s, ignoring",
+ iface, member);
+ g_clear_object (&message);
+ return NULL;
+ }
+
break;
case G_DBUS_MESSAGE_TYPE_SIGNAL:
@@ -188,11 +207,22 @@
break;
case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
- g_test_message ("%s got method reply", conn_info->label);
- break;
-
case G_DBUS_MESSAGE_TYPE_ERROR:
- g_test_message ("%s got error reply", conn_info->label);
+ if (type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN)
+ g_test_message ("%s got method reply", conn_info->label);
+ else
+ g_test_message ("%s got error reply", conn_info->label);
+
+ if (g_str_equal (g_dbus_message_get_signature (message), "s"))
+ {
+ const char *s = NULL;
+
+ g_variant_get (g_dbus_message_get_body (message), "(&s)", &s);
+
+ if (g_str_equal (s, MALICIOUS_BODY))
+ g_error ("Forged reply received");
+ }
+
break;
case G_DBUS_MESSAGE_TYPE_INVALID:
@@ -269,6 +299,51 @@
}
static void
+do_round_trip (const Connection *caller,
+ const Connection *destination)
+{
+ g_autoptr(GAsyncResult) result = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) tuple = NULL;
+
+ g_dbus_connection_call (caller->conn,
+ destination->unique_name,
+ "/",
+ EXAMPLE_IFACE,
+ EXAMPLE_METHOD,
+ NULL,
+ G_VARIANT_TYPE ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, /* timeout */
+ NULL, /* cancellable */
+ ready_cb,
+ &result);
+
+ while (result == NULL)
+ g_main_context_iteration (NULL, TRUE);
+
+ tuple = g_dbus_connection_call_finish (caller->conn, result, &error);
+
+ /* For simplicity we didn't actually implement any method calls,
+ * so the result should be an error. */
+ g_assert_nonnull (error);
+ g_assert_null (tuple);
+ g_assert_cmpstr (g_quark_to_string (error->domain), ==, g_quark_to_string
(G_DBUS_ERROR));
+
+ switch (error->code)
+ {
+ case G_DBUS_ERROR_UNKNOWN_METHOD:
+ case G_DBUS_ERROR_UNKNOWN_INTERFACE:
+ case G_DBUS_ERROR_UNKNOWN_OBJECT:
+ /* OK */
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
setup (Fixture *f,
gconstpointer context G_GNUC_UNUSED)
{
@@ -346,6 +421,7 @@
g_free,
NULL);
+ fixture_connect (f, &f->caller_conn, CALLER_NAME);
fixture_connect (f, &f->cannot_access_conn, CANNOT_ACCESS_NAME);
fixture_connect (f, &f->can_see_conn, CAN_SEE_NAME);
fixture_connect (f, &f->can_talk_conn, CAN_TALK_NAME);
@@ -824,6 +900,200 @@
}
}
+typedef struct
+{
+ const char *label;
+ GDBusMessageType type;
+ unsigned broadcast : 1;
+ unsigned misdirected : 1;
+} ReplyTest;
+
+static const ReplyTest reply_tests[] =
+{
+ { "should not be able to send a unicast signal in reply",
+ G_DBUS_MESSAGE_TYPE_SIGNAL },
+ { "should not be able to send a broadcast signal in reply",
+ G_DBUS_MESSAGE_TYPE_SIGNAL, .broadcast = TRUE },
+ { "should not be able to send a unicast signal to someone else in reply",
+ G_DBUS_MESSAGE_TYPE_SIGNAL, .misdirected = TRUE },
+ { "should not be able to send a method call in reply",
+ G_DBUS_MESSAGE_TYPE_METHOD_CALL },
+ { "should not be able to send a method call to someone else in reply",
+ G_DBUS_MESSAGE_TYPE_METHOD_CALL, .misdirected = TRUE },
+ { "should be able to send a method return in reply",
+ G_DBUS_MESSAGE_TYPE_METHOD_RETURN },
+ { "should not be able to send a method return to someone else in reply",
+ G_DBUS_MESSAGE_TYPE_METHOD_RETURN, .misdirected = TRUE },
+ { "should be able to send an error in reply",
+ G_DBUS_MESSAGE_TYPE_ERROR },
+ { "should not be able to send an error to someone else in reply",
+ G_DBUS_MESSAGE_TYPE_ERROR, .misdirected = TRUE },
+};
+
+static void
+test_reply (Fixture *f,
+ gconstpointer context G_GNUC_UNUSED)
+{
+ alarm (30);
+ fixture_start_proxy (f);
+
+ /* If a process outside the sandbox calls a method on the
+ * sandboxed process, then the sandboxed process is allowed to reply,
+ * but is not allowed to fake a "reply" that is really a method call
+ * or signal. (GHSA-2cgv-pwcq-wvpq) */
+ for (size_t i = 0; i < G_N_ELEMENTS (reply_tests); i++)
+ {
+ const ReplyTest *t = &reply_tests[i];
+ g_autoptr(GDBusMessage) call = NULL;
+ g_autoptr(GDBusMessage) reply = NULL;
+ g_autoptr(GError) error = NULL;
+ guint32 call_serial = 0;
+ int caller_calls_before;
+ int caller_broadcast_before;
+ int caller_unicast_before;
+ int other_calls_before;
+ int other_broadcast_before;
+ int other_unicast_before;
+
+ g_test_message ("#%zu: %s", i, t->label);
+
+ caller_calls_before = g_atomic_int_get (&f->caller_conn.n_method_calls);
+ caller_unicast_before = g_atomic_int_get
(&f->caller_conn.n_unicast_signals);
+ caller_broadcast_before = g_atomic_int_get
(&f->caller_conn.n_broadcasts);
+
+ other_calls_before = g_atomic_int_get
(&f->cannot_access_conn.n_method_calls);
+ other_unicast_before = g_atomic_int_get
(&f->cannot_access_conn.n_unicast_signals);
+ other_broadcast_before = g_atomic_int_get
(&f->cannot_access_conn.n_broadcasts);
+
+ call = g_dbus_message_new_method_call (f->proxied.unique_name,
+ "/",
+ IGNORE_IFACE,
+ IGNORE_METHOD);
+ g_dbus_connection_send_message (f->caller_conn.conn,
+ call,
+ G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+ &call_serial,
+ &error);
+ g_assert_no_error (error);
+ g_assert_cmpuint (call_serial, !=, 0);
+ g_test_message ("Method call was serial number %u", call_serial);
+
+ /* Do a round-trip from the caller to the sandboxed connection and back.
+ * D-Bus messages are delivered sequentially, so by the time this call
+ * has finished, the method call will also have passed through the
+ * xdg-dbus-proxy and the dbus-daemon. */
+ do_round_trip (&f->caller_conn, &f->proxied);
+
+ switch (t->type)
+ {
+ case G_DBUS_MESSAGE_TYPE_SIGNAL:
+ reply = g_dbus_message_new_signal ("/",
+ MALICIOUS_IFACE,
+ MALICIOUS_MEMBER);
+
+ if (t->broadcast)
+ {
+ g_test_message ("Sending malicious broadcast signal as a
reply");
+ }
+ else if (t->misdirected)
+ {
+ g_test_message ("Sending malicious unicast signal reply to
wrong destination");
+ g_dbus_message_set_destination (reply,
f->cannot_access_conn.unique_name);
+ }
+ else
+ {
+ g_test_message ("Sending malicious unicast signal as a reply");
+ g_dbus_message_set_destination (reply,
f->caller_conn.unique_name);
+ }
+
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_METHOD_CALL:
+ if (t->misdirected)
+ {
+ g_test_message ("Sending malicious method call to wrong
destination");
+ reply = g_dbus_message_new_method_call
(f->cannot_access_conn.unique_name,
+ "/",
+ MALICIOUS_IFACE,
+ MALICIOUS_MEMBER);
+ }
+ else
+ {
+ g_test_message ("Sending malicious method call as a reply");
+ reply = g_dbus_message_new_method_call
(f->caller_conn.unique_name,
+ "/",
+ MALICIOUS_IFACE,
+ MALICIOUS_MEMBER);
+ }
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ reply = g_dbus_message_new_method_reply (call);
+
+ if (t->misdirected)
+ {
+ g_test_message ("Sending malicious reply to wrong
destination");
+ g_dbus_message_set_destination (reply,
f->cannot_access_conn.unique_name);
+ g_dbus_message_set_body (reply, g_variant_new ("(s)",
MALICIOUS_BODY));
+ }
+ else
+ {
+ g_test_message ("Sending legitimate reply as a reply");
+ }
+
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_ERROR:
+ reply = g_dbus_message_new_method_error (call,
+ "com.example.No",
+ "That didn't work");
+
+ if (t->misdirected)
+ {
+ g_test_message ("Sending malicious error to wrong
destination");
+ g_dbus_message_set_destination (reply,
f->cannot_access_conn.unique_name);
+ g_dbus_message_set_body (reply, g_variant_new ("(s)",
MALICIOUS_BODY));
+ }
+ else
+ {
+ g_test_message ("Sending legitimate error as a reply");
+ }
+
+ break;
+
+ case G_DBUS_MESSAGE_TYPE_INVALID:
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_dbus_message_set_reply_serial (reply, call_serial);
+ g_dbus_connection_send_message (f->proxied.conn, reply,
+ G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+ NULL, /* serial */
+ &error);
+ g_assert_no_error (error);
+
+ /* Do another round-trip, to make sure everything has been delivered */
+ do_round_trip (&f->caller_conn, &f->proxied);
+
+ /* The caller didn't receive any extraneous messages */
+ g_assert_cmpint (g_atomic_int_get (&f->caller_conn.n_method_calls), ==,
+ caller_calls_before);
+ g_assert_cmpint (g_atomic_int_get (&f->caller_conn.n_unicast_signals),
==,
+ caller_unicast_before);
+ g_assert_cmpint (g_atomic_int_get (&f->caller_conn.n_broadcasts), ==,
+ caller_broadcast_before);
+
+ /* The other connection didn't receive any messages at all */
+ g_assert_cmpint (g_atomic_int_get
(&f->cannot_access_conn.n_method_calls), ==,
+ other_calls_before);
+ g_assert_cmpint (g_atomic_int_get
(&f->cannot_access_conn.n_unicast_signals), ==,
+ other_unicast_before);
+ g_assert_cmpint (g_atomic_int_get (&f->cannot_access_conn.n_broadcasts),
==,
+ other_broadcast_before);
+ }
+}
+
static void
teardown (Fixture *f,
gconstpointer context G_GNUC_UNUSED)
@@ -895,6 +1165,7 @@
g_test_add ("/call", Fixture, NULL, setup, test_call, teardown);
g_test_add ("/own", Fixture, NULL, setup, test_own, teardown);
g_test_add ("/receive", Fixture, NULL, setup, test_receive, teardown);
+ g_test_add ("/reply", Fixture, NULL, setup, test_reply, teardown);
return g_test_run ();
}