Commit Graph

7413 Commits

Author SHA1 Message Date
Andrew Tridgell
36860669cc 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
2026-05-15 12:01:21 +10:00
Andrew Tridgell
0beceb95ac 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>
2026-05-15 12:01:13 +10:00
Andrew Tridgell
4370cb2ce1 testsuite: fixes for 3.2.7 backport 2026-05-08 08:20:48 +10:00
Andrew Tridgell
f406c780ec popt: fix poptDupArgv strlcpy size argument
The bundled popt 1.18 (rsync 3.2.7) calls strlcpy(dst, argv[i], nb)
inside the per-arg loop in poptDupArgv(), where nb is the TOTAL
allocation size — not the remaining bytes after dst has advanced.
The actual write was always within the malloc'd buffer, so it was
silent on older glibcs, but glibc 2.39+ fortified strlcpy compares
the size argument against __bos(dst) and aborts with "*** buffer
overflow detected ***" once dst passes through any bytes.

That broke ~15 tests on Ubuntu 24.04 / glibc 2.39 in CI (any test
spawning a child rsync via popt's argv duplication path). Pass the
remaining bytes (end_buf - dst) so the size argument matches reality.

Master fixed the same bug differently in popt 1.19 (4c8683c8 "update
to popt 1.19") by switching to stpcpy, but pulling that 1500-line
refresh into a security backport is heavier than warranted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
259e04f923 ci: update RSYNC_EXPECT_SKIPPED for 3.2.7 backport baseline
The macOS / Cygwin expected-skipped lists in 3.4.x master assume
features that are present on master but not on 3.2.7 (e.g.
open-noatime semantics) and don't account for new tests added by
the May 2026 sec-patches that get skipped on these platforms
(daemon-chroot-acl, *-symlink-race, sender-flist-symlink-leak).
Realign each list with what the platform actually skips on this
backport branch so RSYNC_EXPECT_SKIPPED matches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
189612f94a testsuite: use integer sleep in clean-fname-underflow.test
Solaris /usr/bin/sleep is POSIX and rejects fractional seconds, which
made the test abort silently under `set -eu` (empty log, FAIL). One
second is more than enough for the daemon to listen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:20:48 +10:00
Jiri Slaby
9354f27f9d exclude: fix crashes with fortified strlcpy()
Fortified (-D_FORTIFY_SOURCE=2 for gcc) builds make strlcpy() crash when
its third parameter (size) is larger than the buffer:
  $ rsync -FFXHav '--filter=merge global-rsync-filter' Align-37-43/ xxx
  sending incremental file list
  *** buffer overflow detected ***: terminated

It's in the exclude code in setup_merge_file():
  strlcpy(y, save, MAXPATHLEN);

Note the 'y' pointer was incremented, so it no longer points to memory
with MAXPATHLEN "owned" bytes.

Fix it by remembering the number of copied bytes into the 'save' buffer
and use that instead of MAXPATHLEN which is clearly incorrect.

Fixes #511.
2026-05-08 08:20:48 +10:00
Andrew Tridgell
a335091e91 CI: added workflows from master for backport testing 2026-05-08 08:20:48 +10:00
Andrew Tridgell
3ac8349e07 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
816aa32059 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
f50c1d59c4 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
74ea276900 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
0a5fa00fdc 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
4255413e0c 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
901041dddc 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
7c8a647c2e 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
a277a06b10 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
39b3074a1a 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
d22b6bc7d1 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
24852cda3d 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
99b36291d0 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
1a5ad81add 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>
2026-05-08 08:20:48 +10:00
Andrew Tridgell
2b3f8aacc7 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>
2026-05-07 07:25:14 +10:00
Andrew Tridgell
61d987c54a 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>
2026-05-07 07:25:14 +10:00
Andrew Tridgell
72d1cf1c28 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>
2026-05-07 07:25:14 +10:00
Andrew Tridgell
9ba21f638c 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>
2026-05-07 07:24:49 +10:00
Andrew Tridgell
acf4b7b839 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>
2026-05-07 07:24:42 +10:00
Andrew Tridgell
968d4c0cd0 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.
2026-05-07 07:24:42 +10:00
Andrew Tridgell
d69162dbf4 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>
2026-05-07 07:24:42 +10:00
Andrew Tridgell
cad6aab534 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.
2026-05-07 07:23:37 +10:00
Andrew Tridgell
7ab20b293f 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>
2026-05-07 07:23:37 +10:00
Holger Hoffstätte
c822d0c404 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>
2026-05-07 07:23:37 +10:00
Andrew Tridgell
f8d8bba793 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>
2026-05-07 07:23:23 +10:00
Andrew Tridgell
43e2af4dd3 acl: fixed ACL ID mapping for non-root
closes issue #618
2026-05-07 07:23:23 +10:00
Andrew Tridgell
50f0add12a 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>
2026-05-07 07:22:46 +10:00
Andrew Tridgell
ada7ba004f 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
2026-05-07 07:22:46 +10:00
Andrew Tridgell
41213d20d1 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
2026-05-07 07:22:46 +10:00
Andrew Tridgell
03d0b41e64 testsuite: added clean-fname-underflow test 2026-05-07 07:22:39 +10:00
Andrew Tridgell
934a6bec96 util: fixed issue in clean_fname()
fixes buffer underflow (not exploitable) in clean_fname
2026-05-07 07:22:39 +10:00
Eli Schwartz
841ab71c7c 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>
2026-05-07 07:22:39 +10:00
Michal Ruprich
ae1bc1b9e7 Using a correct time in log file 2026-05-07 07:22:39 +10:00
Ronnie Sahlberg
350d39a05d 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>
2026-05-07 07:22:39 +10:00
Silent
9141bf1d83 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.
2026-05-07 07:22:39 +10:00
Andrew Tridgell
ad7dc53795 fixed symlink race condition in sender
when we open a file that we don't expect to be a symlink use
O_NOFOLLOW to prevent a race condition where an attacker could change
a file between being a normal file and a symlink
2026-05-07 07:21:57 +10:00
Andrew Tridgell
5ddf470275 make --safe-links stricter
when --safe-links is used also reject links where a '../' component is
included in the destination as other than the leading part of the
filename
2026-05-07 07:21:57 +10:00
Andrew Tridgell
de7a95a775 range check dir_ndx before use 2026-05-07 07:21:57 +10:00
Wayne Davison
14026162dc Refuse a duplicate dirlist. 2026-05-07 07:21:57 +10:00
Andrew Tridgell
16666c6e75 disallow ../ elements in relpath for secure_relative_open 2026-05-07 07:21:57 +10:00
Andrew Tridgell
866dd7131e receiver: use secure_relative_open() for basis file
this prevents attacks where the basis file is manipulated by a
malicious sender to gain information about files outside the
destination tree
2026-05-07 07:21:57 +10:00
Andrew Tridgell
b31301abb7 added secure_relative_open()
this is an open that enforces no symlink following for all path
components in a relative path
2026-05-07 07:21:57 +10:00