1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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.
|