Files
glib2/glib2-CVE-2024-34397.patch

2557 lines
87 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

diff -urpN glib-2.76.2.orig/gio/gdbusconnection.c glib-2.76.2/gio/gdbusconnection.c
--- glib-2.76.2.orig/gio/gdbusconnection.c 2023-04-21 09:46:05.000000000 -0500
+++ glib-2.76.2/gio/gdbusconnection.c 2024-08-19 14:24:48.794374561 -0500
@@ -284,6 +284,153 @@ call_destroy_notify (GMainContext *cont
/* ---------------------------------------------------------------------------------------------------- */
+typedef struct
+{
+ /* All fields are immutable after construction. */
+ gatomicrefcount ref_count;
+ GDBusSignalCallback callback;
+ gpointer user_data;
+ GDestroyNotify user_data_free_func;
+ guint id;
+ GMainContext *context;
+} SignalSubscriber;
+
+static SignalSubscriber *
+signal_subscriber_ref (SignalSubscriber *subscriber)
+{
+ g_atomic_ref_count_inc (&subscriber->ref_count);
+ return subscriber;
+}
+
+static void
+signal_subscriber_unref (SignalSubscriber *subscriber)
+{
+ if (g_atomic_ref_count_dec (&subscriber->ref_count))
+ {
+ /* Destroy the user data. It doesnt matter which thread
+ * signal_subscriber_unref() is called in (or whether its called with a
+ * lock held), as call_destroy_notify() always defers to the next
+ * #GMainContext iteration. */
+ call_destroy_notify (subscriber->context,
+ subscriber->user_data_free_func,
+ subscriber->user_data);
+
+ g_main_context_unref (subscriber->context);
+ g_free (subscriber);
+ }
+}
+
+typedef struct
+{
+ /*
+ * 1 reference while waiting for GetNameOwner() to finish
+ * 1 reference for each SignalData that points to this one as its
+ * shared_name_watcher
+ */
+ grefcount ref_count;
+
+ gchar *owner;
+ guint32 get_name_owner_serial;
+} WatchedName;
+
+static WatchedName *
+watched_name_new (void)
+{
+ WatchedName *watched_name = g_new0 (WatchedName, 1);
+
+ g_ref_count_init (&watched_name->ref_count);
+ watched_name->owner = NULL;
+ return g_steal_pointer (&watched_name);
+}
+
+typedef struct SignalData SignalData;
+
+struct SignalData
+{
+ gchar *rule;
+ gchar *sender;
+ gchar *interface_name;
+ gchar *member;
+ gchar *object_path;
+ gchar *arg0;
+ GDBusSignalFlags flags;
+ GPtrArray *subscribers; /* (owned) (element-type SignalSubscriber) */
+
+ /*
+ * If the sender is a well-known name, this is an unowned SignalData
+ * representing the NameOwnerChanged signal that tracks its owner.
+ * NULL if sender is NULL.
+ * NULL if sender is its own owner (a unique name or DBUS_SERVICE_DBUS).
+ *
+ * Invariants: if not NULL, then
+ * shared_name_watcher->sender == DBUS_SERVICE_DBUS
+ * shared_name_watcher->interface_name == DBUS_INTERFACE_DBUS
+ * shared_name_watcher->member == "NameOwnerChanged"
+ * shared_name_watcher->object_path == DBUS_PATH_DBUS
+ * shared_name_watcher->arg0 == sender
+ * shared_name_watcher->flags == NONE
+ * shared_name_watcher->watched_name == NULL
+ */
+ SignalData *shared_name_watcher;
+
+ /*
+ * Non-NULL if this SignalData is another SignalData's shared_name_watcher.
+ * One reference for each SignalData that has this one as its
+ * shared_name_watcher.
+ * Otherwise NULL.
+ */
+ WatchedName *watched_name;
+};
+
+static SignalData *
+signal_data_new_take (gchar *rule,
+ gchar *sender,
+ gchar *interface_name,
+ gchar *member,
+ gchar *object_path,
+ gchar *arg0,
+ GDBusSignalFlags flags)
+{
+ SignalData *signal_data = g_new0 (SignalData, 1);
+
+ signal_data->rule = rule;
+ signal_data->sender = sender;
+ signal_data->interface_name = interface_name;
+ signal_data->member = member;
+ signal_data->object_path = object_path;
+ signal_data->arg0 = arg0;
+ signal_data->flags = flags;
+ signal_data->subscribers = g_ptr_array_new_with_free_func ((GDestroyNotify) signal_subscriber_unref);
+ return g_steal_pointer (&signal_data);
+}
+
+static void
+signal_data_free (SignalData *signal_data)
+{
+ /* The SignalData should not be freed while it still has subscribers */
+ g_assert (signal_data->subscribers->len == 0);
+
+ /* The SignalData should not be freed while it is watching for
+ * NameOwnerChanged on behalf of another SignalData */
+ g_assert (signal_data->watched_name == NULL);
+
+ /* The SignalData should be detached from its name watcher, if any,
+ * before it is freed */
+ g_assert (signal_data->shared_name_watcher == NULL);
+
+ g_free (signal_data->rule);
+ g_free (signal_data->sender);
+ g_free (signal_data->interface_name);
+ g_free (signal_data->member);
+ g_free (signal_data->object_path);
+ g_free (signal_data->arg0);
+ g_ptr_array_unref (signal_data->subscribers);
+
+ g_free (signal_data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
#ifdef G_OS_WIN32
#define CONNECTION_ENSURE_LOCK(obj) do { ; } while (FALSE)
#else
@@ -410,6 +557,7 @@ struct _GDBusConnection
/* Map used for managing method replies, protected by @lock */
GHashTable *map_method_serial_to_task; /* guint32 -> GTask* */
+ GHashTable *map_method_serial_to_name_watcher; /* guint32 -> unowned SignalData* */
/* Maps used for managing signal subscription, protected by @lock */
GHashTable *map_rule_to_signal_data; /* match rule (gchar*) -> SignalData */
@@ -658,6 +806,7 @@ g_dbus_connection_finalize (GObject *obj
g_error_free (connection->initialization_error);
g_hash_table_unref (connection->map_method_serial_to_task);
+ g_hash_table_unref (connection->map_method_serial_to_name_watcher);
g_hash_table_unref (connection->map_rule_to_signal_data);
g_hash_table_unref (connection->map_id_to_signal_data);
@@ -1062,6 +1211,7 @@ g_dbus_connection_init (GDBusConnection
g_mutex_init (&connection->init_lock);
connection->map_method_serial_to_task = g_hash_table_new (g_direct_hash, g_direct_equal);
+ connection->map_method_serial_to_name_watcher = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
connection->map_rule_to_signal_data = g_hash_table_new (g_str_hash,
g_str_equal);
@@ -2188,6 +2338,191 @@ g_dbus_connection_send_message_with_repl
/* ---------------------------------------------------------------------------------------------------- */
+/*
+ * Called in any thread.
+ * Must hold the connection lock when calling this, unless
+ * connection->finalizing is TRUE.
+ */
+static void
+name_watcher_unref_watched_name (GDBusConnection *connection,
+ SignalData *name_watcher)
+{
+ WatchedName *watched_name = name_watcher->watched_name;
+
+ g_assert (watched_name != NULL);
+
+ if (!g_ref_count_dec (&watched_name->ref_count))
+ return;
+
+ /* Removing watched_name from the name_watcher may result in
+ * name_watcher being freed, so we must make sure name_watcher is no
+ * longer in map_method_serial_to_name_watcher.
+ *
+ * If we stop watching the name while our GetNameOwner call was still
+ * in-flight, then when the reply eventually arrives, we will not find
+ * its serial number in the map and harmlessly ignore it as a result. */
+ if (watched_name->get_name_owner_serial != 0)
+ g_hash_table_remove (connection->map_method_serial_to_name_watcher,
+ GUINT_TO_POINTER (watched_name->get_name_owner_serial));
+
+ name_watcher->watched_name = NULL;
+ g_free (watched_name->owner);
+ g_free (watched_name);
+}
+
+/* called in GDBusWorker thread with lock held */
+static void
+name_watcher_set_name_owner_unlocked (SignalData *name_watcher,
+ const char *new_owner)
+{
+ if (new_owner != NULL && new_owner[0] == '\0')
+ new_owner = NULL;
+
+ g_assert (name_watcher->watched_name != NULL);
+ g_set_str (&name_watcher->watched_name->owner, new_owner);
+}
+
+/* called in GDBusWorker thread with lock held */
+static void
+name_watcher_deliver_name_owner_changed_unlocked (SignalData *name_watcher,
+ GDBusMessage *message)
+{
+ GVariant *body;
+
+ body = g_dbus_message_get_body (message);
+
+ if (G_LIKELY (body != NULL && g_variant_is_of_type (body, G_VARIANT_TYPE ("(sss)"))))
+ {
+ const char *name;
+ const char *new_owner;
+
+ g_variant_get (body, "(&s&s&s)", &name, NULL, &new_owner);
+
+ /* Our caller already checked this */
+ g_assert (g_strcmp0 (name_watcher->arg0, name) == 0);
+
+ if (G_LIKELY (new_owner[0] == '\0' || g_dbus_is_unique_name (new_owner)))
+ name_watcher_set_name_owner_unlocked (name_watcher, new_owner);
+ else
+ g_warning ("Received NameOwnerChanged signal with invalid owner \"%s\" for \"%s\"",
+ new_owner, name);
+ }
+ else
+ {
+ g_warning ("Received NameOwnerChanged signal with unexpected "
+ "signature %s",
+ body == NULL ? "()" : g_variant_get_type_string (body));
+
+ }
+}
+
+/* called in GDBusWorker thread with lock held */
+static void
+name_watcher_deliver_get_name_owner_reply_unlocked (SignalData *name_watcher,
+ GDBusConnection *connection,
+ GDBusMessage *message)
+{
+ GDBusMessageType type;
+ GVariant *body;
+ WatchedName *watched_name;
+
+ watched_name = name_watcher->watched_name;
+ g_assert (watched_name != NULL);
+ g_assert (watched_name->get_name_owner_serial != 0);
+
+ type = g_dbus_message_get_message_type (message);
+ body = g_dbus_message_get_body (message);
+
+ if (type == G_DBUS_MESSAGE_TYPE_ERROR)
+ {
+ if (g_strcmp0 (g_dbus_message_get_error_name (message),
+ "org.freedesktop.DBus.Error.NameHasNoOwner"))
+ name_watcher_set_name_owner_unlocked (name_watcher, NULL);
+ /* else it's something like NoReply or AccessDenied, which tells
+ * us nothing - leave the owner set to whatever we most recently
+ * learned from NameOwnerChanged, or NULL */
+ }
+ else if (type != G_DBUS_MESSAGE_TYPE_METHOD_RETURN)
+ {
+ g_warning ("Received GetNameOwner reply with unexpected type %d",
+ type);
+ }
+ else if (G_LIKELY (body != NULL && g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)"))))
+ {
+ const char *new_owner;
+
+ g_variant_get (body, "(&s)", &new_owner);
+
+ if (G_LIKELY (g_dbus_is_unique_name (new_owner)))
+ name_watcher_set_name_owner_unlocked (name_watcher, new_owner);
+ else
+ g_warning ("Received GetNameOwner reply with invalid owner \"%s\" for \"%s\"",
+ new_owner, name_watcher->arg0);
+ }
+ else
+ {
+ g_warning ("Received GetNameOwner reply with unexpected signature %s",
+ body == NULL ? "()" : g_variant_get_type_string (body));
+ }
+
+ g_hash_table_remove (connection->map_method_serial_to_name_watcher,
+ GUINT_TO_POINTER (watched_name->get_name_owner_serial));
+ watched_name->get_name_owner_serial = 0;
+}
+
+/* Called in a user thread, lock is held */
+static void
+name_watcher_call_get_name_owner_unlocked (GDBusConnection *connection,
+ SignalData *name_watcher)
+{
+ GDBusMessage *message;
+ GError *local_error = NULL;
+ WatchedName *watched_name;
+
+ g_assert (g_strcmp0 (name_watcher->sender, DBUS_SERVICE_DBUS) == 0);
+ g_assert (g_strcmp0 (name_watcher->interface_name, DBUS_INTERFACE_DBUS) == 0);
+ g_assert (g_strcmp0 (name_watcher->member, "NameOwnerChanged") == 0);
+ g_assert (g_strcmp0 (name_watcher->object_path, DBUS_PATH_DBUS) == 0);
+ /* arg0 of the NameOwnerChanged message is the well-known name whose owner
+ * we are interested in */
+ g_assert (g_dbus_is_name (name_watcher->arg0));
+ g_assert (name_watcher->flags == G_DBUS_SIGNAL_FLAGS_NONE);
+
+ watched_name = name_watcher->watched_name;
+ g_assert (watched_name != NULL);
+ g_assert (watched_name->owner == NULL);
+ g_assert (watched_name->get_name_owner_serial == 0);
+ g_assert (name_watcher->shared_name_watcher == NULL);
+
+ message = g_dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "GetNameOwner");
+ g_dbus_message_set_body (message, g_variant_new ("(s)", name_watcher->arg0));
+
+ if (g_dbus_connection_send_message_unlocked (connection, message,
+ G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+ &watched_name->get_name_owner_serial,
+ &local_error))
+ {
+ g_assert (watched_name->get_name_owner_serial != 0);
+ g_hash_table_insert (connection->map_method_serial_to_name_watcher,
+ GUINT_TO_POINTER (watched_name->get_name_owner_serial),
+ name_watcher);
+ }
+ else
+ {
+ g_critical ("Error while sending GetNameOwner() message: %s",
+ local_error->message);
+ g_clear_error (&local_error);
+ g_assert (watched_name->get_name_owner_serial == 0);
+ }
+
+ g_object_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
typedef struct
{
guint id;
@@ -2311,6 +2646,7 @@ on_worker_message_received (GDBusWorker
{
guint32 reply_serial;
GTask *task;
+ SignalData *name_watcher;
reply_serial = g_dbus_message_get_reply_serial (message);
CONNECTION_LOCK (connection);
@@ -2326,6 +2662,19 @@ on_worker_message_received (GDBusWorker
{
//g_debug ("message reply/error for serial %d but no SendMessageData found for %p", reply_serial, connection);
}
+
+ name_watcher = g_hash_table_lookup (connection->map_method_serial_to_name_watcher,
+ GUINT_TO_POINTER (reply_serial));
+
+ if (name_watcher != NULL)
+ {
+ g_assert (name_watcher->watched_name != NULL);
+ g_assert (name_watcher->watched_name->get_name_owner_serial == reply_serial);
+ name_watcher_deliver_get_name_owner_reply_unlocked (name_watcher,
+ connection,
+ message);
+ }
+
CONNECTION_UNLOCK (connection);
}
else if (message_type == G_DBUS_MESSAGE_TYPE_SIGNAL)
@@ -3249,69 +3598,6 @@ g_dbus_connection_remove_filter (GDBusCo
/* ---------------------------------------------------------------------------------------------------- */
-typedef struct
-{
- gchar *rule;
- gchar *sender;
- gchar *sender_unique_name; /* if sender is unique or org.freedesktop.DBus, then that name... otherwise blank */
- gchar *interface_name;
- gchar *member;
- gchar *object_path;
- gchar *arg0;
- GDBusSignalFlags flags;
- GPtrArray *subscribers; /* (owned) (element-type SignalSubscriber) */
-} SignalData;
-
-static void
-signal_data_free (SignalData *signal_data)
-{
- g_free (signal_data->rule);
- g_free (signal_data->sender);
- g_free (signal_data->sender_unique_name);
- g_free (signal_data->interface_name);
- g_free (signal_data->member);
- g_free (signal_data->object_path);
- g_free (signal_data->arg0);
- g_ptr_array_unref (signal_data->subscribers);
- g_free (signal_data);
-}
-
-typedef struct
-{
- /* All fields are immutable after construction. */
- gatomicrefcount ref_count;
- GDBusSignalCallback callback;
- gpointer user_data;
- GDestroyNotify user_data_free_func;
- guint id;
- GMainContext *context;
-} SignalSubscriber;
-
-static SignalSubscriber *
-signal_subscriber_ref (SignalSubscriber *subscriber)
-{
- g_atomic_ref_count_inc (&subscriber->ref_count);
- return subscriber;
-}
-
-static void
-signal_subscriber_unref (SignalSubscriber *subscriber)
-{
- if (g_atomic_ref_count_dec (&subscriber->ref_count))
- {
- /* Destroy the user data. It doesnt matter which thread
- * signal_subscriber_unref() is called in (or whether its called with a
- * lock held), as call_destroy_notify() always defers to the next
- * #GMainContext iteration. */
- call_destroy_notify (subscriber->context,
- subscriber->user_data_free_func,
- subscriber->user_data);
-
- g_main_context_unref (subscriber->context);
- g_free (subscriber);
- }
-}
-
static gchar *
args_to_rule (const gchar *sender,
const gchar *interface_name,
@@ -3423,7 +3709,7 @@ remove_match_rule (GDBusConnection *conn
static gboolean
is_signal_data_for_name_lost_or_acquired (SignalData *signal_data)
{
- return g_strcmp0 (signal_data->sender_unique_name, "org.freedesktop.DBus") == 0 &&
+ return g_strcmp0 (signal_data->sender, "org.freedesktop.DBus") == 0 &&
g_strcmp0 (signal_data->interface_name, "org.freedesktop.DBus") == 0 &&
g_strcmp0 (signal_data->object_path, "/org/freedesktop/DBus") == 0 &&
(g_strcmp0 (signal_data->member, "NameLost") == 0 ||
@@ -3432,6 +3718,43 @@ is_signal_data_for_name_lost_or_acquired
/* ---------------------------------------------------------------------------------------------------- */
+/* called in any thread, connection lock is held */
+static void
+add_signal_data (GDBusConnection *connection,
+ SignalData *signal_data,
+ const char *sender_unique_name)
+{
+ GPtrArray *signal_data_array;
+
+ g_hash_table_insert (connection->map_rule_to_signal_data,
+ signal_data->rule,
+ signal_data);
+
+ /* Add the match rule to the bus...
+ *
+ * Avoid adding match rules for NameLost and NameAcquired messages - the bus will
+ * always send such messages to us.
+ */
+ if (connection->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)
+ {
+ if (!is_signal_data_for_name_lost_or_acquired (signal_data))
+ add_match_rule (connection, signal_data->rule);
+ }
+
+ signal_data_array = g_hash_table_lookup (connection->map_sender_unique_name_to_signal_data_array,
+ sender_unique_name);
+ if (signal_data_array == NULL)
+ {
+ signal_data_array = g_ptr_array_new ();
+ g_hash_table_insert (connection->map_sender_unique_name_to_signal_data_array,
+ g_strdup (sender_unique_name),
+ signal_data_array);
+ }
+ g_ptr_array_add (signal_data_array, signal_data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
/**
* g_dbus_connection_signal_subscribe:
* @connection: a #GDBusConnection
@@ -3520,8 +3843,9 @@ g_dbus_connection_signal_subscribe (GDBu
{
gchar *rule;
SignalData *signal_data;
+ SignalData *name_watcher = NULL;
SignalSubscriber *subscriber;
- GPtrArray *signal_data_array;
+ gboolean sender_is_its_own_owner;
const gchar *sender_unique_name;
/* Right now we abort if AddMatch() fails since it can only fail with the bus being in
@@ -3557,6 +3881,11 @@ g_dbus_connection_signal_subscribe (GDBu
rule = args_to_rule (sender, interface_name, member, object_path, arg0, flags);
if (sender != NULL && (g_dbus_is_unique_name (sender) || g_strcmp0 (sender, "org.freedesktop.DBus") == 0))
+ sender_is_its_own_owner = TRUE;
+ else
+ sender_is_its_own_owner = FALSE;
+
+ if (sender_is_its_own_owner)
sender_unique_name = sender;
else
sender_unique_name = "";
@@ -3578,43 +3907,62 @@ g_dbus_connection_signal_subscribe (GDBu
goto out;
}
- signal_data = g_new0 (SignalData, 1);
- signal_data->rule = rule;
- signal_data->sender = g_strdup (sender);
- signal_data->sender_unique_name = g_strdup (sender_unique_name);
- signal_data->interface_name = g_strdup (interface_name);
- signal_data->member = g_strdup (member);
- signal_data->object_path = g_strdup (object_path);
- signal_data->arg0 = g_strdup (arg0);
- signal_data->flags = flags;
- signal_data->subscribers = g_ptr_array_new_with_free_func ((GDestroyNotify) signal_subscriber_unref);
+ signal_data = signal_data_new_take (g_steal_pointer (&rule),
+ g_strdup (sender),
+ g_strdup (interface_name),
+ g_strdup (member),
+ g_strdup (object_path),
+ g_strdup (arg0),
+ flags);
g_ptr_array_add (signal_data->subscribers, subscriber);
- g_hash_table_insert (connection->map_rule_to_signal_data,
- signal_data->rule,
- signal_data);
+ /* If subscribing to a signal from a specific sender with a well-known
+ * name, we must first subscribe to NameOwnerChanged signals for that
+ * well-known name, so that we can match the current owner of the name
+ * against the sender of each signal. */
+ if (sender != NULL && !sender_is_its_own_owner)
+ {
+ gchar *name_owner_rule = NULL;
+
+ /* We already checked that sender != NULL implies MESSAGE_BUS_CONNECTION */
+ g_assert (connection->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION);
+
+ name_owner_rule = args_to_rule (DBUS_SERVICE_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "NameOwnerChanged",
+ DBUS_PATH_DBUS,
+ sender,
+ G_DBUS_SIGNAL_FLAGS_NONE);
+ name_watcher = g_hash_table_lookup (connection->map_rule_to_signal_data, name_owner_rule);
- /* Add the match rule to the bus...
- *
- * Avoid adding match rules for NameLost and NameAcquired messages - the bus will
- * always send such messages to us.
- */
- if (connection->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)
- {
- if (!is_signal_data_for_name_lost_or_acquired (signal_data))
- add_match_rule (connection, signal_data->rule);
- }
+ if (name_watcher == NULL)
+ {
+ name_watcher = signal_data_new_take (g_steal_pointer (&name_owner_rule),
+ g_strdup (DBUS_SERVICE_DBUS),
+ g_strdup (DBUS_INTERFACE_DBUS),
+ g_strdup ("NameOwnerChanged"),
+ g_strdup (DBUS_PATH_DBUS),
+ g_strdup (sender),
+ G_DBUS_SIGNAL_FLAGS_NONE);
+ add_signal_data (connection, name_watcher, DBUS_SERVICE_DBUS);
+ }
- signal_data_array = g_hash_table_lookup (connection->map_sender_unique_name_to_signal_data_array,
- signal_data->sender_unique_name);
- if (signal_data_array == NULL)
- {
- signal_data_array = g_ptr_array_new ();
- g_hash_table_insert (connection->map_sender_unique_name_to_signal_data_array,
- g_strdup (signal_data->sender_unique_name),
- signal_data_array);
+ if (name_watcher->watched_name == NULL)
+ {
+ name_watcher->watched_name = watched_name_new ();
+ name_watcher_call_get_name_owner_unlocked (connection, name_watcher);
+ }
+ else
+ {
+ g_ref_count_inc (&name_watcher->watched_name->ref_count);
+ }
+
+ signal_data->shared_name_watcher = name_watcher;
+
+ g_clear_pointer (&name_owner_rule, g_free);
}
- g_ptr_array_add (signal_data_array, signal_data);
+
+ add_signal_data (connection, signal_data, sender_unique_name);
out:
g_hash_table_insert (connection->map_id_to_signal_data,
@@ -3628,6 +3976,75 @@ g_dbus_connection_signal_subscribe (GDBu
/* ---------------------------------------------------------------------------------------------------- */
+/*
+ * Called in any thread.
+ * Must hold the connection lock when calling this, unless
+ * connection->finalizing is TRUE.
+ * May free signal_data, so do not dereference it after this.
+ */
+static void
+remove_signal_data_if_unused (GDBusConnection *connection,
+ SignalData *signal_data)
+{
+ const gchar *sender_unique_name;
+ GPtrArray *signal_data_array;
+
+ /* Cannot remove while there are still subscribers */
+ if (signal_data->subscribers->len != 0)
+ return;
+
+ /* Cannot remove while another SignalData is still using this one
+ * as its shared_name_watcher, which holds watched_name->ref_count > 0 */
+ if (signal_data->watched_name != NULL)
+ return;
+
+ /* Point of no return: we have committed to removing it */
+
+ if (signal_data->sender != NULL && signal_data->shared_name_watcher == NULL)
+ sender_unique_name = signal_data->sender;
+ else
+ sender_unique_name = "";
+
+ g_warn_if_fail (g_hash_table_remove (connection->map_rule_to_signal_data, signal_data->rule));
+
+ signal_data_array = g_hash_table_lookup (connection->map_sender_unique_name_to_signal_data_array,
+ sender_unique_name);
+ g_warn_if_fail (signal_data_array != NULL);
+ g_warn_if_fail (g_ptr_array_remove (signal_data_array, signal_data));
+
+ if (signal_data_array->len == 0)
+ {
+ g_warn_if_fail (g_hash_table_remove (connection->map_sender_unique_name_to_signal_data_array,
+ sender_unique_name));
+ }
+
+ /* remove the match rule from the bus unless NameLost or NameAcquired (see subscribe()) */
+ if ((connection->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION) &&
+ !is_signal_data_for_name_lost_or_acquired (signal_data) &&
+ !g_dbus_connection_is_closed (connection) &&
+ !connection->finalizing)
+ {
+ /* The check for g_dbus_connection_is_closed() means that
+ * sending the RemoveMatch message can't fail with
+ * G_IO_ERROR_CLOSED, because we're holding the lock,
+ * so on_worker_closed() can't happen between the check we just
+ * did, and releasing the lock later.
+ */
+ remove_match_rule (connection, signal_data->rule);
+ }
+
+ if (signal_data->shared_name_watcher != NULL)
+ {
+ SignalData *name_watcher = g_steal_pointer (&signal_data->shared_name_watcher);
+
+ name_watcher_unref_watched_name (connection, name_watcher);
+ /* May free signal_data */
+ remove_signal_data_if_unused (connection, name_watcher);
+ }
+
+ signal_data_free (signal_data);
+}
+
/* called in any thread */
/* must hold lock when calling this (except if connection->finalizing is TRUE)
* returns the number of removed subscribers */
@@ -3636,7 +4053,6 @@ unsubscribe_id_internal (GDBusConnection
guint subscription_id)
{
SignalData *signal_data;
- GPtrArray *signal_data_array;
guint n;
guint n_removed = 0;
@@ -3663,40 +4079,8 @@ unsubscribe_id_internal (GDBusConnection
GUINT_TO_POINTER (subscription_id)));
n_removed++;
g_ptr_array_remove_index_fast (signal_data->subscribers, n);
-
- if (signal_data->subscribers->len == 0)
- {
- g_warn_if_fail (g_hash_table_remove (connection->map_rule_to_signal_data, signal_data->rule));
-
- signal_data_array = g_hash_table_lookup (connection->map_sender_unique_name_to_signal_data_array,
- signal_data->sender_unique_name);
- g_warn_if_fail (signal_data_array != NULL);
- g_warn_if_fail (g_ptr_array_remove (signal_data_array, signal_data));
-
- if (signal_data_array->len == 0)
- {
- g_warn_if_fail (g_hash_table_remove (connection->map_sender_unique_name_to_signal_data_array,
- signal_data->sender_unique_name));
- }
-
- /* remove the match rule from the bus unless NameLost or NameAcquired (see subscribe()) */
- if ((connection->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION) &&
- !is_signal_data_for_name_lost_or_acquired (signal_data) &&
- !g_dbus_connection_is_closed (connection) &&
- !connection->finalizing)
- {
- /* The check for g_dbus_connection_is_closed() means that
- * sending the RemoveMatch message can't fail with
- * G_IO_ERROR_CLOSED, because we're holding the lock,
- * so on_worker_closed() can't happen between the check we just
- * did, and releasing the lock later.
- */
- remove_match_rule (connection, signal_data->rule);
- }
-
- signal_data_free (signal_data);
- }
-
+ /* May free signal_data */
+ remove_signal_data_if_unused (connection, signal_data);
goto out;
}
@@ -3911,6 +4295,46 @@ schedule_callbacks (GDBusConnection *con
if (signal_data->object_path != NULL && g_strcmp0 (signal_data->object_path, path) != 0)
continue;
+ if (signal_data->shared_name_watcher != NULL)
+ {
+ /* We want signals from a specified well-known name, which means
+ * the signal's sender needs to be the unique name that currently
+ * owns that well-known name, and we will have found this
+ * SignalData in
+ * connection->map_sender_unique_name_to_signal_data_array[""]. */
+ const WatchedName *watched_name;
+ const char *current_owner;
+
+ g_assert (signal_data->sender != NULL);
+ /* Invariant: We never need to watch for the owner of a unique
+ * name, or for the owner of DBUS_SERVICE_DBUS, either of which
+ * is always its own owner */
+ g_assert (!g_dbus_is_unique_name (signal_data->sender));
+ g_assert (g_strcmp0 (signal_data->sender, DBUS_SERVICE_DBUS) != 0);
+
+ watched_name = signal_data->shared_name_watcher->watched_name;
+ g_assert (watched_name != NULL);
+ current_owner = watched_name->owner;
+
+ /* Skip the signal if the actual sender is not known to own
+ * the required name */
+ if (current_owner == NULL || g_strcmp0 (current_owner, sender) != 0)
+ continue;
+ }
+ else if (signal_data->sender != NULL)
+ {
+ /* We want signals from a unique name or o.fd.DBus... */
+ g_assert (g_dbus_is_unique_name (signal_data->sender)
+ || g_str_equal (signal_data->sender, DBUS_SERVICE_DBUS));
+
+ /* ... which means we must have found this SignalData in
+ * connection->map_sender_unique_name_to_signal_data_array[signal_data->sender],
+ * therefore we would only have found it if the signal's
+ * actual sender matches the required signal_data->sender */
+ g_assert (g_strcmp0 (signal_data->sender, sender) == 0);
+ }
+ /* else the sender is unspecified and we will accept anything */
+
if (signal_data->arg0 != NULL)
{
if (arg0 == NULL)
@@ -3930,6 +4354,17 @@ schedule_callbacks (GDBusConnection *con
continue;
}
+ if (signal_data->watched_name != NULL)
+ {
+ /* Invariant: SignalData should only have a watched_name if it
+ * represents the NameOwnerChanged signal */
+ g_assert (g_strcmp0 (sender, DBUS_SERVICE_DBUS) == 0);
+ g_assert (g_strcmp0 (interface, DBUS_INTERFACE_DBUS) == 0);
+ g_assert (g_strcmp0 (path, DBUS_PATH_DBUS) == 0);
+ g_assert (g_strcmp0 (member, "NameOwnerChanged") == 0);
+ name_watcher_deliver_name_owner_changed_unlocked (signal_data, message);
+ }
+
for (m = 0; m < signal_data->subscribers->len; m++)
{
SignalSubscriber *subscriber = signal_data->subscribers->pdata[m];
@@ -3991,7 +4426,7 @@ distribute_signals (GDBusConnection *con
schedule_callbacks (connection, signal_data_array, message, sender);
}
- /* collect subscribers not matching on sender */
+ /* collect subscribers not matching on sender, or matching a well-known name */
signal_data_array = g_hash_table_lookup (connection->map_sender_unique_name_to_signal_data_array, "");
if (signal_data_array != NULL)
schedule_callbacks (connection, signal_data_array, message, sender);
diff -urpN glib-2.76.2.orig/gio/gdbusmessage.c glib-2.76.2/gio/gdbusmessage.c
--- glib-2.76.2.orig/gio/gdbusmessage.c 2023-04-21 09:46:05.000000000 -0500
+++ glib-2.76.2/gio/gdbusmessage.c 2024-08-19 14:21:36.927904554 -0500
@@ -508,6 +508,7 @@ struct _GDBusMessage
guint32 serial;
GHashTable *headers;
GVariant *body;
+ GVariant *arg0_cache; /* (nullable) (owned) */
#ifdef G_OS_UNIX
GUnixFDList *fd_list;
#endif
@@ -530,6 +531,7 @@ g_dbus_message_finalize (GObject *object
g_hash_table_unref (message->headers);
if (message->body != NULL)
g_variant_unref (message->body);
+ g_clear_pointer (&message->arg0_cache, g_variant_unref);
#ifdef G_OS_UNIX
if (message->fd_list != NULL)
g_object_unref (message->fd_list);
@@ -1168,6 +1170,7 @@ g_dbus_message_set_body (GDBusMessage *
if (body == NULL)
{
message->body = NULL;
+ message->arg0_cache = NULL;
g_dbus_message_set_signature (message, NULL);
}
else
@@ -1178,6 +1181,12 @@ g_dbus_message_set_body (GDBusMessage *
message->body = g_variant_ref_sink (body);
+ if (g_variant_is_of_type (message->body, G_VARIANT_TYPE_TUPLE) &&
+ g_variant_n_children (message->body) > 0)
+ message->arg0_cache = g_variant_get_child_value (message->body, 0);
+ else
+ message->arg0_cache = NULL;
+
type_string = g_variant_get_type_string (body);
type_string_len = strlen (type_string);
g_assert (type_string_len >= 2);
@@ -2330,6 +2339,14 @@ g_dbus_message_new_from_blob (guchar
2,
&local_error);
g_variant_type_free (variant_type);
+
+ if (message->body != NULL &&
+ g_variant_is_of_type (message->body, G_VARIANT_TYPE_TUPLE) &&
+ g_variant_n_children (message->body) > 0)
+ message->arg0_cache = g_variant_get_child_value (message->body, 0);
+ else
+ message->arg0_cache = NULL;
+
if (message->body == NULL)
goto fail;
}
@@ -3369,22 +3386,13 @@ g_dbus_message_set_signature (GDBusMessa
const gchar *
g_dbus_message_get_arg0 (GDBusMessage *message)
{
- const gchar *ret;
-
g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
- ret = NULL;
+ if (message->arg0_cache != NULL &&
+ g_variant_is_of_type (message->arg0_cache, G_VARIANT_TYPE_STRING))
+ return g_variant_get_string (message->arg0_cache, NULL);
- if (message->body != NULL && g_variant_is_of_type (message->body, G_VARIANT_TYPE_TUPLE))
- {
- GVariant *item;
- item = g_variant_get_child_value (message->body, 0);
- if (g_variant_is_of_type (item, G_VARIANT_TYPE_STRING))
- ret = g_variant_get_string (item, NULL);
- g_variant_unref (item);
- }
-
- return ret;
+ return NULL;
}
/* ---------------------------------------------------------------------------------------------------- */
@@ -3827,6 +3835,7 @@ g_dbus_message_copy (GDBusMessage *mess
* to just ref (as opposed to deep-copying) the GVariant instances
*/
ret->body = message->body != NULL ? g_variant_ref (message->body) : NULL;
+ ret->arg0_cache = message->arg0_cache != NULL ? g_variant_ref (message->arg0_cache) : NULL;
g_hash_table_iter_init (&iter, message->headers);
while (g_hash_table_iter_next (&iter, &header_key, (gpointer) &header_value))
g_hash_table_insert (ret->headers, header_key, g_variant_ref (header_value));
diff -urpN glib-2.76.2.orig/gio/gdbusprivate.h glib-2.76.2/gio/gdbusprivate.h
--- glib-2.76.2.orig/gio/gdbusprivate.h 2023-04-21 09:46:05.000000000 -0500
+++ glib-2.76.2/gio/gdbusprivate.h 2024-08-19 14:21:47.504744135 -0500
@@ -27,6 +27,11 @@
G_BEGIN_DECLS
+/* Bus name, interface and object path of the message bus itself */
+#define DBUS_SERVICE_DBUS "org.freedesktop.DBus"
+#define DBUS_INTERFACE_DBUS DBUS_SERVICE_DBUS
+#define DBUS_PATH_DBUS "/org/freedesktop/DBus"
+
/* ---------------------------------------------------------------------------------------------------- */
typedef struct GDBusWorker GDBusWorker;
diff -urpN glib-2.76.2.orig/gio/tests/gdbus-proxy.c glib-2.76.2/gio/tests/gdbus-proxy.c
--- glib-2.76.2.orig/gio/tests/gdbus-proxy.c 2023-04-21 09:46:05.000000000 -0500
+++ glib-2.76.2/gio/tests/gdbus-proxy.c 2024-08-19 14:24:58.241195666 -0500
@@ -780,6 +780,12 @@ kill_test_service (GDBusConnection *conn
while (!name_disappeared)
g_main_context_iteration (NULL, TRUE);
+ /* GDBusConnection doesn't guarantee that different subscriptions to the
+ * same signal will get their callbacks scheduled in any particular order,
+ * so make sure they have all happened */
+ while (g_main_context_iteration (NULL, FALSE))
+ continue;
+
g_bus_unwatch_name (watch_id);
#else
g_warning ("Can't kill com.example.TestService");
diff -urpN glib-2.76.2.orig/gio/tests/gdbus-subscribe.c glib-2.76.2/gio/tests/gdbus-subscribe.c
--- glib-2.76.2.orig/gio/tests/gdbus-subscribe.c 1969-12-31 18:00:00.000000000 -0600
+++ glib-2.76.2/gio/tests/gdbus-subscribe.c 2024-08-19 14:24:56.184495375 -0500
@@ -0,0 +1,1342 @@
+/*
+ * Copyright 2024 Collabora Ltd.
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <gio/gio.h>
+
+#include "gdbus-tests.h"
+
+/* From the D-Bus Specification */
+#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
+
+#define DBUS_SERVICE_DBUS "org.freedesktop.DBus"
+#define DBUS_PATH_DBUS "/org/freedesktop/DBus"
+#define DBUS_INTERFACE_DBUS DBUS_SERVICE_DBUS
+#define NAME_OWNER_CHANGED "NameOwnerChanged"
+
+/* A signal that each connection emits to indicate that it has finished
+ * emitting other signals */
+#define FINISHED_PATH "/org/gtk/Test/Finished"
+#define FINISHED_INTERFACE "org.gtk.Test.Finished"
+#define FINISHED_SIGNAL "Finished"
+
+/* A signal emitted during testing */
+#define EXAMPLE_PATH "/org/gtk/GDBus/ExampleInterface"
+#define EXAMPLE_INTERFACE "org.gtk.GDBus.ExampleInterface"
+#define FOO_SIGNAL "Foo"
+
+#define ALREADY_OWNED_NAME "org.gtk.Test.AlreadyOwned"
+#define OWNED_LATER_NAME "org.gtk.Test.OwnedLater"
+
+/* Log @s in a debug message. */
+static inline const char *
+nonnull (const char *s,
+ const char *if_null)
+{
+ return (s == NULL) ? if_null : s;
+}
+
+typedef enum
+{
+ TEST_CONN_NONE,
+ TEST_CONN_FIRST,
+ /* A connection that subscribes to signals */
+ TEST_CONN_SUBSCRIBER = TEST_CONN_FIRST,
+ /* A mockup of a legitimate service */
+ TEST_CONN_SERVICE,
+ /* A mockup of a second legitimate service */
+ TEST_CONN_SERVICE2,
+ /* A connection that tries to trick @subscriber into processing its signals
+ * as if they came from @service */
+ TEST_CONN_ATTACKER,
+ NUM_TEST_CONNS
+} TestConn;
+
+static const char * const test_conn_descriptions[NUM_TEST_CONNS] =
+{
+ "(unused)",
+ "subscriber",
+ "service",
+ "service 2",
+ "attacker"
+};
+
+typedef enum
+{
+ SUBSCRIPTION_MODE_CONN,
+ SUBSCRIPTION_MODE_PROXY,
+ SUBSCRIPTION_MODE_PARALLEL
+} SubscriptionMode;
+
+typedef struct
+{
+ GDBusProxy *received_by_proxy;
+ TestConn sender;
+ char *path;
+ char *iface;
+ char *member;
+ GVariant *parameters;
+ char *arg0;
+ guint32 step;
+} ReceivedMessage;
+
+static void
+received_message_free (ReceivedMessage *self)
+{
+
+ g_clear_object (&self->received_by_proxy);
+ g_free (self->path);
+ g_free (self->iface);
+ g_free (self->member);
+ g_clear_pointer (&self->parameters, g_variant_unref);
+ g_free (self->arg0);
+ g_free (self);
+}
+
+typedef struct
+{
+ TestConn sender;
+ TestConn unicast_to;
+ const char *path;
+ const char *iface;
+ const char *member;
+ const char *arg0;
+ const char *args;
+ guint received_by_conn;
+ guint received_by_proxy;
+} TestEmitSignal;
+
+typedef struct
+{
+ const char *string_sender;
+ TestConn unique_sender;
+ const char *path;
+ const char *iface;
+ const char *member;
+ const char *arg0;
+ GDBusSignalFlags flags;
+ gboolean unsubscribe_immediately;
+} TestSubscribe;
+
+typedef struct
+{
+ const char *name;
+ TestConn owner;
+ guint received_by_conn;
+ guint received_by_proxy;
+} TestOwnName;
+
+typedef enum
+{
+ TEST_ACTION_NONE = 0,
+ TEST_ACTION_SUBSCRIBE,
+ TEST_ACTION_EMIT_SIGNAL,
+ TEST_ACTION_OWN_NAME,
+} TestAction;
+
+typedef struct
+{
+ TestAction action;
+ union {
+ TestEmitSignal signal;
+ TestSubscribe subscribe;
+ TestOwnName own_name;
+ guint unsubscribe_undo_step;
+ } u;
+} TestStep;
+
+/* Arbitrary, extend as necessary to accommodate the longest test */
+#define MAX_TEST_STEPS 10
+
+typedef struct
+{
+ const char *description;
+ TestStep steps[MAX_TEST_STEPS];
+} TestPlan;
+
+static const TestPlan plan_simple =
+{
+ .description = "A broadcast is only received after subscribing to it",
+ .steps = {
+ {
+ /* We don't receive a signal if we haven't subscribed yet */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 0,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ },
+ },
+ {
+ /* Now it works */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 1,
+ /* The proxy can't be used in this case, because it needs
+ * a bus name to subscribe to */
+ .received_by_proxy = 0
+ },
+ },
+ },
+};
+
+static const TestPlan plan_broadcast_from_anyone =
+{
+ .description = "A subscription with NULL sender accepts broadcast and unicast",
+ .steps = {
+ {
+ /* Subscriber wants to receive signals from anyone */
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ },
+ },
+ {
+ /* First service sends a broadcast */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 1,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* Second service also sends a broadcast */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE2,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 1,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* First service sends a unicast signal */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE,
+ .unicast_to = TEST_CONN_SUBSCRIBER,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 1,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* Second service also sends a unicast signal */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE2,
+ .unicast_to = TEST_CONN_SUBSCRIBER,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 1,
+ .received_by_proxy = 0
+ },
+ },
+ },
+};
+
+static const TestPlan plan_match_twice =
+{
+ .description = "A message matching more than one subscription is received "
+ "once per subscription",
+ .steps = {
+ {
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .unique_sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ },
+ },
+ {
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .path = EXAMPLE_PATH,
+ },
+ },
+ {
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .iface = EXAMPLE_INTERFACE,
+ },
+ },
+ {
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .unique_sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ },
+ },
+ {
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 4,
+ /* Only the first and last work with GDBusProxy */
+ .received_by_proxy = 2
+ },
+ },
+ },
+};
+
+static const TestPlan plan_limit_by_unique_name =
+{
+ .description = "A subscription via a unique name only accepts messages "
+ "sent by that same unique name",
+ .steps = {
+ {
+ /* Subscriber wants to receive signals from service */
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .unique_sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ },
+ },
+ {
+ /* Attacker wants to trick subscriber into thinking that service
+ * sent a signal */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_ATTACKER,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 0,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* Attacker tries harder, by sending a signal unicast directly to
+ * the subscriber */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_ATTACKER,
+ .unicast_to = TEST_CONN_SUBSCRIBER,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 0,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* When the real service sends a signal, it should still get through */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 1,
+ .received_by_proxy = 1
+ },
+ },
+ },
+};
+
+static const TestPlan plan_nonexistent_unique_name =
+{
+ .description = "A subscription via a unique name that doesn't exist "
+ "accepts no messages",
+ .steps = {
+ {
+ /* Subscriber wants to receive signals from service */
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ /* This relies on the implementation detail that the dbus-daemon
+ * (and presumably other bus implementations) never actually generates
+ * a unique name in this format */
+ .string_sender = ":0.this.had.better.not.exist",
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ },
+ },
+ {
+ /* Attacker wants to trick subscriber into thinking that service
+ * sent a signal */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_ATTACKER,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 0,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* Attacker tries harder, by sending a signal unicast directly to
+ * the subscriber */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_ATTACKER,
+ .unicast_to = TEST_CONN_SUBSCRIBER,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 0,
+ .received_by_proxy = 0
+ },
+ },
+ },
+};
+
+static const TestPlan plan_limit_by_well_known_name =
+{
+ .description = "A subscription via a well-known name only accepts messages "
+ "sent by the owner of that well-known name",
+ .steps = {
+ {
+ /* Service already owns one name */
+ .action = TEST_ACTION_OWN_NAME,
+ .u.own_name = {
+ .name = ALREADY_OWNED_NAME,
+ .owner = TEST_CONN_SERVICE
+ },
+ },
+ {
+ /* Subscriber wants to receive signals from service */
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .string_sender = ALREADY_OWNED_NAME,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ },
+ },
+ {
+ /* Subscriber wants to receive signals from service by another name */
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .string_sender = OWNED_LATER_NAME,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ },
+ },
+ {
+ /* Attacker wants to trick subscriber into thinking that service
+ * sent a signal */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_ATTACKER,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 0,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* Attacker tries harder, by sending a signal unicast directly to
+ * the subscriber */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_ATTACKER,
+ .unicast_to = TEST_CONN_SUBSCRIBER,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 0,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* When the service sends a signal with the name it already owns,
+ * it should get through */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 1,
+ .received_by_proxy = 1
+ },
+ },
+ {
+ /* Service claims another name */
+ .action = TEST_ACTION_OWN_NAME,
+ .u.own_name = {
+ .name = OWNED_LATER_NAME,
+ .owner = TEST_CONN_SERVICE
+ },
+ },
+ {
+ /* Now the subscriber gets this signal twice, once for each
+ * subscription; and similarly each of the two proxies gets this
+ * signal twice */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 2,
+ .received_by_proxy = 2
+ },
+ },
+ },
+};
+
+static const TestPlan plan_unsubscribe_immediately =
+{
+ .description = "Unsubscribing before GetNameOwner can return doesn't result in a crash",
+ .steps = {
+ {
+ /* Service already owns one name */
+ .action = TEST_ACTION_OWN_NAME,
+ .u.own_name = {
+ .name = ALREADY_OWNED_NAME,
+ .owner = TEST_CONN_SERVICE
+ },
+ },
+ {
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .string_sender = ALREADY_OWNED_NAME,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .unsubscribe_immediately = TRUE
+ },
+ },
+ {
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_SERVICE,
+ .path = EXAMPLE_PATH,
+ .iface = EXAMPLE_INTERFACE,
+ .member = FOO_SIGNAL,
+ .received_by_conn = 0,
+ /* The proxy can't unsubscribe, except by destroying the proxy
+ * completely, which we don't currently implement in this test */
+ .received_by_proxy = 1
+ },
+ },
+ },
+};
+
+static const TestPlan plan_limit_to_message_bus =
+{
+ .description = "A subscription to the message bus only accepts messages "
+ "from the message bus",
+ .steps = {
+ {
+ /* Subscriber wants to receive signals from the message bus itself */
+ .action = TEST_ACTION_SUBSCRIBE,
+ .u.subscribe = {
+ .string_sender = DBUS_SERVICE_DBUS,
+ .path = DBUS_PATH_DBUS,
+ .iface = DBUS_INTERFACE_DBUS,
+ },
+ },
+ {
+ /* Attacker wants to trick subscriber into thinking that the message
+ * bus sent a signal */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .sender = TEST_CONN_ATTACKER,
+ .path = DBUS_PATH_DBUS,
+ .iface = DBUS_INTERFACE_DBUS,
+ .member = NAME_OWNER_CHANGED,
+ .arg0 = "would I lie to you?",
+ .received_by_conn = 0,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* Attacker tries harder, by sending a signal unicast directly to
+ * the subscriber, and using more realistic arguments */
+ .action = TEST_ACTION_EMIT_SIGNAL,
+ .u.signal = {
+ .unicast_to = TEST_CONN_SUBSCRIBER,
+ .sender = TEST_CONN_ATTACKER,
+ .path = DBUS_PATH_DBUS,
+ .iface = DBUS_INTERFACE_DBUS,
+ .member = NAME_OWNER_CHANGED,
+ .args = "('com.example.Name', '', ':1.12')",
+ .received_by_conn = 0,
+ .received_by_proxy = 0
+ },
+ },
+ {
+ /* When the message bus sends a signal (in this case triggered by
+ * owning a name), it should still get through */
+ .action = TEST_ACTION_OWN_NAME,
+ .u.own_name = {
+ .name = OWNED_LATER_NAME,
+ .owner = TEST_CONN_SERVICE,
+ .received_by_conn = 1,
+ .received_by_proxy = 1
+ },
+ },
+ },
+};
+
+typedef struct
+{
+ const TestPlan *plan;
+ SubscriptionMode mode;
+ GError *error;
+ /* (element-type ReceivedMessage) */
+ GPtrArray *received;
+ /* conns[TEST_CONN_NONE] is unused and remains NULL */
+ GDBusConnection *conns[NUM_TEST_CONNS];
+ /* Proxies on conns[TEST_CONN_SUBSCRIBER] */
+ GPtrArray *proxies;
+ /* unique_names[TEST_CONN_NONE] is unused and remains NULL */
+ const char *unique_names[NUM_TEST_CONNS];
+ /* finished[TEST_CONN_NONE] is unused and remains FALSE */
+ gboolean finished[NUM_TEST_CONNS];
+ /* Remains 0 for any step that is not a subscription */
+ guint subscriptions[MAX_TEST_STEPS];
+ /* Number of times the signal from step n was received */
+ guint received_by_conn[MAX_TEST_STEPS];
+ /* Number of times the signal from step n was received */
+ guint received_by_proxy[MAX_TEST_STEPS];
+ guint finished_subscription;
+} Fixture;
+
+/* Wait for asynchronous messages from @conn to have been processed
+ * by the message bus, as a sequence point so that we can make
+ * "happens before" and "happens after" assertions relative to this.
+ * The easiest way to achieve this is to call a message bus method that has
+ * no arguments and wait for it to return: because the message bus processes
+ * messages in-order, anything we sent before this must have been processed
+ * by the time this call arrives. */
+static void
+connection_wait_for_bus (GDBusConnection *conn)
+{
+ GError *error = NULL;
+ GVariant *call_result;
+
+ call_result = g_dbus_connection_call_sync (conn,
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "GetId",
+ NULL, /* arguments */
+ NULL, /* result type */
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (call_result);
+ g_variant_unref (call_result);
+}
+
+/*
+ * Called when the subscriber receives a message from any connection
+ * announcing that it has emitted all the signals that it plans to emit.
+ */
+static void
+subscriber_finished_cb (GDBusConnection *conn,
+ const char *sender_name,
+ const char *path,
+ const char *iface,
+ const char *member,
+ GVariant *parameters,
+ void *user_data)
+{
+ Fixture *f = user_data;
+ GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER];
+ guint i;
+
+ g_assert_true (conn == subscriber);
+
+ for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
+ {
+ if (g_str_equal (sender_name, f->unique_names[i]))
+ {
+ g_assert_false (f->finished[i]);
+ f->finished[i] = TRUE;
+
+ g_test_message ("Received Finished signal from %s %s",
+ test_conn_descriptions[i], sender_name);
+ return;
+ }
+ }
+
+ g_error ("Received Finished signal from unknown sender %s", sender_name);
+}
+
+/*
+ * Called when we receive a signal, either via the GDBusProxy (proxy != NULL)
+ * or via the GDBusConnection (proxy == NULL).
+ */
+static void
+fixture_received_signal (Fixture *f,
+ GDBusProxy *proxy,
+ const char *sender_name,
+ const char *path,
+ const char *iface,
+ const char *member,
+ GVariant *parameters)
+{
+ guint i;
+ ReceivedMessage *received;
+
+ /* Ignore the Finished signal if it matches a wildcard subscription */
+ if (g_str_equal (member, FINISHED_SIGNAL))
+ return;
+
+ received = g_new0 (ReceivedMessage, 1);
+
+ if (proxy != NULL)
+ received->received_by_proxy = g_object_ref (proxy);
+ else
+ received->received_by_proxy = NULL;
+
+ received->path = g_strdup (path);
+ received->iface = g_strdup (iface);
+ received->member = g_strdup (member);
+ received->parameters = g_variant_ref (parameters);
+
+ for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
+ {
+ if (g_str_equal (sender_name, f->unique_names[i]))
+ {
+ received->sender = i;
+ g_assert_false (f->finished[i]);
+ break;
+ }
+ }
+
+ if (g_str_equal (sender_name, DBUS_SERVICE_DBUS))
+ {
+ g_test_message ("Signal received from message bus %s",
+ sender_name);
+ }
+ else
+ {
+ g_test_message ("Signal received from %s %s",
+ test_conn_descriptions[received->sender],
+ sender_name);
+ g_assert_cmpint (received->sender, !=, TEST_CONN_NONE);
+ }
+
+ g_test_message ("Signal received from %s %s via %s",
+ test_conn_descriptions[received->sender],
+ sender_name,
+ proxy != NULL ? "proxy" : "connection");
+ g_test_message ("\tPath: %s", path);
+ g_test_message ("\tInterface: %s", iface);
+ g_test_message ("\tMember: %s", member);
+
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(su)")))
+ {
+ g_variant_get (parameters, "(su)", &received->arg0, &received->step);
+ g_test_message ("\tString argument 0: %s", received->arg0);
+ g_test_message ("\tSent in step: %u", received->step);
+ }
+ else if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)")))
+ {
+ g_variant_get (parameters, "(uu)", NULL, &received->step);
+ g_test_message ("\tArgument 0: (not a string)");
+ g_test_message ("\tSent in step: %u", received->step);
+ }
+ else if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
+ {
+ const char *name;
+ const char *old_owner;
+ const char *new_owner;
+
+ /* The only signal of this signature that we legitimately receive
+ * during this test is NameOwnerChanged, so just assert that it
+ * is from the message bus and can be matched to a plausible step.
+ * (This is less thorough than the above, and will not work if we
+ * add a test scenario where a name's ownership is repeatedly
+ * changed while watching NameOwnerChanged - so don't do that.) */
+ g_assert_cmpstr (sender_name, ==, DBUS_SERVICE_DBUS);
+ g_assert_cmpstr (path, ==, DBUS_PATH_DBUS);
+ g_assert_cmpstr (iface, ==, DBUS_INTERFACE_DBUS);
+ g_assert_cmpstr (member, ==, NAME_OWNER_CHANGED);
+
+ g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
+
+ for (i = 0; i < G_N_ELEMENTS (f->plan->steps); i++)
+ {
+ const TestStep *step = &f->plan->steps[i];
+
+ if (step->action == TEST_ACTION_OWN_NAME)
+ {
+ const TestOwnName *own_name = &step->u.own_name;
+
+ if (g_str_equal (name, own_name->name)
+ && g_str_equal (new_owner, f->unique_names[own_name->owner])
+ && own_name->received_by_conn > 0)
+ {
+ received->step = i;
+ break;
+ }
+ }
+
+ if (i >= G_N_ELEMENTS (f->plan->steps))
+ g_error ("Could not match message to a test step");
+ }
+ }
+ else
+ {
+ g_error ("Unexpected message received");
+ }
+
+ g_ptr_array_add (f->received, g_steal_pointer (&received));
+}
+
+static void
+proxy_signal_cb (GDBusProxy *proxy,
+ const char *sender_name,
+ const char *member,
+ GVariant *parameters,
+ void *user_data)
+{
+ Fixture *f = user_data;
+
+ fixture_received_signal (f, proxy, sender_name,
+ g_dbus_proxy_get_object_path (proxy),
+ g_dbus_proxy_get_interface_name (proxy),
+ member, parameters);
+}
+
+static void
+subscribed_signal_cb (GDBusConnection *conn,
+ const char *sender_name,
+ const char *path,
+ const char *iface,
+ const char *member,
+ GVariant *parameters,
+ void *user_data)
+{
+ Fixture *f = user_data;
+ GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER];
+
+ g_assert_true (conn == subscriber);
+
+ fixture_received_signal (f, NULL, sender_name, path, iface, member, parameters);
+}
+
+static void
+fixture_subscribe (Fixture *f,
+ const TestSubscribe *subscribe,
+ guint step_number)
+{
+ GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER];
+ const char *sender;
+
+ if (subscribe->string_sender != NULL)
+ {
+ sender = subscribe->string_sender;
+ g_test_message ("\tSender: %s", sender);
+ }
+ else if (subscribe->unique_sender != TEST_CONN_NONE)
+ {
+ sender = f->unique_names[subscribe->unique_sender];
+ g_test_message ("\tSender: %s %s",
+ test_conn_descriptions[subscribe->unique_sender],
+ sender);
+ }
+ else
+ {
+ sender = NULL;
+ g_test_message ("\tSender: (any)");
+ }
+
+ g_test_message ("\tPath: %s", nonnull (subscribe->path, "(any)"));
+ g_test_message ("\tInterface: %s",
+ nonnull (subscribe->iface, "(any)"));
+ g_test_message ("\tMember: %s",
+ nonnull (subscribe->member, "(any)"));
+ g_test_message ("\tString argument 0: %s",
+ nonnull (subscribe->arg0, "(any)"));
+ g_test_message ("\tFlags: %x", subscribe->flags);
+
+ if (f->mode != SUBSCRIPTION_MODE_PROXY)
+ {
+ /* CONN or PARALLEL */
+ guint id;
+
+ g_test_message ("\tSubscribing via connection");
+ id = g_dbus_connection_signal_subscribe (subscriber,
+ sender,
+ subscribe->iface,
+ subscribe->member,
+ subscribe->path,
+ subscribe->arg0,
+ subscribe->flags,
+ subscribed_signal_cb,
+ f, NULL);
+
+ g_assert_cmpuint (id, !=, 0);
+
+ if (subscribe->unsubscribe_immediately)
+ {
+ g_test_message ("\tImmediately unsubscribing");
+ g_dbus_connection_signal_unsubscribe (subscriber, id);
+ }
+ else
+ {
+ f->subscriptions[step_number] = id;
+ }
+ }
+
+ if (f->mode != SUBSCRIPTION_MODE_CONN)
+ {
+ /* PROXY or PARALLEL */
+
+ if (sender == NULL)
+ {
+ g_test_message ("\tCannot subscribe via proxy: no bus name");
+ }
+ else if (subscribe->path == NULL)
+ {
+ g_test_message ("\tCannot subscribe via proxy: no path");
+ }
+ else if (subscribe->iface == NULL)
+ {
+ g_test_message ("\tCannot subscribe via proxy: no interface");
+ }
+ else
+ {
+ GDBusProxy *proxy;
+
+ g_test_message ("\tSubscribing via proxy");
+ proxy = g_dbus_proxy_new_sync (subscriber,
+ (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+ | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
+ NULL, /* GDBusInterfaceInfo */
+ sender,
+ subscribe->path,
+ subscribe->iface,
+ NULL, /* GCancellable */
+ &f->error);
+ g_assert_no_error (f->error);
+ g_assert_nonnull (proxy);
+ g_signal_connect (proxy, "g-signal", G_CALLBACK (proxy_signal_cb), f);
+ g_ptr_array_add (f->proxies, g_steal_pointer (&proxy));
+ }
+ }
+
+ /* As in setup(), we need to wait for AddMatch to happen. */
+ g_test_message ("Waiting for AddMatch to be processed");
+ connection_wait_for_bus (subscriber);
+}
+
+static void
+fixture_emit_signal (Fixture *f,
+ const TestEmitSignal *signal,
+ guint step_number)
+{
+ GVariant *body;
+ const char *destination;
+ gboolean ok;
+
+ g_test_message ("\tSender: %s",
+ test_conn_descriptions[signal->sender]);
+
+ if (signal->unicast_to != TEST_CONN_NONE)
+ {
+ destination = f->unique_names[signal->unicast_to];
+ g_test_message ("\tDestination: %s %s",
+ test_conn_descriptions[signal->unicast_to],
+ destination);
+ }
+ else
+ {
+ destination = NULL;
+ g_test_message ("\tDestination: (broadcast)");
+ }
+
+ g_assert_nonnull (signal->path);
+ g_test_message ("\tPath: %s", signal->path);
+ g_assert_nonnull (signal->iface);
+ g_test_message ("\tInterface: %s", signal->iface);
+ g_assert_nonnull (signal->member);
+ g_test_message ("\tMember: %s", signal->member);
+
+ /* If arg0 is non-NULL, put it in the message's argument 0.
+ * Otherwise put something that will not match any arg0.
+ * Either way, put the sequence number in argument 1 so we can
+ * correlate sent messages with received messages later. */
+ if (signal->args != NULL)
+ {
+ /* floating */
+ body = g_variant_new_parsed (signal->args);
+ g_assert_nonnull (body);
+ }
+ else if (signal->arg0 != NULL)
+ {
+ g_test_message ("\tString argument 0: %s", signal->arg0);
+ body = g_variant_new ("(su)", signal->arg0, (guint32) step_number);
+ }
+ else
+ {
+ g_test_message ("\tArgument 0: (not a string)");
+ body = g_variant_new ("(uu)", (guint32) 0, (guint32) step_number);
+ }
+
+ ok = g_dbus_connection_emit_signal (f->conns[signal->sender],
+ destination,
+ signal->path,
+ signal->iface,
+ signal->member,
+ /* steals floating reference */
+ g_steal_pointer (&body),
+ &f->error);
+ g_assert_no_error (f->error);
+ g_assert_true (ok);
+
+ /* Emitting the signal is asynchronous, so if we want subsequent steps
+ * to be guaranteed to happen after the signal from the message bus's
+ * perspective, we have to do a round-trip to the message bus to sync up. */
+ g_test_message ("Waiting for signal to reach message bus");
+ connection_wait_for_bus (f->conns[signal->sender]);
+}
+
+static void
+fixture_own_name (Fixture *f,
+ const TestOwnName *own_name)
+{
+ GVariant *call_result;
+ guint32 flags;
+ guint32 result_code;
+
+ g_test_message ("\tName: %s", own_name->name);
+ g_test_message ("\tOwner: %s",
+ test_conn_descriptions[own_name->owner]);
+
+ /* For simplicity, we do this via a direct bus call rather than
+ * using g_bus_own_name_on_connection(). The flags in
+ * GBusNameOwnerFlags are numerically equal to those in the
+ * D-Bus wire protocol. */
+ flags = G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE;
+ call_result = g_dbus_connection_call_sync (f->conns[own_name->owner],
+ DBUS_SERVICE_DBUS,
+ DBUS_PATH_DBUS,
+ DBUS_INTERFACE_DBUS,
+ "RequestName",
+ g_variant_new ("(su)",
+ own_name->name,
+ flags),
+ G_VARIANT_TYPE ("(u)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &f->error);
+ g_assert_no_error (f->error);
+ g_assert_nonnull (call_result);
+ g_variant_get (call_result, "(u)", &result_code);
+ g_assert_cmpuint (result_code, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
+ g_variant_unref (call_result);
+}
+
+static void
+fixture_run_plan (Fixture *f,
+ const TestPlan *plan,
+ SubscriptionMode mode)
+{
+ guint i;
+
+ G_STATIC_ASSERT (G_N_ELEMENTS (plan->steps) == G_N_ELEMENTS (f->subscriptions));
+ G_STATIC_ASSERT (G_N_ELEMENTS (plan->steps) == G_N_ELEMENTS (f->received_by_conn));
+ G_STATIC_ASSERT (G_N_ELEMENTS (plan->steps) == G_N_ELEMENTS (f->received_by_proxy));
+
+ f->mode = mode;
+ f->plan = plan;
+
+ g_test_summary (plan->description);
+
+ for (i = 0; i < G_N_ELEMENTS (plan->steps); i++)
+ {
+ const TestStep *step = &plan->steps[i];
+
+ switch (step->action)
+ {
+ case TEST_ACTION_SUBSCRIBE:
+ g_test_message ("Step %u: adding subscription", i);
+ fixture_subscribe (f, &step->u.subscribe, i);
+ break;
+
+ case TEST_ACTION_EMIT_SIGNAL:
+ g_test_message ("Step %u: emitting signal", i);
+ fixture_emit_signal (f, &step->u.signal, i);
+ break;
+
+ case TEST_ACTION_OWN_NAME:
+ g_test_message ("Step %u: claiming bus name", i);
+ fixture_own_name (f, &step->u.own_name);
+ break;
+
+ case TEST_ACTION_NONE:
+ /* Padding to fill the rest of the array, do nothing */
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+ }
+
+ /* Now that we have done everything we wanted to do, emit Finished
+ * from each connection. */
+ for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
+ {
+ gboolean ok;
+
+ ok = g_dbus_connection_emit_signal (f->conns[i],
+ NULL,
+ FINISHED_PATH,
+ FINISHED_INTERFACE,
+ FINISHED_SIGNAL,
+ NULL,
+ &f->error);
+ g_assert_no_error (f->error);
+ g_assert_true (ok);
+ }
+
+ /* Wait until we have seen the Finished signal from each sender */
+ while (TRUE)
+ {
+ gboolean all_finished = TRUE;
+
+ for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
+ all_finished = all_finished && f->finished[i];
+
+ if (all_finished)
+ break;
+
+ g_main_context_iteration (NULL, TRUE);
+ }
+
+ /* Assert that the correct things happened before each Finished signal */
+ for (i = 0; i < f->received->len; i++)
+ {
+ const ReceivedMessage *received = g_ptr_array_index (f->received, i);
+
+ g_assert_cmpuint (received->step, <, G_N_ELEMENTS (f->received_by_conn));
+ g_assert_cmpuint (received->step, <, G_N_ELEMENTS (f->received_by_proxy));
+
+ if (received->received_by_proxy != NULL)
+ f->received_by_proxy[received->step] += 1;
+ else
+ f->received_by_conn[received->step] += 1;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (plan->steps); i++)
+ {
+ const TestStep *step = &plan->steps[i];
+
+ if (step->action == TEST_ACTION_EMIT_SIGNAL)
+ {
+ const TestEmitSignal *signal = &plan->steps[i].u.signal;
+
+ if (mode != SUBSCRIPTION_MODE_PROXY)
+ {
+ g_test_message ("Signal from step %u was received %u times by "
+ "GDBusConnection, expected %u",
+ i, f->received_by_conn[i], signal->received_by_conn);
+ g_assert_cmpuint (f->received_by_conn[i], ==, signal->received_by_conn);
+ }
+ else
+ {
+ g_assert_cmpuint (f->received_by_conn[i], ==, 0);
+ }
+
+ if (mode != SUBSCRIPTION_MODE_CONN)
+ {
+ g_test_message ("Signal from step %u was received %u times by "
+ "GDBusProxy, expected %u",
+ i, f->received_by_proxy[i], signal->received_by_proxy);
+ g_assert_cmpuint (f->received_by_proxy[i], ==, signal->received_by_proxy);
+ }
+ else
+ {
+ g_assert_cmpuint (f->received_by_proxy[i], ==, 0);
+ }
+ }
+ else if (step->action == TEST_ACTION_OWN_NAME)
+ {
+ const TestOwnName *own_name = &plan->steps[i].u.own_name;
+
+ if (mode != SUBSCRIPTION_MODE_PROXY)
+ {
+ g_test_message ("NameOwnerChanged from step %u was received %u "
+ "times by GDBusConnection, expected %u",
+ i, f->received_by_conn[i], own_name->received_by_conn);
+ g_assert_cmpuint (f->received_by_conn[i], ==, own_name->received_by_conn);
+ }
+ else
+ {
+ g_assert_cmpuint (f->received_by_conn[i], ==, 0);
+ }
+
+ if (mode != SUBSCRIPTION_MODE_CONN)
+ {
+ g_test_message ("NameOwnerChanged from step %u was received %u "
+ "times by GDBusProxy, expected %u",
+ i, f->received_by_proxy[i], own_name->received_by_proxy);
+ g_assert_cmpuint (f->received_by_proxy[i], ==, own_name->received_by_proxy);
+ }
+ else
+ {
+ g_assert_cmpuint (f->received_by_proxy[i], ==, 0);
+ }
+ }
+ }
+}
+
+static void
+setup (Fixture *f,
+ G_GNUC_UNUSED const void *context)
+{
+ GDBusConnection *subscriber;
+ guint i;
+
+ session_bus_up ();
+
+ f->proxies = g_ptr_array_new_full (MAX_TEST_STEPS, g_object_unref);
+ f->received = g_ptr_array_new_full (MAX_TEST_STEPS,
+ (GDestroyNotify) received_message_free);
+
+ for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
+ {
+ f->conns[i] = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &f->error);
+ g_assert_no_error (f->error);
+ g_assert_nonnull (f->conns[i]);
+
+ f->unique_names[i] = g_dbus_connection_get_unique_name (f->conns[i]);
+ g_assert_nonnull (f->unique_names[i]);
+ g_test_message ("%s is %s",
+ test_conn_descriptions[i],
+ f->unique_names[i]);
+ }
+
+ subscriber = f->conns[TEST_CONN_SUBSCRIBER];
+
+ /* Used to wait for all connections to finish sending whatever they
+ * wanted to send */
+ f->finished_subscription = g_dbus_connection_signal_subscribe (subscriber,
+ NULL,
+ FINISHED_INTERFACE,
+ FINISHED_SIGNAL,
+ FINISHED_PATH,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ subscriber_finished_cb,
+ f, NULL);
+ /* AddMatch is sent asynchronously, so we don't know how
+ * soon it will be processed. Before emitting signals, we
+ * need to wait for the message bus to get as far as processing
+ * AddMatch. */
+ g_test_message ("Waiting for AddMatch to be processed");
+ connection_wait_for_bus (subscriber);
+}
+
+static void
+test_conn_subscribe (Fixture *f,
+ const void *context)
+{
+ fixture_run_plan (f, context, SUBSCRIPTION_MODE_CONN);
+}
+
+static void
+test_proxy_subscribe (Fixture *f,
+ const void *context)
+{
+ fixture_run_plan (f, context, SUBSCRIPTION_MODE_PROXY);
+}
+
+static void
+test_parallel_subscribe (Fixture *f,
+ const void *context)
+{
+ fixture_run_plan (f, context, SUBSCRIPTION_MODE_PARALLEL);
+}
+
+static void
+teardown (Fixture *f,
+ G_GNUC_UNUSED const void *context)
+{
+ GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER];
+ guint i;
+
+ g_ptr_array_unref (f->proxies);
+
+ if (f->finished_subscription != 0)
+ g_dbus_connection_signal_unsubscribe (subscriber, f->finished_subscription);
+
+ for (i = 0; i < G_N_ELEMENTS (f->subscriptions); i++)
+ {
+ if (f->subscriptions[i] != 0)
+ g_dbus_connection_signal_unsubscribe (subscriber, f->subscriptions[i]);
+ }
+
+ g_ptr_array_unref (f->received);
+
+ for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
+ g_clear_object (&f->conns[i]);
+
+ g_clear_error (&f->error);
+
+ session_bus_down ();
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
+
+ g_test_dbus_unset ();
+
+#define ADD_SUBSCRIBE_TEST(name) \
+ do { \
+ g_test_add ("/gdbus/subscribe/conn/" #name, \
+ Fixture, &plan_ ## name, \
+ setup, test_conn_subscribe, teardown); \
+ g_test_add ("/gdbus/subscribe/proxy/" #name, \
+ Fixture, &plan_ ## name, \
+ setup, test_proxy_subscribe, teardown); \
+ g_test_add ("/gdbus/subscribe/parallel/" #name, \
+ Fixture, &plan_ ## name, \
+ setup, test_parallel_subscribe, teardown); \
+ } while (0)
+
+ ADD_SUBSCRIBE_TEST (simple);
+ ADD_SUBSCRIBE_TEST (broadcast_from_anyone);
+ ADD_SUBSCRIBE_TEST (match_twice);
+ ADD_SUBSCRIBE_TEST (limit_by_unique_name);
+ ADD_SUBSCRIBE_TEST (nonexistent_unique_name);
+ ADD_SUBSCRIBE_TEST (limit_by_well_known_name);
+ ADD_SUBSCRIBE_TEST (limit_to_message_bus);
+ ADD_SUBSCRIBE_TEST (unsubscribe_immediately);
+
+ return g_test_run();
+}
diff -urpN glib-2.76.2.orig/gio/tests/meson.build glib-2.76.2/gio/tests/meson.build
--- glib-2.76.2.orig/gio/tests/meson.build 2023-04-21 09:46:05.000000000 -0500
+++ glib-2.76.2/gio/tests/meson.build 2024-08-19 14:21:39.927953601 -0500
@@ -455,6 +455,10 @@ if host_machine.system() != 'windows'
'extra_sources' : extra_sources,
'extra_programs': extra_programs,
},
+ 'gdbus-subscribe' : {
+ 'extra_sources' : extra_sources,
+ 'extra_programs': extra_programs,
+ },
'gdbus-test-codegen' : {
'extra_sources' : [extra_sources, gdbus_test_codegen_generated, gdbus_test_codegen_generated_interface_info],
'c_args' : ['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_32'],
diff -urpN glib-2.76.2.orig/gio/tests/org.gtk.test.gschema.override.orig glib-2.76.2/gio/tests/org.gtk.test.gschema.override.orig
--- glib-2.76.2.orig/gio/tests/org.gtk.test.gschema.override.orig 2023-04-21 09:46:05.000000000 -0500
+++ glib-2.76.2/gio/tests/org.gtk.test.gschema.override.orig 1969-12-31 18:00:00.000000000 -0600
@@ -1,2 +0,0 @@
-[org.gtk.test.per-desktop:GNOME-Classic]
-desktop = "GNOME Classic"
diff -urpN glib-2.76.2.orig/gio/tests/org.gtk.test.gschema.xml.orig glib-2.76.2/gio/tests/org.gtk.test.gschema.xml.orig
--- glib-2.76.2.orig/gio/tests/org.gtk.test.gschema.xml.orig 2023-04-21 09:46:05.000000000 -0500
+++ glib-2.76.2/gio/tests/org.gtk.test.gschema.xml.orig 1969-12-31 18:00:00.000000000 -0600
@@ -1,222 +0,0 @@
-<schemalist>
-
- <schema id="org.gtk.test" path="/tests/" gettext-domain="test">
- <key name="greeting" type="s">
- <default l10n="messages">"Hello, earthlings"</default>
- <summary>A greeting</summary>
- <description>
- Greeting of the invading martians
- </description>
- </key>
- <key name="farewell" type="s">
- <default l10n="messages">"So long"</default>
- </key>
-
- <child name='basic-types' schema='org.gtk.test.basic-types'/>
- <child name='complex-types' schema='org.gtk.test.complex-types'/>
- <child name='localized' schema='org.gtk.test.localized'/>
- </schema>
-
- <schema id="org.gtk.test.no-path">
- <key name="test-boolean" type="b">
- <default>true</default>
- </key>
- </schema>
-
- <schema id="org.gtk.test.basic-types" path="/tests/basic-types/">
- <key name="test-boolean" type="b">
- <default>true</default>
- </key>
- <key name="test-byte" type="y">
- <default>25</default>
- </key>
- <key name="test-int16" type="n">
- <default>-1234</default>
- </key>
- <key name="test-uint16" type="q">
- <default>1234</default>
- </key>
- <key name="test-int32" type="i">
- <default>-123456</default>
- </key>
- <key name="test-uint32" type="u">
- <default>123456</default>
- </key>
- <key name="test-int64" type="x">
- <default>-123456789</default>
- </key>
- <key name="test-uint64" type="t">
- <default>123456789</default>
- </key>
- <key name="test-double" type="d">
- <default>123.456</default>
- </key>
- <key name="test-string" type="s">
- <default>"a string, it seems"</default>
- </key>
- <key name="test-objectpath" type="o">
- <default>"/a/object/path"</default>
- </key>
- </schema>
-
- <schema id="org.gtk.test.complex-types" path="/tests/complex-types/">
- <key name="test-tuple" type="(s(ii))">
- <default>("one",(2,3))</default>
- </key>
- <key name="test-array" type="ai">
- <default>[0,1,2,3,4,5]</default>
- </key>
- <key name="test-dict" type="a{sau}">
- <default>
- {
- "AC": [0,0, 0,0,0,0,0,0],
- "IV": [0,0, 0,0,0,0,0,0]
- }
- </default>
- </key>
- </schema>
-
- <schema id="org.gtk.test.localized" path="/tests/localized/" gettext-domain="test">
- <key name="error-message" type="s">
- <default l10n="messages">"Unnamed"</default>
- </key>
- <key name="backspace" type="s">
- <default l10n="messages" context="keyboard label">"BackSpace"</default>
- </key>
- </schema>
-
- <schema id="org.gtk.test.binding" path="/tests/binding/">
- <key name="bool" type="b">
- <default>false</default>
- </key>
- <key name="anti-bool" type="b">
- <default>false</default>
- </key>
- <key name="byte" type="y">
- <default>0</default>
- </key>
- <key name="int16" type="n">
- <default>0</default>
- </key>
- <key name="uint16" type="q">
- <default>0</default>
- </key>
- <key name="int" type="i">
- <default>0</default>
- </key>
- <key name="uint" type="u">
- <default>0</default>
- </key>
- <key name="int64" type="x">
- <default>0</default>
- </key>
- <key name="uint64" type="t">
- <default>0</default>
- </key>
- <key name="double" type="d">
- <default>0</default>
- </key>
- <key name="string" type="s">
- <default>""</default>
- </key>
- <key name="chararray" type="ay">
- <default>[48, 49]</default>
- </key>
- <key name="strv" type="as">
- <default>[]</default>
- </key>
- <key name="enum" enum="org.gtk.test.TestEnum">
- <default>'foo'</default>
- </key>
- <key name="flags" flags="org.gtk.test.TestFlags">
- <default>['mourning', 'laughing']</default>
- </key>
- <key name="range" type='u'>
- <default>33</default>
- <range min="2" max="44"/>
- </key>
- </schema>
-
- <schema id='org.gtk.test.enums' path='/tests/enums/'>
- <key name='test' enum='org.gtk.test.TestEnum'>
- <default>'bar'</default>
- <aliases>
- <alias value='qux' target='quux'/>
- </aliases>
- </key>
- <key name='f-test' flags='org.gtk.test.TestFlags'>
- <default>[]</default>
- <aliases>
- <alias value='speaking' target='talking'/>
- </aliases>
- </key>
- </schema>
-
- <schema id='org.gtk.test.enums.direct' path='/tests/enums/'>
- <key name='f-test' type='as'>
- <default>[]</default>
- </key>
- <key name='test' type='s'>
- <default>'bar'</default>
- </key>
- </schema>
-
- <schema id='org.gtk.test.range' path='/tests/range/'>
- <key name='val' type='i'>
- <default>33</default>
- <range min='2' max='44'/>
- </key>
- </schema>
-
- <schema id='org.gtk.test.range.direct' path='/tests/range/'>
- <key name='val' type='i'>
- <default>33</default>
- </key>
- </schema>
-
- <schema id='org.gtk.test.mapped' path='/tests/mapped/'>
- <key name='val' type='i'>
- <default>0</default>
- </key>
- </schema>
-
- <schema id="org.gtk.test.descriptions" path="/a/">
- <key name='a' type='i'>
- <summary>
- a paragraph.
-
- with some whitespace.
-
- because not everyone has a great editor.
-
-
-
-
- lots of space is as one.
- </summary>
- <default>0</default>
- </key>
- </schema>
-
- <schema id='org.gtk.test.extends.base'>
- <key name='int32' type='i'>
- <default>0</default>
- </key>
- <key name='string' type='s'>
- <default>''</default>
- </key>
- </schema>
- <schema id='org.gtk.test.extends.extended' extends='org.gtk.test.extends.base'>
- <override name="int32">42</override>
- <key name='another-int32' type='i'>
- <default>0</default>
- </key>
- </schema>
-
- <schema id="org.gtk.test.per-desktop" path="/tests/per-desktop/">
- <key name="desktop" type="s">
- <default>"GNOME"</default>
- </key>
- </schema>
-
-</schemalist>