Andrew Tridgell da014b91d1 rrsync: pin receiver option directories the peer can pivot
A checked receiver-side directory option whose leaf does not exist can
be created as attacker-controlled transfer content and then consumed by
the same transfer: --backup-dir=a, with "a" arriving in-band as a
symlink pointing out of the restricted directory.  The same shape works
against --copy-dest, where it reads an outside file and delivers it to
the client.  Passing the leaf under the parent's /proc/self/fd pin is
not enough: the peer's symlink wins the race to the name, and rsync
creates or reads through it.

The answer is to hand rsync an inode rather than a name -- but what
inode depends on what rsync does with the option, so the policy is per
option rather than per type ("type 2" means "check when receiving", not
"is a directory"):

  --backup-dir, --partial-dir   rsync creates them on demand.  rrsync
                                creates them instead, walking down from
                                the restricted dir one component at a
                                time with O_NOFOLLOW (nested names too:
                                make_bak_dir() builds a hierarchy), and
                                pins the result.  The partial dir is
                                made 0700, as rsync makes it.
  --temp-dir                    rsync requires it to exist, so a missing
                                one stays an error.
  --link-dest, --compare-dest,  rsync only reads through these, and a
  --copy-dest                   missing one is the ordinary first-run
                                case that must keep working.  rrsync
                                pins an empty directory it then unlinks:
                                the transfer behaves as with a missing
                                one, and there is no name left for the
                                peer to take over.  Not quite identical:
                                rsync prints "--link-dest arg does not
                                exist" for a genuinely missing basis and
                                the placeholder suppresses that.

If the transfer later replaces a created name, the held inode is merely
detached -- the backup fails, it does not escape.

Without /proc/self/fd there is no way to name an inode, so on those
platforms every missing type-2 option path is refused instead -- the six
above, not the type-3 paths covered at the end.
That is the same fail-closed behaviour this change originally had
everywhere; the pinning is what buys back first use where it can.

An earlier version refused every missing type-2 leaf on every platform.
That closed the pivot but broke first-use --backup-dir and --partial-dir
-- including --partial-dir=.rsync-partial, the documented resumable-
upload idiom -- and would have broken first-run --link-dest, which is
how every rotating-snapshot script starts.

The regressions run their security assertions BEFORE their controls, so
an environment where a control fails for an unrelated reason cannot mask
the escape check by aborting first -- which is exactly what happened to
one reviewer.  The alt-dest one also has to beat a race: the generator
runs ahead of the receiver, so sorting the pivot symlink first does not
guarantee it is installed before the basis lookup, and a run where the
generator won would pass vacuously.  A few thousand files in between
give the receiver the head start, and the test asserts the symlink was
really installed rather than trusting the ordering.  Measured 5/5 RED on
the parent, 3/3 GREEN here.

The regressions cover each branch of the policy: the backup-dir pivot,
the --copy-dest read escape (its own test), first-use --backup-dir,
nested first-use --backup-dir, first-use --partial-dir including its
mode, a refused missing --temp-dir, an accepted first-run --link-dest
with no placeholder left behind, and an existing basis still working.
Each requires the specific mechanism rather than just "the outside file
was left alone", which any unrelated failure would satisfy, and each
takes the refusal branch where the pin primitive is unavailable.

Behaviour worth knowing about, since rrsync now creates these rather
than rsync: they are created while the ARGUMENTS are parsed, so they
appear even under --dry-run, where rsync's own make_path() deliberately
does not mkdir; and one is left behind if the transfer then fails.  A
pinned --partial-dir is also one directory rather than one per
destination directory, and rsync will not auto-remove it -- which is not
new, rrsync already rewrote an existing relative partial dir to its pin.

A nested --partial-dir now behaves differently from plain rsync, which
documents creating "just the last directory -- not the whole path".  With
--partial-dir=a/b and "a" missing, rsync creates nothing and rrsync
creates both.  Containment is unaffected -- each component is made
beneath the fd already held, O_NOFOLLOW -- but rrsync accepts a shape
rsync would not honour, and since rsync only removes the last component
of a relative partial dir, an empty parent is left behind.

Not fixed here, and NOT claimed to be: this covers the six type-2
directory options.  --files-from, --log-file and receiver positional
paths are type 3; where the leaf and its parent are both missing, or on
a platform without /proc/self/fd, those still reach rsync unpinned, as
SECURITY.md describes.  That is the rest of the issue, not this one.

The wrapper is handed a shim rather than RSYNC directly: RSYNC is a
multi-word command whenever the runner forces --protocol=N, and rrsync
execlp()s its RSYNC as a single executable name, so this test died
before reaching the policy under test.  A fleet run caught it on the
protocol columns of three targets.

(cherry picked from commit 41f411d6e9)
2026-08-02 21:30:23 +10:00
2022-10-16 10:28:58 -07:00
2020-07-15 10:20:52 -07:00
2022-08-14 10:15:08 -07:00
2021-09-26 18:55:46 -07:00
2020-06-16 21:24:23 -07:00
2020-06-16 21:24:23 -07:00
2021-09-26 16:57:55 -07:00
2019-03-16 09:15:49 -07:00
2020-06-25 20:54:21 -07:00
2020-06-13 19:15:02 -07:00
2020-06-27 21:19:52 -07:00
2022-01-15 17:21:01 -08:00
2022-08-03 09:55:51 -07:00
2019-03-16 09:15:49 -07:00
2019-03-16 09:15:49 -07:00
2022-01-15 17:21:01 -08:00
2021-12-27 17:40:31 -08:00
2022-08-23 15:30:32 -07:00
2020-06-13 19:15:02 -07:00
2022-03-26 08:58:51 -07:00
2022-01-15 17:21:01 -08:00
2020-06-22 14:21:15 -07:00
2022-01-15 17:21:01 -08:00
2020-09-02 14:49:20 -07:00
2026-05-07 07:22:39 +10:00
2020-06-22 14:21:15 -07:00

WHAT IS RSYNC?

Rsync is a fast and extraordinarily versatile file copying tool for both remote and local files.

Rsync uses a delta-transfer algorithm which provides a very fast method for bringing remote files into sync. It does this by sending just the differences in the files across the link, without requiring that both sets of files are present at one of the ends of the link beforehand. At first glance this may seem impossible because the calculation of diffs between two files normally requires local access to both files.

A technical report describing the rsync algorithm is included with this package.

USAGE

Basically you use rsync just like scp, but rsync has many additional options. To get a complete list of supported options type:

rsync --help

See the manpage for more detailed information.

BUILDING AND INSTALLING

If you need to build rsync yourself, check out the INSTALL page for information on what libraries and packages you can use to get the maximum features in your build.

SETUP

Rsync normally uses ssh or rsh for communication with remote systems. It does not need to be setuid and requires no special privileges for installation. You must, however, have a working ssh or rsh system. Using ssh is recommended for its security features.

Alternatively, rsync can run in `daemon' mode, listening on a socket. This is generally used for public file distribution, although authentication and access control are available.

To install rsync, first run the "configure" script. This will create a Makefile and config.h appropriate for your system. Then type "make".

Note that on some systems you will have to force configure not to use gcc because gcc may not support some features (such as 64 bit file offsets) that your system may support. Set the environment variable CC to the name of your native compiler before running configure in this case.

Once built put a copy of rsync in your search path on the local and remote systems (or use "make install"). That's it!

RSYNC DAEMONS

Rsync can also talk to "rsync daemons" which can provide anonymous or authenticated rsync. See the rsyncd.conf(5) manpage for details on how to setup an rsync daemon. See the rsync(1) manpage for info on how to connect to an rsync daemon.

WEB SITE

For more information, visit the main rsync web site.

You'll find a FAQ list, downloads, resources, HTML versions of the manpages, etc.

MAILING LISTS

There is a mailing list for the discussion of rsync and its applications that is open to anyone to join. New releases are announced on this list, and there is also an announcement-only mailing list for those that want official announcements. See the mailing-list page for full details.

BUG REPORTS

The bug-tracking web page has full details on bug reporting.

That page contains links to the current bug list, and information on how to do a good job when reporting a bug. You might also like to try searching the Internet for the error message you've received, or looking in the mailing list archives.

To send a bug report, follow the instructions on the bug-tracking page of the web site.

Alternately, email your bug report to rsync@lists.samba.org.

GIT REPOSITORY

If you want to get the very latest version of rsync direct from the source code repository, then you will need to use git. The git repo is hosted on GitHub and on Samba's site.

See the download page for full details on all the ways to grab the source.

Rsync was originally written by Andrew Tridgell. It has been improved by many developers from around the world.

Special thanks go to Wayne Davison, who maintained rsync from 2004 to 2024.

Rsync may be used, modified and redistributed only under the terms of the GNU General Public License, found in the file COPYING in this distribution, or at the Free Software Foundation.

S
Description
No description provided
Readme GPL-3.0
31 MiB
0 Stars 1 Watchers 0 Forks
Languages
C 47.3%
Python 43.1%
HTML 4.2%
M4 1.2%
Shell 0.8%
Other 3.3%