fix(receiver): close re-review findings — dry-run basis oracle, ACL capture, fsync reopen

Follow-up to a237043 addressing three security/correctness re-review findings.

(1) MEDIUM: a server-contacting --dry-run with --compare-dest/--copy-dest/
    --link-dest still read and hashed the basis file and compared it with the
    client-supplied digest, a 1-bit content oracle. basis_match_find() gains a
    hash_content parameter; the dry-run shortcut passes false and returns no
    match without touching basis bytes, so an otherwise-matching entry is
    reported as would-transfer. The real (non-dry-run) path is unchanged.

(2) LOW: xattr_capture_path() hardcoded preserve_acls=true, so the receiver's
    hard-link copy fallback re-applied system.posix_acl_* even when -A was not
    negotiated. The function now takes preserve_acls and members.* is
    unaffected; scanner and receiver callers thread the negotiated flag.

(3) INFO: the --fsync --link-dest temp reopen now uses O_NONBLOCK and treats
    a raced-in FIFO's ENXIO as a benign fsync-skip instead of blocking.

Tests: dry-run + basis unit test (asserts would-transfer, no content read) and
integration test; xattr capture ACL-filter test. Verified strict build, ASan,
clang-format, cppcheck, and the CI integration subset.
This commit is contained in:
2026-09-14 17:19:27 +02:00
parent a2370433b2
commit 5893de4a34
8 changed files with 240 additions and 23 deletions
+4 -3
View File
@@ -116,7 +116,7 @@ static bool xattr_name_is_posix_acl(const char* name) {
/* ---- SENDER: capture ---- */
FileXattrList* xattr_capture_path(const char* path) {
FileXattrList* xattr_capture_path(const char* path, bool preserve_acls) {
if (!path)
return NULL;
ssize_t list_size = listxattr(path, NULL, 0);
@@ -144,8 +144,9 @@ FileXattrList* xattr_capture_path(const char* path) {
break; /* trailing double NUL not expected; stop */
offset += (ssize_t)name_len + 1;
/* Capture is sender-side: the scanner has already gated on -X/-A, so the
per-name whitelist here allows the ACL names (true). */
if (!xattr_name_appliable(name, true))
per-name whitelist here allows the ACL names only when --acls was
negotiated. Without it a plain -X capture never carries an ACL. */
if (!xattr_name_appliable(name, preserve_acls))
continue;
ssize_t value_size = getxattr(path, name, NULL, 0);
if (value_size < 0)