From: Andrew Tridgell Date: Sat, 23 May 2026 22:12:39 +0000 (+1000) Subject: testsuite: probe RESOLVE_BENEATH support functionally for the #715 test X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=b0ba699031a697e816091cfb805be3eec2bfc6d7;p=thirdparty%2Frsync.git testsuite: probe RESOLVE_BENEATH support functionally for the #715 test Add resolve_beneath_supported() to rsyncfns: it functionally probes whether the rsync binary can follow an in-tree directory symlink under its secure resolver (an initial transfer plus a delta update through a dir-symlink, the operation issue #715 is about). This tracks the actual binary instead of a platform name. Use it in symlink-dirlink-basis_test.py in place of the SunOS/OpenBSD/NetBSD/ Cygwin name check: it skips on those platforms too, and additionally on Linux < 5.6, a seccomp-blocked openat2, and the new --disable-openat2 build, where the portable O_NOFOLLOW fallback rejects the in-tree symlink. Co-Authored-By: Claude Opus 4.7 (1M context) --- diff --git a/testsuite/rsyncfns.py b/testsuite/rsyncfns.py index b9bcbf17..3a3b37b1 100644 --- a/testsuite/rsyncfns.py +++ b/testsuite/rsyncfns.py @@ -1126,6 +1126,48 @@ def assert_not_exists(path, label: str = '') -> 'None': test_fail(f"{_tag(label)}{path} exists but should not") +_rb_cache = None + + +def resolve_beneath_supported() -> bool: + """True if this rsync can FOLLOW an in-tree directory symlink under its + secure resolver -- i.e. update a file through a dir-symlink on the receiver + (--keep-dirlinks; issue #715). + + False wherever the portable per-component O_NOFOLLOW fallback is the active + resolver: a platform with no kernel "beneath" primitive, Linux < 5.6, a + seccomp-blocked openat2, or a --disable-openat2 build. There the delta + update through the symlinked directory fails verification. Probed + functionally (an initial transfer plus a delta update through a dir-symlink) + so it tracks the actual binary rather than a platform name, and cached.""" + global _rb_cache + if _rb_cache is not None: + return _rb_cache + probe = SCRATCHDIR / '.rb_probe' + rmtree(probe) + (probe / 'home' / 'real').mkdir(parents=True) + os.symlink('real', probe / 'home' / 'link') + (probe / 'src' / 'link').mkdir(parents=True) + f = probe / 'src' / 'link' / 'f' + make_data_file(f, 40000) + + def push(): + subprocess.run( + rsync_argv('-KRl', '--no-whole-file', 'link/f', + f"{probe / 'home'}/"), + cwd=str(probe / 'src'), + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + push() + with open(f, 'ab') as fh: # size change -> forces a delta update + fh.write(b'appended tail for delta\n') + push() + dst = probe / 'home' / 'real' / 'f' + _rb_cache = dst.is_file() and filecmp.cmp(str(f), str(dst), shallow=False) + rmtree(probe) + return _rb_cache + + def write_daemon_conf(modules, globals=None, *, name: str = 'test-rsyncd.conf') -> 'Path': """Write a custom rsyncd.conf for daemon-parameter tests. diff --git a/testsuite/symlink-dirlink-basis_test.py b/testsuite/symlink-dirlink-basis_test.py index b952b4de..fd880144 100644 --- a/testsuite/symlink-dirlink-basis_test.py +++ b/testsuite/symlink-dirlink-basis_test.py @@ -14,20 +14,27 @@ import filecmp import os -import platform import subprocess import time from rsyncfns import ( RSYNC, SCRATCHDIR, SRCDIR, TMPDIR, - make_data_file, rsync_argv, test_fail, test_skipped, + make_data_file, resolve_beneath_supported, rsync_argv, test_fail, + test_skipped, ) -if platform.system() in ('SunOS', 'OpenBSD', 'NetBSD') or platform.system().startswith('CYGWIN'): +# Following an in-tree directory symlink under the secure resolver needs a +# kernel "beneath" primitive (RESOLVE_BENEATH/O_RESOLVE_BENEATH). Probe the +# actual binary rather than guessing from the platform name: this also skips +# correctly on Linux < 5.6, a seccomp-blocked openat2, and a --disable-openat2 +# build, where the portable O_NOFOLLOW fallback rejects the in-tree symlink +# (issue #715). +if not resolve_beneath_supported(): test_skipped( - f"secure_relative_open lacks RESOLVE_BENEATH equivalent on " - f"{platform.system()}; issue #715 still affects this platform" + "this rsync can't follow an in-tree directory symlink under its " + "secure resolver (no RESOLVE_BENEATH equivalent / --disable-openat2); " + "issue #715 still affects this configuration" ) os.environ['RSYNC_RSH'] = str(SRCDIR / 'support' / 'lsh.sh')