mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-09-14 14:18:23 -04:00
The exclude-self rule that a ":e" merge synthesizes is built by hand with
new0(), so it inherited no flags. While the merge file was still being
parsed the global parse state masked that, but once parsing finished the
stored rule looked argument-origin, and report_filter_result() printed its
pattern -- a merge file's own text -- verbatim:
[sender] hiding file PAT-x9 because of pattern PAT-[x]9 [per-dir ...]
Plain -vv reaches this on a stock client; no --debug is involved. That is
the fifth site of this shape, and the first to get there by constructing a
rule rather than by printing one, so the redaction helper could not catch it.
Also fix the location a per-directory merge reports. Its fname points into
dirbuf, which is cut back to the directory before the name was saved, so the
error said "<rule from .../src/ line 1>" instead of naming .rsync-filter --
no leak, but it breaks the "redact what, keep where" bargain the rest of this
work depends on. Save the name before the truncation.
The rrsync test's claim to close "the rest of the FILTER trace family" was
too strong and is corrected: options.c maps verbosity onto the debug flags,
so -vvv still raises a restricted server to FILTER2 and its trace metadata
comes back. Rule text stays redacted at every verbosity, which is the
property that matters; -vvv is added to the unaffected-transfer cases.
79 lines
3.4 KiB
Python
79 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""rrsync must not let a peer turn on the server's --debug output.
|
|
|
|
Defence in depth, not the fix: what actually stops a merge file's contents
|
|
coming back is the redaction in exclude.c (see filter-merge-content-echo).
|
|
|
|
This closes ONE spelling of the control -- an explicit -M--debug -- and no
|
|
more. It does not close the FILTER trace family: options.c maps verbosity
|
|
onto the debug flags, so -vvv still raises a restricted server to FILTER2 and
|
|
forwards it, and the trace metadata (merge-file openability, the server's
|
|
absolute paths, mergelist bookkeeping) comes back with it. Rule TEXT stays
|
|
redacted at every verbosity, which is the property that matters and which
|
|
filter-merge-content-echo pins. Do not read this test as sealing -vvv.
|
|
|
|
It costs nothing: server_options() forwards --info but never --debug, so no
|
|
stock client sends one, and the only way it reaches a server is a peer asking
|
|
for it by name with -M.
|
|
"""
|
|
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
|
|
signal.signal(signal.SIGUSR1, signal.SIG_IGN)
|
|
signal.signal(signal.SIGUSR2, signal.SIG_IGN)
|
|
if '--shell' in sys.argv:
|
|
index = sys.argv.index('--shell') + 2
|
|
env = {**os.environ, 'SSH_ORIGINAL_COMMAND': ' '.join(sys.argv[index:])}
|
|
signal.signal(signal.SIGUSR1, signal.SIG_DFL)
|
|
signal.signal(signal.SIGUSR2, signal.SIG_DFL)
|
|
wrapper, root = env['RRSYNC_WRAPPER'], env['RRSYNC_ROOT']
|
|
os.execve(wrapper, [wrapper, '-wo', root], env)
|
|
|
|
from rsyncfns import RSYNC, SCRATCHDIR, makepath, patched_rrsync, rmtree, rsync_argv
|
|
|
|
base = SCRATCHDIR / 'rrsync-debug-denied'
|
|
rmtree(base)
|
|
src = base / 'src'
|
|
root = base / 'root'
|
|
makepath(src, root)
|
|
(src / 'f').write_bytes(b'hi\n')
|
|
|
|
# RSYNC may be a multi-word command (the runner's --protocol=N, or valgrind)
|
|
# while rrsync hands its RSYNC to execlp() as a single executable name, so it
|
|
# has to be wrapped before patched_rrsync() sees it.
|
|
shim = base / 'rsync-shim'
|
|
shim.write_text('#!/bin/sh\nexec ' + RSYNC + ' "$@"\n')
|
|
shim.chmod(0o755)
|
|
wrapper = patched_rrsync(base, rsync_path=str(shim))
|
|
rsh = f'{sys.executable} {os.path.abspath(__file__)} --shell'
|
|
env = {**os.environ, 'RRSYNC_WRAPPER': str(wrapper), 'RRSYNC_ROOT': str(root)}
|
|
|
|
for spelling in ('-M--debug=FILTER2', '--remote-option=--debug=ALL3'):
|
|
got = subprocess.run(
|
|
rsync_argv('-r', spelling, '-e', rsh, f'{src}/', 'ignored:'),
|
|
env=env, capture_output=True, text=True, timeout=20)
|
|
out = got.stdout + got.stderr
|
|
assert got.returncode != 0 and 'option --debug has been disabled' in out, (
|
|
f'rrsync accepted a peer-selected {spelling}: rc={got.returncode}, '
|
|
f'out={out!r}')
|
|
|
|
# The option table is generated by the cull-options script, so the entry this
|
|
# relies on can come back on a regeneration. Check the value, not just the
|
|
# behaviour, so that is unambiguous when it happens.
|
|
assert "'debug': -1," in wrapper.read_text(), (
|
|
"support/rrsync no longer disables 'debug' -- a cull-options regeneration "
|
|
'probably restored it')
|
|
|
|
# Ordinary transfers must be unaffected -- including -vvv, which reaches
|
|
# FILTER2 by verbosity alone and so must NOT be caught by the refusal above.
|
|
for extra in ([], ['-vv'], ['-vvv']):
|
|
ok = subprocess.run(
|
|
rsync_argv('-r', *extra, '-e', rsh, f'{src}/', 'ignored:'),
|
|
env=env, capture_output=True, text=True, timeout=20)
|
|
assert ok.returncode == 0 and (root / 'f').read_bytes() == b'hi\n', (
|
|
f'an ordinary transfer broke with {extra}: rc={ok.returncode}, '
|
|
f'stderr={ok.stderr!r}')
|