Files
rsync/support
Andrew TridgellandLeonid Bugaev 0894e0422e rrsync: pin a sender argument where rsync will actually resolve it
88cee089 rewrote every validated argument to /proc/self/fd/N so the
spawned rsync re-resolves it to the pinned inode.  That is right for a
receiver, which open()s its destination, but a sender never opens its
source argument: send_file_list() lstat()s it first, and lstat of a
procfs magic link is always S_IFLNK.  So the sender described the
argument as a symlink and sent no data -- silent data loss on
"rsync -a user@host:file dest/", the most ordinary command there is.
Of the argument shapes now covered, only a trailing-slash directory
survived.

Leonid Bugaev reported the regression, diagnosed the lstat-vs-magic-link
mechanism, and supplied the first regression test.

Which pin is usable depends on what rsync does with the argument:

  * a trailing "/" or "/." directory is opened, not lstat()ed, and rsync
    does follow a symlink there, so it keeps the leaf pin -- verified:
    without it a flipped leaf transfers the outside directory's content;
  * anything else pins one level up and passes the leaf by name.  rsync
    will not follow a symlink at that position (it sends the symlink
    itself), -L/-k/--copy-unsafe-links are already disabled for a
    restricted dir, and rsync's own leaf open is O_NOFOLLOW.

The directory pin resolves normally, including a symlink at its last
component, which is legitimate and which 3.4.4 accepts; the readlink
check afterwards is what proves the held inode is in-tree.  It uses
O_PATH because reaching a known name beneath a directory needs only
search permission, and a mode 0111 parent is an ordinary way to publish
a file without letting it be listed.

Under --relative the transmitted name is the whole argument rather than
its basename, so the pin moves up to where that name starts and the rest
is spelled after a /./ marker.  The client's own first marker wins if it
supplied one, including when nothing follows it; -R is parsed out of the
short-option cluster rather than sniffed for the letter, so the trailing
capability blob (-e.iLsfxC) cannot be mistaken for it.

Directory pins are keyed by (st_dev, st_ino), so a glob whose matches
share a parent inherits one descriptor rather than one per argument.

Every shape now delivers what a pristine 3.4.4 delivers, which is what
rrsync-pull-arg-shapes asserts -- it passes against 3.4.4 itself, so the
expectations are that behaviour and not this implementation's.  The
"-R --no-implied-dirs" case is gated on protocol >= 30: at protocol 29
the receiver rejects it with "invalid path from sender" and transfers
nothing, which 3.4.4 does identically.

Moving the sender's pin off the leaf invalidates rrsync-symlink's oracle,
so it is reworked here rather than left failing.  It patches rsync to a
stub that open()s its last argument, which is a faithful model for an
intermediate path component -- whatever rsync does with the final name,
it must not reach it through a flipped parent -- but not for the leaf: a
sender lstat()s its source and transmits a symlink there rather than
reading through it.  So it now flips an intermediate directory, and the
leaf is covered against the real binary by rrsync-sender-leaf-flip, which
races both a plain file argument and a trailing-slash directory and
asserts no outside CONTENT is delivered rather than requiring a symptom
from a race that may not be won on a given run.  Its trailing-slash half
is RED against a pristine 3.4.4 rrsync, which delivers outside/dir/loot.

rrsync-symlink is now sender-only.  Measured over a 5s race, the stub
reached the outside marker 28 times in 98 runs as a sender and 25 in 97
as a receiver against 3.4.4; with the pin it is 0 as a sender but still
7-9 as a receiver, on the branch base as well as here.  That residual is
the receiver's not-yet-existing-destination fallback, which predates this
work and is tracked in #139 rather than folded in.

Clearing FD_CLOEXEC goes through F_SETFD rather than os.set_inheritable(),
which prefers ioctl(FIONCLEX) and gets EBADF from an O_PATH descriptor on
older kernels -- every sender pull on Ubuntu 18.04 aborted with "Bad file
descriptor" the moment a directory pin was taken.

Co-authored-by: Leonid Bugaev <leonsbox@gmail.com>

(cherry picked from commit 6edb7dea2a)
2026-08-02 21:33:25 +10:00
..
2022-11-20 09:38:12 -08:00
2022-11-16 00:10:09 -08:00
2021-12-27 17:57:53 -08:00
2022-01-09 14:03:31 -08:00
2005-01-11 18:37:37 +00:00
2023-05-22 08:29:15 -07:00
2020-08-05 21:32:44 -07:00