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
+12 -4
View File
@@ -1248,11 +1248,19 @@ static bool file_to_disk_secure_link_impl(const char* path, const char* basis_pa
if (linked) {
int target_dirfd = scratch_dirfd >= 0 ? scratch_dirfd : dirfd;
if (use_fsync) {
int tfd = openat(target_dirfd, tmp, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (tfd < 0 || fsync(tfd) != 0) {
/* O_NONBLOCK: the freshly linked temp is normally the basis's regular
file, but a raced-in FIFO at the name must not block this reopen
forever. With O_NONBLOCK such an open fails with ENXIO instead of
blocking, which is treated as a benign fsync-skip (the link itself
is still installed); any other open/fsync failure falls back to the
byte-copy path as before. */
int tfd = openat(target_dirfd, tmp, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
if (tfd < 0) {
if (errno != ENXIO)
linked = false;
} else if (fsync(tfd) != 0) {
linked = false;
if (tfd >= 0)
close(tfd);
close(tfd);
} else {
close(tfd);
}