* Honor $(STRIP) in install-strip for cross-compilation
The install-strip target hard-coded `install -s`, which strips via the
install program using the build host's strip and ignores the STRIP
variable. When cross-compiling this runs the host strip against a
target binary and fails.
Pass --strip-program=$(or $(STRIP),strip) so the target strip is used
when STRIP is set (as cross toolchains and build systems provide),
falling back to plain `strip` for native builds. A plain `make install`
is unaffected.
* Make install-strip portable (address review)
- Detect the target strip via AC_CHECK_TOOL([STRIP],[strip],[strip]) in
configure.ac (picks up the cross-prefixed strip when cross-compiling,
defaults to plain strip otherwise) and substitute @STRIP@ in Makefile.in.
- Rewrite install-strip to run a normal install then $(STRIP) on the
installed rsync binary, dropping the GNU Make $(or ...) and the GNU
install --strip-program extension that broke with install-sh/BSD install.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`make check` died immediately when the build directory had a space in it:
./runtests.py --rsync-bin=`pwd`/rsync -j 8
rsync_bin /Volumes/Untitled is not a file
Reported by Roland Kletzing building in "/Volumes/Untitled 2"; it reproduces
anywhere, and is not macOS-specific.
Makefile.in interpolated an unquoted `pwd` into --rsync-bin at five sites, so
the shell word-split it. Quote those, and --tooldir at the installcheck site,
which had the same bug and was not in the report. Quote "$(srcdir)/runtests.py"
too: the script's own path word-splits just as readily.
That alone only gets as far as starting the runner. rsync_argv() then did
shlex.split(RSYNC), which turns "/ws test/rsync" into two nonexistent programs.
RSYNC may legitimately be a wrapper command line ("valgrind ... /build/rsync"),
so it cannot simply stop splitting; split_rsync_cmd() checks whether the string
names an existing file first -- a path that exists is one word by definition --
and only falls back to shlex for a real command line. Nine tests that called
shlex.split(RSYNC)/(RSYNC_PEER) directly go through it as well.
Deliberately a function called at use time rather than a pre-split constant:
chown-fake, devices-fake, chown, devices and partial_nowrite append
' --fake-super' or ' --super' to rsyncfns.RSYNC part-way through, and a cached
split hands back the pre-mutation command. Caching it is what broke those two
tests while I was writing this.
The suite is still not space-clean -- in a directory with a space 157 pass and
97 fail, against 0 able to run before. The rest is a separate problem: mostly
transfers whose --rsync-path is re-parsed by a remote shell, which needs
quoting at a different layer. No change in a normal path: 257 passed, 0 failed.
T_SAFE_ARG_OBJ used $(filter-out main.o,$(OBJS)), a GNU-make-only
function that BSD make and Solaris make silently expand to nothing,
so t_safe_arg linked with no rsync objects at all and failed with
undefined symbols (am_daemon, io_timeout, send_msg_int, ...). The
fleet missed this because those VMs build with gmake; the FreeBSD,
OpenBSD and Solaris CI VMs use the native make.
Spell out the object list via a new OBJS1_NO_MAIN macro instead;
plain macro expansion works in every make.
The KI-54 helper linked options.o compiled with -ffunction-sections and relied on
-Wl,--gc-sections to drop the option parser. That is GNU-ld-only: the fleet
showed macOS (ld64; also its custom rule dropped the openssl include path) and the
cygwin PE linker leave parse_arguments and its deps undefined, failing the build
of every CHECK_PROGS target on those hosts. Instead link the real rsync objects
(the same set as the rsync binary, minus main.o -- supplied renamed via
t_safe_arg_main.o) so safe_arg's deps all resolve with a plain link on every
platform. t_safe_arg_main.o depends on $(HEADERS) so the generated proto.h is
built before it under a parallel make. No behaviour change to the test itself.
Reported-by: Leonid Bugaev
t_clean_fname links the real util1.o and checks that clean_fname() with
CFN_COLLAPSE_DOT_DOT_DIRS collapses ".." for multi-component and absolute
paths. RED before the fix: an off-by-one left the collapse dead for all such
paths.
Reported-by: Leonid Bugaev
t_safe_arg links the real options.o (via --gc-sections so only safe_arg is
pulled in), poisons the heap, and checks that filename-mode quoting is exact.
RED before the fix: the counter/writer backslash miscount leaks an
uninitialized heap byte into the returned string.
Reported-by: Leonid Bugaev
t_iwildmatch links the real lib/wildmatch.o and checks that iwildmatch()
folds case on BOTH the pattern and the text. RED before the fix: an
upper-case pattern token (a daemon 'hosts deny = *.BADDOMAIN.COM') fails to
match a lower-case host -> access-control fail-open.
Reported-by: Leonid Bugaev
Add t_hashtable_overflow (links the real hashtable.o, sets a realistic
--max-alloc, requests an absurd size) and a test asserting hashtable_create now
rejects it with RERR_MALLOC instead of under-allocating and crashing on the OOB
node access a regressed build would hit. The helper defines its own
info_levels/debug_levels (as the other t_* helpers do) for the DEBUG_GTE macro
in the linked hashtable.o.
Adds the coverage/regression tests from the code-scanner run and the
gcov plumbing they rely on:
- scanner-argv-bounds, scanner-batch-flag-mismatch,
scanner-delete-delay-overread, scanner-daemon-log-checksum:
regression tests for the argv/-v/--info/--skip-compress bounds, the
batch metadata-ndx corruption, the read_delay_line off-by-one, and
the daemon -c/%C checksum-slot leak.
- daemon-proxy-protocol, daemon-early-exec-nameconv, daemon-auth-group,
daemon-standalone-detach, misc-coverage, nonroot-restrictive-perms,
backup-acl-xattr-cache: daemon and path coverage tests.
- rsyncfns.py: CAP_MKNOD probe in devices_supported().
- gcov_flush() macro (rsync.h) + calls in the daemon fork/_exit paths
(clientserver.c, socket.c); no-op without --enable-coverage. Makefile.in
COVERAGE_EXCLUDE / gcovr / setuid .gcda refinements.
- CI: list the new TCP/root/ACL tests in the per-platform
RSYNC_EXPECT_SKIPPED sets.
Co-authored-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
configure.ac: detect fdopendir + a working dirfd() (a macro/inline on the BSDs,
so the default link probe mis-detects) and getrlimit/setrlimit for the resolver's
deep-path fd budget; add the race-safe-ACL build probes.
Makefile.in: build lib/acl.o; build the new t_rename_secure / t_symlink_secure /
t_acl unit harnesses; drop the obsolete android.o object.
mkgitver: version a git build by the dev version + commit (not the nearest tag),
and harden the version.h parse.
log_delete() builds a struct file_struct inside a char buffer offset by the
(EXTRA_LEN-granular) extra data. The EXTRA_ROUNDING block that rounds that
offset up to the struct's alignment (exactly as flist.c does for its pool
allocations) was dead code here: log.c never included rounding.h, so
EXTRA_ROUNDING was undefined and the rounding never ran, leaving the
file_struct pointer potentially under-aligned. That trips UBSan's alignment
check and would fault on strict-alignment arches.
Include rounding.h (and add the Makefile dependency) so the existing rounding
actually applies -- fixing the alignment at the source rather than suppressing
the sanitizer.
Android's seccomp sandbox traps openat2() with SECCOMP_RET_TRAP, which
raises SIGSYS and kills the process instead of returning ENOSYS, so the
secure resolver cannot simply try openat2() and inspect errno. Add
openat2_usable() in a new android.c: it probes openat2() once behind a
temporary SIGSYS handler and caches the result.
Gate every SYS_openat2 call on openat2_usable(): in the resolver via an
openat2_beneath() wrapper, and in t_chmod_secure's kernel probe directly,
so a blocked openat2 reports ENOSYS and the caller falls back to the
portable O_NOFOLLOW resolver. Only openat2 is gated -- a plain openat()
(e.g. opening an operator-trusted absolute basedir) is left free.
The probe body compiles only on Android -- __ANDROID__ is a Bionic target
macro, so it is set for NDK cross-builds and native Termux alike and unset
everywhere else, where openat2_usable() collapses to a constant 1. Link
android.o into the secure-resolver test helpers too so their self-tests
survive on Termux.
Adapted from PR #909.
Adds .github/workflows/ubuntu-version-mix.yml (ubuntu-latest) and a
per-release manifest testsuite/expect/rsync_<ver>.expect for each of the
nine peers. The workflow builds the current rsync, then runs the two-
sided suite against every old binary over both the pipe and --use-tcp
daemon transports. All peers run in a SINGLE looped job (not a matrix)
so the PR shows one check line; each peer/transport is a foldable log
group and a failure annotates which one broke.
A new phony `check-progs` target builds rsync plus the test helper
programs and check symlinks without running the suite -- the build half
of `make check` -- so the workflow's direct runtests.py invocation has
the helpers it needs.
Notable expected results encoded in the manifests:
- The four May-2026 security tests xfail against every released peer:
the suite demonstrates each release is vulnerable to those findings
while current master is fixed.
- symlink-dirlink-basis xfails on 3.4.0/3.4.1 (issue #715: their
secure_relative_open O_NOFOLLOW-confines the basedir, breaking a -K
dir-symlink update; current master fixes it with secure_basis_open).
- Older peers carry more xfails for options/negotiation they lack;
2.6.0 (protocol 27) fails most daemon tests. reverse-daemon-delta
passes against all peers, confirming backward compat down to 2004.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The coverage report counted bundled third-party code (zlib/, popt/, and the
PostgreSQL/ISC lib/ imports getaddrinfo/getpass/inet_ntop/inet_pton) that rsync
ships but does not own, muddying the percentages. Add a COVERAGE_EXCLUDE gcovr
filter (shared by all coverage targets) so the report reflects rsync's own code:
on the same data, lines 63.9%->65.5%, functions 81.4%->85.0%, branches
55.0%->56.5% (rsync's own md5/mdfour/wildmatch/etc. stay in the report).
Add 'make coverage-all': run the suite under pipe + --protocol=30 + --protocol=29
+ --use-tcp, accumulating into the shared .gcda (not cleared between runs), then
one merged scoped report -- covers the daemon/TCP and protocol-compat paths a
single pipe run misses (lines 67.6%, functions 87.6%, branches 58.6%). Also add
'make coverage-fallback' for a separate --disable-openat2 build (different .gcno,
so it can't merge with the openat2 report). CI is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
coverage-tcp reuses the coverage recipe with --use-tcp (daemon tests over a real
loopback rsyncd, which also runs the require_tcp-only tests) and a separate
report directory, via COVERAGE_RUNFLAGS / COVERAGE_DIR. Verified end to end:
pipe run reports 63.9% lines, the TCP run 64.5% (it exercises more code).
Also drop gcovr's --branches flag: it is deprecated in gcovr 8 and branch +
decision coverage still appear in --print-summary and the HTML without it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two test-coverage build knobs (both behaviour-neutral by default):
--enable-coverage appends '--coverage -fprofile-update=atomic -O0' and adds
a 'make coverage' target (whole suite, run serially, then
gcovr HTML with branch + decision coverage). rsync forks
and its children exit without running the gcov atexit
flush -- the generator via its SIGUSR1 handler
(_exit_cleanup) and the receiver via the SIGUSR2 handler
-- so under GCOV_COVERAGE we call __gcov_dump() at both, or
receiver.c/generator.c record no coverage at all.
--disable-openat2 gates the Linux openat2(RESOLVE_BENEATH) sites in syscall.c
on HAVE_OPENAT2 (defined by default), so disabling it forces
the portable per-component O_NOFOLLOW resolver to run as the
primary on ordinary Linux -- exercising and
coverage-counting that fallback tier without a pre-5.6
kernel. NOTE: coordinate with the parallel syscall.c
path-resolution restructure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Daemon-mode tests default to the stdio-pipe transport (RSYNC_CONNECT_PROG),
which opens no listening socket -- so `make check` never exposes a network
service. Real TCP is opt-in via `runtests.py --use-tcp`, with the daemon
bound to loopback (127.0.0.1) on a claim_ports()-reserved port; CI runs the
suite both ways.
start_test_daemon() is the single seam every daemon test uses: the secure
pipe by default, a real rsyncd on a claimed loopback port under --use-tcp.
Tests with no pipe equivalent (the fake-proxy listener and the reverse-DNS
hostname-ACL daemon test) are gated behind require_tcp().
`make check` also now runs the suite in parallel by default (CHECK_J=8);
the claim_ports() byte-range locks make that safe across concurrent runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the entire shell-based testsuite with Python. runtests.py
already drove the suite (it had replaced runtests.sh earlier); this
converts all 60 test scripts from *.test shell to *_test.py and adds
testsuite/rsyncfns.py as the shared helper module -- the Python
counterpart of the now-removed rsync.fns.
runtests.py:
* Discovers and runs both *.test and *_test.py; dispatches the
Python tests via the same python3 that runs the harness.
* Extends PYTHONPATH so tests can `import rsyncfns`.
testsuite/rsyncfns.py provides everything the ports need:
* environment wiring (scratchdir / srcdir / TOOLDIR / RSYNC /
TLS_ARGS, and HOME pointed at the per-test scratch dir);
* result reporting -- test_fail / test_skipped / test_xfail mapping
to the 0 / 1 / 77 / 78 exit-code convention;
* the transfer-and-verify helpers checkit, checkdiff, verify_dirs,
rsync_ls_lR, check_perms and the v_filt output filter;
* fixture builders hands_setup, build_symlinks, build_rsyncd_conf,
make_data_file, cp_p / cp_touch, makepath / rmtree.
All 60 tests are converted, including the four split-variant tests
that share one source via a Makefile-built symlink (chown/chown-fake,
devices/devices-fake, xattrs/xattrs-hlink, exclude/exclude-lsh);
Makefile.in's CHECK_SYMLINKS now points at the *_test.py names.
The dead rsync.fns shell library is removed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the rest of the path-based syscall wrappers and migrate every
receiver-side caller:
- do_lchown_at, do_rename_at, do_mkdir_at, do_symlink_at,
do_mknod_at, do_link_at, do_unlink_at, do_rmdir_at,
do_utimensat_at, do_stat_at, do_lstat_at
Same shape as do_chmod_at: open each parent under
secure_relative_open(), call the *at() variant against the dirfd,
fall through to the bare path-based syscall in non-daemon /
chrooted / absolute-path / no-parent cases. macOS's
setattrlist-based set_times tier is also routed through the
utimensat_at path on daemon-no-chroot.
Hardenings to secure_relative_open() itself:
- confine basedir resolution under the same kernel mechanism
used for relpath (basedirs from --copy-dest / --link-dest are
sender-controllable in daemon mode)
- reject any '..' component (bare '..', 'foo/..', 'subdir/..')
so the per-component O_NOFOLLOW fallback can't escape
- return the dirfd we built up from the per-component fallback
when the caller passed O_DIRECTORY (otherwise every do_*_at
failed with EINVAL on platforms without RESOLVE_BENEATH)
Adds testsuite/alt-dest-symlink-race.test and
testsuite/secure-relpath-validation.test (with t_secure_relpath
helper) as regression coverage for the new hardenings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CVE-2026-29518's fix routed the receiver's open() through
secure_relative_open(), but every other path-based syscall the
receiver runs on sender-controllable paths is vulnerable to the
same TOCTOU primitive. This commit closes the chmod variant.
Add do_chmod_at() that opens the parent of fname under
secure_relative_open() and uses fchmodat() against the resulting
dirfd. Gate the secure path on am_daemon && !am_chrooted (the same
gate use_secure_symlinks already uses for the receiver basis-file
open), so non-daemon callers and chrooted daemons keep the original
do_chmod() fast path.
Migrate the receiver-side do_chmod() call sites in delete.c,
generator.c, rsync.c, and xattrs.c.
Adds testsuite/chmod-symlink-race.test (with t_chmod_secure helper)
as regression coverage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrite the test runner in Python with proper command-line options
including --valgrind which directs valgrind output to per-process
log files so it doesn't interfere with test output comparisons.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The AVX2 get_checksum1_avx2_64() read mul_one before initializing it,
which is undefined behavior. Replace the cmpeq/abs trick with
_mm256_set1_epi8(1) to match the SSSE3 and SSE2 versions.
Add a TEST_SIMD_CHECKSUM1 test mode that verifies all SIMD paths
(SSE2, SSSE3, AVX2, and the full dispatch chain) produce identical
results to the C reference, across multiple buffer sizes with both
aligned and unaligned buffers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change the developer flow to not require updating the git-version repo
that the builds used to download a git-version.h file. The Actions now
do a full repo fetch so that the .h file can be generated via the git
history.
- Get rid of the gensend Makefile target that was used for the above.
- Get rid of the pre-push git hook file that called "Make gensend".
- Change the FreeBSD build to save an artifact with its built binaries.
[buildall]
- Make the SIMD ASM code off by default. Use configure --enable-simd-asm
to enable.
- Allow MD5 ASM code to be requested even when OpenSSL is handling MD4
checksums. Use configure --enable-md5-asm to enable.
- Convert rrsync to python.
- Enhance security of arg & option checking.
- Reject `-L` (`--copy-links`) by default.
- Add `-munge` and `-no-del` options.
- Tweak the logfile line format.
- Created an rrsync man page.
- Use `configure --with-rrsync` if you want `make install` to install
rrsync and its man page.
- Give lsh more rrsync testing support.
Some hosts were not running `mkgitver` when they should, so tweak the
script to only update the timestamp when the file's data changes and
then always run the script when performing a build.
For a non-git build or for a git build w/o adequate git history, we now
allow the git-version.h file to be provided before the build. If the
file does not exist, we either create an empty file or put a define of
RSYNC_GITVER in it based on the output of git describe. The github
builds now snag the git-version.h file that was generated for the last
commit so that they all get the same version string, even with a shallow
checkout.