Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions testsuite/clean-fname-underflow.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/bin/sh
# clean-fname-underflow.test
# Ensure clean_fname() does not read-before-buffer when collapsing "..".
# This exercises the --server path where a crafted merge filename hits clean_fname().
#
# Usage:
# ./configure && make
# make check TESTS='clean-fname-underflow.test'

set -eu

# Try to find the just-built rsync binary if RSYNC_BIN isn't set.
if [ -z "${RSYNC_BIN:-}" ]; then
if [ -x "./rsync" ]; then
RSYNC_BIN=./rsync
elif [ -x "../rsync" ]; then
RSYNC_BIN=../rsync
else
RSYNC_BIN=rsync
fi
fi

workdir="${TMPDIR:-/tmp}/rsync-clean-fname.$$"
mkdir -p "$workdir"
trap 'rm -rf "$workdir"' EXIT INT TERM
cd "$workdir"

# Minimal rsyncd.conf using chroot so the crafted path reaches the server parser.
cat > rsyncd.conf <<'EOF'
pid file = rsyncd.pid
use chroot = true
[mod]
path = ./mod
read only = false
EOF
mkdir -p mod

# Start daemon on a random high port.
PORT=$(awk 'BEGIN{srand(); printf "%d", 20000+int(rand()*20000)}')
"$RSYNC_BIN" --daemon --no-detach --config=rsyncd.conf --port="$PORT" >/dev/null 2>&1 &
DAEMON_PID=$!
# Give the daemon a moment to come up.
sleep 0.3

# Invoke the server-side path. We don't need a real transfer; we just want to
# ensure clean_fname() doesn't crash when given "a/../test" via --filter=merge.
EXIT_OK=0
if "$RSYNC_BIN" --server --sender -vlr --filter='merge a/../test' . mod/ >/dev/null 2>&1; then
EXIT_OK=1
else
status=$?
# Non-zero exit is expected for bogus input; ensure it wasn't a signal/crash.
if [ $status -lt 128 ]; then
EXIT_OK=1
fi
fi

kill "$DAEMON_PID" >/dev/null 2>&1 || true

if [ "$EXIT_OK" -ne 1 ]; then
echo "clean-fname-underflow.test: rsync exited due to a signal or unexpected status"
exit 1
fi

echo "OK: clean_fname() handled 'a/../test' without crashing"
exit 0
12 changes: 8 additions & 4 deletions util1.c
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ int count_dir_elements(const char *p)
* resulting name would be empty, returns ".". */
int clean_fname(char *name, int flags)
{
char *limit = name - 1, *t = name, *f = name;
char *limit = name, *t = name, *f = name;
int anchored;

if (!name)
Expand Down Expand Up @@ -987,9 +987,13 @@ int clean_fname(char *name, int flags)
f += 2;
continue;
}
while (s > limit && *--s != '/') {}
if (s != t - 1 && (s < name || *s == '/')) {
t = s + 1;
/* backing up for ".." — avoid reading before 'name' */
while (s > limit && s[-1] != '/')
s--;

/* If found prior '/', or we reached the start, adjust t. */
if (s != t - 1 && (s <= name || *s == '/')) {
t = (s == name) ? name : s + 1;
f += 2;
continue;
}
Expand Down