]> git.ipfire.org Git - thirdparty/rsync.git/log
thirdparty/rsync.git
119 min agostart on 3.5.0 master
Andrew Tridgell [Fri, 22 May 2026 21:52:55 +0000 (07:52 +1000)] 
start on 3.5.0

18 hours agotestsuite: read xattrs natively instead of shelling out to getfattr
Andrew Tridgell [Fri, 22 May 2026 04:52:46 +0000 (14:52 +1000)] 
testsuite: read xattrs natively instead of shelling out to getfattr

xattr_set() sets attributes with the native os.setxattr(), but
xattr_dump() read them back by running "getfattr -d". That asymmetry
breaks "make check" on any system where rsync is built with xattr
support (libattr headers present) but the attr package's CLI tools are
not installed -- common on Android/Termux and minimal CI images: setting
succeeds via os.setxattr, then xattr_dump's getfattr raises
FileNotFoundError, which crashes the test (reported FAIL) instead of
running or skipping it. That's why "make check" was failing here on
xattrs / xattrs-hlink.

Read the xattrs natively with os.listxattr()/os.getxattr() on Linux,
symmetric with xattr_set(), so the suite needs no external getfattr; the
output still mimics "getfattr -d" and only has to be self-consistent
between the source and destination dumps. Cygwin keeps the CLI path
(Python there lacks os.*xattr). make check now passes with no attr
package installed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
18 hours agotestsuite: generate predictable fixture files instead of reading /etc, /bin, /
Andrew Tridgell [Fri, 22 May 2026 04:41:17 +0000 (14:41 +1000)] 
testsuite: generate predictable fixture files instead of reading /etc, /bin, /

The Python rewrite of the suite carried over the shell habit of
populating the test tree by capturing "ls -l /etc" / "ls -l /bin"
(falling back to "ls /"): hands_setup() built etc-ltr-list / bin-lt-list
that way, and longdir_test.py did the same for its leaf files. That ties
the fixtures to the host filesystem layout -- those directories are
absent or unreadable on Android/Termux and other minimal environments,
where "ls /" fails outright -- and the captured content was never
reproducible from run to run.

Add a deterministic make_text_file() helper to rsyncfns.py and use it for
hands_setup()'s two fixture files and longdir's leaf files. The names
etc-ltr-list / bin-lt-list are unchanged (chmod, chmod-temp-dir and
alt-dest reference them by name); only the content source changes, so the
fixtures are now self-contained and identical on every platform. This
also drops longdir_test.py's date(1) and ls(1) subprocess calls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
18 hours agodocs: add rsync Discord server link
Andrew Tridgell [Fri, 22 May 2026 05:05:10 +0000 (15:05 +1000)] 
docs: add rsync Discord server link

Add a link to the rsync Discord server (https://discord.gg/Avfvy9zhdp)
below the mailing lists section in README.md and on the lists.html web
page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
19 hours agotestsuite: restore non-Linux xattr/fake-super coverage
Andrew Tridgell [Thu, 21 May 2026 04:29:29 +0000 (14:29 +1000)] 
testsuite: restore non-Linux xattr/fake-super coverage

The Python rewrite had gated the xattr / fake-super tests (xattrs,
xattrs-hlink, chown-fake, devices-fake) to Linux because it used the
Linux-only os.*xattr. Restore them on macOS, FreeBSD, Cygwin and Solaris
via a per-OS xattr surface in rsyncfns.py (xattrs_supported / xattr_set /
xattr_dump):
  * Linux   -- os.*xattr
  * macOS   -- xattr
  * FreeBSD -- setextattr / lsextattr / getextattr
  * Cygwin  -- getfattr / setfattr (from the `attr` package; CPython on
               Cygwin has no os.*xattr)
  * Solaris -- runat(1), with the script on stdin and the attr name/value
               passed via the environment (the runat -c form mangles args)

Test attribute names are logical; the "user." namespace prefix is added
only on the Linux-style platforms (Linux, Cygwin). RSYNC_PREFIX/RUSR vary
per OS (macOS and Solaris use rsync.nonuser to avoid rsync's reserved
rsync.* space). The macOS and Cygwin workflows no longer skip these tests;
the FreeBSD/Solaris jobs use IGNORE skip-checking so need no change.

Verified on real Linux, macOS, FreeBSD, Cygwin and Solaris hosts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
19 hours agotestsuite: post-review fixes and lock-file hardening
Andrew Tridgell [Thu, 21 May 2026 04:14:13 +0000 (14:14 +1000)] 
testsuite: post-review fixes and lock-file hardening

  * chmod-option: pin umask to the suite-wide 022 baseline (mirroring the
    old rsync.fns) so rsync's --chmod `D+w` is computed and applied under
    the same umask -- fixes failures under a different ambient umask (077).
  * daemon module-list test: assert the `list = no` module does NOT leak
    into the listing (the substring check alone missed regressions).
  * claim_ports() lock file: open with O_NOFOLLOW and only fchmod a file we
    O_EXCL-created, rejecting a symlink OR hard link planted at the
    well-known /tmp path -- which, with the TCP tests running under sudo in
    CI, could otherwise chmod an arbitrary root-owned target. Require a
    pristine (regular, nlink==1) file.
  * CI: extend the Linux/Cygwin expected-skip lists for the gated tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
19 hours agosocket: enforce socketpair_tcp()'s anti-hijack guarantee
Andrew Tridgell [Thu, 21 May 2026 02:38:14 +0000 (12:38 +1000)] 
socket: enforce socketpair_tcp()'s anti-hijack guarantee

socketpair_tcp() fakes a connected socket pair via a loopback TCP
self-connect (socket -> bind 127.0.0.1:0 -> listen -> connect ->
accept), used by sock_exec() for RSYNC_CONNECT_PROG. Its comment has
long promised that "nobody else can attach to the socket, or if they
do that this function fails", but nothing actually verified it: the
code accept()ed whatever connection arrived first without checking it
was the one our own connect() made.

Between listen() and accept() the ephemeral loopback port is
connectable by any local user. With backlog 1 a same-host attacker who
races a connection in before our connect() lands could have their
socket returned by accept(), handing them one end of the rsync
protocol stream. The exposure is small (loopback only, random
ephemeral port, sub-millisecond window, local users only), but the
promised guarantee was simply not enforced.

Enforce it: after the connection is established, require that the peer
address of the accepted end (fd[0]) equals the local address of our
connecting end (fd[1]), and that both are 127.0.0.1. A hijacked
connection has a different source port and is rejected (errno EPERM,
fail closed). The legitimate self-connect always matches, so there is
no behaviour change for the normal path.

Verified: rebuilds clean with -Wall -W; the full testsuite still
passes in both transports (pipe `make check` 57/3, `runtests.py
--use-tcp` 59/1) -- the pipe transport exercises this code path on
every daemon test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
19 hours agotestsuite: secure stdio-pipe daemon transport by default, opt-in TCP
Andrew Tridgell [Thu, 21 May 2026 04:14:13 +0000 (14:14 +1000)] 
testsuite: secure stdio-pipe daemon transport by default, opt-in TCP

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>
19 hours agotestsuite: add claim_ports() for parallel-safe TCP-port coordination
Andrew Tridgell [Thu, 21 May 2026 01:47:44 +0000 (11:47 +1000)] 
testsuite: add claim_ports() for parallel-safe TCP-port coordination

rsyncfns.claim_ports(*ports) takes exclusive POSIX byte-range locks on
/tmp/rsync_test.lck (offset = port number) so any number of test
processes can run concurrently without colliding on a TCP port: a test
asking for a port already held blocks until the holder exits. The
kernel drops the locks automatically when the holding process dies, so
a crashed test releases its ports with no manual cleanup.

Ports are claimed in sorted order so two callers requesting the same
set in different orders can't deadlock. The lock file is forced to
mode 0o666 after creation (the umask would otherwise trim it and lock
out a second user on a shared CI runner; EPERM when we're not the
owner is fine).

proxy-response-line-too-long is the first user: it switches from an
ephemeral port to a claimed fixed port (12873).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
19 hours agotestsuite: rewrite the shell testsuite in Python
Andrew Tridgell [Thu, 21 May 2026 01:47:34 +0000 (11:47 +1000)] 
testsuite: rewrite the shell testsuite in Python

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>
20 hours agoci: add static Android NDK build workflow
Andrew Tridgell [Fri, 22 May 2026 02:40:17 +0000 (12:40 +1000)] 
ci: add static Android NDK build workflow

Cross-compiles statically-linked rsync binaries with the Android NDK for
arm64-v8a (all modern phones) and armeabi-v7a (older 32-bit devices), and
uploads them as workflow artifacts for adb push / Termux use.

The build is self-contained (optional external libraries disabled; keeps
md5/md4 and the bundled zlib) and forces a few configure cache values
that can't be probed when cross-compiling: lchmod()/lutimes() off (Bionic
doesn't declare them until API 36 though the symbols link), and
socketpair / mknod-FIFO / mknod-socket on (Android runs a Linux kernel,
so these match the native result). IPv6 is enabled explicitly.

Since the binaries are cross-compiled the test suite can't run; the job
instead asserts each binary is static and the correct architecture, and
smoke-tests `--version` under qemu-user.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agotestsuite: portable make_data_file helper; drop hard /dev/urandom dependency
Andrew Tridgell [Wed, 20 May 2026 21:28:03 +0000 (07:28 +1000)] 
testsuite: portable make_data_file helper; drop hard /dev/urandom dependency

symlink-dirlink-basis.test and chdir-symlink-race.test both
require a multi-kilobyte non-trivial-content source file for the
rsync delta algorithm to exercise.  Both used dd / head against
/dev/urandom directly, which fails on platforms that don't ship
/dev/urandom (e.g. HPE NonStop).  The dd error gets swallowed by
'2>/dev/null' and the test then fails with a misleading 'failed
to create test file' that hides the real cause.

Add make_data_file <path> <size> to testsuite/rsync.fns.  Prefers
/dev/urandom when readable (kernel-provided randomness, fast),
falling back to a deterministic awk LCG seeded from PID and a
POSIX cksum of the destination path.  Output is constrained to
printable ASCII (33..126) so the helper survives two awk-portability
quirks:

  - printf '%c', 0 terminates the string in some awks, emitting
    fewer than sz bytes;
  - gawk in UTF-8 locales encodes printf '%c', N for N > 127 as
    a 2-byte UTF-8 sequence, emitting more than sz bytes.

The tests don't need 8-bit binary entropy -- they just need
non-trivial bytes for rsync's block-matching algorithm.

Update both call sites to use the helper.  Linux/FreeBSD/macOS
still take the /dev/urandom fast path; NonStop and any other
platform missing the device get the awk fallback transparently.
Both paths verified locally with the symlink-dirlink-basis test.

2 days agot_chmod_secure: probe kernel RESOLVE_BENEATH at runtime; drop test skip
Andrew Tridgell [Wed, 20 May 2026 21:13:36 +0000 (07:13 +1000)] 
t_chmod_secure: probe kernel RESOLVE_BENEATH at runtime; drop test skip

The chmod-symlink-race test was previously a no-op on Solaris,
OpenBSD, NetBSD, and Cygwin via a case 'uname -s' skip.  The skip
was too broad: of the four scenarios the helper exercises, only
the 'legitimate within-tree dir-symlink' one actually needs
RESOLVE_BENEATH-equivalent kernel support.  The other three
(attack rejection, plain relative path, top-level file) behave
identically on the per-component O_NOFOLLOW fallback and would
have caught the t_stub.c max_alloc=0 bug fixed in the previous
commit if the test had been allowed to run.

Make the helper probe the running kernel for either
openat2(RESOLVE_BENEATH) on Linux 5.6+ or openat(O_RESOLVE_BENEATH)
on FreeBSD 13+ / macOS 15+ by opening '.' under the requested
confinement.  Honour the result:

  - If RESOLVE_BENEATH-equivalent confinement is available, the
    within-tree symlink scenario must succeed (status quo).
  - If not, the per-component O_NOFOLLOW fallback rejects every
    symlink including legitimate ones; expect the within-tree
    symlink scenario to be rejected (rc != 0) and the file mode
    to remain unchanged.

The attack-rejection, plain-path and top-level scenarios are
unchanged: they expect the same outcome on both code paths.

Drop the case-based skip from chmod-symlink-race.test so the test
runs everywhere and the per-component fallback gets the CI
coverage that the SunOS/OpenBSD/NetBSD/Cygwin runners can
provide.  HPE NonStop -- which lacks RESOLVE_BENEATH but isn't in
the existing skip list -- is also covered by this change.

2 days agot_stub.c: raise max_alloc default so test helpers can allocate
Andrew Tridgell [Wed, 20 May 2026 21:11:30 +0000 (07:11 +1000)] 
t_stub.c: raise max_alloc default so test helpers can allocate

The t_stub.c shim defined max_alloc = 0 as a placeholder to satisfy
the link against util2.o.  This was harmless when the test helpers
made no allocations, but the secure_relative_open() implementation
in 3.4.0+ calls my_strdup() in its per-component O_NOFOLLOW
fallback (syscall.c around line 1857), and the 3.4.3 do_*_at()
hardening series added more such calls.  With max_alloc=0, every
allocation in that path trips the 'exceeded --max-alloc=0' check in
util2.c's my_alloc(), and t_chmod_secure (which exercises
do_chmod_at via secure_relative_open) fails on the very first
my_strdup.

The failure is invisible on Linux 5.6+ / FreeBSD 13+ / macOS 15+ /
recent Cygwin because those platforms take the kernel-enforced
openat2(RESOLVE_BENEATH) or openat(O_RESOLVE_BENEATH) branch and
never reach the per-component fallback.  It also goes unobserved
on the SunOS/OpenBSD/NetBSD/CYGWIN* CI runners because the
chmod-symlink-race.test script case-skips on those platforms (the
legitimate dir-symlink scenario the test exercises can't pass on
the per-component fallback).  HPE NonStop is the first platform
that lacks RESOLVE_BENEATH support AND isn't in the skip list AND
has someone actually running the test suite, so it surfaced the
latent bug.

Raise max_alloc to SIZE_MAX so the helpers can allocate freely.
A follow-up patch makes t_chmod_secure adapt at runtime so the
skip list can be removed and the per-component fallback gets real
CI coverage.

2 days agopackaging: add ftp.filt, the FTP mirror filter file
Andrew Tridgell [Wed, 20 May 2026 05:18:25 +0000 (15:18 +1000)] 
packaging: add ftp.filt, the FTP mirror filter file

The .filt file in /home/ftp/pub/rsync on samba.org controls which
subtrees release.py's FTP mirror excludes (currently /binaries/
and /generated-files/).  Without it, step-10-push-ftp's
'rsync --del' would propagate local deletions to the server even
for those archive subtrees.

Until now the only copy of this two-line file lived on the server.
Bundle it in source at packaging/ftp.filt so it survives a disaster
on samba.org, and have step_1_fetch seed FTP_DIR/.filt from the
bundled copy on every run (with --exclude=/.filt on the rsync pull,
so the server's copy can't silently drift the bundled one).
step-10-push-ftp then propagates any in-source updates to the
filter back to the server.

2 days agopackaging: remove obsolete samba-rsync and send-news scripts
Andrew Tridgell [Wed, 20 May 2026 05:02:32 +0000 (15:02 +1000)] 
packaging: remove obsolete samba-rsync and send-news scripts

Both scripts were pre-release.py legacy helpers:

  * samba-rsync rsync'd ~/samba-rsync-{ftp,html}/ to the samba.org
    server.  release.py step-10-push-ftp and step-11-push-html now
    do exactly this, using ../release/rsync-{ftp,html}/ as the
    local mirrors.

  * send-news copied README/INSTALL/NEWS .md + .html files into
    ~/samba-rsync-ftp/ and rsync'd them to samba.org.
    release.py step-8-update-ftp already does this
    (./md-convert --dest=FTP_DIR README.md NEWS.md INSTALL.md and
    the surrounding rsync of html files into FTP_DIR), and
    step-10-push-ftp pushes the result.

Update the trailing instructions printed at the end of
step-12-push-git to drop the now-obsolete 'run packaging/send-news'
suggestion, and tighten the comment in step_1_fetch that referred
to samba-rsync as a current sibling tool.

2 days agopackaging/release.py: rsync-web is now an in-tree subdirectory
Andrew Tridgell [Wed, 20 May 2026 04:58:35 +0000 (14:58 +1000)] 
packaging/release.py: rsync-web is now an in-tree subdirectory

Track the move of rsync-web from sibling git checkout to a regular
subdirectory of the rsync source tree:

  * HTML_SRC: '../rsync-web' -> 'rsync-web'.
  * step_1_fetch: drop the .git-presence probe and the 'make sure
    it's up to date' reminder.  Both made sense when rsync-web was
    a separate repo the maintainer had to clone and pull, but the
    directory is now part of the same checkout as this script.
  * rsync invocation no longer needs --exclude=/.git: there is no
    .git inside rsync-web/ (it is just a subdir of the parent
    rsync-git checkout).
  * Header comment block and step-1 help text rewritten to describe
    the new layout.

2 days agoimport rsync-web website content as a subdirectory
Andrew Tridgell [Wed, 20 May 2026 04:57:50 +0000 (14:57 +1000)] 
import rsync-web website content as a subdirectory

Fold the standalone rsync-web repo into the rsync source tree as
rsync-web/, eliminating the sibling-checkout convention and the
drift it causes between the release-time HTML snapshot in
../release/rsync-html and the source of truth in ../rsync-web.

Flat-copy import (no git history merge).  The standalone repo at
github.com/RsyncProject/rsync-web is retained for historical
reference and will be archived once the in-tree copy proves itself.

Add /rsync-web/ to .gitattributes with export-ignore so the
website content does not bloat the release source tarball
produced by 'git archive' in packaging/release.py step_7_tarball.

A follow-up commit repoints HTML_SRC in packaging/release.py at
the new in-tree location.

2 days agoINSTALL.md: point Ubuntu users at the ppa:rsyncproject/rsync PPA
Andrew Tridgell [Wed, 20 May 2026 04:36:02 +0000 (14:36 +1000)] 
INSTALL.md: point Ubuntu users at the ppa:rsyncproject/rsync PPA

Most Ubuntu users landing on INSTALL.md want to install rsync, not
build it.  Add a short section near the top that offers the
Launchpad PPA as the one-line path for the four currently supported
series (jammy 22.04 LTS, noble 24.04 LTS, questing 25.10,
resolute 26.04 LTS), and clarify that the rest of the file is about
building from source.

2 days agostart on 3.4.4
Andrew Tridgell [Wed, 20 May 2026 01:50:33 +0000 (11:50 +1000)] 
start on 3.4.4

2 days agoPreparing for release of 3.4.3 [buildall] v3.4.3
Andrew Tridgell [Wed, 20 May 2026 00:07:26 +0000 (10:07 +1000)] 
Preparing for release of 3.4.3 [buildall]

2 days agoversion.h: bump to 3.4.3 for the release
Andrew Tridgell [Thu, 7 May 2026 21:50:56 +0000 (07:50 +1000)] 
version.h: bump to 3.4.3 for the release

Drops the "dev" suffix on RSYNC_VERSION ahead of the
2026-05-20 00:00 UTC public release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agoNEWS: prepare 3.4.3 release entry with six CVEs
Andrew Tridgell [Thu, 7 May 2026 21:01:39 +0000 (07:01 +1000)] 
NEWS: prepare 3.4.3 release entry with six CVEs

Set the date to 20 May 2026, add a SECURITY FIXES section listing
all six May 2026 CVEs (CVE-2026-29518, -43617, -43618, -43619,
-43620, -45232) with reach, root cause, fix and reporter for each,
plus a note on the defence-in-depth hardening that goes with them.
Also list the new symlink-race regression tests under DEVELOPER
RELATED.

2 days agoutil1: handle out-of-range times in timestring
Andrew Tridgell [Fri, 15 May 2026 00:27:22 +0000 (10:27 +1000)] 
util1: handle out-of-range times in timestring

2 days agomain: reject hyphen-prefixed remote-shell hostnames
Andrew Tridgell [Fri, 15 May 2026 00:17:03 +0000 (10:17 +1000)] 
main: reject hyphen-prefixed remote-shell hostnames

2 days agosocket: reject over-long proxy response line
Andrew Tridgell [Wed, 13 May 2026 10:35:35 +0000 (20:35 +1000)] 
socket: reject over-long proxy response line

fixes a one byte stack overflow when using RSYNC_PROXY with a
malicious proxy.

Reach: only when RSYNC_PROXY is set and a malicious or MITM'd
proxy returns the pathological response.  The byte written is
always '\0' and the attacker doesn't choose the offset, so impact
is corruption of one adjacent stack byte and possible later
misbehaviour or crash -- no information disclosure beyond the
existing rprintf of buffer contents.

Reported by Aisle Research via Michal Ruprich

2 days agorsync.h: lower MAX_WIRE_DEL_STAT to avoid signed-int overflow in read_del_stats
Andrew Tridgell [Thu, 14 May 2026 04:45:21 +0000 (14:45 +1000)] 
rsync.h: lower MAX_WIRE_DEL_STAT to avoid signed-int overflow in read_del_stats

read_del_stats() in main.c accumulates 5 wire-supplied counts into
the int32 stats.deleted_files field:

    stats.deleted_files  = read_varint_bounded(..., MAX_WIRE_DEL_STAT, ...);
    stats.deleted_files += stats.deleted_dirs     = ...;
    stats.deleted_files += stats.deleted_symlinks = ...;
    stats.deleted_files += stats.deleted_devices  = ...;
    stats.deleted_files += stats.deleted_specials = ...;

With the previous MAX_WIRE_DEL_STAT = 2^30 (1.07 GB) the worst-case
sum is 5 * 2^30 = 5.37 GB; three maximal values already exceed
INT32_MAX = 2.15 GB on the third "+=", triggering signed integer
overflow (C99 6.5/5 -- undefined behaviour, the compiler may assume
it cannot happen and elide subsequent checks).

The bound was introduced in f0155902 ("defence-in-depth: bound
wire-supplied counts and lengths") with a commit message claiming
"per-summand cap so the total can't overflow", but 2^30 * 5 does
overflow.  Lower the per-summand cap to 2^28 (= 268M) so the worst
case is 5 * 2^28 = 1.34 GB < INT32_MAX with margin.  2^28 deletions
per category is still vastly above any plausible real transfer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agodefence-in-depth: receiver block-index bounds + read_delay_line null check
Andrew Tridgell [Wed, 31 Dec 2025 03:01:34 +0000 (14:01 +1100)] 
defence-in-depth: receiver block-index bounds + read_delay_line null check

Two assorted audit findings:

  - receive_data() never bounds-checked the block index returned
    by recv_token() against sum.count before computing offset2
    and feeding it to map_ptr(). An out-of-bounds index from a
    hostile sender produces invalid memory access. Add a
    sum.count bounds check.

  - read_delay_line()'s strchr() call could return NULL when no
    space was found, but the code unconditionally added 1 to the
    result before dereferencing. Low impact (just a disconnect on
    exit of the client-specific forked process) but the NULL
    deref is real. Guard the NULL.

Both reported by Joshua Rogers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agodefence-in-depth: guard cumulative snprintf against length underflow
Andrew Tridgell [Thu, 30 Apr 2026 23:30:31 +0000 (09:30 +1000)] 
defence-in-depth: guard cumulative snprintf against length underflow

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>
2 days agodefence-in-depth: bound wire-supplied counts and lengths
Andrew Tridgell [Wed, 31 Dec 2025 01:56:54 +0000 (12:56 +1100)] 
defence-in-depth: bound wire-supplied counts and lengths

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>
2 days agoclientserver: fix hostname ACL bypass when using daemon chroot
Andrew Tridgell [Wed, 31 Dec 2025 02:50:35 +0000 (13:50 +1100)] 
clientserver: fix hostname ACL bypass when using daemon chroot

On an rsync daemon configured with "daemon chroot", the reverse-DNS
lookup of the connecting client was performed *after* the chroot
had been entered. If the chroot did not contain the files glibc
needs for resolution (/etc/resolv.conf, /etc/nsswitch.conf,
/etc/hosts, NSS service modules), the lookup failed and
client_name() returned "UNKNOWN". Hostname-based deny rules
("hosts deny = *.evil.example") therefore could not match, and
an attacker controlling their PTR record could connect from a
hostname the administrator had intended to deny. IP-based ACLs
were unaffected.

Do the reverse DNS lookup before chroot/setuid; client_name()
caches its result, so the post-chroot call uses the cached value
and hostname-based ACLs work even when DNS is unavailable
post-chroot.

Adds testsuite/daemon-chroot-acl.test as end-to-end regression
coverage. The test sets up an empty chroot directory, configures
"hosts deny = <localhost-resolved-name>" with daemon chroot, and
asserts the connection is refused with @ERROR access denied.
Uses unshare --user --map-root-user for non-root CAP_SYS_CHROOT;
skips cleanly on non-Linux or when user namespaces aren't
available.

Reporter: Joshua Rogers (MegaManSec).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agoreceiver: add parent_ndx<0 guard, mirroring 797e17f
Andrew Tridgell [Tue, 5 May 2026 06:48:16 +0000 (16:48 +1000)] 
receiver: add parent_ndx<0 guard, mirroring 797e17f

Commit 797e17f ("fixed an invalid access to files array") added a
parent_ndx < 0 guard to send_files() in sender.c, but the visually-
identical block in recv_files() in receiver.c was not updated. A
malicious rsync:// server can therefore drive any connecting client
into the same out-of-bounds dir_flist->files[-1] read followed by a
file_struct dereference in f_name() one line later.

Reach: protocol-30+ default (inc_recurse) makes flist.c:2745 set
parent_ndx = -1 on the first received flist when the sender omits a
leading "." entry; rsync.c flist_for_ndx() does not reject ndx == 0
in that state because the range check evaluates 0 < 0 = false; and
read_ndx_and_attrs() only validates ndx with the ITEM_TRANSFER bit
set, so iflags=ITEM_IS_NEW (or any other non-transfer iflag word)
bypasses the check.

Apply the same guard receiver-side. Confirmed: the same PoC (a
minimal Python rsyncd that handshakes with CF_INC_RECURSE, sends a
no-leading-"." flist, and emits ndx=0 with ITEM_IS_NEW) crashes
unpatched 3.4.2 with SEGV_MAPERR si_addr=0x4101a-class in the
receiver child; with this guard it exits cleanly with code 2
(RERR_PROTOCOL).

The attack surface delta over the sender variant is large:
the original was malicious-client -> daemon, this is
malicious-server -> any rsync client doing a normal rsync://
or remote-shell pull.

Reported by Pratham Gupta (alchemy1729).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agotestsuite: cover 'refuse options = compress' for the daemon
Andrew Tridgell [Fri, 1 May 2026 00:56:17 +0000 (10:56 +1000)] 
testsuite: cover 'refuse options = compress' for the daemon

Add a daemon-refuse-compress test that builds a module configured with
'refuse options = compress' and asserts that:
  1. an attempted -z transfer to that module fails with an error
     mentioning --compress, and
  2. the same transfer without -z still succeeds.

This pins down the documented way to disable all compression on a
daemon, which previously had no automated coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agotoken: harden compressed-token decoding against integer overflow
Andrew Tridgell [Wed, 29 Apr 2026 01:10:59 +0000 (11:10 +1000)] 
token: harden compressed-token decoding against integer overflow

The receiver's three compressed-token decoders --
recv_deflated_token (zlib), recv_zstd_token, and
recv_compressed_token (lz4) -- accumulated rx_token (a 32-bit
signed counter) without overflow checking. A malicious sender
could craft a compressed-token stream that walked rx_token past
INT32_MAX, with careful manipulation leaking process memory
contents to the wire (environment variables, passwords, heap
pointers, library pointers -- significantly weakening ASLR
and facilitating further exploitation).

Cap rx_token at MAX_TOKEN_INDEX = 0x7ffffffe. Fold the
bookkeeping into recv_compressed_token_num() and
recv_compressed_token_run() shared by all three decoders. Reject
negative or out-of-range token values explicitly. Also cap the
simple_recv_token literal-block length at the source: any
wire-supplied length > CHUNK_SIZE is ill-formed (the matching
simple_send_token never writes a chunk larger than CHUNK_SIZE),
so reject before looping on attacker-controlled bytes.

Reach: an authenticated daemon connection with compression
enabled (the default for protocols >= 30 when both peers
advertise it). Disabling compression on the daemon
("refuse options = compress" in rsyncd.conf) is the available
workaround.

Reporter: Omar Elsayed (seks99x).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agoci(cygwin): mark all symlink-race regression tests as expected-skipped
Andrew Tridgell [Tue, 5 May 2026 21:44:47 +0000 (07:44 +1000)] 
ci(cygwin): mark all symlink-race regression tests as expected-skipped

Cygwin lacks RESOLVE_BENEATH-equivalent kernel support and the
per-component O_NOFOLLOW fallback also can't be exercised meaningfully
under the cygwin runner's filesystem semantics, so every test that
asserts the secure_relative_open / do_*_at machinery actually blocks
the attack would skip. Make those skips expected in the workflow's
RSYNC_EXPECT_SKIPPED list:

  - chdir-symlink-race
  - chmod-symlink-race
  - bare-do-open-symlink-race
  - sender-flist-symlink-leak
  - daemon-chroot-acl

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agotestsuite: end-to-end regression test for chdir-symlink-race
Andrew Tridgell [Tue, 5 May 2026 04:34:50 +0000 (14:34 +1000)] 
testsuite: end-to-end regression test for chdir-symlink-race

testsuite/chdir-symlink-race.test runs an actual rsync daemon
(via RSYNC_CONNECT_PROG to avoid the network) configured with
"use chroot = no", plants a symlink at module/subdir -> ../outside,
and runs four flavours of attacker-shaped transfer (single-file
poc_chmod, -r push into the symlinked subdir with --size-only and
without, -r push into the module root). All four must leave the
outside-the-module sentinel file's mode AND content unchanged.

Portability:
  - file_mode() helper falls back to BSD stat -f %Lp when GNU
    stat -c %a is unavailable (macOS, FreeBSD).
  - Pre-saved pristine copy + cmp(1) replaces sha1sum, which
    differs across platforms (sha1sum / shasum / sha1).

Tests are kept running as root in the user-namespace re-exec
wrapper used by symlink-race tests so the daemon's setuid path
doesn't drop into the test user's identity (which on Linux
would mean the chmod-escape code path can't trigger because
the test user doesn't have CAP_FOWNER over the outside file).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agoutil1+syscall: secure copy_file source/dest opens; bare-path defence-in-depth
Andrew Tridgell [Tue, 5 May 2026 23:45:30 +0000 (09:45 +1000)] 
util1+syscall: secure copy_file source/dest opens; bare-path defence-in-depth

Three related codex audit findings:

  Finding 3a: copy_file()'s source open in util1.c used
  do_open_nofollow(), which only rejects a final-component
  symlink. A parent-component symlink (e.g. --copy-dest=cd where
  cd -> /outside) follows freely and reads outside the module.
  Route through secure_relative_open() with O_NOFOLLOW.

  Finding 3b: generator.c's in-place backup-file create still
  used a bare do_open with O_CREAT, leaving a tiny but reachable
  parent-symlink window between the secure unlink (already
  through do_unlink_at) and the create. Add do_open_at() that
  goes through a secure parent dirfd, and route the call site
  through it.

  Finding 3c: copy_file()'s destination open in
  unlink_and_reopen() had the same bare-do_open pattern; route
  through do_open_at as well.

Adds testsuite/copy-dest-source-symlink.test and
testsuite/bare-do-open-symlink-race.test as regression coverage
for both attack shapes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agosyscall: add symlink-race-safe do_*_at() wrappers and harden secure_relative_open
Andrew Tridgell [Tue, 5 May 2026 05:02:48 +0000 (15:02 +1000)] 
syscall: add symlink-race-safe do_*_at() wrappers and harden secure_relative_open

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>
2 days agoutil1: secure change_dir() against symlink-race chdir-escape
Andrew Tridgell [Tue, 5 May 2026 04:34:33 +0000 (14:34 +1000)] 
util1: secure change_dir() against symlink-race chdir-escape

The receiver's chdir(2) into a destination subdirectory followed
attacker-planted symlinks at every path component. Once CWD
escaped the module, every subsequent path-relative syscall (open,
chmod, lchown, ...) inherited the escape -- defeating
secure_relative_open's RESOLVE_BENEATH anchor against AT_FDCWD,
since the anchor itself was now outside the module.

Route change_dir's relative target through secure_relative_open()
and fchdir() to the resulting dirfd in am_daemon && !am_chrooted
mode, so the chdir step itself can no longer follow a parent-
symlink. Same treatment applied to the CD_SKIP_CHDIR /
set_path_only path so it also can't follow attacker symlinks
during path tracking.

Adds testsuite/sender-flist-symlink-leak.test covering the
sender-side flist resolution variant of the same primitive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 days agosyscall+receiver: secure receiver-side do_chmod against symlink-race TOCTOU
Andrew Tridgell [Mon, 4 May 2026 11:53:14 +0000 (21:53 +1000)] 
syscall+receiver: secure receiver-side do_chmod against symlink-race TOCTOU

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>
2 days agosender: fix read-path TOCTOU by opening from module root (CVE-2026-29518)
Andrew Tridgell [Sat, 28 Feb 2026 22:28:40 +0000 (09:28 +1100)] 
sender: fix read-path TOCTOU by opening from module root (CVE-2026-29518)

The sender's file open was vulnerable to the same TOCTOU symlink
race as the receiver-side basis-file open. change_pathname() calls
chdir() into subdirectories, which follows symlinks; an attacker
could race to swap a directory for a symlink between the chdir and
the file open, allowing reads of privileged files through the
daemon.

Reconstruct the full relative path (F_PATHNAME + fname) and open
via secure_relative_open() from the trusted module_dir, which
walks each path component without following symlinks. This is
independent of CWD, so the chdir race is neutralised.

CVE-2026-29518.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2 days agosyscall+clientserver: am_chrooted and use_secure_symlinks for daemon-no-chroot (CVE...
Andrew Tridgell [Tue, 30 Dec 2025 23:01:23 +0000 (10:01 +1100)] 
syscall+clientserver: am_chrooted and use_secure_symlinks for daemon-no-chroot (CVE-2026-29518)

CVE-2026-29518: an rsync daemon configured with "use chroot = no"
is exposed to a TOCTOU race on parent path components. A local
attacker with write access to a module can replace a parent
directory component with a symlink between the receiver's check
and its open(), redirecting reads (basis-file disclosure) and
writes (file overwrite) outside the module. Under elevated daemon
privilege this allows privilege escalation. Default
"use chroot = yes" is not exposed.

Add secure_relative_open() in syscall.c. It walks the parent
components under RESOLVE_BENEATH (Linux 5.6+) /
O_RESOLVE_BENEATH (FreeBSD 13+, macOS 15+) / per-component
O_NOFOLLOW elsewhere, anchored at a trusted dirfd, so a parent-
symlink swap is rejected by the kernel. Route the receiver's
basis-file open in receiver.c through it when use_secure_symlinks
is set in clientserver.c rsync_module().

Reporters: Nullx3D (Batuhan SANCAK); Damien Neil; Michael Stapelberg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 weeks agoci(almalinux-8): use python39 module for runtests.py
Andrew Tridgell [Wed, 6 May 2026 19:34:54 +0000 (05:34 +1000)] 
ci(almalinux-8): use python39 module for runtests.py

The default python3 on AlmaLinux 8 is 3.6, but runtests.py uses
subprocess.run(capture_output=...) and check_output(text=...) which
were introduced in 3.7. Install the python39 module stream and point
/usr/bin/python3 at it via alternatives so the existing shebang
resolves correctly.

Reproduced as: TypeError: __init__() got an unexpected keyword
argument 'capture_output' at runtests.py line 75.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 weeks agoci: add Ubuntu 22.04 and AlmaLinux 8 workflows for backporting
Andrew Tridgell [Wed, 6 May 2026 19:27:45 +0000 (05:27 +1000)] 
ci: add Ubuntu 22.04 and AlmaLinux 8 workflows for backporting

The intent is to validate that future security fixes still build and
test cleanly on the oldest still-supported LTS releases of the two
mainstream Linux families, so backports can be developed against the
same CI surface as the trunk:

  - ubuntu-22.04: oldest GitHub Actions runner image still available
    (20.04 was retired in April 2025). Mirrors the existing
    ubuntu-build.yml step list.
  - almalinux-8: RHEL 8 rebuild, full support until 2029. Runs in an
    almalinux:8 container on ubuntu-latest because GHA has no native
    runner for the Fedora/RHEL family. Pulls libzstd/xxhash/lz4 dev
    headers from PowerTools + EPEL; commonmark via pip for the man
    page generator.

Both jobs follow the same paths-ignore convention as the other
workflows so a workflow-only change to one file won't fan out across
the whole CI matrix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 weeks agotestsuite: run protected-regular test as non-root using unshare
Andrew Tridgell [Wed, 22 Apr 2026 02:36:50 +0000 (12:36 +1000)] 
testsuite: run protected-regular test as non-root using unshare

Use unshare with user namespace UID mapping to run the
protected-regular test without real root privileges. Falls back
to skipping if unshare or uidmap is not available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3 weeks agoStart 3.4.3dev going.
Andrew Tridgell [Wed, 29 Apr 2026 23:34:22 +0000 (09:34 +1000)] 
Start 3.4.3dev going.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 weeks agoci: add symlink-dirlink-basis to Cygwin's expected-skipped list
Andrew Tridgell [Wed, 29 Apr 2026 23:22:58 +0000 (09:22 +1000)] 
ci: add symlink-dirlink-basis to Cygwin's expected-skipped list

The test correctly skips on Cygwin (which lacks RESOLVE_BENEATH), but
the workflow's RSYNC_EXPECT_SKIPPED list still treats any change in
the skipped set as a CI failure. Add the new test name so the
skipped/got comparison matches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 weeks agotestsuite: skip symlink-dirlink-basis on platforms without RESOLVE_BENEATH
Andrew Tridgell [Wed, 29 Apr 2026 23:00:09 +0000 (09:00 +1000)] 
testsuite: skip symlink-dirlink-basis on platforms without RESOLVE_BENEATH

secure_relative_open() has a kernel-enforced "stay below dirfd" path
on Linux 5.6+ (openat2 RESOLVE_BENEATH) and FreeBSD 13+ (openat
O_RESOLVE_BENEATH). On Solaris, OpenBSD, NetBSD, and Cygwin the code
falls back to the per-component O_NOFOLLOW walk, which by design
rejects every directory symlink in the path -- the very case this
test exercises. Mark the test skipped there rather than have it
fail with a known regression that's tracked separately.

macOS is intentionally not in the skip list: although it does not
have O_RESOLVE_BENEATH either, the test passes there in practice;
investigation of the underlying reason is left as follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 weeks agosyscall: also use O_RESOLVE_BENEATH on FreeBSD and MacOS
Andrew Tridgell [Wed, 29 Apr 2026 22:44:11 +0000 (08:44 +1000)] 
syscall: also use O_RESOLVE_BENEATH on FreeBSD and MacOS

FreeBSD and MacOS have O_RESOLVE_BENEATH as an openat() flag with the same
"must not escape dirfd" semantics as Linux's RESOLVE_BENEATH. The
kernel rejects ".." escapes, absolute symlinks, and symlinks whose
target lies outside dirfd, while still following symlinks that
resolve within it -- the same trade-off that fixes issue #715 on
Linux.

Add a parallel BSD path in secure_relative_open(), gated on
declared. Unlike Linux, BSD doesn't have the header/runtime split
where the symbol can exist without kernel support, so no runtime
fallback is needed: if the flag compiles in, the kernel honours it.

OpenBSD and NetBSD have no equivalent kernel primitive and continue
to use the existing per-component O_NOFOLLOW walk; issue #715
remains visible on those platforms (a userland resolver or
unveil(2)-based fence would be follow-up work).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 weeks agosyscall: use openat2(RESOLVE_BENEATH) on Linux for secure_relative_open
Andrew Tridgell [Wed, 29 Apr 2026 22:39:22 +0000 (08:39 +1000)] 
syscall: use openat2(RESOLVE_BENEATH) on Linux for secure_relative_open

The CVE fix in commit c35e283 made secure_relative_open() walk every
component of relpath with O_NOFOLLOW. That blocks every symlink in the
path, which is stricter than the threat model required: legitimate
directory symlinks within the destination tree (e.g. when using -K /
--copy-dirlinks) are also rejected, breaking delta transfers with
"failed verification -- update discarded".  See issue #715.

On Linux 5.6+, openat2(RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS) gives
us exactly what we want: the kernel rejects any resolution that would
escape the starting directory (via "..", absolute paths, or symlinks
pointing outside dirfd) while still following symlinks that resolve
within it. /proc magic-links are blocked too.

Use openat2 first; fall back to the existing per-component O_NOFOLLOW
walk on ENOSYS (kernel < 5.6). The lexical "../" checks at the head
of the function are kept as defense in depth. The Linux gate is
plain #ifdef __linux__: the runtime ENOSYS fallback covers the only
case that actually matters (header present + old kernel), and any
Linux build environment without linux/openat2.h will fail with a
clear "no such file" error rather than silently disabling the
protection.

Verified manually that openat2(RESOLVE_BENEATH) blocks all four
escape patterns (absolute symlink, ../ symlink, lexical .., absolute
path) while allowing direct and within-tree symlinks. The new
testsuite/symlink-dirlink-basis.test (taken from PR #864 by Samuel
Henrique) exercises the issue #715 regression and passes; full
make check passes 47/47.

Test: testsuite/symlink-dirlink-basis.test (8 scenarios)
Fixes: https://github.com/RsyncProject/rsync/issues/715
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 weeks agotestsuite/xattrs: ignore SUNWattr_* in the Solaris xls helper
Andrew Tridgell [Wed, 29 Apr 2026 22:18:01 +0000 (08:18 +1000)] 
testsuite/xattrs: ignore SUNWattr_* in the Solaris xls helper

The Solaris xls() function listed every entry in the file's xattr
directory, which on Solaris includes OS-managed SUNWattr_ro and
SUNWattr_rw pseudo-attributes. SUNWattr_rw embeds the file creation
time, so its bytes naturally differ between the source and destination
files, making the xattrs and xattrs-hlink tests fail with diffs that
have nothing to do with rsync.

Rsync's own listxattr wrapper already filters these out
(lib/sysxattrs.c), so the right fix is to filter them in the test
display too. Other platforms are unaffected because each has its own
xls() branch in the case statement.

With the test now actually passing on Solaris, drop the CI hack that
overwrote testsuite/xattrs.test with a skip stub.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 weeks agoci: add OpenBSD and NetBSD build jobs, run 'make check' on the BSDs
Andrew Tridgell [Wed, 29 Apr 2026 22:02:26 +0000 (08:02 +1000)] 
ci: add OpenBSD and NetBSD build jobs, run 'make check' on the BSDs

Mirror the existing FreeBSD workflow for OpenBSD and NetBSD using
vmactions/openbsd-vm and vmactions/netbsd-vm so we get cross-BSD
coverage on push, PR, and the nightly schedule.

Also extend the FreeBSD and Solaris workflows to actually exercise the
test suite by running 'make check' after the build. The Linux, macOS,
and Cygwin jobs already did this.

The Solaris xattrs and xattrs-hlink tests are removed before 'make
check' because the Solaris SUNWattr_ro / SUNWattr_rw system attributes
leak into the test diff; that's a real rsync-on-Solaris issue to follow
up on, but skip the tests for now so the suite goes green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 weeks agoruntests.py: error early when test helper programs are missing
Andrew Tridgell [Wed, 29 Apr 2026 01:35:47 +0000 (11:35 +1000)] 
runtests.py: error early when test helper programs are missing

When invoked directly (rather than via 'make check'), runtests.py
previously left the user with a wall of confusing "not found" errors
from inside individual test scripts if the CHECK_PROGS helpers had not
been built. Detect this up front and point the user at the make
target that builds them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 weeks agopackaging: remove old release system
Andrew Tridgell [Tue, 28 Apr 2026 04:53:29 +0000 (14:53 +1000)] 
packaging: remove old release system

3 weeks agoPreparing for release of 3.4.2 [buildall] v3.4.2
Andrew Tridgell [Tue, 28 Apr 2026 04:29:48 +0000 (14:29 +1000)] 
Preparing for release of 3.4.2 [buildall]

3 weeks agopackaging: new release script
Andrew Tridgell [Tue, 28 Apr 2026 04:27:41 +0000 (14:27 +1000)] 
packaging: new release script

3 weeks agoupdate NEWS.md ready for 3.4.2
Andrew Tridgell [Wed, 22 Apr 2026 04:33:43 +0000 (14:33 +1000)] 
update NEWS.md ready for 3.4.2

3 weeks agopackaging: remove support for rsync-patches
Andrew Tridgell [Tue, 28 Apr 2026 02:54:31 +0000 (12:54 +1000)] 
packaging: remove support for rsync-patches

4 weeks agoDo not clean DISPLAY unconditionally
Michal Ruprich [Mon, 8 Sep 2025 07:49:22 +0000 (09:49 +0200)] 
Do not clean DISPLAY unconditionally

4 weeks agocall tzset() before chroot to cache timezone data
Andrew Tridgell [Wed, 22 Apr 2026 02:53:13 +0000 (12:53 +1000)] 
call tzset() before chroot to cache timezone data

localtime/localtime_r need /etc/localtime for timezone info.
After chroot this file is inaccessible, causing log timestamps
to fall back to UTC. Calling tzset() before chroot ensures the
timezone data is cached by glibc for subsequent calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 weeks agoUsing a correct time in log file
Michal Ruprich [Fri, 31 Jan 2025 13:35:18 +0000 (14:35 +0100)] 
Using a correct time in log file

4 weeks agorsyncd.conf: document the temp dir parameter
Andrew Tridgell [Wed, 22 Apr 2026 02:14:29 +0000 (12:14 +1000)] 
rsyncd.conf: document the temp dir parameter

The temp dir parameter was functional but undocumented in the man page.

Fixes: https://github.com/RsyncProject/rsync/issues/820
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 weeks agoruntests.py: preserve test-execution order in skipped list
Andrew Tridgell [Wed, 22 Apr 2026 02:21:48 +0000 (12:21 +1000)] 
runtests.py: preserve test-execution order in skipped list

The sorted() call reordered skipped test names alphabetically,
causing CI expected-skipped mismatches (e.g. acls,acls-default
instead of acls-default,acls). Sort by original test order instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 weeks agoruntests.py: add -j/--parallel option for parallel test execution
Andrew Tridgell [Wed, 22 Apr 2026 02:07:31 +0000 (12:07 +1000)] 
runtests.py: add -j/--parallel option for parallel test execution

Add parallel test execution using concurrent.futures. With -j8 the
test suite completes in ~4s vs ~29s sequential (~7x speedup).

Also fix two issues that caused failures under parallel execution:
- rsync_ls_lR now prunes testtmp/ so parallel tests don't see each
  other's temp files when scanning the source tree
- clean-fname-underflow.test now uses $scratchdir instead of /tmp

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 weeks agoreplace runtests.sh with runtests.py
Andrew Tridgell [Wed, 22 Apr 2026 01:45:24 +0000 (11:45 +1000)] 
replace runtests.sh with runtests.py

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>
4 weeks agoFix glibc-2.43 constness warnings
Holger Hoffstätte [Sun, 5 Apr 2026 22:44:02 +0000 (00:44 +0200)] 
Fix glibc-2.43 constness warnings

Glibc 2.43 added C23 const-preserving overloads to various string functions,
which change the return type depending on the constness of the argument(s).
Currently this leads to warnings from calls to strtok() or strchr().
Fix this by properly declaring the respective variable types.

Signed-off-by: Holger Hoffstätte <holger@applied-asynchrony.com>
4 weeks agozero all new memory from allocations
Andrew Tridgell [Wed, 22 Apr 2026 00:59:11 +0000 (10:59 +1000)] 
zero all new memory from allocations

Change my_alloc() to use calloc instead of malloc so all fresh
allocations return zeroed memory. Also zero the expanded portion
in expand_item_list() after realloc, since it knows both old and
new sizes. This gives more predictable behaviour in case of bugs
where uninitialised or stale memory is accidentally accessed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 weeks agoxattrs: fixed count in qsort
Andrew Tridgell [Tue, 21 Apr 2026 23:57:45 +0000 (09:57 +1000)] 
xattrs: fixed count in qsort

this fixes the count passed to the sort of the xattr list. This issue
was reported here:

https://www.openwall.com/lists/oss-security/2026/04/16/2

the bug is not exploitable due to the fork-per-connection design of
rsync, the attack is the equivalent of the user closing the socket
themselves.

5 weeks agofix signed integer overflow in proxy protocol v2 header parsing
Andrew Tridgell [Thu, 16 Apr 2026 00:50:49 +0000 (10:50 +1000)] 
fix signed integer overflow in proxy protocol v2 header parsing

The len field in the proxy v2 header was declared as signed char,
allowing a negative size to bypass the validation check and cause
a stack buffer overflow when passed to read_buf() as size_t.

This bug was reported by John Walker from ZeroPath, many thanks for
the clear report!

With the current code this bug does not represent a security issue as
it only results in the exit of the forked process that is specific to
the attached client, so it is equivalent to the client closing the
socket, so no CVE for this, but it is good to fix it to prevent a
future issue.

5 weeks agozlib: convert K&R function definitions to ANSI style
Andrew Tridgell [Thu, 16 Apr 2026 03:40:59 +0000 (13:40 +1000)] 
zlib: convert K&R function definitions to ANSI style

The bundled zlib 1.2.8 used K&R-style function definitions which are
rejected by clang 16+ as hard errors. Convert all 90 functions across
9 files to ANSI-style prototypes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 months agoCI: add simd-checksum to expected-skipped on macOS and Cygwin
Andrew Tridgell [Sun, 1 Mar 2026 22:35:09 +0000 (09:35 +1100)] 
CI: add simd-checksum to expected-skipped on macOS and Cygwin

The new simd-checksum test is skipped on platforms where SIMD
instructions are unavailable (macOS ARM, Cygwin). Add it to the
RSYNC_EXPECT_SKIPPED lists so CI doesn't fail on the mismatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2 months agofix uninitialized mul_one in AVX2 checksum and add SIMD checksum test
Andrew Tridgell [Sat, 28 Feb 2026 21:42:04 +0000 (08:42 +1100)] 
fix uninitialized mul_one in AVX2 checksum and add SIMD checksum test

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>
4 months agoacl: fixed ACL ID mapping for non-root
Andrew Tridgell [Mon, 19 Jan 2026 00:14:40 +0000 (11:14 +1100)] 
acl: fixed ACL ID mapping for non-root

closes issue #618

4 months agoCI: fixed MacOS test
Andrew Tridgell [Tue, 30 Dec 2025 23:45:36 +0000 (10:45 +1100)] 
CI: fixed MacOS test

fixed multiple MacOS issues

4 months agoreject negative token values in compressed stream receivers
Andrew Tridgell [Tue, 30 Dec 2025 07:49:34 +0000 (18:49 +1100)] 
reject negative token values in compressed stream receivers

Validate that token numbers read from compressed streams are
non-negative. A negative token value would cause the return value
of recv_*_token() to become positive, which callers interpret as
literal data length, but no data pointer is set on this code path.

While this only causes the receiver to crash (which is process-isolated
and only affects the attacker's own connection), it's still undefined
behavior.

Reported-by: Will Sergeant <wlsergeant@gmail.com>
4 months agoutil: fixed issue in clean_fname()
Andrew Tridgell [Sat, 23 Aug 2025 09:14:59 +0000 (19:14 +1000)] 
util: fixed issue in clean_fname()

fixes buffer underflow (not exploitable) in clean_fname

4 months agotestsuite: added clean-fname-underflow test
Andrew Tridgell [Sat, 23 Aug 2025 08:29:06 +0000 (18:29 +1000)] 
testsuite: added clean-fname-underflow test

4 months agofix uninitialized buf1 in get_checksum2() MD4 path
Andrew Tridgell [Tue, 30 Dec 2025 05:21:41 +0000 (16:21 +1100)] 
fix uninitialized buf1 in get_checksum2() MD4 path

The static buf1 pointer was only allocated when len > len1, but on
first call with len == 0, this condition is false (0 > 0), leaving
buf1 NULL when passed to memcpy().

Fixes #673

4 months agorsync: Add missing `dirs` long option
Nebojša Cvetković [Tue, 25 Nov 2025 13:30:46 +0000 (13:30 +0000)] 
rsync: Add missing `dirs` long option

8 months agofixed an invalid access to files array
Andrew Tridgell [Sat, 23 Aug 2025 07:26:53 +0000 (17:26 +1000)] 
fixed an invalid access to files array

this was found by Calum Hutton from Rapid7. It is a real bug, but
analysis shows it can't be leverged into an exploit. Worth fixing
though.

Many thanks to Calum and Rapid7 for finding and reporting this

8 months agooptions.c: Fix segv if poptGetContext returns NULL
Ronnie Sahlberg [Thu, 30 Jan 2025 03:27:38 +0000 (13:27 +1000)] 
options.c: Fix segv if poptGetContext returns NULL

If poptGetContext returns NULL, perhaps due to OOM,
a NULL pointer is passed into poptReadDefaultConfig()
which in turns SEGVs when trying to dereference it.

This was found using https://github.com/sahlberg/malloc-fail-tester.git
$ ./test_malloc_failure.sh rsync -Pav crash crosh

Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
8 months agosyscall: fix a Y2038 bug by replacing Int32x32To64 with multiplication
Silent [Mon, 13 Jan 2025 14:01:06 +0000 (15:01 +0100)] 
syscall: fix a Y2038 bug by replacing Int32x32To64 with multiplication

Int32x32To64 macro internally truncates the arguments to int32,
while time_t is 64-bit on most/all modern platforms.
Therefore, usage of this macro creates a Year 2038 bug.

8 months agoIgnore `directory has vanished` errors.
Jeremy Norris [Wed, 12 Mar 2025 15:09:57 +0000 (10:09 -0500)] 
Ignore `directory has vanished` errors.

8 months agomake lots of global variables `const`
Max Kellermann [Tue, 25 Mar 2025 14:13:43 +0000 (15:13 +0100)] 
make lots of global variables `const`

This way, they can live in `.rodata` and the compiler is allowed to do
certain optimizations.

8 months agoFix handling of objects with many xattrs on FreeBSD
Peter Eriksson [Tue, 13 May 2025 12:01:01 +0000 (14:01 +0200)] 
Fix handling of objects with many xattrs on FreeBSD

8 months agochore: gitignore MacOS debug symbols
Rahul Mehta [Thu, 22 May 2025 07:47:36 +0000 (03:47 -0400)] 
chore: gitignore MacOS debug symbols

8 months agoAllow `ls(1)` to fail in test setup
Emily [Tue, 5 Aug 2025 14:55:24 +0000 (15:55 +0100)] 
Allow `ls(1)` to fail in test setup

This can happen when the tests are unable to `stat(2)` some files in
`/etc`, `/bin`, or `/`, due to Unix permissions or other sandboxing. We
still guard against serious errors, which use exit code 2.

8 months agofixed remove multiple leading slashes
fbuescher [Wed, 11 Jun 2025 07:27:59 +0000 (09:27 +0200)] 
fixed remove multiple leading slashes

8 months agobool is a keyword in C23
Michal Ruprich [Fri, 17 Jan 2025 11:37:57 +0000 (12:37 +0100)] 
bool is a keyword in C23

8 months agoconfigure.ac: check for xattr support both in libc and in -lattr
Eli Schwartz [Tue, 22 Apr 2025 20:17:55 +0000 (16:17 -0400)] 
configure.ac: check for xattr support both in libc and in -lattr

In 2015, the attr/xattr.h header was fully removed from upstream attr.

In 2020, rsync started preferring the standard header, if it exists:
https://github.com/RsyncProject/rsync/pull/22

But the fix was incomplete. We still looked for the getxattr function in
-lattr, and used it if -lattr exists. This was the case even if the
system libc was sufficient to provide the needed functions. Result:
overlinking to -lattr, if it happened to be installed for any other
reason.

```
checking whether to support extended attributes... Using Linux xattrs
checking for getxattr in -lattr... yes
```

Instead, use a different autoconf macro that first checks if the
function is available for use without any libraries (e.g. it is in
libc).

Result:

```
checking whether to support extended attributes... Using Linux xattrs
checking for library containing getxattr... none required
```

Signed-off-by: Eli Schwartz <eschwartz@gentoo.org>
8 months agofeat: add compress threads to man page
Ethan Halsall [Sat, 24 Dec 2022 23:43:09 +0000 (17:43 -0600)] 
feat: add compress threads to man page

8 months agofeat: validate compress threads option
Ethan Halsall [Sat, 24 Dec 2022 23:42:47 +0000 (17:42 -0600)] 
feat: validate compress threads option

8 months agofeat: add threads to zstd compression
Ethan Halsall [Fri, 23 Dec 2022 22:22:44 +0000 (16:22 -0600)] 
feat: add threads to zstd compression

8 months agoFix flaky hardlinks test
Arnaud Rebillout [Thu, 6 Mar 2025 03:54:05 +0000 (10:54 +0700)] 
Fix flaky hardlinks test

The test was added in dc34990, it turns out that it's flaky. It failed
once on the Debian build infra, cf. [1].

The problem is that the command `rsync -aH '$fromdir/sym' '$todir'`
updates the mod time of `$todir`, so there might be a diff between the
output of `rsync_ls_lR $fromdir` and `rsync_ls_lR $todir`, if ever rsync
runs 1 second (or more) after the directories were created.

To clarify: it's easy to make the test fails 100% of the times with this
change:

```
 makepath "$fromdir/sym" "$todir"
+sleep 5
 checkit "$RSYNC -aH '$fromdir/sym' '$todir'" "$fromdir" "$todir"
```

With the fix proposed here, we don't use `checkit` anymore, instead we
just run the rsync command, then a simple `diff` to compare the two
directories. This is exactly what the other `-H` test just above does.

In case there's some doubts, `diff` fails if `sym` is missing:

```
$ mkdir -p foo/sym bar
$ diff foo bar || echo KO!
Only in foo: sym
KO!
```

I tested that, after this commit, the test still catches the `-H`
regression in rsync 3.4.0.

Fixes: https://github.com/RsyncProject/rsync/issues/735
[1]: https://buildd.debian.org/status/fetch.php?pkg=rsync&arch=ppc64el&ver=3.4.1%2Bds1-1&stamp=1741147156&raw=0

8 months agoFix --open-noatime option not working on files
Krzysztof Płocharz [Mon, 27 Jan 2025 16:20:47 +0000 (17:20 +0100)] 
Fix --open-noatime option not working on files

atime of source files could sometimes be overwritten
even though --open-noatime option was used.

To fix that, optional O_NOATIME flag was added
to do_open_nofollow which is also used to open regular
files since fix:
  "fixed symlink race condition in sender"
Previously optional O_NOATIME flag was only in do_open.

8 months agoMake the build reproducible
Chris Lamb [Tue, 12 Aug 2025 19:23:59 +0000 (20:23 +0100)] 
Make the build reproducible

From https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1093201:
Whilst working on the Reproducible Builds effort [0], we noticed that
rsync could not be built reproducibly.

This is because the date in the manual page can vary depending on
whether there is a .git directory and the modification time of version.h
and Mafile, which might get modified when patching via quilt.

A patch is attached that makes this use SOURCE_DATE_EPOCH, which
will always be reliable.

15 months agoTest on ubuntu-latest.
Wayne Davison [Tue, 11 Feb 2025 21:37:12 +0000 (13:37 -0800)] 
Test on ubuntu-latest.

16 months agopopt: remove obsolete findme.c & findme.h
Alan Coopersmith [Wed, 15 Jan 2025 21:17:38 +0000 (13:17 -0800)] 
popt: remove obsolete findme.c & findme.h

popt 1.14 merged these into popt.c but the import into rsync
missed removing them.

Fixes: https://github.com/RsyncProject/rsync/issues/710
Signed-off-by: Alan Coopersmith <alan.coopersmith@oracle.com>
16 months agoUpdate MAINTAINER_TZ_OFFSET on release.
Wayne Davison [Thu, 16 Jan 2025 07:24:26 +0000 (23:24 -0800)] 
Update MAINTAINER_TZ_OFFSET on release.

This also fixes a string with \s that wasn't a r'...' string.

16 months agoFix python deprecation warning.
Wayne Davison [Thu, 16 Jan 2025 06:36:29 +0000 (22:36 -0800)] 
Fix python deprecation warning.