Commit Graph
646 Commits
Author SHA1 Message Date
Zen Dodd f47dce87e9 Backport 3.5.1 fixes 2026-09-21 19:44:21 +10:00
Codex 8371da580d flist: keep synthetic and legacy implied parents non-content 2026-07-24 16:40:15 +10:00
Codex b64f6972e5 flist: reject non-directory transfer-root entries 2026-07-24 16:40:15 +10:00
Andrew Tridgell 61fa7299cf flist: mask peer-supplied io_error to defined bits
recv_file_list() OR's the wire-supplied end-of-list error value straight
into the local io_error at three sites (the varint-flags path, the
XMIT_IO_ERROR_ENDLIST byte path, and the protocol < 30 int flag).  Like
the MSG_IO_ERROR path fixed in io.c, a malicious peer could set arbitrary
undefined bits, which then accumulate in io_error and can be re-forwarded
to other peers.

Mask each with IOERR_VALID_MASK so only the defined IOERR_* bits survive,
matching the io.c MSG_IO_ERROR handler.

(cherry picked from commit 9da5b5450f)
2026-07-20 14:18:38 +10:00
Andrew Tridgell 8cbfd8a434 xattrs/backup: read source metadata through a held fd, not a path
The hardened receiver confined the destination side of an xattr/ACL copy (the
fsetxattr/acl_set_fd through a held O_NOFOLLOW fd) but still read the SOURCE side
by path: copy_xattrs() did get_xattr_names/get_xattr_data on the source path, and
make_backup() cached the backed-up file's ACL/xattr via get_acl()/get_xattr() by
path.  A local module writer could race the source/basis parent to a symlink
after the confined content/stat open and before that path-based metadata read,
so out-of-module xattrs/ACLs got copied onto an in-module destination or backup.

Thread a source fd through the read side, mirroring the existing dest-fd plumbing:
 - get_xattr_data(), get_xattr() and get_xattr_acl() gain an fd arg (get_xattr_names
   already had one) and use sys_fgetxattr/sys_flistxattr when fd >= 0; this also
   covers the --fake-super ACL-as-xattr read in get_rsync_acl().
 - copy_xattrs() gains a source_fd; copy_file() passes its held source fd (ifd)
   and keeps it open across the xattr copy (closing it on the fsync error path
   too); gen_entry_copy_xattrs() O_NOFOLLOW-opens the basis leaf under the
   confined resolver (with O_DIRECTORY for a directory basis) and passes it.
 - make_backup() pins the source leaf with a confined O_NOFOLLOW fd
   (backup_source_fd, like set_file_attrs's op_leaf_fd) and reads its ACL via
   get_acl_fdat() and its xattrs via get_xattr(fd); the in-place delta-backup in
   the generator pins fname the same way.  On a hardened receiver a raced/absent
   leaf skips the cache rather than reading through a flippable path.

Non-hardened receivers (fd < 0) keep the path-based behaviour unchanged.  The
basis COMPARE reads (the generator deciding a match) stay path-based: they never
copy out-of-module metadata onto a file, so they are not part of this sink.
2026-06-27 18:22:33 +10:00
Andrew Tridgell 13d023e617 hashtable, flist: fix integer overflows in size computations
hashtable_create() and the grow path computed the slot-array byte count as
new_array0(char, size * node_size) in 32-bit int arithmetic; for a large
peer/data-driven size the product wrapped to a tiny value, bypassing my_alloc's
--max-alloc guard (which only saw the already-wrapped count), so the table was
under-allocated while tbl->size kept the huge size -- a later node access then
ran out of bounds (heap overflow; ASan-confirmed).  Pass size and node_size as
SEPARATE factors so my_alloc checks both before multiplying; guard each *2
doubling against int overflow BEFORE it happens; make HASH_LOAD_LIMIT divide
before multiplying; promote the HT_NODE index multiply to size_t; and test
size < 16 first so a negative req short-circuits the size-1 (INT_MIN UB).

flist_expand()'s int growth math (used+extra and *=4 / *=2 / += FLIST_LINEAR)
could overflow past INT_MAX on a very large file list; guard each operation
before it overflows and refuse rather than under-size the realloc.

(Leonid Bugaev May-2026 re-audit, KI-11/12/13.)
2026-06-27 18:18:11 +10:00
Andrew Tridgell cf0c361988 flist/xattrs/generator/uidlist/clientserver: plug audit-reported leaks
Five error-path/cleanup memory leaks found by an external audit:

- flist.c send_file_name: free the ACL loaded by get_acl() when a later
  get_xattr() fails (and on the get_acl error path).
- xattrs.c copy_xattrs: free the xattr datum buffer when the setxattr fails.
- generator.c recv_generator: free real_sx at the cleanup label (the
  directory branch loaded its ACL via set_file_attrs but only the
  regular-file path freed it); zero-init real_sx so the early gotos are safe.
- uidlist.c send_one_list: free the strdup'd id-0 name after send_one_name.
- clientserver.c start_inband_exchange: free modname on the early error
  returns (it was freed only on the success path).

ASan/LSan regression tests cover the generator, uidlist and clientserver
leaks; the flist and xattrs leaks need a forced syscall failure and are
covered by the audit's standalone harnesses.

Reported-by: Leonid Bugaev <leonsbox@gmail.com>
(cherry picked from commit 078f3b99f4004510d418ee9d97d9b775dc8587bc)
2026-06-27 18:15:18 +10:00
Andrew TridgellandGreg Kroah-Hartman 762d0be048 flist/receiver: zero/empty the device & symlink extra slots
recv_file_entry (normal and XMIT_HLINKED abbrev branches) and make_file
left F_RDEV_P / the symlink-name slot uninitialized when the matching
preserve option was off, so a later read walked into adjacent pool
memory. Empty the symlink name when !preserve_links, zero F_RDEV_P when
!preserve_devices, and mirror both in the abbreviated branch. receiver.c
saves/restores the --write-devices S_IFBLK mode flip around receive_data
so dest_mode() never sees the mutated mode.

Co-authored-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2026-06-22 09:21:59 +10:00
Andrew Tridgell a74cce06a7 confine operator- and peer-supplied paths to the served module
Route operator-supplied paths (--partial-dir, --backup-dir, alt/copy/link-dest,
--temp-dir, --files-from, --log-file, config and secrets files) and the sender's
own enumeration through the race-safe resolver and an ownership walk that refuses a
target redirected outside the module root.  open_tmpfile() now creates the temp via
the cached held dir fd (do_mkstemp_atfd) so an in-tree dir symlink is followed
safely instead of refused.  Adds --insecure-links to opt back into legacy following;
the daemon hard-refuses the client flag (use the "insecure links" module param).
2026-06-21 18:25:28 +10:00
Andrew Tridgell 47addd8609 flist: refuse sub-flist after the final flist has been freed
In incremental-recursion mode the receiver frees each flist as it is
finished with.  When the last one goes, flist_free() sets first_flist
to NULL, flist_cnt to 0 and pool_destroy()s the shared file pool, but
the global dir_flist is left untouched: its files[] array still points
into the now-destroyed pool.

A malicious sender can then send an NDX_FLIST_OFFSET-encoded directory
index.  read_ndx_and_attrs() bounds-checks dir_ndx against the *stale*
dir_flist->used and calls recv_file_list(), which:

  - reads/writes the freed dir_flist->files[dir_ndx] entry
    (FLAG_GOT_DIR_FLIST), and
  - because first_flist is NULL, gets ndx_start == 1 from flist_new(),
    so re-creates dir_flist as a fresh empty FLIST_TEMP list, and then
  - dereferences dir_flist->files[dir_ndx] (uninitialised malloc heap)
    in the dirname check, crashing in f_name().

On a stock glibc the 256 KiB realloc is mmap-zeroed, so the uninit
deref is caught by the existing !d guard and turns into a clean
RERR_UNSUPPORTED exit, but the use-after-free write into the destroyed
pool happens regardless.

A sub-flist marker after every flist has been freed is never valid
protocol, so refuse it up front before touching the stale dir_flist.

(cherry picked from commit 382d7df9a23b976ea6f09598ecea66f269aa213d)
2026-06-15 17:15:28 +10:00
Andrew Tridgell 2db471207e flist: refuse FLAG_HLINKED on directory entries
The F_HL_GNUM/F_HL_PREV optional-extras slots ("non-dirs") and the
F_DIR_DEV_P slots ("receiver dirs") occupy the same OPT_EXTRA offsets;
the macros assume an entry is one or the other.  recv_file_entry()
suppressed FLAG_HLINKED for symlinks and specials/devices on platforms
that cannot hard-link those, but never for directories, so a malicious
sender could mark a directory entry XMIT_HLINKED.

With -H -x and inc_recurse the generator then writes
major/minor(st_dev) into F_DIR_DEV_P(file) (generator.c:1679),
clobbering what the hard-link code reads as F_HL_PREV/F_HL_GNUM.  A
forwarded MSG_NO_SEND for that ndx pushes it onto hlink_list
(got_flist_entry_status() only checks F_IS_HLINKED), and
check_prior() reads F_HL_PREV(file) == major(dev) — typically 0.
flist_for_ndx(0) returns first_flist (ndx_start-1 is the parent
sentinel), and flist->files[0 - ndx_start] underflows the files[]
array by one slot, then dereferences the resulting wild pointer.

Directories cannot be hard-linked on POSIX; do not honour XMIT_HLINKED
on an S_ISDIR entry.

(cherry picked from commit 63be23ed575a24430b058d54724a643c89344b5b)
2026-06-15 17:15:28 +10:00
Andrew Tridgell 3c49c401cd flist: gate FLAG_HLINKED on preserve_hard_links in recv_file_entry
A malicious sender can set XMIT_HLINKED on a file-list entry even when
the receiver was not invoked with -H.  recv_file_entry() only reserves
the (inc_recurse+1) hard-link extras slots when preserve_hard_links is
set (the gate at create_object), but it set FLAG_HLINKED on the struct
unconditionally from the peer's xflags.  HLINK_BUMP() then offsets
F_SUM() and F_RDEV_P() past the start of the pool_alloc()'d region,
and the subsequent read_buf(f, F_SUM(file), flist_csum_len) writes 16
attacker-controlled checksum bytes 8 bytes below the allocation — into
the adjacent file_struct in the same pool extent.

Vanilla ASAN does not see this because lib/pool_alloc.c bumps inside a
single 32 KiB malloc; it surfaces as use-after-poison once the pool is
instrumented with ASAN_POISON_MEMORY_REGION/ASAN_UNPOISON_MEMORY_REGION.

Mirror the gating already present at the extra_len computation and at
the F_HL_GNUM/F_HL_PREV write site: only set FLAG_HLINKED when
preserve_hard_links is enabled.  This also closes the symmetric
F_RDEV_P() underflow for device entries and the F_SUM(first)/
F_RDEV_P(first) reads in the first_hlink_ndx copy path.

(cherry picked from commit adfae919876c9f3454ce12a1b7becbed47415ff7)
2026-06-15 17:15:28 +10:00
Andrew Tridgell 4d1044156b flist: refuse sub-flist for a cleared dir_flist slot
Under inc_recurse the receiver appends every received directory entry to
dir_flist before flist_sort_and_clean() runs.  If the peer sends entries
that the dedup/prune pass then clear_file()s, the cleared file_struct
(basename and mode zeroed) remains in dir_flist->files[].  A later
sub-flist tagged with that dir_ndx passed the existing >= used bounds
check, and the dirname-validation strcmp() in recv_file_list() then
called f_name() on the cleared entry — which returns NULL — and
dereferenced it: a remote NULL-pointer crash on any anonymous writable
module.

Reject a dir_ndx whose entry is no longer active alongside the existing
range and duplicate-flist checks, and guard the strcmp() against a NULL
f_name() result as defence in depth.

Found by fuzzing the daemon protocol over stdio.

(cherry picked from commit a900b25008151a8a8ec1c34aa7e2914b23416019)
2026-06-15 17:15:28 +10:00
Andrew Tridgell 45dd14950a sender: read symlink targets through scan dirfds
The directory-scan confinement resolves the opendir and the per-entry stat
through the held scan dirfd, but readlink_stat() still read the symlink target
with a path-based do_readlink(): a raced parent symlink could redirect that read
and leak an out-of-tree symlink target for a same-name entry.

Add do_readlink_atfd() (readlinkat via the held dirfd) and scan_readlink(), which
routes the read through scan_dirfd for an entry directly in the scan dir (the
same gate scan_link_stat uses), else falls back to do_readlink().  Completes the
enumeration confinement's symlink-target leg.
2026-06-15 15:24:42 +10:00
Andrew Tridgell 5aa125d472 flist/clientserver: confine the directory-scan enumeration
The sender/daemon enumerated each source directory with a plain opendir()
on the full accumulated path -- not through the secure resolver -- so the
enumeration itself was unconfined: a parent component raced into a symlink
after the scan let the listing (and per-entry lstat) escape the transfer
root / module.

 - secure_opendir() resolves the directory through the secure resolver and
   turns the held fd into a DIR* via fdopendir(); the daemon anchors it at
   the identity-pinned module_dirfd (captured at module setup while the
   daemon was positioned at the module root, before any privilege drop);
 - the per-entry scan stats through the opendir fd (scan_dirfd) with
   fstatat(), so the listing can't be redirected out of the tree.

Closes daemon-subdir-climb-symlink and sender-scan-dir-escape.
2026-06-15 12:21:57 +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 Tridgell 407fe4ac48 exclude+flist: confine daemon filter-merge and implied-parent delete-scope
Backport of two 3.5.0 daemon-filter fixes:
 - parse_filter_file(): strip module_dirlen off an absolute merge-file
   path before the daemon_filter_list check (parse_merge_name() prepended
   module_dir), so an anchored module-relative rule actually matches; a
   hidden merge file is treated as non-existent rather than tripping the
   fatal-vs-silent oracle (Mitchell Benjamin finding 1);
 - downgrade implied-parent directories to non-content so a malicious
   sender cannot expand --delete scope via a content-dir entry
   (Mitchell Benjamin finding 3).

Tests: daemon-filter-merge-bypass, malicious-sender-delete-scope.

Reported-by: Mitchell Benjamin <mitchell@revampstudio.com.au>
2026-06-12 15:52:39 +10:00
Mike-Goutokuji 2d90ca6213 Always clear st out and validate nanoseconds before using it
Otherwise we get errors.
Fixes: https://github.com/RsyncProject/rsync/issues/927

(cherry picked from commit 044cfd2a5e)
2026-06-07 18:47:07 +10:00
Andrew Tridgell c9992a47dc flist: accept the missing-args mode-0 entry in recv_file_entry (#910)
--delete-missing-args (missing_args==2) sends a missing --files-from arg as a
mode-0 entry (IS_MISSING_FILE), the generator's delete signal.  The mode-type
validation in recv_file_entry() rejected mode 0 as an invalid file type,
aborting the transfer with 'invalid file mode 00 ... code 2' before the
generator could act (a regression from 3.4.1).  Allow mode 0 through only when
missing_args==2 (the delete mode -- not --ignore-missing-args, which never
sends a mode-0 entry); all other modes are still rejected.

Thanks to @mgkeeley for the report (#910).

(cherry picked from commit 73fe630a70)
2026-06-07 18:47:07 +10:00
Andrew TridgellandClaude Opus 4.7 ddd7b59a4f 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 0590b09d9a 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
2025-01-15 05:30:32 +11:00
Andrew Tridgell 344327385f range check dir_ndx before use 2025-01-15 05:30:32 +11:00
Wayne Davison 688f5c379a Refuse a duplicate dirlist. 2025-01-15 05:30:32 +11:00
Wayne Davison 6f3c5eccee Fix old stats bug that counted devices as symlinks. 2023-05-16 22:44:54 -07:00
Wayne Davison 9a06b2edb0 Preparing for release of 3.3.0pre1 [buildall] 2023-04-29 09:01:43 -07:00
Wayne Davison 90df93e446 Don't call memcmp() on an empty lastdir. 2023-01-08 21:35:39 -08:00
Wayne Davison fdf5e577f5 Read a 4-byte mtime as unsigned (old-protocol).
When conversing with a protocol 29 or earlier rsync, the modtime values
are arriving as 4-byte integers.  This change interprets these short
values as unsigned integers, allowing the time that can be conveyed to
range from 1-Jan-1970 to 7-Feb-2106 instead of the signed range of
13-Dec-1901 to 19-Jan-2038.  Given that we are fast approaching 2038,
any old-protocol transfers will be better served using the unsigned
range rather than the signed.

It is important to keep in mind that protocol 30 & 31 convey the full
8-byte mtime value (plus nanoseconds), allowing for a huge span of time
that is not affected by this change.
2022-10-02 09:54:54 -07:00
Wayne Davison 71c2b5d0e3 Fix exclusion of /. with --relative. 2022-09-14 07:14:13 -07:00
Wayne Davison 8b1b81e054 Use UNSUPPORTED instead of PROTOCOL for various validation checks. 2022-09-13 23:38:01 -07:00
Wayne Davison 950730313d Fix bug with validing remote filter rules. 2022-09-12 22:02:00 -07:00
Wayne Davison 7e2711bb2b Improve various things in the checksum code
- Size flist checksum data to hold the active size, not the max.
- Add a negotiated hash method to the daemon auth code.
- Use EVP for all openssl digests. This makes it easy to add more
  openssl digest methods and avoids deprecation warnings.
- Support a way to re-enable deprecated digests via openssl conf
  file and allow a default file to be configured.
- Supply a simple openssl-rsync.cnf file to enable legacy digests.
2022-09-10 11:39:37 -07:00
Wayne Davison b7231c7d02 Some extra file-list safety checks. 2022-07-31 17:46:34 -07:00
Wayne Davison 8aa465117f Add new & improved --copy-devices option. 2022-03-27 14:04:59 -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
Jindřich Makovička ae1f002999 Reduce memory usage (#235)
In 2004, an allocation optimization has been added to the file
list handling code, that preallocates 32k of file_struct pointers
in a file_list. This optimization predates the incremental
recursion feature, for which it is not appropriate anymore. When
copying a tree containing a large number of small directories,
using the incremental recursion, rsync allocates many short
file_lists. Suddenly, the unused file_struct pointers can easily
take 90-95% of the memory allocated by rsync.
2021-10-01 12:04:59 -07:00
Wayne Davison 291a042b3e Support --crtimes on Cygwin. 2021-07-08 18:59:26 -07:00
Wayne Davison 2066024981 Fix issue where rdev major could get out of sync
If the receiving side read a hard-linked device, it needs to set the
value of rdev_major to the value it snags from the hard-linked data
because the sender set their rdev_major value for that file entry.
2020-07-26 00:11:28 -07:00
Wayne Davison 974f49e22a Add --crtimes option. 2020-07-22 12:12:18 -07:00
Wayne Davison f47e5a7732 Mention file & line on OOM and overflow errors.
Also simplify output of src file paths in errors & warnings when
built in a alternate build dir.
2020-07-12 23:25:21 -07:00
Wayne Davison a6da3c67f8 Must read the nsec val even w/o CAN_SET_NSEC. 2020-07-08 14:17:01 -07:00
Wayne Davison ab110fc8fb Warning fixes & impossible-failure improvements
- Silence a couple warnings for less-common builds.
- Use a better impossible-failure idiom than assert(0).
2020-07-08 12:26:19 -07:00
Wayne Davison 7610f76aea Remove another file_struct kluge. 2020-07-06 09:31:22 -07:00
Wayne Davison bb16db1747 Send the uid/gid 0 name since not all systems use 0 for root. 2020-07-05 22:51:12 -07:00
Wayne Davison e1e4ffe057 Some C99 flexible array changes
- Use C99 flexible arrays when possible, and fall back on the existing
  fname[1] kluges on older compilers.
- Avoid static initialization of a flexible array, which is not really
  in the C standard.
2020-07-05 17:25:53 -07:00
Wayne Davison fe2ef556d9 A few more tweaks. 2020-06-25 22:30:02 -07:00
Wayne Davison 11eb67eec9 Some memory allocation improvements
- All the memory-allocation macros now auto-check for failure and exit
   with a failure message that incudes the caller's file and lineno
   info.  This includes strdup().

 - Added the `--max-alloc=SIZE` option to be able to override the memory
   allocator's sanity-check limit.  It defaults to 1G (as before).
   Fixes bugzilla bug 12769.
2020-06-25 20:54:21 -07:00
Wayne Davison f800557824 Tweak the hashtable routines to be a little clearer and easier. 2020-06-12 17:42:41 -07:00
Wayne Davison c117fa4bf9 Create a get_device_size() helper function. 2020-06-11 16:09:36 -07:00
Wayne Davison 87f2984df0 Improve how negotiated info affects batch files. 2020-05-25 19:19:59 -07:00