fix(a7-auth): publish dummy-key sidecar atomically and harden reads

Address review findings on the persistent dummy-key sidecar:

- Publish atomically: write a private same-directory temp file
  (<store>.dummykey.tmp.<pid>, 0600), fsync, then link(2) into place;
  fsync the containing directory and drop the temp name. A concurrent
  starter can no longer observe a zero/partial sidecar and fail closed.
  On EEXIST adopt the winner's sidecar; otherwise warn and use a
  transient ephemeral key.
- Harden the read path (initial and EEXIST-adopt) with
  O_RDONLY|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC: reject planted symlinks
  (ELOOP fails closed) and never block on a planted FIFO.
- Require the exact owner-only mode (st_mode & 07777) == 0600 and make
  the rejection message truthful.
- Report a clear "short write" instead of a stale strerror(errno) when
  write() returns 0.
- Document the artifact and its creation-failure caveat (FIFO store
  path, read-only filesystem, missing directory) in README.md and
  RSYNC_COMPAT.md.
- Tests: known-key sidecar adoption (dummy salt KAT + reload), symlink
  rejection, and the exact-0600 rule (0400 now rejected).
This commit is contained in:
2026-09-12 19:16:11 +02:00
parent 42f01c0968
commit f0381a6b8e
4 changed files with 254 additions and 43 deletions
+5 -1
View File
@@ -526,7 +526,11 @@ redirect that output to an owner-only (mode 0600) file, and note that legacy
(mode 0600) `<store>.dummykey` sidecar next to the store: it holds the store-wide
dummy key, is auto-created on first load, and must be preserved across daemon
restarts so the dummy challenge for an unknown user stays stable (the key is
never regenerated while the sidecar exists). One residual is accepted: the store
never regenerated while the sidecar exists). If the sidecar cannot be created
(process-substitution/FIFO store path such as `/dev/fd/N`, a read-only
filesystem, or a missing directory), the daemon logs a warning and uses a
transient key, so the cross-restart guarantee does not hold for those
deployments. One residual is accepted: the store
iteration count is observable pre-auth by design, since the miss path must match
a hit.