Commit Graph
329 Commits
Author SHA1 Message Date
Andrew Tridgell 76b54cdba4 exclude: exempt the daemon's own filter parameters from the confinement
Confining every parse_filter_file() open to the module root also caught
"filter", "include from" and "exclude from" from rsyncd.conf.  Those name
operator-configured paths and pointing them outside the module -- at
/etc/rsync/excludes, say -- is the ordinary way to write them; rsyncd.conf(5)
puts no constraint on where the file lives.  The result was not a refused
rule but a refused connection:

    failed to open exclude file /etc/rsync/excludes:
        Too many levels of symbolic links (40)
    rsync error: error in file IO (code 11) at exclude.c(1582)

with no symlink involved anywhere -- just a regular file outside the module.

Mark the window in which the daemon loads its own parameters and skip the
confinement there.  Everything else, in particular the peer-driven dir-merge
the leak test exercises, is still confined.  Also fix the trailing whitespace
in the original hunk.

(cherry picked from commit 5eb99bb6b2)
2026-08-02 21:33:25 +10:00
Andrew Tridgell 5a15ea8fda daemon: make "insecure links = yes" fully restore 3.2.7 symlink following
The opt-out is meant to restore stock-3.2.7 "follow existing symlinks in the
module" behaviour, but several daemon symlink-resolution sites called the
confined resolver unconditionally and never consulted symlink_optout_allowed(),
so a module with "insecure links = yes" still refused to follow a symlinked
directory (change_dir ELOOP) or an alt-dest basis.  Gate every such site on the
opt-out -- follow plainly like 3.2.7 when set, confine otherwise:

  - change_dir() relative daemon branch (util1.c): plain chdir() under the
    opt-out instead of secure_relative_open(), so a peer can read a path through
    a symlinked directory again.
  - basis_link_stat() (generator.c): the chrooted-relative and non-chroot
    branches honour the opt-out.  The non-chroot ABSOLUTE branch (a basis rooted
    under the module by check_alt_basis_dirs, which can reach an in-module
    symlink) is, when NOT opted out, resolved through owner_walk_parent so a
    target landing outside the module root is refused -- closing a confirmed
    --compare-dest=/symlink out-of-module read oracle.  The leaf is taken
    O_NOFOLLOW under the confined parent so --copy-links can't follow a leaf
    symlink out; --fake-super folds its %stat via the held fd.  A relative
    sibling basis (--link-dest=../01) keeps the plain path (#915/#930).
  - secure_basis_open() (receiver.c): plain do_open() under the opt-out.
  - use_secure_symlinks (clientserver.c): cleared under the opt-out, so the
    receiver's protected-regular EACCES write fallback is legacy too.
  - secure_sender_parent_fd() (sender.c): declines (errno=0) under the opt-out
    so --remove-source-files re-stats the plain path.

Default modules (insecure links = no) are unchanged and stay fully confined;
the opt-out is per-module and a client cannot enable it.
2026-06-27 18:22:08 +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 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 6e00a3b867 daemon: bound argument lists + proxy-protocol peer/length hardening
- io: bound daemon argument lists so a malicious daemon client cannot grow
   argv without limit (DoS);
 - socket: bound the PROXY CONNECT request and proxy response header lines;
 - daemon: require a trusted-proxy host list for "proxy protocol = true"
   (reject untrusted proxy peers, fail-closed), and warn at startup when the
   trusted-proxy list is unset so the fail-closed behaviour is not silent.

Tests: daemon-argv-limit, proxy-connect-request-too-long,
proxy-response-header-too-long, proxy-protocol-trusted-peer.
2026-06-15 15:24:42 +10:00
Andrew Tridgell 8639f647aa daemon: secure inner chroot module paths
A daemon module with a /./ inner boundary (use chroot=yes, path=.../.inner)
relies on the inner module root, not the kernel chroot, as its trust
boundary: the chroot confines the outer path, but a symlink inside the
module can point to a sibling that is inside the chroot yet outside the
inner module.  Extend the receiver finish/rename gate (use_secure_symlinks),
change_dir()'s confined fchdir, and the basis-open gate from
"am_daemon && !am_chrooted" to "am_daemon && (!am_chrooted || module_dirlen)"
so an inner module gets the same confinement as a non-chroot module.
secure_relpath_active() already carries the inner-module clause.

Tests: chroot-basis-forge/receiver-write/special-inner-module.
2026-06-15 15:24:42 +10:00
Andrew Tridgell b33571a1df daemon/authenticate: open operator-supplied paths refusing planted symlinks
Route the daemon's operator-supplied path opens through
safe_open_no_attacker_symlinks() / a pinned parent dir so a local attacker
cannot plant a symlink at one and redirect the daemon's privileged
read/write: motd, --early-input, lock and --config paths
(clientserver/connection/params); the --password-file and daemon secrets
file, fstat'ing the opened fd (authenticate); and the pid-file parent dir.

Tests: daemon-config-symlink, early-input-symlink, daemon-secrets-file-symlink,
password-file-symlink, daemon-module-chdir-symlink.
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 91b09b6e38 clientserver: harden daemon name-converter responses
Backport of the 3.5.0 daemon name-converter hardening:
 - reject an empty or non-numeric namecvt response instead of letting it
   collapse to uid/gid 0 (Mitchell Benjamin finding 2);
 - parse the id with overflow/sign checking;
 - reject unsafe (newline/control) tokens in the converter response.

Tests: daemon-namecvt-empty-response, daemon-namecvt-newline-token.

Reported-by: Mitchell Benjamin <mitchell@revampstudio.com.au>
2026-06-12 15:52:39 +10:00
Andrew Tridgell ce4c0cfb48 daemon: un-backslash escaped option args (#829)
Without --secluded-args, the client's safe_arg() backslash-escapes shell
and wildcard chars in option values before sending them to the server, so
--chown's --usermap=*:user is transmitted as --usermap=\*:user.  Over ssh a
remote shell removes the backslashes before rsync parses the args, but a
daemon has no shell and read_args() stored option args verbatim -- so the
receiver saw the literal "\*", the usermap/groupmap wildcard never matched,
and the module's configured uid/gid won instead.  A regression from the
secluded-args hardening; rsync 3.2.3 (protocol 31) worked.

Un-backslash option args in read_args() on the daemon's first
(non-protected) read, mirroring what the ssh-side shell does.  File args
after the dot are already handled by glob_expand(); the protected (NUL,
already-unescaped) re-read and the server's stdin read pass unescape=0 so
their raw args are left untouched.

Thanks to @elcamlost for the report (#829).

Fixes: #829
(cherry picked from commit 8dc5fd1408)
2026-06-07 18:47:07 +10:00
Andrew TridgellandClaude Opus 4.7 ac735929dc 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 TridgellandClaude Opus 4.7 f629772cc6 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 TridgellandClaude Opus 4.6 892b48a60b 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 06:35:39 +10:00
Wayne Davison 4d44bf122d A few more doc tweaks & comment tweaks. 2022-09-30 12:34:58 -07:00
Wayne Davison 97e02bf21a Some "use chroot" improvements.
- The sanitize_paths variable was set too often. It only needs to be set
  when the "inner" path is not "/".  This change avoids sanitizing &
  munging things for a path=/ module just because chroot is off.
- The default for "use chroot" is now "unset" instead of "true".  When
  unset it checks if chrooting works, and if not, it proceeds with a
  sanitized copy instead of totally failing to work.  This makes it
  easier to setup a non-root rsync daemon, for instance.  It will have
  no effect on a typical Linux root-run daemon where the default will
  continue to use chroot (because chrooting works).  A config file can
  explicitly set "use chroot = true | false" to force the choice.
- Try to improve the "use chroot" manpage.
2022-09-20 00:08:16 -07:00
Wayne Davison a6a0d2f77c Require a newer protocol to specify the digest list. 2022-09-10 22:12:24 -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 0a09df2c5e Rename --protect-args to --secluded-args. 2022-08-23 14:56:23 -07:00
Wayne Davison 1174d97072 Fix --old-args interaction with a daemon
Ensure that a remote rsync daemon will not split a filename arg unless
the user asked for `--old-args`.
2022-01-17 18:11:03 -08: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 a6bdf313f2 Unset DISPLAY in environment.
Without a DISPLAY var, ssh won't try to forward X11 when making an
ssh connection.  This patch also makes use of setenv() and unsetenv()
if they are available.
2021-05-01 09:14:51 -07:00
Wayne Davison 47351c2b16 Use RSYNC_GITVER in more output 2020-08-03 10:46:31 -07:00
Wayne Davison f9bb8f76ee Change daemon variable & simplify some option code
- Rename daemon_over_rsh -> daemon_connection since it is also used to
  indicate if a non-rsh daemon connection is active.
- Move the daemon-over-rsh exception out of server_options() to the one
  caller that needs that behavior.
- Don't allow noop_io_until_death() to be short-circuited when talking
  to a daemon over a socket, because it can't send errors via stderr.
2020-07-25 09:36:42 -07:00
Wayne Davison 7e07a32504 Add the name converter daemon parameter.
This is based on the long-standing patch but with the protocol changed
to just use newlines as delimiters instead of null chars (since names
should not contain a newline AND it makes it easier to write a helper
script).  Lots of other small improvements and a better default value
for "numeric ids" when using "use chroot" with "name converter".
2020-07-17 10:30:59 -07:00
Wayne Davison 59cb358fda More TANDEM changes
- Handle a non-0 root uid.
- Handle alternate major/minor/MAKEDEV funcs.
- Other misc compatibility tweaks.
2020-07-06 00:05:46 -07:00
Wayne Davison f4184849c4 Use module_id more consistently after it is set. 2020-07-04 10:28:39 -07:00
Wayne Davison 565cde84a7 Don't turn off the user's open-noatime unless the module is forcing the value. 2020-07-04 10:28:38 -07:00
Paul Slootman f0e670b4c6 Add "open noatime" module option to rsyncd.conf 2020-07-04 09:30:35 -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 a7c1690d62 One more >= tweak. 2020-06-21 15:30:34 -07:00
Wayne Davison 662fedd74b Get the early-input reading code right. 2020-06-21 15:23:13 -07:00
Wayne Davison e16b22751a Add --early-input=FILE option. 2020-06-21 14:32:00 -07:00
Wayne Davison 2e1b46db39 Close STDIN for early exec script. 2020-06-21 11:17:09 -07:00
Wayne Davison 3c56896d21 Simplify a variable. 2020-06-19 11:07:02 -07:00
Wayne Davison 249e28c75a Rename "haproxy header" to "proxy protocol". 2020-06-15 11:33:23 -07:00
Wayne Davison e63ff70eae Some indentation fixes. 2020-06-13 19:15:02 -07:00
Wayne Davison cff0764b7f Add haproxy header parameter to rsync daemon 2020-06-11 14:22:25 -07:00
Wayne Davison a3377921eb Add early exec daemon parameter.
Inspired by Ciprian Dorin Craciun's `bootstrap exec` patch.
2020-06-10 21:38:37 -07:00
Wayne Davison 2c6f0581ac A couple minor fixes. 2020-06-04 22:54:38 -07:00
Wayne Davison 916faecb83 Only sender can output non-final stats on error
The receiving side's stats are split between 2 processes until the very end.
2020-06-04 21:40:43 -07:00
Wayne Davison 5d7b71b7a7 Make use of O_NOFOLLOW if it is defined. 2020-06-04 19:47:59 -07:00
Wayne Davison b177311aee Use a lock to not fail on a left-over pid file. 2020-06-04 19:08:03 -07:00
Wayne Davison c0268d9217 Some improvements for --msgs2stderr and --daemon.
- Set am_daemon to -1 (from 1) when the daemon is run via rsh.
- Only disable --msgs2stderr for a normal (socket) daemon.
- Forward a -q to the server if --msgs2stderr was also specified.
- Added --no-msgs2stderr option to allow it to be overridden.
2020-05-31 16:02:46 -07:00
Wayne Davison f60bd811e9 Use MSG_FLUSH in a couple more spots. 2020-05-28 00:41:39 -07:00
Wayne Davison e448d31d63 Need to flush early errors before we exit. 2020-05-17 21:20:15 -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 70cbc66b7f Set RSYNC_PORT in the env for a daemon-over-rsh connection.
Fixes bug #14163.
2020-04-05 19:34:27 -07:00
Wayne Davison 3e2e4b5a33 Tweak the copyright year. 2019-03-16 09:15:49 -07:00
Wayne Davison 5df9847f06 Allow some pre-/post-xfer exec shell restrictions.
Support both RSYNC_SHELL & RSYNC_NO_XFER_EXEC environment variables.
2018-03-25 11:02:50 -07:00