fix(a7-auth): harden dummy-key temp creation
- Make the atomic-publish temp name unpredictable by appending 16 random hex chars to the pid, so a leftover/planted temp cannot be targeted. - On EEXIST, unlink the stale temp and retry the O_EXCL create once (bounded), so a crash leftover or reused pid cannot silently defeat sidecar persistence. - fchmod the temp fd to 0600 after creation (umask can clear owner bits) and treat failure as a create failure, so the published sidecar is always exactly 0600. - Clarify comments: the sidecar requires exact 0600 while the store and password files only reject group/other bits. - Add a unit test that a restrictive umask still yields an exact 0600 sidecar; clean random-suffixed temps in tests.
This commit is contained in:
@@ -26,14 +26,16 @@
|
||||
* hard-rejected with an actionable "legacy" error; there is no auto-upgrade.
|
||||
* Use `fastsync-server --hash-credentials` to generate new-format lines.
|
||||
*
|
||||
* Alongside the store, credentials_load maintains an owner-only (0600)
|
||||
* Alongside the store, credentials_load maintains an exact-mode-0600
|
||||
* `<store_path>.dummykey` sidecar holding the store-wide random dummy key. It
|
||||
* is auto-created on first load and MUST be preserved across restarts: it makes
|
||||
* the dummy challenge for an unknown user stable for the life of the store, so
|
||||
* a daemon restart cannot be used as a username-enumeration oracle. A sidecar
|
||||
* that is not an owner-only regular file of exactly 32 bytes fails the load
|
||||
* (fail closed); if it cannot be created (e.g. a read-only mount) the daemon
|
||||
* warns and uses a transient per-run key instead.
|
||||
* that is not an exact-mode-0600 regular file of exactly 32 bytes fails the load
|
||||
* (fail closed); if it cannot be created (e.g. a read-only mount or a restrictive
|
||||
* umask the fchmod cannot repair) the daemon warns and uses a transient per-run
|
||||
* key instead. NOTE: the sidecar requires EXACT 0600, whereas the store /
|
||||
* password files only reject group/other bits (a deliberate difference).
|
||||
*
|
||||
* Client --password-file format: the FIRST meaningful (non-comment, non-blank)
|
||||
* line is `user:password`, holding the literal password. The client keeps it
|
||||
@@ -165,8 +167,9 @@ bool credentials_verify_response(const CredentialVerifier* v, const char* user,
|
||||
bool credentials_hash_store_line(const char* user, const char* password, uint32_t iters, char* out,
|
||||
size_t out_sz, char* err, size_t err_size);
|
||||
|
||||
/* Read `user:password` lines from `path` (the same owner-only check as the
|
||||
* other secret files) and write one new-format store line per entry to `out`.
|
||||
/* Read `user:password` lines from `path` (the same no-group/other-bits check as
|
||||
* the other secret files) and write one new-format store line per entry to
|
||||
* `out`.
|
||||
* Blank/comment lines are skipped; a malformed line fails the whole run.
|
||||
* Returns 0 on success, -1 on error (err filled). Used by
|
||||
* `--hash-credentials`. */
|
||||
|
||||
Reference in New Issue
Block a user