I can reproduce the following issue on the Debian package but I asked an AI for a reproducer that doesn't require the Debian package.
I believe the reason I did not spot this earlier is because the embargo patchset didn't carry the runtests.py refactor, but have not double checked.
What happens
In 3.5.0, transfers through rrsync fail when rsync runs inside a user namespace
that does not map global uid 0 — rootless podman/docker, Docker userns-remap,
sbuild --chroot-mode=unshare, etc:
rsync: [Receiver] change_dir#1 "/proc/self/fd/4/" failed: Too many levels of symbolic links (40)
rsync error: errors selecting input/output files, dirs (code 3) at main.c(768) [Receiver=3.5.0]
Outside such a namespace the same commands work. This also makes the Debian package
FTBFS, since sbuild's default build mode is one of these namespaces.
Version: 3.5.0, also reproduced on git master (dcef974c). x86_64, Linux 7.1.7,
glibc 2.43.
Reproducing with the testsuite
In a built tree — make, plus make check-progs for the test helpers — the only
change is the unshare prefix:
$ unshare --user --map-root-user --mount --pid --fork --mount-proc \
./runtests.py --rsync-bin=$PWD/rsync --always-log \
rrsync-pull-delivers-content rrsync-backup-dir-inband-pivot
----- rrsync-pull-delivers-content log follows
push through rrsync failed (rc=3, output='rsync: [Receiver] change_dir#1 "/proc/self/fd/4/" failed: Too many levels of symbolic links (40)\nrsync error: errors selecting input/output files, dirs (code 3) at main.c(768) [Receiver=3.5.0]')
FAIL rrsync-pull-delivers-content
----- rrsync-backup-dir-inband-pivot log follows
AssertionError: the transfer failed, but not through the pinned backup dir: an unrelated failure would satisfy the check above: stderr='rsync error: error in file IO (code 11) at rsync.c(900) [receiver=3.5.0]\n'
FAIL rrsync-backup-dir-inband-pivot
0 passed
2 failed
Without the prefix both tests pass.
Reproducing without rrsync
Any /proc/self/fd/... path handed to the resolver hits it, so it can be shown with
one rsync invocation:
cat > /tmp/min.sh <<'EOF'
#!/bin/sh
rm -rf /tmp/r; mkdir -p /tmp/r/dst /tmp/r/src; echo hello > /tmp/r/src/f
echo " /proc/self is owned by: $(stat -c '%U (uid %u)' /proc/self)"
exec 9< /tmp/r/dst
"$1" -a --confine-root=/tmp/r/dst --log-file=/proc/self/fd/9/rsync.log \
/tmp/r/src/ /tmp/r/dst/ 2>&1 | sed 's/^/ /'
EOF
chmod +x /tmp/min.sh
echo '### outside a user namespace ###'; /tmp/min.sh ./rsync
echo '### inside a user namespace ###'
unshare --user --map-root-user --mount --pid --fork --mount-proc /tmp/min.sh ./rsync
### outside a user namespace ###
/proc/self is owned by: root (uid 0)
### inside a user namespace ###
/proc/self is owned by: nobody (uid 65534)
rsync: [client] failed to open log-file /proc/self/fd/9/rsync.log: Too many levels of symbolic links (40)
Ignoring "log file" setting.
The difference between the two runs is the reported owner of /proc/self: inside the
namespace global uid 0 has no mapping, so the link reads as the overflow uid, and the
symlink-ownership check in ona_open() (syscall.c) refuses to traverse it with
ELOOP. Every pin is spelled /proc/self/..., so no pin resolves.
Bisect
git bisect run over v3.4.4..v3.5.0, testing a push through rrsync under the
unshare prefix above:
88cee08 — rrsync: pin path components against
a TOCTOU and fail closed on anomalies
That is where rsync starts being handed /proc/self/fd/N paths. The ownership check
they run into came in seven commits earlier, in
54965efca919996786b19e9bda527e04ee89c334.
I can reproduce the following issue on the Debian package but I asked an AI for a reproducer that doesn't require the Debian package.
I believe the reason I did not spot this earlier is because the embargo patchset didn't carry the runtests.py refactor, but have not double checked.
What happens
In 3.5.0, transfers through
rrsyncfail when rsync runs inside a user namespacethat does not map global uid 0 — rootless podman/docker, Docker
userns-remap,sbuild --chroot-mode=unshare, etc:Outside such a namespace the same commands work. This also makes the Debian package
FTBFS, since sbuild's default build mode is one of these namespaces.
Version: 3.5.0, also reproduced on git master (
dcef974c). x86_64, Linux 7.1.7,glibc 2.43.
Reproducing with the testsuite
In a built tree —
make, plusmake check-progsfor the test helpers — the onlychange is the
unshareprefix:Without the prefix both tests pass.
Reproducing without rrsync
Any
/proc/self/fd/...path handed to the resolver hits it, so it can be shown withone rsync invocation:
The difference between the two runs is the reported owner of
/proc/self: inside thenamespace global uid 0 has no mapping, so the link reads as the overflow uid, and the
symlink-ownership check in
ona_open()(syscall.c) refuses to traverse it withELOOP. Every pin is spelled
/proc/self/..., so no pin resolves.Bisect
git bisect runoverv3.4.4..v3.5.0, testing a push through rrsync under theunshareprefix above:That is where rsync starts being handed
/proc/self/fd/Npaths. The ownership checkthey run into came in seven commits earlier, in
54965efca919996786b19e9bda527e04ee89c334.