Commit Graph
321 Commits
Author SHA1 Message Date
Andrew Tridgell 9b15300efa rsync: sanitize the peer-supplied basis xname on the client too
read_ndx_and_attrs() sanitized the wire-supplied xname (the alternate-basis
leaf name sent with ITEM_XNAME_FOLLOWS) only when sanitize_paths was set,
which is the daemon side. A client receiver has sanitize_paths == 0, so a
malicious server could send an xname containing ".." and, joined to an
operator basedir (--link-dest / --compare-dest / --copy-dest, or the fuzzy
dir), have the client open an out-of-tree file as the delta basis -- a
client-side arbitrary-read / file-existence-oracle / FIFO-hang. The ownership
walk in secure_basis_open() does not stop this: it deliberately follows a
plain ".." to a regular file (the legitimate --link-dest=../01 sibling, #915)
and only refuses foreign-owned symlink components.

Sanitize xname unconditionally. The operator basedir may legitimately be
relative, but the leaf name that arrives over the wire never legitimately
needs ".." or a leading "/".

Reported by z3r0s.
2026-07-24 16:39:13 +10:00
Andrew Tridgell 9e439251c7 rsync: never path-resolve a confined receiver's xattr/ACL write (copy-xattrs race)
set_file_attrs() pins the entry via the cached held dir fd (held_dfd_for) and
drives the xattr/ACL ops off that fd (fsetxattr).  But when the pin missed --
held_dfd_for() returns -1 (the path is deeper than the dirfd cache, or its dir
isn't the held one), or the leaf openat() loses a race -- held_fd stayed -1 and
set_stat_xattr()/set_xattr()/get_acl_fdat()/set_acl_fdat() fell through to the
path-based branch (sys_lsetxattr(fname,...)).  Unlike the chmod/chown/times path
wrappers (which secure-resolve), that raw lsetxattr re-resolves the parent, so a
concurrent flip of a dest parent component to a symlink->outside lands the xattr
OUTSIDE the destination tree (the intermittent copy-xattrs-symlink-race escape
that surfaces under -j load, which widens the open->setxattr window).

Re-pin through secure_relative_open() when the cached pin misses on a confined,
non-operator receiver path, so the xattr/ACL ops always use a confined fd -- NOT
a raw path lsetxattr; if the re-pin also fails (a genuinely raced parent/leaf
symlink) skip the path-based ops (xattr_refuse) rather than redirecting them.
The re-pin passes O_DIRECTORY for a directory leaf.  Apply the same re-pin/refuse
to gen_entry_copy_xattrs() (the dir xattr copy), whose dfd<0 path likewise fell
to copy_xattrs() with dest_fd==-1.  chmod/chown/times are unchanged (confined via
their *at wrappers); operator paths keep op_pin/op_refuse.
2026-06-28 08:57:01 +10:00
Andrew Tridgell 8778f3f9e3 rsync: pin cross-tree fake-super metadata writes to a confined fd
set_file_attrs() pins a cross-tree operator leaf (an absolute --temp-dir /
--backup-dir / --*-dest path) with an O_NOFOLLOW fd (op_leaf_fd) and drives
chmod/chown/xattr/ACL/times off it so a flipped parent can't redirect them, but
the pin was opened only when am_root >= 0.  A daemon module with "fake super =
yes" runs with am_root < 0, so the cross-tree leaf kept op_leaf_fd/held_fd == -1
and the fake-super %stat, preserved-xattr and ACL-as-xattr writes fell back to
path-based sys_lsetxattr(): a local module writer racing the staging parent to a
symlink could redirect those metadata writes outside the module
(CVE-2026-53799 residual on the fake-super path).

Open the pin for fake-super too -- it has nothing to do with privilege: the
daemon owns the freshly-staged leaf it is about to set metadata on, so any
O_NOFOLLOW open failure is a race and is refused rather than redirected through a
re-resolvable path.  Every metadata op already routes through
held_fd/op_leaf_fd/op_refuse, so they all become fd-based; strace confirms the
cross-tree fake-super write uses fsetxattr()/fchmod(), never the l-variant.
2026-06-27 18:22:08 +10:00
Andrew Tridgell cf4cffcccb confine the remaining cross-tree operator-path syscalls
The backup-dir fix pinned chmod/chown for an operator leaf; extend the same
confinement to every other sink that re-resolves a cross-tree operator path
(an absolute --temp-dir/--partial-dir/--*-dest), each confirmed by a cross-uid
race PoC that is RED on stock 3.2.7 and GREEN here:

- set_file_attrs (rsync.c): route times (do_futimens), and -- by aliasing
  held_fd to the pinned op_leaf_fd -- the xattr/ACL ops through the same
  O_NOFOLLOW leaf fd, not a re-resolvable path.  A raced/refused pin skips them
  (op_refuse) rather than redirecting.  finish_transfer now wraps the pre-rename
  set_file_attrs in operator_path_resolve so an absolute --temp-dir temp file's
  metadata is pinned too (in-tree temps keep their held dirfd, so op_pin is off).

- do_rename_at / do_link_at (syscall.c): an ABSOLUTE side was left at AT_FDCWD
  and followed a flipped parent symlink, letting a name-disclosed --temp-dir /
  predictable --partial-dir rename pull an attacker file into the destination
  (content injection).  Resolve an absolute (operator) side via the ownership
  walk -- with module-exclude enforced -- while a relative (transfer) side stays
  on secure_relative_open.  --insecure-links keeps the legacy path.

- secure_basis_open (receiver.c): an alt-dest basis read
  (--copy-dest/--compare-dest/--link-dest) on a non-daemon receiver used a bare
  do_open; route it through the ownership walk (refuses a foreign-owned basis
  symlink, still allows the "../sibling" basis of #915).  Daemons keep their
  existing confinement branch.

- configure.ac: probe futimens (do_futimens is gated on HAVE_FUTIMENS).

New fd wrappers: do_fchmod, do_fchown, do_futimens.  Documented residuals left
as-is: copy_altdest_file's basis copy (routing it re-opens copy-xattrs-symlink-
race; basis_link_stat already refuses the foreign symlink), crtimes
(do_setattrlist_crtime/do_SetFileTime, path-based on macOS/Cygwin), and
device/socket leaf metadata (pinning a device via open has side effects).
2026-06-27 18:17:30 +10:00
Andrew Tridgell a802ffa58b backup: confine cross-tree operator-path metadata via a pinned fd
A symlink race on a non-daemon --backup-dir let a local attacker redirect
rsync's backup writes and chmods outside the backup tree when rsync runs as
root.  make_backup() sets operator_path_resolve, but set_file_attrs() had no
held parent dir fd for an absolute backup path (held_dfd_for() returns -1), so
its chmod/chown fell through to the path-based wrappers and, for an absolute
path, to raw chmod()/lchown().  copy_valid_path() mirrors the source dir's
attrs onto each backup subdir; when an attacker flips a backup component to a
symlink in that window the raw lchown retags the planted symlink as root-owned
-- laundering it into a "trusted" (uid 0) symlink that the owner-walk then
follows, so the backup rename/chmod escapes the tree.

Pin the leaf inode of a cross-tree operator path with an O_NOFOLLOW open via
the operator owner-walk resolver and drive fchmod/fchown off that fd; a raced
symlink leaf makes the open fail and the op is refused, never redirected.  Gate
on the INTENDED type (new_mode), not the attacker-controlled on-disk type.  As
root any open failure is the race (a real owned leaf never fails); a non-root
operator, which cannot launder a uid-0 symlink, falls back to the legacy path
op on a benign EACCES.  --insecure-links opts back out.

Adds do_fchmod()/do_fchown() fd wrappers.  Residual cross-tree metadata sinks
(times, ACLs, xattrs, and --temp-dir's finish_transfer set_file_attrs) are not
covered here and are tracked for a follow-up.
2026-06-27 18:17:30 +10:00
Andrew Tridgell 0a4e480202 rsync: stat through the held dir fd in set_file_attrs
set_file_attrs() resolved the entry's directory into a held dirfd (for the
chmod/chown/times *at ops) but its initial link_stat() -- and the post-chown
setuid/setgid re-stat -- still went through x_lstat()/do_lstat_at(), re-walking
the full path per file.  Do the stat with link_stat_at() against the held dirfd
(mirroring gen_entry_stat, guarded on am_root >= 0 for the fake-super xattr
fold) so the directory is opened once and reused.
2026-06-15 15:24:42 +10:00
Andrew Tridgell 26783f76fe rsync/acls/xattrs: apply received xattrs and ACLs through the held fd
set_file_attrs() holds an O_RDONLY|O_NOFOLLOW fd (held_fd) for the entry's
regular-file/dir/FIFO inode so metadata is applied to the pinned inode rather
than by re-resolving the path, which a parent-symlink race could redirect.

- xattrs: the set/remove/list of -X xattrs use the f-variant calls on held_fd
  (lib/sysxattrs gains the wrappers); a raced FIFO can't block the open
  (O_NONBLOCK).
- ACLs (real root): get_acl_fdat()/set_acl_fdat() apply the ACL via lib/acl.c's
  fd path (xacl_*_fd), or setxattrat(AT_SYMLINK_NOFOLLOW) on the dirfd+leaf for
  a socket/device, covering both the access and default ACL.  pack_smb_acl()+
  change_sacl_perms() still build the entries, so the bytes written match
  acl_set_file() exactly.  With no safe primitive we skip rather than re-resolve
  the path.  The legacy path-based libacl calls remain only for the cross-tree
  (no held dirfd) and non-Linux cases.
- --fake-super ACL-as-xattr writes/deletes also go through held_fd, closing the
  last path-based metadata write under fake-super.
2026-06-15 15:24:42 +10:00
Andrew Tridgell c7aeb639bf rsync/generator/receiver/flist: route attr/basis/scan ops through held dirfds
Behaviour-neutral held-dirfd conversion (the substrate the broadened gate
hangs off):
 - set_file_attrs() resolves the entry's dir once (held_dfd_for) and issues
   chown/chmod/times as single-component *at() calls against that held fd;
 - the generator routes per-entry stat/mkdir/chmod through a held dirfd;
 - the receiver reads the delta basis via the held dirfd and resets the
   one-slot dir-fd cache at flist-chunk boundaries;
 - the sender scan stats entries via the opendir fd.
2026-06-12 17:03:19 +10:00
Andrew TridgellandClaude Opus 4.7 72a6634479 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 TridgellandClaude Opus 4.7 d870b43a32 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
Wayne Davison 0902b52f66 Some checksum buffer fixes.
- Put sum2_array into sum_struct to hold an array of sum2 checksums
  that are each xfer_sum_len bytes.
- Remove sum2 buf from sum_buf.
- Add macro sum2_at() to access each sum2 array element.
- Throw an error if a sums header has an s2length larger than
  xfer_sum_len.
2024-10-29 23:06:34 -07:00
Wayne Davison 9cb7529ba6 Remove some trailing whitespace. 2022-08-13 10:53:53 -07:00
Wayne Davison c3b553a93f Preparing for release of 3.2.4pre2 2022-01-15 17:21:01 -08:00
Wayne Davison 3e44bbd313 Preparing for release of 3.2.4pre1 2022-01-02 15:13:19 -08:00
Wayne Davison 0f87eafa2f A couple minor tweaks. 2021-10-13 10:39:44 -07:00
Wayne Davison 3af00277ee We need stat memcpy. 2021-10-10 14:01:59 -07:00
Wayne Davison b774dbc1c0 Improve --omit-dir-times & --omit-link-times
The code now better handles skipping time setting on dirs and/or links
when --atimes and/or --crtimes is specified without --times.
2021-10-10 13:39:09 -07:00
Wayne Davison 296352ecb0 Tweak atime/crtime code a bit more. 2021-10-10 12:43:11 -07:00
Wayne Davison 291a042b3e Support --crtimes on Cygwin. 2021-07-08 18:59:26 -07:00
Wayne Davison 974f49e22a Add --crtimes option. 2020-07-22 12:12:18 -07:00
Wayne Davison 2c681b874e Some fixes after compiling on cygwin. 2020-06-16 22:58:24 -07:00
Wayne Davison e63ff70eae Some indentation fixes. 2020-06-13 19:15:02 -07:00
Wayne Davison d326961290 Fix overzealous setting of mtime & tweak time comparisons
- Stop setting the mtime on a file we didn't transfer (or didn't verify
  the checksum) when the time diff is within the modify window.
- Stop computing a time difference (-1|0|1) when all we care about is
  time equality.
2020-06-13 02:41:30 -07:00
Wayne Davison b936741032 Added --atimes and --set-noatime options. 2020-04-23 13:24:15 -07:00
Wayne Davison d29702134a Spelling fixes from a Fossies run done by Jens. 2020-04-15 17:42:23 -07:00
Wayne Davison 1c82a1e1e5 A few file-data improvements. 2020-04-12 15:51:20 -07:00
Wayne Davison c5fabfb068 Set Copyright years and make them easier to update
I replaced git-set-file-times with an improved version that I wrote
recently (in python3). A new script uses it to figure out the
last-modified year for each *.[ch] file and updates its copyright.
It also puts the latest year into the latest-year.h file for the
output of --version.
2020-04-09 15:11:37 -07:00
Wayne Davison b32aa4797d Make exit_cleanup() use _exit() if called from a signal handler.
Fixes bug #13982.
2020-04-05 10:26:40 -07:00
Wayne Davison 3e2e4b5a33 Tweak the copyright year. 2019-03-16 09:15:49 -07:00
Wayne Davison 0f8e9e2d86 Don't force nanoseconds if a file wasn't transferred or checksummed. 2018-01-15 10:58:31 -08:00
Wayne Davison 473108ae6e Tweak copyright date. 2018-01-14 19:55:07 -08:00
Jeriko One 70aeb5fddd Sanitize xname in read_ndx_and_attrs. 2017-12-03 16:13:05 -08:00
Wayne Davison b973bffa94 If a backup fails (e.g. full disk) rsync should fail.
Fixes bug 11668.
2016-04-17 16:31:57 -07:00
Wayne Davison 453914e35b Update the copyright year. 2015-08-08 12:47:03 -07:00
Wayne Davison 23afe20780 Brant Gurganus's autoconf updates.
This improves some obsolete autoconf macros and increases the minimum
autoconf version from 2.60 to 2.69.  Fixes bug 11369.
2015-07-07 10:37:12 -07:00
Wayne Davison 962f8b9004 Complain if an inc-recursive path is not right for its dir.
This ensures that a malicious sender can't use a just-sent
symlink as a trasnfer path.
2014-12-31 13:48:42 -08:00
Wayne Davison 5b34561cf7 Call set_modtime even if only NSEC is different. 2014-12-31 13:10:37 -08:00
Wayne Davison dfa5b49110 Bump the year to 2014. 2014-01-26 09:29:15 -08:00
Wayne Davison 0e3152febd Change owner+group before setting xattrs to avoid xattr loss.
Fixes bug 10163.
2013-12-23 09:49:17 -08:00
Wayne Davison d4070db631 Avoid I/O via signal-handler thread.
The cleanup code will try to flush the output buffer in some
circumstances, which is not valid if we're handling an async signal
(since it might have interrupted some partial I/O in the main thread).
These signals now set a flag and try to let the main I/O handler take
care of the exit strategy.  Fixes a protocol error that could happen
when trying to exit after a kill signal.
2013-05-26 16:22:56 -07:00
Wayne Davison cb784f18ec Improve iconvbufs() to do more buffer size checks.
- If iconv() returns EINVAL or EILSEQ and the error is being ignored, make
  sure that there is room in the output buffer to store the erroneous char.
- When accepting an erroneous char, be sure to break if there are no more
  input characters (without calling iconv() with a zero input length).
2013-05-19 23:56:34 +00:00
Wayne Davison 7e1a9c4d79 Update copyright year. 2013-01-19 11:05:53 -08:00
Wayne Davison d42e7181d5 Add free_stat_x() inline function. 2013-01-19 10:25:18 -08:00
Wayne Davison 0d34fbdf5a Make daemon listener exit w/code 0 on SIGTERM. 2012-06-16 10:31:14 -07:00
Wayne Davison eee2c77a93 Some uid/gid fixes for (id_t)-1 and other large ID values.
The code now avoids any special internal meaning for uid/gid -1, which
allows it to be mapped to a better value (use 4294967295 instead of -1
as the ID to map).  Replaced atol() with something than can return a
value > 0x7FFFFFFF and that will error-out if the value overflows.  If
chown() is called with a uid or gid of -1, complain that the ID is not
settable and signal a transfer error.  Fixes bug 6936.
2011-02-22 10:27:35 -08:00
Wayne Davison 69be312b5e Some minor variable and flag cleanup. 2011-01-29 22:01:37 -08:00
Wayne Davison 743348e848 Fix issues with unchanged_attrs() for symlinks. 2010-12-18 08:48:07 -08:00
Wayne Davison ba342e22e7 Undo unintended mode-reference tweak. 2010-08-28 18:02:22 -07:00
Wayne Davison 2624e005e2 Add --omit-link-times and use CAN_SET_SYMLINK_TIMES less. 2010-08-26 11:12:58 -07:00
Wayne Davison 3b22184d4c Avoid a non-writable-by-the-user file when copying xattrs.
Fixes part of the problem in bug 5147.
2010-08-21 14:14:31 -07:00