mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-09-12 21:28:25 -04:00
The race tests are the suite's slowest by a wide margin -- a race test is a negative oracle, so it passes by spending its entire budget. Most of them wrote `max(RACE_TIMEOUT, 10.0)`, which ignored --race-timeout below 10s: the documented knob did nothing for 10 of the 16 tests. Replace the floor idiom with race_budget(default), where the per-test default applies only when the operator did not pass --race-timeout, and runtests.py exports race_timeout only when the flag was actually given. Defaults are unchanged (measured identical at 15.3s/10.3s/5.2s). Validate the value rather than take it on trust. A race test loops `while monotonic() < deadline`, so a zero, negative or NaN budget runs the body zero times and the test reports PASS without ever racing, and an infinite one runs until the unrelated per-test timeout; the old max(..., 10.0) floor had made all of that unreachable, so removing the floor had to come with rejecting the input. An unparsable value in the environment counts as unset for the same reason -- falling back to the 5s baseline while still counting as "set" would silently halve a 10s or 15s oracle that nobody asked to shorten. NB the *_test.py glob spans four committed symlinks (chown-fake, devices-fake, exclude-lsh, xattrs-hlink); sed -i would replace each with a copy of its target, so they are rewritten with --follow-symlinks semantics and left as symlinks.
71 lines
2.8 KiB
Python
71 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
import os
|
|
import platform
|
|
import subprocess
|
|
import time
|
|
|
|
from rsyncfns import (
|
|
race_budget, SCRATCHDIR, rmtree, rsync_argv,
|
|
start_path_flipper, start_test_daemon, stop_flipper, test_fail, test_xfail,
|
|
write_daemon_conf,
|
|
)
|
|
|
|
_CYGWIN = platform.system().startswith('CYGWIN')
|
|
|
|
# The sender's secure parent-dir open resolves on held dirfds with O_NOFOLLOW
|
|
# (race-free by construction on every platform), so a flipped parent component
|
|
# cannot redirect the --remove-source-files unlink outside the module.
|
|
|
|
base = SCRATCHDIR / 'sender-remove-source'
|
|
mod = base / 'module'
|
|
outside = base / 'outside'
|
|
dest = base / 'dest'
|
|
rmtree(base)
|
|
(mod / 'real').mkdir(parents=True)
|
|
outside.mkdir(parents=True)
|
|
dest.mkdir(parents=True)
|
|
|
|
inside_file = mod / 'real' / 'file'
|
|
outside_file = outside / 'file'
|
|
inside_file.write_text('payload\n')
|
|
outside_file.write_text('payload\n')
|
|
st = inside_file.stat()
|
|
os.utime(outside_file, (st.st_atime, st.st_mtime))
|
|
os.symlink(outside, mod / 'evil')
|
|
|
|
conf = write_daemon_conf([
|
|
('src', {'path': str(mod), 'read only': 'no', 'use chroot': 'no'}),
|
|
])
|
|
url = start_test_daemon(conf, 12937)
|
|
|
|
flip = start_path_flipper(mod / 'real', mod / 'evil')
|
|
deadline = time.monotonic() + race_budget()
|
|
try:
|
|
while time.monotonic() < deadline and outside_file.exists():
|
|
if not inside_file.exists() and (mod / 'real').is_dir() and not os.path.islink(mod / 'real'):
|
|
try:
|
|
inside_file.write_text('payload\n')
|
|
os.utime(inside_file, (st.st_atime, st.st_mtime))
|
|
except FileNotFoundError:
|
|
pass
|
|
subprocess.run(
|
|
rsync_argv('-a', '--remove-source-files', f'{url}src/real/file', str(dest) + '/'),
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, text=True)
|
|
finally:
|
|
stop_flipper(flip)
|
|
|
|
if not outside_file.exists():
|
|
if _CYGWIN:
|
|
# Cygwin resolves directory descriptors by path name rather than a pinned
|
|
# inode and emulates symlinks as special files, so the confined sender's
|
|
# held-fd O_NOFOLLOW walk -- though compiled in -- does not enforce the
|
|
# parent-component pin here, and a raced parent flip can still redirect
|
|
# the --remove-source-files unlink. Documented Cygwin platform residual
|
|
# (see SECURITY.md and the matching xfail in symlink-race-source_test.py);
|
|
# Cygwin is a dev/interop target, not a privilege boundary host.
|
|
test_xfail("cygwin: --remove-source-files parent-flip race still unlinks "
|
|
"the outside victim -- documented Cygwin platform residual")
|
|
test_fail("daemon sender --remove-source-files cleanup unlinked the outside victim through a raced parent symlink")
|
|
|
|
print("sender-remove-source-secure: remove-source cleanup did not unlink outside the module")
|