Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
GNOME:STABLE:3.34
glib2
glib2-CVE-2024-34397.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File glib2-CVE-2024-34397.patch of Package glib2
diff -urpN glib-2.62.6.orig/gio/gdbusconnection.c glib-2.62.6/gio/gdbusconnection.c --- glib-2.62.6.orig/gio/gdbusconnection.c 2024-05-15 15:57:06.505056032 -0500 +++ glib-2.62.6/gio/gdbusconnection.c 2024-05-15 16:10:34.042763005 -0500 @@ -300,6 +300,153 @@ _g_strv_has_string (const gchar* const * /* ---------------------------------------------------------------------------------------------------- */ +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 doesn’t matter which thread + * signal_subscriber_unref() is called in (or whether it’s 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 @@ -426,6 +573,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 */ @@ -673,6 +821,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); @@ -1068,6 +1217,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,208 @@ 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); +} + +static inline gboolean +g_set_str (char **str_pointer, + const char *new_str) +{ + char *copy; + + if (*str_pointer == new_str || + (*str_pointer && new_str && strcmp (*str_pointer, new_str) == 0)) + return FALSE; + + copy = g_strdup (new_str); + g_free (*str_pointer); + *str_pointer = copy; + + return TRUE; +} + +/* 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; @@ -2299,6 +2651,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); @@ -2314,6 +2667,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) @@ -3239,60 +3605,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 -{ - 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)) - { - g_main_context_unref (subscriber->context); - g_free (subscriber); - } -} - static gchar * args_to_rule (const gchar *sender, const gchar *interface_name, @@ -3404,7 +3716,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 || @@ -3413,6 +3725,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 @@ -3483,8 +3832,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 @@ -3520,6 +3870,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 = ""; @@ -3541,43 +3896,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, @@ -3591,6 +3965,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) */ static void @@ -3599,7 +4042,6 @@ unsubscribe_id_internal (GDBusConnection GPtrArray *out_removed_subscribers) { SignalData *signal_data; - GPtrArray *signal_data_array; guint n; signal_data = g_hash_table_lookup (connection->map_id_to_signal_data, @@ -3625,40 +4067,8 @@ unsubscribe_id_internal (GDBusConnection GUINT_TO_POINTER (subscription_id))); g_ptr_array_add (out_removed_subscribers, signal_subscriber_ref (subscriber)); 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; } @@ -3879,6 +4289,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) @@ -3898,6 +4348,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]; @@ -3961,7 +4422,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.62.6.orig/gio/gdbusprivate.h glib-2.62.6/gio/gdbusprivate.h --- glib-2.62.6.orig/gio/gdbusprivate.h 2020-03-18 08:16:11.000000000 -0500 +++ glib-2.62.6/gio/gdbusprivate.h 2024-05-15 16:09:37.639111384 -0500 @@ -29,6 +29,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.62.6.orig/gio/tests/gdbus-subscribe.c glib-2.62.6/gio/tests/gdbus-subscribe.c --- glib-2.62.6.orig/gio/tests/gdbus-subscribe.c 1969-12-31 18:00:00.000000000 -0600 +++ glib-2.62.6/gio/tests/gdbus-subscribe.c 2024-05-15 16:09:37.639111384 -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.62.6.orig/gio/tests/meson.build glib-2.62.6/gio/tests/meson.build --- glib-2.62.6.orig/gio/tests/meson.build 2020-03-18 08:16:11.000000000 -0500 +++ glib-2.62.6/gio/tests/meson.build 2024-05-15 16:09:37.639111384 -0500 @@ -268,6 +268,10 @@ if host_machine.system() != 'windows' }, 'gdbus-proxy-unique-name' : {'extra_sources' : extra_sources}, 'gdbus-proxy-well-known-name' : {'extra_sources' : extra_sources}, + '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], },
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor