sigusr2_handler called output_summary() (rprintf/vsnprintf/fwrite/iconv/malloc)
and close_all() (fstat/shutdown/close) directly from signal context before
_exit(). SIGUSR2 is sent by the generator/parent to tell the receiver child to
print its summary and exit; if it interrupted the receiver while it was inside
malloc/stdio, the handler re-entered those and could deadlock or corrupt.
The handler now only sets a flag (got_sigusr2, a volatile sig_atomic_t); the
actual summary + shutdown moves to receive_sigusr2(), run from a safe point in
the receiver's post-transfer wait paths:
- the perform_io() flag checks (next to got_kill_signal);
- the safe_read()/safe_write() loops (which a --read-batch / --write-batch fd
uses without going through perform_io);
- the whine_about_eof() kluge loop, where the receiver waits out the race of
the sender dying before the kill-signal arrives -- this loop polls for the
signal, so without the flag check it slept the full 10s and then errored with
RERR_STREAMIO instead of exiting cleanly;
- the trailing `while (!got_sigusr2) msleep()` loop in do_recv().
(Leonid Bugaev May-2026 re-audit, KI-14.)
hlink.c must confirm S_ISREG before quick_check_ok(FT_REG,...) reads
F_SUM, and start_server() must set sender_keeps_checksum when a daemon
sender runs -c with a %C log format so make_file() allocates
SUM_EXTRA_CNT. Without these, F_SUM() reads past the pool slot and (for
%C) hex-encodes adjacent heap into the transfer log.
Co-authored-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
The race-safe path resolver holds one open fd per directory component, so a deep
tree needs more descriptors than the typically-low default soft limit (e.g. 128
on OpenBSD), where it otherwise fails with EMFILE. Raise RLIMIT_NOFILE's soft
limit toward the hard limit at startup (best-effort, never above the admin hard
cap, never lowering an inherited limit), matching the 3.5.0 branch. Adds the
getrlimit/setrlimit + sys/resource.h autoconf probes the guard needs.
The daemon exclude/filter chain is a name-based visibility filter, exactly as in
stock rsync: a symlink whose own name is not excluded is still followed to an
excluded target (the documented defense for a writable module is "munge symlinks").
Drop the in-module physical-path refusal from abspath_excluded_by_module() (it now
only confines operator/peer paths to the module ROOT) and restore stock rsync's
NAME-based ".." collapse for the daemon-filter checks on the destination,
basis/partial dirs and --temp-dir/--backup-dir via sanitize_path().
Preparation for broadening the symlink-race resolver to non-daemon
receivers: a --link-dest/--copy-dest/--compare-dest dir is operator-
supplied and legitimately outside the destination tree (e.g.
--copy-dest=../to), so the confined resolver must not reject its "..".
- check_alt_basis_dirs() now absolutizes a relative basis dir against the
destination curr_dir for local (!sanitize_paths) transfers too, not just
dry_run>1. An absolute path makes the do_*_at() wrappers and copy_file()
use plain (operator-trusted) resolution.
- copy_file() routes the source open through the secure resolver only for a
*relative* source under the broadened gate; an absolute (absolutized)
basis source uses do_open_nofollow().
A single-file --mkpath copy whose destination parent does not exist
failed under --dry-run: make_path() only *reports* the directories it
would create in a dry run, so change_dir#3 then tried to chdir into a
parent that isn't there and aborted with "change_dir#3 ... failed".
When the parent is genuinely missing in a dry run, skip the chdir and
mark the destination as not-yet-present (dry_run++), exactly as the
multi-file/dir-creation path already does, so the generator doesn't
probe the missing tree. Gating it on the missing-parent case keeps an
ordinary file-to-file dry run chdir'ing into and itemizing against an
existing destination.
Fixes: #880
Thanks to @pkzc for the report (#880).
Co-authored-by: Stiliyan Tonev (Bark) <stiliyan21@gmail.com>
(cherry picked from commit e096bbd64e)
Without --secluded-args, the client's safe_arg() backslash-escapes shell
and wildcard chars in option values before sending them to the server, so
--chown's --usermap=*:user is transmitted as --usermap=\*:user. Over ssh a
remote shell removes the backslashes before rsync parses the args, but a
daemon has no shell and read_args() stored option args verbatim -- so the
receiver saw the literal "\*", the usermap/groupmap wildcard never matched,
and the module's configured uid/gid won instead. A regression from the
secluded-args hardening; rsync 3.2.3 (protocol 31) worked.
Un-backslash option args in read_args() on the daemon's first
(non-protected) read, mirroring what the ssh-side shell does. File args
after the dot are already handled by glob_expand(); the protected (NUL,
already-unescaped) re-read and the server's stdin read pass unescape=0 so
their raw args are left untouched.
Thanks to @elcamlost for the report (#829).
Fixes: #829
(cherry picked from commit 8dc5fd1408)
Two cumulative-snprintf patterns in log.c (rsyserr) and main.c
(output_itemized_counts) had the shape
len = snprintf(buf, sizeof buf, ...);
len += snprintf(buf+len, sizeof buf - len, ...);
with no guard between calls. snprintf returns the would-have-been
length on truncation, so a truncated first call leaves
"sizeof buf - len" as a negative-then-promoted-to-size_t value,
underflowing into a huge size_t and writing past buf.
Realistic exposure is small in both cases (log header well under
buffer, only ~5 itemized iterations writing ~25 chars each into a
1024-byte buffer) but the defect class matches bb0a8118 and the
fix is cheap. Guard before each subsequent call.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multiple receiver-side fields read from the wire were trusted
without upper-bound checks. A hostile peer could either request
extreme allocations (DoS via --max-alloc) or, on platforms where
read_varint returned a negative value, push ~SIZE_MAX through the
size_t conversion to wrap downstream length checks.
Introduce read_int_bounded(), read_varint_bounded() and
read_varint_size() in io.c so wire-derived integer ranges are
checked at the read site rather than scattered across each
caller, with RERR_PROTOCOL on out-of-range input.
Apply the bounded primitives to:
- sum->count (checksum count -- previously could overflow
(size_t)count * xfer_sum_len on 32-bit with raised max-alloc)
- xattrs: count, name_len, datum_len, plus rel_pos overflow
detect to stop chain wrapping the num accumulator
- acls: ida-entry count
- flist: file mode S_IFMT validation, modtime_nsec range check
- delete-stat counters in main: per-summand cap so the total
can't overflow a signed 32-bit accumulator
Reporters include Joshua Rogers (checksum-count overflow finding).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Size flist checksum data to hold the active size, not the max.
- Add a negotiated hash method to the daemon auth code.
- Use EVP for all openssl digests. This makes it easy to add more
openssl digest methods and avoids deprecation warnings.
- Support a way to re-enable deprecated digests via openssl conf
file and allow a default file to be configured.
- Supply a simple openssl-rsync.cnf file to enable legacy digests.
- Avoid implied rules on generator and (with extra certainty) on server
- Add -R implied-directory path elements as directory includes
- Log about extra file-list checking using a new --debug=FILTER3 level
The new default is to protect args and options from unintended shell
interpretation using backslash escapes. See the new `--old-args` option
for a way to get the old-style splitting. This idiom was chosen over
making `--protect-args` enabled by default because it is more backward
compatible (e.g. it works with rrsync). Fixes#272.
Without a DISPLAY var, ssh won't try to forward X11 when making an
ssh connection. This patch also makes use of setenv() and unsetenv()
if they are available.
- Rename daemon_over_rsh -> daemon_connection since it is also used to
indicate if a non-rsh daemon connection is active.
- Move the daemon-over-rsh exception out of server_options() to the one
caller that needs that behavior.
- Don't allow noop_io_until_death() to be short-circuited when talking
to a daemon over a socket, because it can't send errors via stderr.
- All the memory-allocation macros now auto-check for failure and exit
with a failure message that incudes the caller's file and lineno
info. This includes strdup().
- Added the `--max-alloc=SIZE` option to be able to override the memory
allocator's sanity-check limit. It defaults to 1G (as before).
Fixes bugzilla bug 12769.
Fix the code that writes the options and the default destination path
into the batch.sh file to be able to handle options being specified
after source/dest args.
I replaced git-set-file-times with an improved version that I wrote
recently (in python3). A new script uses it to figure out the
last-modified year for each *.[ch] file and updates its copyright.
It also puts the latest year into the latest-year.h file for the
output of --version.