aboutsummaryrefslogtreecommitdiff
path: root/working/keyring-crash/status.org
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-10-17 07:07:25 -0500
committerCraig Jennings <c@cjennings.net>2026-10-17 07:07:25 -0500
commitcd6e11b46ecf2b56bf63d6c11886dccc70875e10 (patch)
treee2d17ef968ae24ef1b65babaadfc7637cc2024bb /working/keyring-crash/status.org
parente530fd597b62bda520f9804be7da216eea582750 (diff)
downloadarchsetup-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 'working/keyring-crash/status.org')
-rw-r--r--working/keyring-crash/status.org118
1 files changed, 118 insertions, 0 deletions
diff --git a/working/keyring-crash/status.org b/working/keyring-crash/status.org
new file mode 100644
index 0000000..aa0a952
--- /dev/null
+++ b/working/keyring-crash/status.org
@@ -0,0 +1,118 @@
+#+TITLE: Keyring crash: cause, fix, validation, deployment
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-10-17
+#+STARTUP: showall
+
+Supersedes the status in the main checkout's status-and-plan.org from 10-16.
+Everything below was done in a disposable VM and this worktree; the host
+keyring, its packages and services were untouched until the deployment step.
+
+* Cause
+
+gnome-keyring aborts when a Secret Service method call is dispatched before
+the daemon has created the caller's per-client record. The record is created
+by a G_PRIORITY_HIGH idle queued from a GDBus message filter on the worker
+thread; that idle is not reliably ordered against the method dispatch, so
+under concurrent bursts a call occasionally arrives first, get_pkcs11_session
+asserts, the negotiate helper returns FALSE without a GError, OpenSession
+completes with a NULL body, and g_variant_new aborts the daemon. systemd
+restarts it, the unlocked state is gone, and the next request shows the
+keyring password dialog.
+
+This is upstream bug 190 (also 194, 195, 196, 199), present in 48 through
+51.1, unmerged fix in merge request !112 since 2026-08-25. On ratio the first
+abort was 2026-10-11 19:03:59, the evening the ChatGPT desktop app was
+installed; openai/codex issue 51694 documents its bundled app-server opening
+a fresh Secret Service connection per keyring read in bursts and crashing
+gnome-keyring with exactly this signature. The same-day glib and keyring
+upgrades were a coincidence: the VM reproduced the crash on 50.0 as well.
+
+A second, pre-existing defect surfaced while testing the fix: the filter's
+alias rewrite reads the alias table on the worker thread while the main
+thread replaces and frees its entries on every client registration.
+AddressSanitizer confirmed it as a heap-use-after-free read in
+gkd_secret_util_build_path from rewrite_default_alias, freed by
+g_hash_table_replace in update_default. Stock and !112 both carry it.
+
+* Fix (packages/gnome-keyring, patch 0002)
+
+- Register the client at the point of use in get_pkcs11_session and
+ publish_dispatch (as !112).
+- aes_negotiate and plain_negotiate return G_DBUS_ERROR_FAILED instead of
+ asserting; OpenSession branches on the return value and never completes
+ with a NULL body (as !112).
+- lookup_collection takes the caller's PKCS#11 session before resolving an
+ alias, so the lazy registration cannot free a borrowed alias string (the
+ earlier reviewer's use-after-free finding).
+- The property-path lookup returns NO_SUCH_OBJECT instead of asserting when
+ there is no session (upstream 195).
+- A mutex guards the alias table; the worker-thread filter and store_default
+ take owned copies under it; the default alias is replaced only when its
+ value changes so main-thread borrowers stay valid.
+
+Packaged as Arch's own recipe for 1:51.1-1 (Cinnamon patch kept, arch-meson,
+selinux disabled) built from GNOME's release tarball (published sha256
+dfe7adc5876ef0670e7eba2907799bdbb8875ce58bb4208e1a52169bd16a47ca) plus the
+patch, at pkgrel 1.1: above stock, below any future 1:51.1-2 or 51.2, so a
+normal upgrade replaces it when a real fix lands. Nothing pins or holds it.
+
+* Validation
+
+Probe (working/keyring-crash/probe.py): real GIO clients, seven modes, every
+reply validated, a daemon round trip after each batch of 100, the synthetic
+secret read back after each mode, bus owner and RSS recorded. Counts: 4000
+clients at 32 workers with bursts of 8 for plain, dh, invalid, disconnect and
+pipeline; 2000 for alias and isolation; alias also at 8000.
+
+| Daemon | Result |
+|-----------------------------+-----------------------------------------------------|
+| stock 51.1 | aborts on the first plain batch, collection locked, |
+| | ratio's exact journal signature (three runs) |
+|-----------------------------+-----------------------------------------------------|
+| v4 (lazy registration only) | passes all modes; alias at 8000 returns 9 bad paths |
+| | and the filter asserts on a malformed object path |
+|-----------------------------+-----------------------------------------------------|
+| v4 under ASan | heap-use-after-free in rewrite_default_alias |
+|-----------------------------+-----------------------------------------------------|
+| v5 and v6, normal and ASan | all modes pass, alias at 8000 passes, no sanitizer |
+| | report, stable owner, RSS flat, secret intact |
+|-----------------------------+-----------------------------------------------------|
+| v6 package via pacman -U | all modes pass through the installed /usr/bin daemon|
+|-----------------------------+-----------------------------------------------------|
+
+Independent review of v5 (patch, pristine and patched trees, prior reviewer's
+requirements, no rationale): approve, no memory-safety defect after checking
+every caller. Two minor notes taken in v6 (store_default under the lock, plain
+NULL return); two declined as pre-existing and bounded (a late dispatch after
+NameOwnerChanged leaks one PKCS#11 session per vanished peer, unique names
+never recur; the remove_filter shutdown race).
+
+Upstream meson suites, identical options and a private bus, run back to back:
+stock and v6 both 56 ok, 1 fail, 5 timeouts, the same names in both
+(gnome2-store import fails intermittently; stock scored 57 ok and 0 fail on
+its first run; the five dbus fixture tests time out identically). No
+regression attributable to the patch. Arch's own recipe ignores this suite's
+failures, which is why it is a comparison here and not a gate.
+
+Installer: install_gnome_keyring_fix runs after pacman_install gnome-keyring,
+builds and installs the recipe with makepkg as the user when stock 51.1 (or
+older) is installed, is a no-op on the rebuild, and skips with a visible
+warning when something newer is installed. Eleven tests in
+tests/installer-steps/test_install_gnome_keyring_fix.py; the fast suite (619
+Python plus 17 ERT tests) passes. The false autologin comment on the
+keyring-dir block is corrected.
+
+* Deployment
+
+working/keyring-crash/deploy-ratio.sh: build (done on ratio 06:53, package at
+~/.cache/archsetup-build/gnome-keyring/), install (permission-protected copy
+of ~/.local/share/keyrings, pacman -U, restart only
+gnome-keyring-daemon.service, verify), rollback (pacman -U of the cached stock
+1:51.1-1, restart), verify. One unlock prompt is expected after the restart;
+answer it in the system dialog. The same script applies to velox once it is
+online, after confirming its versions match.
+
+Observation period: the stock daemon aborted about three times a day on
+ratio. A restart counter that stays flat for several days of normal ChatGPT
+and Claude desktop use is the evidence of lasting stability; a short check
+after install is not.