Commit Graph
1048 Commits
Author SHA1 Message Date
TapTap 5754b9a952 Merge branch 'fix/audit-misc2' into fix/audit-cycle 2026-09-21 22:02:05 +02:00
TapTap b8a0efef7b Merge branch 'fix/audit-filter2' into fix/audit-cycle 2026-09-21 22:02:05 +02:00
TapTap 2e77c09447 Merge branch 'fix/audit-creds2' into fix/audit-cycle 2026-09-21 22:02:05 +02:00
TapTap 06c4026b74 fix(filter): accept e/n/w/- merge modifiers on merge/dir-merge rules
The earlier modifier-rejection change rejected e/n/w on all rules, but rsync
3.4.1 accepts them (plus the '-' merge-only modifier) on merge and dir-merge
rules.  Restrict the rejection to non-merge rules and consume the merge-file
modifiers (e/n/w/-) so they no longer leak into the merge filename.

- is_merge_rule()/is_merge_modifier_char() gate the merge-only modifiers.
- scan vs consume sets: e/n/w still count as modifier-run chars on every rule
  (pure tokens like -new/-press stay rejected), but are only consumed on merge
  rules, preserving mixed-token parsing such as H,!secret -> ecret.
- '-' is accepted/consumed only on merge/dir-merge (e.g. dir-merge,- .rules).
- x remains rejected everywhere with its dedicated message.
- e/n/w/- semantics remain unimplemented and are documented as accepted-but-
  ignored in filter.h.

Tests: split the merge forms out of the rejection test into a new acceptance
test asserting the merge file is read and dir_merge_names keeps the modifier-
free basename; non-merge pure-modifier forms still rejected.
2026-09-21 22:01:44 +02:00
TapTap 9f47b13712 fix(credentials): bound-wait on FIFO reads so slow process substitution works
secret_file_open() opened secret files with O_NONBLOCK and only cleared it
for S_ISREG, so on a FIFO/process-substitution source (--password-file
<(...), --early-input <(...)) fgets() failed immediately with EAGAIN when
the writer had not yet produced data, breaking slow producers.

Keep O_NONBLOCK at open() (a writer-less FIFO must not block the open) and
route all three readers through a new secret_read_line() helper.  It
accumulates a line across reads and, on EAGAIN/EWOULDBLOCK (or a partial
line) with no newline and no EOF, clearerr()s and polls for readability
against one overall CLOCK_MONOTONIC deadline of
CREDENTIAL_FIFO_READ_TIMEOUT_MS (3000 ms); on timeout or a real read error
it fails with a clear message.  EOF finishes normally.  Regular files are
left blocking and read exactly as before.

Handles a line split across several write()s and keeps the owner/mode
fstat gate, O_NOFOLLOW and the /dev/fd/N exception unchanged.
2026-09-21 22:01:18 +02:00
TapTap b1eddf0133 fix: config leak, compression log, inplace+partial-dir rejection, umask/root test fixes 2026-09-21 21:59:58 +02:00
TapTap 338c27db73 fix: resolve cppcheck shadow/always-true findings 2026-09-21 21:28:36 +02:00
TapTap 8d46a26c04 Merge branch 'fix/audit-docs2' into fix/audit-cycle 2026-09-21 21:18:48 +02:00
TapTap 707ba272df Merge branch 'fix/audit-docs1' into fix/audit-cycle 2026-09-21 21:18:48 +02:00
TapTap bc71a3c0a5 docs: correct README build deps, delete defaults, scanner, flags; AGENTS deps/CI 2026-09-21 21:18:26 +02:00
TapTap cee9b7647c docs: fix parity tally, compat rows, changelog, handoff for audit cycle 2026-09-21 21:14:37 +02:00
TapTap 3adb6dddb5 chore: drop tracked scratch data and extend .gitignore 2026-09-21 21:11:04 +02:00
TapTap d77849774e Merge branch 'fix/audit-refb' into fix/audit-cycle 2026-09-21 21:09:20 +02:00
TapTap b5c6f8b60f Merge branch 'fix/audit-refa' into fix/audit-cycle 2026-09-21 21:09:20 +02:00
TapTap 134dcd74cc refactor: drop dead filter_rules_apply, unify set_error, dedup path_is_within 2026-09-21 21:09:05 +02:00
TapTap 42f2f845ac refactor: drop Config**, dead old-args plumbing, dedup constants, -Wformat-signedness 2026-09-21 19:50:33 +02:00
TapTap 0669335ca5 Merge branch 'fix/audit-logfmt' into fix/audit-cycle 2026-09-21 19:43:17 +02:00
TapTap 391f76cd56 fix(log): add printf format attributes and fix format mismatches 2026-09-21 19:42:44 +02:00
TapTap 2021afe613 Merge branch 'fix/audit-fdpaths' into fix/audit-cycle 2026-09-21 19:30:21 +02:00
TapTap ba914e8ab3 fix(credentials): allow fd-backed store paths without O_NOFOLLOW 2026-09-21 19:30:01 +02:00
TapTap df8fe1ae4e Merge branch 'fix/audit-signal' into fix/audit-cycle 2026-09-21 19:27:03 +02:00
TapTap 2c490d58b7 Merge branch 'fix/audit-creds' into fix/audit-cycle 2026-09-21 19:27:03 +02:00
TapTap d55dabff2e Merge branch 'fix/audit-help' into fix/audit-cycle 2026-09-21 19:27:03 +02:00
TapTap b478a59a81 fix(cli): correct help text, per-codec compression default, and stale test 2026-09-21 19:26:42 +02:00
TapTap 865941f850 fix(credentials): open secret files with O_NOFOLLOW|O_NONBLOCK; drop dup includes
secret_file_open() previously opened --password-file/--early-input with
plain O_RDONLY, so a symlinked path was followed before the owner/mode
fstat gate ran, and an empty/planted FIFO could block fgets forever.
Open with O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC (mirroring the dummy-key
sidecar): ELOOP now fails closed, and a writer-less FIFO yields EOF/EAGAIN
instead of hanging. Clear O_NONBLOCK again for regular files, where it is
a no-op, so their stdio read path is unchanged.

file.c: drop the duplicate <fcntl.h>/<unistd.h> includes (kept the first
occurrences).

Tests: a symlinked password file is rejected, and a writer-less named
FIFO fails cleanly without hanging.
2026-09-21 19:25:58 +02:00
TapTap 221cefa7cc fix(signal): use sigaction and async-signal-safe handlers
Client: replace the non-async-signal-safe signal(3) call inside
client_signal_handler() with a precomputed SIG_DFL sigaction(2), which is
on the POSIX async-signal-safe list.  The handler stays installed while a
transfer is armed so a repeated Ctrl-C still leads to a graceful abort
rather than a hard kill mid-cleanup.

Server: cleanup() now only calls _exit(2) (async-signal-safe).  The former
server_delete()/daemon_conf_free()/credentials_free() teardown called
free()/close()/SSL_CTX_free() from signal context, which can deadlock or
corrupt the heap if the signal lands inside malloc/free.  Handlers are
installed with sigaction(2) instead of signal(3).  The normal shutdown
path in main() still performs the full teardown; the signal path relies on
process exit to reclaim the parent daemon's socket, anonymous shared
mapping and heap (no named/persistent parent resource is left behind).
2026-09-21 19:25:24 +02:00
TapTap bb9b59024a Merge branch 'fix/audit-tests' into fix/audit-cycle 2026-09-21 19:19:36 +02:00
TapTap 5baf120243 Merge branch 'fix/audit-misc' into fix/audit-cycle 2026-09-21 19:19:36 +02:00
TapTap 84b7e1fd2f Merge branch 'fix/audit-filterx' into fix/audit-cycle 2026-09-21 19:19:36 +02:00
TapTap 800a978e15 Merge branch 'fix/audit-config' into fix/audit-cycle 2026-09-21 19:19:36 +02:00
TapTap 0f40e747f0 fix(filter): reject the x modifier in the --filter list parser
The standalone filter_rule_parse() already rejected the rsync xattr-name
'x' modifier, but the list parser used by --filter/-f silently dropped the
flag for merge/dir-merge rules (and relied on a second parse for plain
rules).  Reject it explicitly in filter_list_parse_append_depth() with the
same diagnostic, so '-x', 'merge,x' and 'dir-merge,x' all fail cleanly.

Also reject the unimplemented rsync merge modifiers 'e', 'n' and 'w'
instead of folding them into the pattern, which previously produced
misleading errors such as "could not read merge file 'n file'".  Only a
token made up solely of modifier characters is treated as a modifier run,
so glued patterns ('-newfile', '-e2e') and mixed tokens ("H,!secret")
keep their historical parsing.

Adds tests/test_filter.c with focused rejection and supported-syntax
cases.
2026-09-21 19:19:06 +02:00
TapTap b07306d5bc fix(config): check ssh-dest allocation and enforce MAX_FILTER_RULES client-side 2026-09-21 18:53:07 +02:00
TapTap f2c89b6e7c fix: mutex leak, errno-after-free, log_perror misuse, status validation
- multiprocessing: destroy mutex_progress on the dir_entries_mutex
  init-failure path (init >= 7); drop bogus log_perror
- delete_plan: capture errno before free() in apply_deferred_path
- queue/array_list: log_message instead of log_perror for non-errno
  conditions
- protocol: reject unknown wire Status values via status_is_valid() in
  receive_status, receive_status_timed and the keepalive reader; declare
  protocol_receive_status_timed in protocol.h
- protocol: %llu for unsigned long long debug counters
- tests: out-of-range status rejection test
2026-09-21 18:52:46 +02:00
TapTap 379f127859 test: add client timeouts and de-flake default-port test 2026-09-21 18:50:55 +02:00
TapTap 3799200f71 Merge branch 'fix/audit-partial' into fix/audit-cycle 2026-09-21 18:45:48 +02:00
TapTap 91c4a967e6 Merge branch 'fix/audit-receiver' into fix/audit-cycle 2026-09-21 18:45:48 +02:00
TapTap 88c8968b4c Merge branch 'fix/audit-transport' into fix/audit-cycle 2026-09-21 18:45:48 +02:00
TapTap 082b31886a Merge branch 'fix/audit-compression' into fix/audit-cycle 2026-09-21 18:45:48 +02:00
TapTap 423a62e691 fix(receiver): confine --temp-dir scratch dir and gate setuid bits
Three receiver security fixes from the audit:

1. --temp-dir symlink escape (High): file_open_temp_dir() opened the
   client-controlled scratch dir with a bare open(), so a symlink planted
   under the receive root let a peer redirect receiver scratch files
   outside the authorized root.  The opened dir is now judged by the REAL
   path of its fd (via /proc/self/fd), and any target outside the
   authorized receive root is refused with a logged error (EACCES).  An
   in-root symlink (the EXDEV cross-filesystem fallback case) still works,
   and the no-root local batch path is unchanged.

2. setuid/setgid/sticky under SUPER_MODE_OFF (High): the special bits were
   applied under --perms (and via --chmod) even when the connection forbade
   super-user activities.  FileAttrPolicy gains super_permitted, set by
   file_attr_policy_from_config() from privilege_super_mode_permitted();
   metadata_mode_for_policy(), the symlink path, the special-node creation
   path, and the deferred directory-mode apply now strip the special bits
   when it is false.  Exact rsync semantics are preserved when permitted.

3. daemon umask (Low): daemonize() forced umask(0), so implied parent
   directories created without -p were world-writable 0777.  Set the
   conventional daemon umask 022 instead (rsync never forces 0); -p/-a mode
   preservation is unaffected because it restores modes via fchmod.

Tests: new unit tests for file_open_temp_dir confinement and the
masked/unmasked special-bit policy (incl. the --chmod path), a daemon
world-writable-dir regression test, an integration escape test, and a
root-only integration test asserting special bits are masked without
--allow-super.  The old cross-filesystem test encoded the vulnerable
behavior (symlink target outside the root) and is replaced by the escape
test; the EXDEV fallback code is retained for in-root links.
2026-09-21 18:45:29 +02:00
TapTap bc18ae205b fix(cli): --partial-dir implies --partial (rsync parity)
rsync 3.4.1 resolves --partial-dir after option parsing and sets keep_partial,
so --partial-dir=DIR alone retains an interrupted transfer's partial file.
FastSync only used the partial dir when --partial was also given, silently
discarding it otherwise.

Set Config->partial in cli_finalize_config whenever partial_dir is set.
Following rsync, an explicit --no-partial does NOT win (verified on rsync
3.4.1 in either option order); --inplace is guarded because it writes the
destination in place with no partial staging.

Tests: CLI unit coverage for the implication/precedence/inplace guard, and a
deterministic integration case that blocks the final install (non-empty
directory at the destination) and asserts the staged partial survives under
--partial-dir alone.
2026-09-21 18:37:39 +02:00
TapTap 10a61c6101 fix(compression): raise decompression ceiling to the protocol whole-file limit
MAX_DECOMPRESSED_SIZE was 100 MiB while the receiver advertises and the
sender compresses whole files up to MAX_RECEIVE_WHOLE_FILE_SIZE (256 MiB),
so -z on a 100-256 MiB regular file failed with 'Declared decompressed
size exceeds 104857600 bytes'.  Define the internal bomb-guard ceiling in
terms of the protocol constant so the two bounds cannot drift, and add
unit coverage for a 130 MiB payload (accepted) and an over-ceiling
declared size (still rejected).
2026-09-21 18:31:25 +02:00
TapTap bc1e1191af fix(io): pace sendfile with --bwlimit, retry poll EINTR, clamp SSL_read 2026-09-21 18:30:14 +02:00
TapTap 0fbb9de915 Merge PR #305: rsync-parity cycle 2.29 (120/10/27, no wire change)
CI / lint (push) Successful in 1m57s
CI / parity-fast (push) Skipped
CI / parity-full (push) Successful in 24s
CI / sanitizers (address) (push) Successful in 51s
CI / sanitizers (undefined) (push) Successful in 44s
CI / build-and-test (push) Successful in 1m16s
CI / fuzz-build (push) Successful in 46s
CI / coverage (push) Successful in 42s
CI / valgrind (push) Successful in 2m12s
2026-09-20 15:10:17 +02:00
TapTap 9b05972375 test: assert partial --max-delete survivor order matches rsync
CI / lint (pull_request) Successful in 1m58s
CI / parity-full (pull_request) Skipped
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
CI / parity-fast (pull_request) Successful in 18s
CI / build-and-test (pull_request) Successful in 54s
2026-09-20 15:02:51 +02:00
TapTap d119f35066 docs: record parity cycle 2.29 (120/10/27) and deferred residuals 2026-09-20 15:02:44 +02:00
TapTap 00829fd265 parity: --info=mount/stats, --stats dir breakdown, --debug categories
--info=mount now prints rsync's mount-point skip line (matching rsync
3.4.1, which emits it for repeated -xx and drops the mount-point dir);
--info=stats enables the same block as --stats; -x is repeatable.
--stats counts traversed directories for the Number of files breakdown
even when no directory metadata is captured (-r without -t/-p).
--debug enables real output for flist/del/hash/deltasum/recv/filter/send
at their natural FastSync events (synthetic categories stay inert).

--stats and --debug rows keep their documented residual status.
2026-09-20 14:55:47 +02:00
TapTap ff261bc38a test: extend rsync order parity to dry-run and delete-delay 2026-09-20 14:55:47 +02:00
TapTap b235721f8b delete: rsync-exact abort boundary and -d per-directory plans
Transmit the complete --delete-during/--delete-delay per-directory plan
set before the first data frame, so a mid-transfer abort has already
applied every planned removal like rsync's generator; completed runs are
unchanged.  Route -d/--dirs through the same per-directory plans: the
generator records only directories whose direct children it enumerated,
so extras directly inside a listed directory are removed while an
untraversed subdirectory's mirror is shielded (rsync's -d DIR/ --delete).
Also shields a -x mount point's untraversed destination content.
2026-09-20 14:22:22 +02:00
TapTap 79a28cdb96 scanner: emit entries in rsync's sorted depth-first flist order
Buffer and sort each directory's inspected entries (non-directories
ascending, then directories ascending) and walk them depth-first via a
LIFO directory stack, so the sequential scanner's stream matches rsync
3.4.1's flist order.  This makes the --info=name transfer order and the
--delete-during/--delete-delay deletion sequence byte-identical to rsync
(differential tests in test_parity_order.py); --threads stays unordered
(no rsync analogue) and is documented as such.

Adds LIFO queue_push/queue_pop over the existing ring buffer.
2026-09-20 13:49:04 +02:00
TapTap 402cae80ad parity: rsync-exact relative basis-dir resolution and fuzzy eligibility
Resolve a relative --compare-dest/--copy-dest/--link-dest DIR against the
destination directory and append the file's transfer-relative name, as
rsync 3.4.1 does, instead of appending FastSync's source-mirrored wire
path (the historical spelling stays as a fallback for existing layouts).

Stop inheriting the ordinary delta engine's 16 KiB minimum and 10x size
ratio in the -y/--fuzzy candidate search: rsync's find_fuzzy has no
delta-size gate, so an oversized or sub-16-KiB sibling is now reused.
The ordinary delta path's bounds are unchanged.
2026-09-20 13:39:04 +02:00