diff options
| author | Craig Jennings <c@cjennings.net> | 2026-10-17 07:07:25 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-10-17 07:07:25 -0500 |
| commit | cd6e11b46ecf2b56bf63d6c11886dccc70875e10 (patch) | |
| tree | e2d17ef968ae24ef1b65babaadfc7637cc2024bb /packages | |
| parent | e530fd597b62bda520f9804be7da216eea582750 (diff) | |
| download | archsetup-cd6e11b46ecf2b56bf63d6c11886dccc70875e10.tar.gz archsetup-cd6e11b46ecf2b56bf63d6c11886dccc70875e10.zip | |
feat(keyring): ship a patched gnome-keyring and install it from the repoclaude/hungry-franklin-09fffc
gnome-keyring 48 through 51.1 aborts when a client's first OpenSession is dispatched before the daemon has created its per-client record. Each abort drops the unlocked state and the next request pops the password dialog. On ratio that happened about three times a day from 2026-10-11, when the ChatGPT desktop app's codex app-server started bursting keyring reads. The same-day glib and keyring upgrades were a coincidence, since 50.0 reproduces it too. Upstream's fix (merge request !112) has sat unmerged since August.
packages/gnome-keyring is Arch's recipe for 1:51.1-1 built from GNOME's tarball plus one patch. It registers the client at the point of use and returns a real D-Bus error instead of completing OpenSession with a NULL body. It resolves a collection alias only after the caller's session exists. It also locks the alias table so the worker-thread filter can't read an entry the main thread is freeing, a pre-existing defect that AddressSanitizer confirmed. I chose pkgrel 1.1 so stock sorts below the rebuild and any future Arch or upstream fix replaces it on a normal upgrade. Nothing pins the package.
install_gnome_keyring_fix replaces the plain stock install, which pacman's --needed would otherwise use to downgrade the rebuild on every re-run. It installs stock only when no gnome-keyring is present, compares versions with vercmp, then stages the recipe into the source dir and builds it with makepkg as the user. The rebuild is a no-op, anything newer skips with a visible note, and a failed build leaves stock in place. I also corrected the keyring-dir comment: the default alias only picks the collection and doesn't unlock anything under autologin.
working/keyring-crash holds the probe, the guest fixture and runners, and the deployment script with backup and rollback. status.org there records the cause and results. Stock aborts on the probe's first batch. The patched daemon, its sanitizer build and the installed package pass every mode, and the upstream suite scores the same for both.
Diffstat (limited to 'packages')
4 files changed, 422 insertions, 0 deletions
diff --git a/packages/gnome-keyring/0001-daemon-Add-Cinnamon-to-autostart-files.patch b/packages/gnome-keyring/0001-daemon-Add-Cinnamon-to-autostart-files.patch new file mode 100644 index 0000000..980d537 --- /dev/null +++ b/packages/gnome-keyring/0001-daemon-Add-Cinnamon-to-autostart-files.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Jan Alexander Steffens (heftig)" <heftig@archlinux.org> +Date: Sat, 21 May 2022 18:47:23 +0000 +Subject: [PATCH] daemon: Add Cinnamon to autostart files + +Cinnamon also wants to use gnome-keyring-daemon. +--- + daemon/gnome-keyring-pkcs11.desktop.in.in | 2 +- + daemon/gnome-keyring-secrets.desktop.in.in | 2 +- + daemon/gnome-keyring-ssh.desktop.in.in | 2 +- + 3 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/daemon/gnome-keyring-pkcs11.desktop.in.in b/daemon/gnome-keyring-pkcs11.desktop.in.in +index 3e36603fac70..bb729caddfd1 100644 +--- a/daemon/gnome-keyring-pkcs11.desktop.in.in ++++ b/daemon/gnome-keyring-pkcs11.desktop.in.in +@@ -3,7 +3,7 @@ Type=Application + Name=Certificate and Key Storage + Comment=GNOME Keyring: PKCS#11 Component + Exec=@bindir@/gnome-keyring-daemon --start --components=pkcs11 +-OnlyShowIn=GNOME;Unity;MATE; ++OnlyShowIn=GNOME;Unity;MATE;Cinnamon; + NoDisplay=true + X-GNOME-Autostart-Phase=PreDisplayServer + X-GNOME-AutoRestart=false +diff --git a/daemon/gnome-keyring-secrets.desktop.in.in b/daemon/gnome-keyring-secrets.desktop.in.in +index 799caeea4306..6c836a2a7ec9 100644 +--- a/daemon/gnome-keyring-secrets.desktop.in.in ++++ b/daemon/gnome-keyring-secrets.desktop.in.in +@@ -3,7 +3,7 @@ Type=Application + Name=Secret Storage Service + Comment=GNOME Keyring: Secret Service + Exec=@bindir@/gnome-keyring-daemon --start --components=secrets +-OnlyShowIn=GNOME;Unity;MATE; ++OnlyShowIn=GNOME;Unity;MATE;Cinnamon; + NoDisplay=true + X-GNOME-Autostart-Phase=PreDisplayServer + X-GNOME-AutoRestart=false +diff --git a/daemon/gnome-keyring-ssh.desktop.in.in b/daemon/gnome-keyring-ssh.desktop.in.in +index fd7657a4d0cc..8f9847033507 100644 +--- a/daemon/gnome-keyring-ssh.desktop.in.in ++++ b/daemon/gnome-keyring-ssh.desktop.in.in +@@ -3,7 +3,7 @@ Type=Application + Name=SSH Key Agent + Comment=GNOME Keyring: SSH Agent + Exec=@bindir@/gnome-keyring-daemon --start --components=ssh +-OnlyShowIn=GNOME;Unity;MATE; ++OnlyShowIn=GNOME;Unity;MATE;Cinnamon; + X-GNOME-Autostart-Phase=PreDisplayServer + X-GNOME-AutoRestart=false + X-GNOME-Autostart-Notify=true diff --git a/packages/gnome-keyring/0002-secret-service-missing-client.patch b/packages/gnome-keyring/0002-secret-service-missing-client.patch new file mode 100644 index 0000000..9ef298b --- /dev/null +++ b/packages/gnome-keyring/0002-secret-service-missing-client.patch @@ -0,0 +1,275 @@ +diff -ruN a/daemon/dbus/gkd-secret-objects.c b/daemon/dbus/gkd-secret-objects.c +--- a/daemon/dbus/gkd-secret-objects.c 2026-09-17 15:41:00.000000000 -0500 ++++ b/daemon/dbus/gkd-secret-objects.c 2026-10-17 06:47:19.275009074 -0500 +@@ -430,7 +430,11 @@ + + /* The session we're using to access the object */ + session = gkd_secret_service_get_pkcs11_session (self->service, sender); +- g_return_val_if_fail (session, FALSE); ++ if (session == NULL) { ++ g_free (c_ident); ++ g_free (i_ident); ++ goto out; ++ } + + if (i_ident) { + gck_builder_add_ulong (&builder, CKA_CLASS, CKO_SECRET_KEY); +@@ -1055,17 +1059,23 @@ + if (!gkd_secret_util_parse_path (path, &identifier, NULL)) + return NULL; + +- if (g_str_has_prefix (path, SECRET_ALIAS_PREFIX)) +- real_identifier = gkd_secret_service_get_alias (self->service, identifier); +- else +- real_identifier = identifier; +- +- /* The session we're using to access the object */ ++ /* The session we're using to access the object. Resolve it before the ++ * alias: fetching a caller's session may register the caller, which ++ * refreshes the default alias and frees the string the alias table ++ * held, so an alias looked up earlier would dangle here. */ + if (caller == NULL) + session = gkd_secret_service_internal_pkcs11_session (self->service); + else + session = gkd_secret_service_get_pkcs11_session (self->service, caller); +- g_return_val_if_fail (session, NULL); ++ if (session == NULL) { ++ g_free (identifier); ++ return NULL; ++ } ++ ++ if (g_str_has_prefix (path, SECRET_ALIAS_PREFIX)) ++ real_identifier = gkd_secret_service_get_alias (self->service, identifier); ++ else ++ real_identifier = identifier; + + gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_COLLECTION); + gck_builder_add_string (&builder, CKA_ID, real_identifier); +diff -ruN a/daemon/dbus/gkd-secret-service.c b/daemon/dbus/gkd-secret-service.c +--- a/daemon/dbus/gkd-secret-service.c 2026-09-17 15:41:00.000000000 -0500 ++++ b/daemon/dbus/gkd-secret-service.c 2026-10-17 06:47:19.274715149 -0500 +@@ -137,6 +137,9 @@ + GHashTable *clients; + GkdSecretObjects *objects; + GHashTable *aliases; ++ /* The message filter reads aliases on the GDBus worker thread while ++ * the main thread updates them; every access goes through this lock. */ ++ GMutex aliases_lock; + GckSession *internal_session; + gchar *default_path; + }; +@@ -190,6 +193,18 @@ + return g_build_filename (alias_directory, "default", NULL); + } + ++/* An owned copy of an alias, safe to call from any thread. */ ++static gchar * ++lookup_alias_copy (GkdSecretService *self, const gchar *alias) ++{ ++ gchar *copy; ++ ++ g_mutex_lock (&self->aliases_lock); ++ copy = g_strdup (g_hash_table_lookup (self->aliases, alias)); ++ g_mutex_unlock (&self->aliases_lock); ++ return copy; ++} ++ + static void + update_default (GkdSecretService *self) + { +@@ -206,21 +221,31 @@ + /* Default to to 'login' if no default keyring */ + if (contents == NULL) + contents = g_strdup ("login"); +- g_hash_table_replace (self->aliases, g_strdup ("default"), contents); ++ ++ /* Replace only on a real change: callers on the main thread hold the ++ * previous value borrowed, and the common case (another client ++ * registering) must not free it out from under them. */ ++ g_mutex_lock (&self->aliases_lock); ++ if (g_strcmp0 (g_hash_table_lookup (self->aliases, "default"), contents) == 0) ++ g_free (contents); ++ else ++ g_hash_table_replace (self->aliases, g_strdup ("default"), contents); ++ g_mutex_unlock (&self->aliases_lock); + } + + static void + store_default (GkdSecretService *self) + { + GError *error = NULL; +- const gchar *identifier; ++ gchar *identifier; + +- identifier = g_hash_table_lookup (self->aliases, "default"); ++ identifier = lookup_alias_copy (self, "default"); + if (!identifier) + return; + + if (!g_file_set_contents (self->default_path, identifier, -1, &error)) + g_message ("couldn't store default keyring: %s", egg_error_message (error)); ++ g_free (identifier); + } + + static gboolean +@@ -331,7 +356,7 @@ + GDBusMessage *message) + { + const char *path = g_dbus_message_get_path (message); +- const char *replace; ++ char *replace; + char *collection = NULL, *item = NULL; + char *collection_path, *item_path; + GDBusMessage *rewritten; +@@ -346,7 +371,9 @@ + if (!gkd_secret_util_parse_path (path, &collection, &item)) + return message; + +- replace = gkd_secret_service_get_alias (self, collection); ++ /* This runs on the GDBus worker thread: take a copy under the lock ++ * rather than borrowing from a table the main thread may be updating. */ ++ replace = lookup_alias_copy (self, collection); + if (!replace) { + g_free (item); + g_free (collection); +@@ -356,6 +383,9 @@ + rewritten = g_dbus_message_copy (message, &error); + if (error != NULL) { + g_error_free (error); ++ g_free (replace); ++ g_free (item); ++ g_free (collection); + return message; + } + +@@ -372,6 +402,7 @@ + } + + g_free (collection_path); ++ g_free (replace); + g_free (item); + g_free (collection); + g_object_unref (message); +@@ -424,18 +455,30 @@ + GError *error = NULL; + const gchar *caller; + GVariant *input_payload; ++ gboolean opened; + + caller = g_dbus_method_invocation_get_sender (invocation); + + /* Now we can create a session with this information */ + session = gkd_secret_session_new (self, caller); + input_payload = g_variant_get_variant (input); +- gkd_secret_session_handle_open (session, algorithm, input_payload, +- &output, &result, +- &error); ++ opened = gkd_secret_session_handle_open (session, algorithm, input_payload, ++ &output, &result, ++ &error); + g_variant_unref (input_payload); + +- if (error != NULL) { ++ /* Branch on the return value, not on the error alone: a negotiate helper ++ * that bails out through g_return_val_if_fail() reports failure without ++ * ever setting one, and completing the call with a NULL body makes ++ * g_variant_new() abort the whole daemon. */ ++ if (!opened || output == NULL || result == NULL) { ++ if (error == NULL) ++ g_set_error_literal (&error, G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Couldn't open a session for this caller."); ++ if (output != NULL) ++ g_variant_unref (g_variant_ref_sink (output)); ++ g_free (result); + g_dbus_method_invocation_take_error (invocation, error); + } else { + gkd_secret_service_publish_dispatch (self, caller, +@@ -986,6 +1029,7 @@ + static void + gkd_secret_service_init_aliases (GkdSecretService *self) + { ++ g_mutex_init (&self->aliases_lock); + self->aliases = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert (self->aliases, g_strdup ("session"), g_strdup ("session")); + /* TODO: We should be using CKA_G_LOGIN_COLLECTION */ +@@ -1151,6 +1195,7 @@ + + g_hash_table_destroy (self->aliases); + self->aliases = NULL; ++ g_mutex_clear (&self->aliases_lock); + + g_free (self->default_path); + self->default_path = NULL; +@@ -1283,7 +1328,18 @@ + g_return_val_if_fail (caller, NULL); + + client = g_hash_table_lookup (self->clients, caller); +- g_return_val_if_fail (client, NULL); ++ if (client == NULL) { ++ /* The record is normally created by a high priority idle queued from ++ * the message filter, but that idle is not ordered against the method ++ * call that queued it: a peer which connects and calls straight away ++ * can reach us first. Create it here instead of failing the call -- ++ * it is a hash table insert, not I/O. Callers must not hold borrowed ++ * alias strings across this call: registering a client refreshes the ++ * default alias. */ ++ gkd_secret_service_ensure_client (self, caller); ++ client = g_hash_table_lookup (self->clients, caller); ++ g_return_val_if_fail (client, NULL); ++ } + + /* Open a new session if necessary */ + if (!client->pkcs11_session) { +@@ -1399,7 +1455,9 @@ + g_return_if_fail (GKD_SECRET_IS_SERVICE (self)); + g_return_if_fail (alias); + ++ g_mutex_lock (&self->aliases_lock); + g_hash_table_replace (self->aliases, g_strdup (alias), g_strdup (identifier)); ++ g_mutex_unlock (&self->aliases_lock); + + if (g_str_equal (alias, "default")) + store_default (self); +@@ -1418,6 +1476,10 @@ + + /* Take ownership of the session */ + client = g_hash_table_lookup (self->clients, caller); ++ if (client == NULL) { ++ gkd_secret_service_ensure_client (self, caller); ++ client = g_hash_table_lookup (self->clients, caller); ++ } + g_return_if_fail (client); + path = gkd_secret_dispatch_get_object_path (object); + g_return_if_fail (!g_hash_table_lookup (client->dispatch, path)); +diff -ruN a/daemon/dbus/gkd-secret-session.c b/daemon/dbus/gkd-secret-session.c +--- a/daemon/dbus/gkd-secret-session.c 2026-09-17 15:41:00.000000000 -0500 ++++ b/daemon/dbus/gkd-secret-session.c 2026-10-17 06:47:19.270295281 -0500 +@@ -183,7 +183,12 @@ + gboolean ret; + + session = gkd_secret_service_get_pkcs11_session (self->service, self->caller); +- g_return_val_if_fail (session, FALSE); ++ if (session == NULL) { ++ g_set_error_literal (error_out, G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Couldn't open a session for this caller."); ++ return FALSE; ++ } + + if (!aes_create_dh_keys (session, "ietf-ike-grp-modp-1024", &pub, &priv)) { + g_set_error_literal (error_out, G_DBUS_ERROR, +@@ -250,7 +255,12 @@ + GckSession *session; + + session = gkd_secret_service_get_pkcs11_session (self->service, self->caller); +- g_return_val_if_fail (session, FALSE); ++ if (session == NULL) { ++ g_set_error_literal (error_out, G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Couldn't open a session for this caller."); ++ return FALSE; ++ } + + gck_builder_add_ulong (&builder, CKA_CLASS, CKO_SECRET_KEY); + gck_builder_add_ulong (&builder, CKA_KEY_TYPE, CKK_G_NULL); diff --git a/packages/gnome-keyring/PKGBUILD b/packages/gnome-keyring/PKGBUILD new file mode 100644 index 0000000..a8e32a2 --- /dev/null +++ b/packages/gnome-keyring/PKGBUILD @@ -0,0 +1,82 @@ +# Local rebuild of Arch's gnome-keyring 1:51.1-1 with the Secret Service +# missing-client fix applied. I build from GNOME's release tarball (checksum +# published by GNOME) instead of the signed git tag so the build does not need +# the maintainer's key in the keyring of whichever machine builds it. pkgrel +# 1.1 sorts above the stock 1:51.1-1 and below any future 1:51.1-2, so the +# next upstream or Arch fix replaces it on a normal upgrade. +# Maintainer: Jan Alexander Steffens (heftig) <heftig@archlinux.org> +# Contributor: Jan De Groot <jgc@archlinux.org> + +pkgname=gnome-keyring +pkgver=51.1 +pkgrel=1.1 +epoch=1 +pkgdesc="Stores passwords and encryption keys (Secret Service missing-client fix)" +url="https://gitlab.gnome.org/GNOME/gnome-keyring" +arch=(x86_64) +license=( + GPL-2.0-or-later + LGPL-2.1-or-later +) +depends=( + dconf + gcr + gcr-4 + glib2 + glibc + libcap-ng + libgcc + libgcrypt + pam + systemd-libs +) +makedepends=( + docbook-xsl + git + glib2-devel + meson + p11-kit + python +) +provides=(org.freedesktop.secrets) +groups=(gnome) +install=gnome-keyring.install +source=( + "https://download.gnome.org/sources/gnome-keyring/${pkgver%%.*}/gnome-keyring-$pkgver.tar.xz" + 0001-daemon-Add-Cinnamon-to-autostart-files.patch + 0002-secret-service-missing-client.patch +) +sha256sums=('dfe7adc5876ef0670e7eba2907799bdbb8875ce58bb4208e1a52169bd16a47ca' + '138ad9a41dcbf9faede3a8b7fcb61dd5894ca8b66599802e58f8bb81e9607eb2' + '2463bf109af415412bf99e9aca98c51f215cfdcc9de728bbf7155db861bf79d7') + +prepare() { + cd gnome-keyring-$pkgver + + # Autolaunch in Cinnamon (Arch's own patch, unchanged) + patch -Np1 -i ../0001-daemon-Add-Cinnamon-to-autostart-files.patch + # Register Secret Service clients at the point of use, return real errors + # instead of completing OpenSession with a NULL body, and resolve the + # collection alias only after the caller's session exists. + patch -Np1 -i ../0002-secret-service-missing-client.patch +} + +build() { + local meson_options=( + -D selinux=disabled + ) + + arch-meson gnome-keyring-$pkgver build "${meson_options[@]}" + meson compile -C build +} + +check() { + # Secure memory tests fail + dbus-run-session meson test -C build --print-errorlogs || : +} + +package() { + meson install -C build --no-rebuild --destdir "$pkgdir" +} + +# vim:set sw=2 sts=-1 et: diff --git a/packages/gnome-keyring/gnome-keyring.install b/packages/gnome-keyring/gnome-keyring.install new file mode 100644 index 0000000..6143dd1 --- /dev/null +++ b/packages/gnome-keyring/gnome-keyring.install @@ -0,0 +1,14 @@ +post_install() { + # Enable socket by default + systemctl --global enable gnome-keyring-daemon.socket +} + +post_upgrade() { + if (( $(vercmp $2 1:42.0-1) < 0)); then + systemctl --global enable gnome-keyring-daemon.socket + fi +} + +pre_remove() { + systemctl --global disable gnome-keyring-daemon.socket +} |
