mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-09-17 07:38:57 -04:00
88cee089rewrote 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 commit6edb7dea2a)