Compare commits

..
23 Commits
Author SHA1 Message Date
Andrew Tridgell f26f747b80 Preparing for release of 3.4.4 [buildall] 2026-06-08 13:53:52 +10:00
Andrew Tridgell ed2950f867 version.h: bump to 3.4.4 for the release 2026-06-08 13:17:32 +10:00
Andrew Tridgell 37d0080e92 t_stub: give test helpers an unlimited max_alloc
Helpers link util2.o but not options.c, so they used the stub's
max_alloc = 0, which makes every my_alloc()/my_strdup() in util2.c abort
with "exceeded --max-alloc=0". CI didn't catch it because the openat2
path avoids those allocations, but the secure_relative_open() fallback
hits my_strdup() and aborts. Set max_alloc = (size_t)-1, matching the
v34-stable-testsuite fix. Reported by steadytao on PR #980.
2026-06-08 13:17:32 +10:00
Andrew Tridgell 5073e6a575 ci: run the v34-stable-testsuite regression suite against this build
The stable branch keeps the old shell test suite, so the modern Python
suite lives on the v34-stable-testsuite branch. Build rsync here and run
that suite against the built binary (helpers/config.h as tooldir from
this build, test scripts via --srcdir), giving regression coverage for
3.4.x without importing the full master suite.

Runs on ubuntu-latest and ubuntu-22.04 (older-LTS coverage for backports).
Each does a pipe-transport pass (with the same RSYNC_EXPECT_SKIPPED list
the v34-stable-testsuite ubuntu jobs use) and a --use-tcp pass for the
daemon tests the pipe run skips. Addresses review on PR #980.
2026-06-08 13:17:32 +10:00
Andrew Tridgell bb8d1c14c5 NEWS: add the 3.4.4 release entry
Add the NEWS entry for rsync 3.4.4 (8 June 2026): the backported
regression fixes, the PORTABILITY note documenting the #915 alt-basis
platform limitation, the openat2 autodetect/mknodat fallback build
notes, the stable-testsuite CI addition, and a CREDITS section for the
contributors, reporters, and the PR #980 review.
2026-06-08 13:17:32 +10:00
Andrew Tridgell 517c35e2db ci: also run the build workflows on *-stable release branches
The workflows triggered only on 'master', so PRs targeting a release branch
(e.g. v3.4-stable for 3.4.4) got no CI. Add a '*-stable' branch wildcard to
the push and pull_request filters.
2026-06-08 13:17:32 +10:00
pterror ee4f668f29 receiver: fix NULL deref on the delta discard path
receive_data() crashed a receiver that was merely DISCARDING a file's
delta stream. discard_receive_data() calls receive_data() with
fname == NULL and fd == -1, so size_r == 0 and mapbuf == NULL. A normal
block-MATCH token (against a block the basis and source share) then
reaches the !mapbuf branch added in 31fbb17d ("receiver: fix absolute
--partial-dir delta resume"), which calls full_fname(fname). full_fname()
dereferences its argument unconditionally (util1.c: `if (*fn == '/')`),
so fname == NULL faults there -> receiver SIGSEGV.

This is a normal-operation crash with a stock cooperating sender, not an
adversarial one. The generator hands the sender real block sums whenever
the basis is readable and we're in delta mode; the receiver only decides
to discard afterwards, when its output cannot be produced -- e.g. the
destination directory is not writable (mkstemp fails), the basis turns
out to be a directory, or a --partial-dir resume is skipped. A MATCH
token arriving during that discard hit the NULL deref.

The 31fbb17d branch is correct only for a REAL output transfer (fd != -1,
fname valid): there, a block match with no mapped basis is a genuine
protocol inconsistency (the generator promised a basis the receiver could
not open), and honoring it would silently omit those bytes from the
verification checksum or leave a hole, so hard-erroring -- and
full_fname(fname) -- is right. It conflated that with the discard path.

The discriminator is fd, not mapbuf: on the discard path fd == -1 always;
on the real-output inconsistency fd != -1. Scope the "no basis file"
protocol error to fd != -1 (where fname is non-NULL and full_fname is
safe) and, on the discard path (fd == -1), absorb the matched bytes
benignly (offset += len; continue) -- symmetric with the literal-token
handling just above, and restoring the pre-31fbb17d behavior. The
real-transfer inconsistency check is preserved unchanged.
2026-06-08 13:17:32 +10:00
Andrew Tridgell c14e2258b5 build: openat2 autodetect + android probe (R1 #924/#905/#900, R10 #904)
configure now probes for <linux/openat2.h> + SYS_openat2 and defines
HAVE_OPENAT2 only when both are present; syscall.c gates the openat2 include
and the openat2(RESOLVE_BENEATH) tier on HAVE_OPENAT2, so the build no longer
fails on kernels/headers that lack the openat2 header (3.4.3 included it
unconditionally on Linux).  android.c probes openat2 usability behind a SIGSYS
handler so the Android/Termux seccomp sandbox falls back to the portable
resolver instead of killing the process.

Backport combining c73e0063, 83a24c21, the syscall.c guards from 1d5b5ab8, and
4634b0ad; the --disable-openat2/gcov coverage knobs and test changes are omitted.

Thanks to @mmayer (#924), @fda77 (#905), @darkshram (#900) and @ketas (#904) for the reports.
2026-06-08 13:17:32 +10:00
Zen Dodd 499ed5e1ab fix: update skips different file type 2026-06-08 13:17:32 +10:00
Mike-Goutokuji c7ca5217a7 Always clear st out and validate nanoseconds before using it
Otherwise we get errors.
Fixes: https://github.com/RsyncProject/rsync/issues/927
2026-06-08 13:17:32 +10:00
Andrew TridgellandStiliyan Tonev 20cc824592 main: fix --mkpath + --dry-run file-to-file copy (#880)
A single-file --mkpath copy whose destination parent does not exist
failed under --dry-run: make_path() only *reports* the directories it
would create in a dry run, so change_dir#3 then tried to chdir into a
parent that isn't there and aborted with "change_dir#3 ... failed".

When the parent is genuinely missing in a dry run, skip the chdir and
mark the destination as not-yet-present (dry_run++), exactly as the
multi-file/dir-creation path already does, so the generator doesn't
probe the missing tree.  Gating it on the missing-parent case keeps an
ordinary file-to-file dry run chdir'ing into and itemizing against an
existing destination.

Fixes: #880

Thanks to @pkzc for the report (#880).

Co-authored-by: Stiliyan Tonev (Bark) <stiliyan21@gmail.com>
2026-06-08 13:17:32 +10:00
Zen Dodd f86309f230 fix: daemon upload delete stats 2026-06-08 13:17:32 +10:00
Andrew Tridgell ee7c8a5783 token: drain the matched-block insert deflate (#951)
send_deflated_token() adds a matched block to the compressor history with
deflate(Z_INSERT_ONLY).  Our bundled zlib implements Z_INSERT_ONLY (it
produces no output and consumes the input in one call), but a build
against a system zlib lacks it and falls back to Z_SYNC_FLUSH (see the top
of the file), which emits a flush block into obuf.  For a large
incompressible matched token that block exceeds AVAIL_OUT_SIZE(CHUNK_SIZE),
so deflate returned with avail_in != 0 and the transfer aborted:

    "deflate on token returned 0 (N bytes left)"  at token.c

The insert output is never sent -- the receiver rebuilds the matching
history itself in see_deflate_token() -- so loop, resetting the output
buffer, and discard it.  Drain with the same condition as the data loop
above: until the input is consumed AND avail_out != 0.  Stopping at
avail_in == 0 alone can leave pending output in the deflate stream (a
full output buffer with bytes still buffered), which would then be emitted
by the next real deflate send and corrupt the stream.  A bundled-zlib
build still finishes in one iteration.

Thanks to @brabalan for the report (#951).

Fixes: #951
2026-06-08 13:17:32 +10:00
Zen Dodd b29c149529 fix: install generated manpages out of tree 2026-06-08 13:17:32 +10:00
Andrew Tridgell 7811f2b1b9 daemon: un-backslash escaped option args (#829)
Without --secluded-args, the client's safe_arg() backslash-escapes shell
and wildcard chars in option values before sending them to the server, so
--chown's --usermap=*:user is transmitted as --usermap=\*:user.  Over ssh a
remote shell removes the backslashes before rsync parses the args, but a
daemon has no shell and read_args() stored option args verbatim -- so the
receiver saw the literal "\*", the usermap/groupmap wildcard never matched,
and the module's configured uid/gid won instead.  A regression from the
secluded-args hardening; rsync 3.2.3 (protocol 31) worked.

Un-backslash option args in read_args() on the daemon's first
(non-protected) read, mirroring what the ssh-side shell does.  File args
after the dot are already handled by glob_expand(); the protected (NUL,
already-unescaped) re-read and the server's stdin read pass unescape=0 so
their raw args are left untouched.

Thanks to @elcamlost for the report (#829).

Fixes: #829
2026-06-08 13:17:32 +10:00
Andrew Tridgell f3757a470a build: fall back to do_mknod() when mknodat() is unavailable (#896)
do_mknod_at() (the symlink-race-safe variant used by a non-chrooted
daemon receiver) calls mknodat()/mkfifoat(), but the at-variant was
gated only on AT_FDCWD.  Older Darwin declares AT_FDCWD without
mknodat(), so the build failed with "mknodat undeclared".

Probe mknodat()/mkfifoat() in configure and require HAVE_MKNODAT for the
at-variant; without it do_mknod_at() falls back to do_mknod(), exactly
as it already does where AT_FDCWD is missing.  Linux keeps the mknodat
path since HAVE_MKNODAT is defined there.

Thanks to @debohman for the report (#896).

Fixes: #896
2026-06-08 13:17:32 +10:00
Andrew Tridgell 6c8295fd62 alloc: revert "zero all new memory from allocations" (#959)
Commit d046525d made my_alloc() calloc every fresh allocation and made
expand_item_list() memset the freshly grown tail, to hand out predictably
zeroed memory.  But that forces the kernel to back pages callers never
touch: each per-directory file_list pre-allocates a FLIST_START-entry
(32768) pointer array -- 256KB -- and calloc now zeroes the whole array
even for an empty directory.  With incremental recursion over many
directories the resident set explodes; 80000 empty dirs went from ~336MB
to ~10.8GB.

Restore the pre-d046525d malloc/calloc split: fresh allocations use
malloc (so untouched tails stay lazy) and only explicit do_calloc
requests (new_array0) are zeroed.  Callers that need zeroed memory
already ask for it, and the full test suite passes.

Thanks to @guilherme-puida for the report (#959).

Fixes: #959
2026-06-08 13:17:32 +10:00
Andrew Tridgell a8f80f5a12 generator: cap block s2length at the negotiated checksum length
sum_sizes_sqroot() capped the strong-sum length at SUM_LENGTH (16), the
legacy MD4/MD5 digest size.  Since 0902b52f the sum2 array elements are
xfer_sum_len bytes and the sender rejects a sums header whose s2length
exceeds xfer_sum_len.  When the negotiated transfer checksum is shorter
than 16 bytes -- xxh64 (8), used when the build's libxxhash lacks
xxh128/xxh3 (e.g. Ubuntu 20.04) -- the generator still emitted s2length
up to 16, so --append-verify and other full-checksum (redo) transfers
died with "Invalid checksum length 16 [sender]" (protocol incompatibility).

Cap s2length at MIN(SUM_LENGTH, xfer_sum_len): unchanged for any checksum
>= 16 bytes (md5/xxh128/sha1), corrected for short ones.  Also closes a
latent over-read of the xfer_sum_len-sized digest buffer.
2026-06-08 13:17:32 +10:00
Andrew Tridgell d8847ff7a8 syscall/receiver: honour a relative alt-basis dir on a daemon receiver (#915)
The symlink-race hardening routed the receiver's basis open through
secure_relative_open(), which rejects any '..' -- so a sibling
--link-dest=../01 on a use-chroot=no daemon was silently ignored and every file
re-transferred (#915/#928, a regression from 3.4.1).

Narrow the confinement to the sanitizing daemon (am_daemon && !am_chrooted) and
re-anchor it at the module root, the real trust boundary: secure_relative_open()
prefixes the cwd's module-relative path (from rsync's logical curr_dir[], a
guaranteed lexical prefix of module_dir) and resolves beneath module_dir, so
RESOLVE_BENEATH permits an in-module '..' climb while still rejecting one that
escapes the module.  secure_basis_open() opens with a bare do_open() in the
non-sanitizing cases.  t_stub.c gains weak curr_dir[]/curr_dir_len for the
helpers (via #pragma weak on non-GNU compilers, where rsync.h erases
__attribute__).

Two tests: link-dest-relative-basis asserts the in-module '..' is honoured;
link-dest-module-escape asserts a --link-dest=../../OUTSIDE climb that leaves
the module is refused (not hard-linked to an outside file).  See upstream
PR #930.

Thanks to @fufu65 (#915) and @JetAppsClark (#928) for the reports.
2026-06-08 13:17:32 +10:00
Andrew Tridgell 51c5f05771 sender: open a module-root-absolute path for a path = / module (#897)
A daemon module with path=/ makes F_PATHNAME absolute, so the secure_path built
for the content open starts with '/'.  secure_relative_open() rejects an
absolute relpath with EINVAL, so a use-chroot=no daemon with path=/ could not
send any file ('failed to open ...: Invalid argument (22)') -- a regression
from 3.4.2.  Strip leading slashes to a module-relative path; resolution stays
confined beneath module_dir.

Thanks to @moonlitbugs for the report (#897).
2026-06-08 13:17:32 +10:00
Andrew Tridgell f68facd22f flist: accept the missing-args mode-0 entry in recv_file_entry (#910)
--delete-missing-args (missing_args==2) sends a missing --files-from arg as a
mode-0 entry (IS_MISSING_FILE), the generator's delete signal.  The mode-type
validation in recv_file_entry() rejected mode 0 as an invalid file type,
aborting the transfer with 'invalid file mode 00 ... code 2' before the
generator could act (a regression from 3.4.1).  Allow mode 0 through only when
missing_args==2 (the delete mode -- not --ignore-missing-args, which never
sends a mode-0 entry); all other modes are still rejected.

Thanks to @mgkeeley for the report (#910).
2026-06-08 13:17:32 +10:00
Andrew TridgellandClaude Opus 4.7 9e2e9f3362 receiver: fix absolute --partial-dir delta resume (false verification)
A delta (--no-whole-file) resume whose basis is an absolute --partial-dir
looped forever on exit code 23 ("failed verification -- update put into
partial-dir"), stranding the correct data in the partial-dir and never
populating the destination.

Cause: an absolute --partial-dir makes the basis path absolute, but the
receiver opened it with secure_relative_open(NULL, fnamecmp, ...), which by
design rejects an absolute relpath (EINVAL). The basis fd was then -1, so
receive_data() mapped no basis and (because the matched-block sum_update() is
guarded by "if (mapbuf)") computed the whole-file verification checksum over
the literal data only -> a spurious mismatch every run. (The data itself was
correct, since the in-place update leaves the matched basis bytes in place.)
Under a non-chroot daemon the in-place write went through the same call and
failed outright.

Fix: add secure_basis_open(), which treats an operator-trusted absolute basis
path as (trusted directory + confined leaf) -- the same way secure_relative_open
already trusts an absolute basedir while keeping O_NOFOLLOW on the leaf -- and
use it for both the basis read and the inplace-partial write. The strict
"reject absolute relpath" contract of secure_relative_open is left intact.

Defense-in-depth: receive_data() now treats a block-match token with no mapped
basis as a protocol inconsistency (it can only arise from a basis that the
generator opened but the receiver could not), failing cleanly instead of
silently dropping those bytes from the verify checksum or the output.

Thanks to @sylvain-ilm for the report (#724, #725).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 13:17:32 +10:00
Andrew Tridgell 3786926703 build: add check-progs target for fleettest
Build the test-helper programs without running the suite, so an external
harness (fleettest.py) can invoke runtests.py with its own options.
2026-06-08 13:17:32 +10:00
659 changed files with 10088 additions and 66915 deletions

No files matched your search

-16
View File
@@ -1,17 +1 @@
* text=auto eol=lf
# The rsync-web/ subdirectory holds the project website source content
# (mirrors what gets pushed to https://rsync.samba.org). Exclude it from
# `git archive` output so the release source tarball produced by
# packaging/release.py step_7_tarball does not bloat with HTML the
# tarball doesn't need.
/rsync-web/ export-ignore
# old_versions/ holds static binaries of historical rsync releases, used by the
# version-mixing test suite (.github/workflows/ubuntu-version-mix.yml) to run
# the current code against a real old peer over the daemon / remote-shell.
# Mark the binaries as binary so the `text=auto eol=lf` rule above can't try to
# normalise line endings and corrupt them; export-ignore keeps them out of the
# release source tarball.
/old_versions/rsync_* binary
/old_versions/rsync_* export-ignore
-4
View File
@@ -1,4 +0,0 @@
# These are supported funding model platforms
github: RsyncProject
patreon: AndrewTridgell
-46
View File
@@ -1,46 +0,0 @@
name: Lint GitHub Actions workflows
# Static-check the workflow YAML with rhysd/actionlint. Catches missing
# secrets, bad expressions, expression-type errors, unsupported runner
# images, and (via embedded shellcheck) common pitfalls in `run:` scripts.
# Trigger only on changes under .github/workflows/ so the rest of the
# matrix isn't billed when nothing here moves.
on:
push:
branches: [ master ]
paths:
- '.github/workflows/*.yml'
- '.github/actionlint.yaml'
- '.github/actionlint.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
paths:
- '.github/workflows/*.yml'
- '.github/actionlint.yaml'
- '.github/actionlint.yml'
permissions:
contents: read
jobs:
actionlint:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: actionlint
steps:
- uses: actions/checkout@v4
- name: install actionlint
# Pin a version so this job is reproducible; bump deliberately.
# The download script verifies a SHA256 of the release tarball.
run: |
bash <(curl --proto '=https' --tlsv1.2 -fsSL \
https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) \
1.7.12
echo "$PWD" >>"$GITHUB_PATH"
- name: actionlint --version
run: actionlint -version
- name: actionlint .github/workflows/*.yml
run: actionlint -color
+4 -13
View File
@@ -8,12 +8,12 @@ name: Test rsync on AlmaLinux 8
on:
push:
branches: [ master ]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/almalinux-8-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/almalinux-8-build.yml'
@@ -22,9 +22,6 @@ on:
jobs:
test:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
container:
image: almalinux:8
@@ -62,19 +59,13 @@ jobs:
run: ./rsync --version
- name: check
# In the container we already run as root, so no sudo. The
# crtimes-not-supported skip matches the other Linux jobs;
# daemon-chroot-acl and proxy-response-line-too-long skip because
# the default (secure) transport opens no listening socket.
run: RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt make check
- name: check (TCP daemon transport)
# Second run exercising the real loopback-TCP daemon path.
run: ./runtests.py --rsync-bin="$PWD/rsync" --use-tcp -j 8
# crtimes-not-supported skip matches the other Linux jobs.
run: RSYNC_EXPECT_SKIPPED=crtimes make check
- name: ssl file list
run: ./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: almalinux-8-bin
path: |
rsync
-124
View File
@@ -1,124 +0,0 @@
name: Build static rsync for Android
# Cross-compiles statically-linked rsync binaries with the Android NDK,
# suitable for dropping onto a phone (adb push / Termux) with no shared
# libraries. arm64-v8a covers all modern phones; armeabi-v7a covers older
# 32-bit devices. The binaries are uploaded as workflow artifacts.
#
# These are cross-compiled, so the test suite can't run here; we sanity
# check that each binary is the right architecture, is static, and that
# it executes (`--version`) under qemu-user.
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/android-static-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/android-static-build.yml'
schedule:
- cron: '42 8 * * 1'
workflow_dispatch:
env:
# Minimum supported API level. 24 (Android 7.0) runs on every modern
# phone while keeping broad reach; bump if you need newer Bionic APIs.
ANDROID_API: 24
jobs:
build:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: ${{ matrix.abi }}
strategy:
fail-fast: false
matrix:
include:
- abi: arm64-v8a # modern phones
triple: aarch64-linux-android
qemu: qemu-aarch64-static
- abi: armeabi-v7a # older 32-bit phones
triple: armv7a-linux-androideabi
qemu: qemu-arm-static
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install build prerequisites
run: sudo apt-get update && sudo apt-get install -y autoconf automake gawk qemu-user-static
- name: Configure and build (${{ matrix.abi }})
shell: bash
run: |
set -euo pipefail
NDK="${ANDROID_NDK_LATEST_HOME:-$ANDROID_NDK_ROOT}"
TC="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
export CC="$TC/${{ matrix.triple }}${ANDROID_API}-clang"
export AR="$TC/llvm-ar" RANLIB="$TC/llvm-ranlib" STRIP="$TC/llvm-strip"
export CFLAGS="-O2" LDFLAGS="-static"
# Bionic doesn't declare lchmod()/lutimes() until API 36, but the
# symbols link, so configure mis-detects them -- force them off so
# rsync uses its fallbacks. The other cache vars restore values
# that configure can't probe when cross-compiling (Android runs a
# normal Linux kernel, so these match the native Linux result).
export ac_cv_func_lchmod=no ac_cv_func_lutimes=no \
rsync_cv_HAVE_SOCKETPAIR=yes \
rsync_cv_MKNOD_CREATES_FIFOS=yes \
rsync_cv_MKNOD_CREATES_SOCKETS=yes
# Self-contained build: drop optional external libraries so the
# static binary needs nothing at runtime. rsync keeps md5/md4
# checksums and its bundled zlib.
./configure --host=${{ matrix.triple }} --build=x86_64-pc-linux-gnu \
--enable-ipv6 \
--disable-zstd --disable-lz4 --disable-xxhash --disable-openssl \
--disable-iconv --disable-iconv-open \
--disable-acl-support --disable-xattr-support \
--disable-md2man --disable-roll-simd \
--with-included-popt --with-included-zlib
# Generate the awk-built headers serially first so the parallel
# build can't race on proto.h <- daemon-parm.h.
make proto.h
make -j"$(nproc)" rsync
"$STRIP" rsync
- name: Verify binary
shell: bash
run: |
set -euo pipefail
file rsync
# Gate: must be a statically-linked executable (no interpreter).
file rsync | grep -q "statically linked"
if file rsync | grep -q "dynamically linked"; then
echo "ERROR: binary is not static" >&2; exit 1
fi
# Best-effort: confirm it actually runs under qemu-user.
${{ matrix.qemu }} ./rsync --version | head -3 || \
echo "WARNING: qemu smoke test did not run cleanly (check on a real device)"
- name: Package
shell: bash
run: |
set -euo pipefail
VER=$(sed -n 's/.*RSYNC_VERSION "\([^"]*\)".*/\1/p' version.h)
out="rsync-${VER}-android-${{ matrix.abi }}"
mkdir -p dist
cp rsync "dist/$out"
( cd dist && sha256sum "$out" > "$out.sha256" )
echo "ARTIFACT_NAME=rsync-android-${{ matrix.abi }}" >>"$GITHUB_ENV"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: ${{ env.ARTIFACT_NAME }}
path: dist/
-75
View File
@@ -1,75 +0,0 @@
name: rsync ASan+UBSan (clang)
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/asan-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/asan-build.yml'
schedule:
# Weekly (Mon 09:42 UTC): catch breakage from a moving ubuntu-latest/clang
# toolchain (a new clang can add a UBSan check, or change ASan behaviour)
# that no code push would otherwise trigger. Push/PR already gate every
# code change, so daily would just re-run an unchanged tree.
- cron: '42 9 * * 1'
workflow_dispatch:
jobs:
asan:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: rsync ASan+UBSan (clang)
env:
# rsync intentionally leaks small allocations at process exit, so leak
# detection would be all noise; chase only memory-safety errors.
ASAN_OPTIONS: detect_leaks=0:abort_on_error=1
# UBSan is a gate: -fno-sanitize-recover=undefined (below) aborts on the
# first finding and halt_on_error=1 makes that fatal, so any undefined
# behaviour fails the run. This needs the tree to be UBSan-clean: the
# remaining findings are fixed in code (hashtable/mdfour shifts, xattrs,
# and log.c's file_struct, kept aligned via rounding.h); only byteorder.h's
# intentional unaligned accessors are suppressed, with no_sanitize.
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get update
sudo apt-get install -y clang acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev openssl
echo "/usr/local/bin" >>"$GITHUB_PATH"
- name: configure
# -DNDEBUG builds as a shipped release does (assert() compiled out), so
# AddressSanitizer catches the over-reads/over-writes that an "assert()
# instead of a real bounds check" bug would cause in a production build.
# UBSan rides along on the same build; -fno-sanitize-recover=undefined
# makes any undefined behaviour abort (and thus fail the run) instead of
# merely printing it.
run: |
CC=clang \
CFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer -g -O1 -DNDEBUG" \
LDFLAGS="-fsanitize=address,undefined" \
./configure --with-rrsync --disable-md2man --enable-strict-confinement
- name: make
# check-progs builds rsync plus the test helper programs (tls, trimslash,
# t_unsafe, ...) that runtests.py requires; plain "make" builds only rsync
# and runtests aborts on the missing helpers.
run: make check-progs
- name: info
run: ./rsync --version
- name: check (stdio-pipe transport)
# ASan+UBSan-instrumented coverage of the transfer, daemon, sender,
# receiver and metadata paths over the default stdio-pipe transport.
run: ./runtests.py --rsync-bin="$PWD/rsync" -j8
- name: check (TCP daemon transport)
# --use-tcp also exercises the loopback rsyncd listener and the client's
# TCP connection path.
run: ./runtests.py --rsync-bin="$PWD/rsync" --use-tcp -j8
-75
View File
@@ -1,75 +0,0 @@
name: Coverage (Ubuntu)
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/coverage.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/coverage.yml'
schedule:
- cron: '42 9 * * 1'
workflow_dispatch:
jobs:
coverage:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: gcov coverage
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get update
sudo apt-get install -y acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl gcovr
echo "/usr/local/bin" >>"$GITHUB_PATH"
- name: configure
run: ./configure --enable-coverage --with-rrsync
- name: make
run: make
- name: info
run: rsync --version
# Two coverage runs: the default pipe transport, then a second pass over a
# real loopback rsyncd (--use-tcp) which also exercises the require_tcp-only
# tests. gcovr's --print-summary line/branch/decision totals go to the step
# log (and the job summary below), so the numbers are visible in CI.
# `make coverage` exits with the suite's status, so a regression fails CI.
- name: coverage (pipe transport)
run: |
set -o pipefail
sudo make coverage 2>&1 | tee cov-pipe.log
- name: coverage (TCP transport)
run: |
set -o pipefail
sudo make coverage-tcp 2>&1 | tee cov-tcp.log
- name: coverage summary
if: always()
run: |
{
echo "## gcov coverage"
echo "### Pipe transport (\`make coverage\`)"
echo '```'
grep -E '^(lines|functions|branches|decisions):' cov-pipe.log || echo '(no summary -- see step log)'
echo '```'
echo "### TCP transport (\`make coverage-tcp\`)"
echo '```'
grep -E '^(lines|functions|branches|decisions):' cov-tcp.log || echo '(no summary -- see step log)'
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: upload HTML reports
if: always()
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: coverage-html
path: |
coverage
coverage-tcp
+3 -16
View File
@@ -2,12 +2,12 @@ name: Test rsync on Cygwin
on:
push:
branches: [ master ]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/cygwin-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/cygwin-build.yml'
@@ -16,9 +16,6 @@ on:
jobs:
test:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: windows-2022
name: Test rsync on Cygwin
steps:
@@ -42,22 +39,12 @@ jobs:
- name: info
run: bash -c '/usr/local/bin/rsync --version'
- name: check
# chown-fake / devices-fake / xattrs / xattrs-hlink now RUN on Cygwin
# (rsyncfns.py drives xattrs via getfattr/setfattr from the `attr`
# package installed above), verified on a real Cygwin host. The real
# chown/devices tests still skip (need root/mknod), as do the
# RESOLVE_BENEATH symlink-race tests.
run: bash -c 'RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/cygwin.txt make check'
- name: check (TCP daemon transport)
# Second run with daemon tests over a real loopback rsyncd; the default
# 'make check' above uses the secure stdio-pipe transport.
run: bash -c './runtests.py --rsync-bin=`pwd`/rsync.exe --use-tcp -j 8'
run: bash -c 'RSYNC_EXPECT_SKIPPED=acls-default,acls,bare-do-open-symlink-race,chdir-symlink-race,chmod-symlink-race,chown,daemon-chroot-acl,devices,dir-sgid,open-noatime,protected-regular,sender-flist-symlink-leak,simd-checksum,symlink-dirlink-basis make check'
- name: ssl file list
run: bash -c 'PATH="/usr/local/bin:$PATH" rsync-ssl --no-motd download.samba.org::rsyncftp/ || true'
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: cygwin-bin
path: |
rsync.exe
-73
View File
@@ -1,73 +0,0 @@
name: Test fleettest harness
# Bitrot check for testsuite/fleettest.py (the developer fleet CI harness).
# fleettest is meant to be run by developers on a modern Ubuntu box, so this
# job runs only on ubuntu-latest: it stands up a one-host "fleet" of two
# targets that both ssh to localhost and runs a real fleettest pass against it.
# It does not run on the BSD/Solaris/macOS/Cygwin matrix.
on:
push:
branches: [ master ]
paths:
- 'testsuite/fleettest.py'
- '.github/workflows/fleettest.yml'
- 'runtests.py'
- 'testsuite/skiplist/**'
- 'testsuite/skiplist-spec_test.py'
pull_request:
types: [opened, synchronize, reopened, labeled]
paths:
- 'testsuite/fleettest.py'
- '.github/workflows/fleettest.yml'
- 'runtests.py'
- 'testsuite/skiplist/**'
- 'testsuite/skiplist-spec_test.py'
workflow_dispatch:
schedule:
- cron: '17 7 * * 1'
jobs:
fleettest:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: fleettest against localhost
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get update
sudo apt-get install -y gcc g++ gawk autoconf automake \
acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev \
python3-cmarkgfm openssl rsync openssh-server
- name: set up ssh to localhost
run: |
mkdir -p ~/.ssh && chmod 700 ~/.ssh
ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_ed25519
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
sudo systemctl start ssh || sudo service ssh start
# fleettest connects with `ssh -o BatchMode=yes localhost`, which won't
# answer a host-key prompt -- so pre-trust localhost in known_hosts.
ssh-keyscan -H localhost 127.0.0.1 >> ~/.ssh/known_hosts 2>/dev/null
ssh -o BatchMode=yes -o ConnectTimeout=15 localhost 'echo ssh-to-localhost-ok'
- name: write localhost fleet config
run: |
cat > fleettest-ci.json <<'EOF'
{ "targets": [
{ "name": "local-a", "ssh_host": "localhost", "workflow": "none.yml",
"configure_flags": [], "builddir": "rsync-citest-a", "privilege": "sudo" },
{ "name": "local-b", "ssh_host": "localhost", "workflow": "none.yml",
"configure_flags": [], "builddir": "rsync-citest-b", "privilege": "sudo" }
] }
EOF
- name: fleettest --list (config sanity)
run: python3 testsuite/fleettest.py --fleet fleettest-ci.json --list
- name: run fleettest against localhost
# Two targets both on localhost exercise the parallel multi-target path
# and the per-run dir / port isolation; exit 0 iff every cell is OK.
run: python3 testsuite/fleettest.py --fleet fleettest-ci.json --timing
+3 -8
View File
@@ -2,23 +2,20 @@ name: Test rsync on FreeBSD
on:
push:
branches: [ master ]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/freebsd-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/freebsd-build.yml'
schedule:
- cron: '42 8 * * 1'
- cron: '42 8 * * *'
jobs:
test:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: Test rsync on FreeBSD
steps:
@@ -38,12 +35,10 @@ jobs:
make
./rsync --version
make check
./runtests.py --rsync-bin=`pwd`/rsync --use-tcp -j 8
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: freebsd-bin
path: |
rsync
+4 -15
View File
@@ -2,12 +2,12 @@ name: Test rsync on macOS
on:
push:
branches: [ master ]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/macos-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/macos-build.yml'
@@ -16,9 +16,6 @@ on:
jobs:
test:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: macos-latest
name: Test rsync on macOS
steps:
@@ -29,7 +26,7 @@ jobs:
run: |
brew install automake openssl xxhash zstd lz4
pip3 install --user --break-system-packages commonmark
echo "$(brew --prefix)/bin" >>"$GITHUB_PATH"
echo "$(brew --prefix)/bin" >>$GITHUB_PATH
- name: configure
run: |
BREW_PREFIX=$(brew --prefix)
@@ -44,20 +41,12 @@ jobs:
- name: info
run: rsync --version
- name: check
# chown-fake / devices-fake / xattrs / xattrs-hlink now RUN on macOS
# (rsyncfns.py drives xattrs via the `xattr` command), verified on a
# real macOS host, so they're no longer in the skip set.
run: sudo RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/macos.txt make check
- name: check (TCP daemon transport)
# Second run with daemon tests over a real loopback rsyncd; the default
# 'make check' above uses the secure stdio-pipe transport.
run: sudo ./runtests.py --rsync-bin="$PWD/rsync" --use-tcp -j 8
run: sudo RSYNC_EXPECT_SKIPPED=acls-default,chmod-temp-dir,chown-fake,daemon-chroot-acl,devices-fake,dir-sgid,open-noatime,protected-regular,simd-checksum,xattrs-hlink,xattrs make check
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: macos-bin
path: |
rsync
+3 -8
View File
@@ -2,23 +2,20 @@ name: Test rsync on NetBSD
on:
push:
branches: [ master ]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/netbsd-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/netbsd-build.yml'
schedule:
- cron: '42 8 * * 1'
- cron: '42 8 * * *'
jobs:
test:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: Test rsync on NetBSD
steps:
@@ -39,12 +36,10 @@ jobs:
make
./rsync --version
make check
./runtests.py --rsync-bin=`pwd`/rsync --use-tcp -j 8
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: netbsd-bin
path: |
rsync
+3 -23
View File
@@ -2,23 +2,20 @@ name: Test rsync on OpenBSD
on:
push:
branches: [ master ]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/openbsd-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/openbsd-build.yml'
schedule:
- cron: '42 8 * * 1'
- cron: '42 8 * * *'
jobs:
test:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: Test rsync on OpenBSD
steps:
@@ -39,28 +36,11 @@ jobs:
./configure --with-rrsync --disable-zstd --disable-md2man --disable-xxhash --disable-lz4
make
./rsync --version
# The flipper (symlink-race) tests are excluded on OpenBSD, as on the
# fleet's OpenBSD box: this kernel has a connect()-under-rename-load
# lost-wakeup and an FFS rename-storm corruption that hang them to
# the 300s timeout for non-rsync reasons (see
# dev-notes/openbsd-connect-lost-wakeup-report.txt); the protections
# they exercise are verified on the Linux/BSD boxes.
export RSYNC_EXCLUDE=acl-symlink-race,sender-readlink-atfd,sender-remove-source-secure
make check
# The --use-tcp daemon tests run at -j2 here (vs -j8 elsewhere): this
# job runs inside a nested VM, and at -j8 the many concurrent loopback
# daemons occasionally lose a connection-handshake timing race under
# that resource pressure, hanging one test to the 300s timeout. It is
# an environment artifact, not an rsync bug (the handshake is
# deadlock-free and unreproducible elsewhere, even pinned to 1 CPU at
# -j8); -j2 keeps the VM from over-subscribing. The pipe `make check`
# above stays at the default parallelism.
./runtests.py --rsync-bin=`pwd`/rsync --use-tcp -j 2
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: openbsd-bin
path: |
rsync
-94
View File
@@ -1,94 +0,0 @@
name: rsync scan-build (clang analyzer)
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/scan-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/scan-build.yml'
workflow_dispatch:
jobs:
# GATING run: pinned clang-18 on a pinned runner so the checker set -- and
# thus the expected zero -- is deterministic. The tree is kept clean for
# clang-18, so --status-bugs (non-zero exit on any report) fails the build
# when a new finding appears. Pin both the analyzer (clang-18/clang-tools-18)
# and the runner (ubuntu-24.04, whose apt repos carry those packages).
gate-clang18:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-24.04
name: scan-build gate (clang-18, pinned)
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get update
sudo apt-get install -y clang-18 clang-tools-18 acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev libpopt-dev openssl
- name: configure (under scan-build)
# Run configure under scan-build so its analyzer compiler-wrapper is baked
# into the Makefile's $(CC); --disable-md2man avoids the doc toolchain.
run: scan-build-18 ./configure --with-rrsync --disable-md2man
- name: scan-build (gating)
# --status-bugs makes scan-build exit non-zero if it finds ANY report.
# pipefail + 'exit $status' propagate that through the tee so the job goes
# red while still printing the summary; the report uploads for triage.
run: |
set -o pipefail
status=0
scan-build-18 --status-bugs -o "$PWD/scan-report" make check-progs -j"$(nproc)" 2>&1 | tee scan-build.out || status=$?
echo '## scan-build gate (clang-18)' >>"$GITHUB_STEP_SUMMARY"
grep -E 'scan-build: .* bugs? found|scan-build: No bugs found' scan-build.out >>"$GITHUB_STEP_SUMMARY" || true
exit $status
- name: upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: scan-build-report-clang18
path: scan-report
if-no-files-found: ignore
# INFORMATIONAL run: whatever clang ubuntu-latest currently ships. Newer
# clang releases enable extra, FP-heavy checkers (e.g. unix.Chroot
# "no chdir after chroot", alpha.unix.Stream) that the gate deliberately
# avoids, so this is NOT a gate (no --status-bugs). It surfaces what the
# newest analyzer sees -- useful for spotting genuine new findings before a
# gate bump -- without blocking merges. continue-on-error keeps a noisy or
# broken run from affecting the workflow's required status.
informational-latest:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: scan-build (latest clang, informational)
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get update
sudo apt-get install -y clang clang-tools acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev libpopt-dev openssl
- name: configure (under scan-build)
run: scan-build ./configure --with-rrsync --disable-md2man
- name: scan-build (informational)
run: |
scan-build -o "$PWD/scan-report" make check-progs -j"$(nproc)" 2>&1 | tee scan-build.out
echo '## scan-build informational (latest clang)' >>"$GITHUB_STEP_SUMMARY"
grep -E 'scan-build: .* bugs? found|scan-build: No bugs found' scan-build.out >>"$GITHUB_STEP_SUMMARY" || true
- name: upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: scan-build-report-latest
path: scan-report
if-no-files-found: ignore
+3 -8
View File
@@ -2,23 +2,20 @@ name: Test rsync on Solaris
on:
push:
branches: [ master ]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/solaris-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/solaris-build.yml'
schedule:
- cron: '42 8 * * 1'
- cron: '42 8 * * *'
jobs:
test:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: Test rsync on Solaris
steps:
@@ -38,12 +35,10 @@ jobs:
make
./rsync --version
make check
./runtests.py --rsync-bin=`pwd`/rsync --use-tcp -j 8
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: solaris-bin
path: |
rsync
+76
View File
@@ -0,0 +1,76 @@
name: Stable testsuite
# Regression coverage for the 3.4.x stable branch. The stable branch keeps the
# old shell test suite, so the modern Python suite is maintained separately on
# the v34-stable-testsuite branch. This job builds rsync from this branch and
# runs that suite against the freshly-built binary (the same "testsuite from one
# branch, code from another" split fleettest uses). Helper programs and
# config.h come from this branch's build (tooldir); the test scripts come from
# the stable-testsuite checkout (--srcdir).
on:
push:
branches: [ v3.4, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/stable-testsuite.yml'
pull_request:
branches: [ v3.4, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/stable-testsuite.yml'
workflow_dispatch:
schedule:
- cron: '23 6 * * *'
jobs:
stable-testsuite:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, ubuntu-22.04 ]
name: Stable testsuite on ${{ matrix.os }}
steps:
- name: checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: checkout stable testsuite
uses: actions/checkout@v4
with:
repository: RsyncProject/rsync
ref: v34-stable-testsuite
path: stable-testsuite
fetch-depth: 1
- name: prep
run: |
sudo apt-get update
sudo apt-get install -y gcc g++ gawk autoconf automake \
acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev \
python3-cmarkgfm openssl
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: ./configure --with-rrsync
- name: make check-progs
run: make check-progs
- name: info
run: ./rsync --version
# Pipe transport (the secure stdio default). The TCP-only daemon tests
# (daemon-access-ip, proxy-response-line-too-long) skip here and are run in
# the --use-tcp pass below; crtimes/daemon-chroot-acl/recv-discard-nullderef
# skip on the runner's filesystem / under root.
- name: run stable testsuite (pipe)
run: |
sudo RSYNC_EXPECT_SKIPPED=crtimes,daemon-access-ip,daemon-chroot-acl,proxy-response-line-too-long,recv-discard-nullderef \
./stable-testsuite/runtests.py \
--srcdir="$GITHUB_WORKSPACE/stable-testsuite" \
--rsync-bin="$GITHUB_WORKSPACE/rsync" \
-j16
# TCP transport over loopback, exercising the daemon paths the pipe run skips.
- name: run stable testsuite (tcp)
run: |
sudo ./stable-testsuite/runtests.py \
--srcdir="$GITHUB_WORKSPACE/stable-testsuite" \
--rsync-bin="$GITHUB_WORKSPACE/rsync" \
--use-tcp -j8
+6 -14
View File
@@ -6,12 +6,12 @@ name: Test rsync on Ubuntu 22.04
on:
push:
branches: [ master ]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-22.04-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-22.04-build.yml'
@@ -20,9 +20,6 @@ on:
jobs:
test:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-22.04
name: Test rsync on Ubuntu 22.04
steps:
@@ -32,7 +29,7 @@ jobs:
- name: prep
run: |
sudo apt-get install acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl
echo "/usr/local/bin" >>"$GITHUB_PATH"
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: ./configure --with-rrsync
- name: make
@@ -42,21 +39,16 @@ jobs:
- name: info
run: rsync --version
- name: check
run: sudo RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt make check
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check
- name: check30
run: sudo RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt make check30
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check30
- name: check29
run: sudo RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt,@testsuite/skiplist/proto29.txt make check29
- name: check (TCP daemon transport)
# Second run with daemon tests over a real loopback rsyncd; the default
# 'make check' above uses the secure stdio-pipe transport.
run: sudo ./runtests.py --rsync-bin="$PWD/rsync" --use-tcp -j 8
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check29
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: ubuntu-22.04-bin
path: |
rsync
+6 -32
View File
@@ -2,12 +2,12 @@ name: Test rsync on Ubuntu
on:
push:
branches: [ master ]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-build.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [ master, '*-stable' ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-build.yml'
@@ -16,9 +16,6 @@ on:
jobs:
test:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: Test rsync on Ubuntu
steps:
@@ -28,7 +25,7 @@ jobs:
- name: prep
run: |
sudo apt-get install acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl
echo "/usr/local/bin" >>"$GITHUB_PATH"
echo "/usr/local/bin" >>$GITHUB_PATH
- name: configure
run: ./configure --with-rrsync
- name: make
@@ -38,39 +35,16 @@ jobs:
- name: info
run: rsync --version
- name: check
run: sudo RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt make check
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check
- name: check30
run: sudo RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt make check30
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check30
- name: check29
run: sudo RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt,@testsuite/skiplist/proto29.txt make check29
- name: check (TCP daemon transport)
# Second run with daemon tests over a real loopback rsyncd. The default
# 'make check' above uses the secure stdio-pipe transport (no listening
# sockets); this run exercises the real TCP accept/auth path. Skip-set
# is env-dependent here (chroot-acl), so leave RSYNC_EXPECT_SKIPPED unset.
run: sudo ./runtests.py --rsync-bin="$PWD/rsync" --use-tcp -j 8
- name: check (non-root, targeted)
# Every run above is root (sudo), so privilege-sensitive tests never hit
# their non-root path. Run those here as the unprivileged 'runner' user
# (NO sudo). Explicit test names make runtests.py full_run False, so
# RSYNC_EXPECT_SKIPPED is bypassed -- no per-platform skip list needed.
# daemon-namecvt-empty-response -- REQUIRES non-root (skips as root by
# design); the only test with no other CI coverage (Benjamin #2).
# ownership-depth -- non-root takes the group-only remap path.
# daemon -- non-root takes the default-config path.
# CONVENTION: a new test that requires/meaningfully exercises a non-root
# path must be added to the list below (kept in sync with the fleet
# harness's nonroot_tests).
run: |
sudo rm -rf testtmp # prior root steps left it root-owned
./runtests.py --rsync-bin="$PWD/rsync" \
daemon-namecvt-empty-response ownership-depth daemon
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check29
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: ubuntu-bin
path: |
rsync
-80
View File
@@ -1,80 +0,0 @@
name: Test rsync version mixing on Ubuntu
# Runs the CURRENT test suite with two different rsync binaries: the freshly
# built ./rsync as the client/driver, and a committed OLD static binary
# (old_versions/rsync_<ver>) as the daemon / remote-shell peer. This exercises
# real version mixing over the wire -- more convincing than --protocol forcing,
# which only makes the current binary speak an old protocol.
#
# Direction is fixed: the current binary always drives (only it understands the
# new test scripts); the old binary is only ever the server/daemon side. The
# reverse (old client driving new scripts) is not possible -- but one test,
# reverse-daemon-delta, swaps the roles internally (current build as the daemon,
# old binary as the client) to cover the backward-compat direction: a current
# daemon serving the installed base of old clients.
#
# The per-version manifest testsuite/expect/rsync_<ver>.expect lists exactly
# which tests run and each one's expected outcome (pass/skip/fail/xfail), so an
# old peer's known feature gaps are recorded rather than treated as breakage.
#
# All peers run in a SINGLE job (looped, not a matrix) so the PR shows one check
# line rather than one per version. Each peer/transport is a foldable ::group::
# in the log, and a failure annotates which peer/transport broke.
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-version-mix.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/ubuntu-version-mix.yml'
schedule:
- cron: '52 8 * * 1'
jobs:
version-mix:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
name: rsync version-mix
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get install acl libacl1-dev attr libattr1-dev liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl
echo "/usr/local/bin" >>"$GITHUB_PATH"
- name: configure
run: ./configure --with-rrsync
- name: make
# check-progs builds rsync AND the test helper programs (tls, trimslash,
# t_unsafe, ...) that runtests.py requires; plain `make` does not.
run: make check-progs
- name: info
run: ./rsync --version | head -1
- name: version mixing (all peers, pipe + TCP transports)
run: |
rc=0
for peer in old_versions/rsync_*; do
chmod +x "$peer"
name=$(basename "$peer")
expect="testsuite/expect/$name.expect"
for transport in pipe tcp; do
tcp=()
[ "$transport" = tcp ] && tcp=(--use-tcp)
echo "::group::$name ($transport): $("$peer" --version | head -1)"
if ! ./runtests.py --rsync-bin="$PWD/rsync" --rsync-bin2="$PWD/$peer" \
--expect-result "$expect" "${tcp[@]}" -j 8; then
echo "::error::version-mix failed: $name ($transport)"
rc=1
fi
echo "::endgroup::"
done
done
exit $rc
-99
View File
@@ -1,99 +0,0 @@
name: Valgrind memcheck
on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/valgrind.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/valgrind.yml'
schedule:
- cron: '17 4 * * *'
workflow_dispatch:
jobs:
memcheck:
# temporary gate: PR CI runs only for PRs labeled 'run-ci', to save CI
# minutes; labels need triage access, so fork PRs can't self-enable.
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-ci')
runs-on: ubuntu-latest
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
privilege: [ user, root ]
transport: [ pipe, tcp ]
name: memcheck (${{ matrix.privilege }}, ${{ matrix.transport }})
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
sudo apt-get update
sudo apt-get install -y valgrind acl libacl1-dev attr libattr1-dev \
liblz4-dev libzstd-dev libxxhash-dev python3-cmarkgfm openssl
echo "/usr/local/bin" >>"$GITHUB_PATH"
- name: configure
run: ./configure --with-rrsync --enable-debug
- name: make
run: make check-progs # builds rsync + the test helper programs runtests.py needs
- name: info
run: ./rsync --version
# Run the whole suite under valgrind. We gate on memory *errors* (uninit
# reads, invalid read/write, bad frees, uninit syscall params), not leaks:
# rsync deliberately leaves file-list/socket/option memory unfreed at exit
# (short-lived process; the OS reclaims), so --leak-check=no avoids a sea of
# by-design "definitely lost" reports. Functional pass/fail is covered by
# the other workflows, so the suite is allowed to finish regardless of
# per-test results; the scan step below is the gate. --error-exitcode=0
# keeps valgrind from perturbing test exit codes; the bundled
# testsuite/valgrind.supp silences known-benign reports.
- name: run testsuite under valgrind
run: |
SUDO=
[ "${{ matrix.privilege }}" = root ] && SUDO="sudo -E"
TCP=
[ "${{ matrix.transport }}" = tcp ] && TCP="--use-tcp"
$SUDO ./runtests.py --valgrind \
--valgrind-opts="--leak-check=no --error-exitcode=0" \
$TCP -j8 --preserve-scratch || true
- name: scan for unsuppressed valgrind errors
run: |
sudo chown -R "$USER" testtmp 2>/dev/null || true
mapfile -t logs < <(find testtmp -name 'valgrind.*.log' 2>/dev/null)
if [ "${#logs[@]}" -eq 0 ]; then
echo "::error::no valgrind logs were produced -- the suite did not run"
exit 1
fi
echo "scanned ${#logs[@]} valgrind log(s)"
bad=()
for f in "${logs[@]}"; do
grep -qE 'ERROR SUMMARY: [1-9][0-9]* errors' "$f" && bad+=("$f")
done
if [ "${#bad[@]}" -ne 0 ]; then
echo "::error::valgrind reported unsuppressed errors in ${#bad[@]} run(s)"
for f in "${bad[@]}"; do
echo "===== $f ====="
sed 's/==[0-9]*== //' "$f" | grep -A18 \
-E 'depends on uninitialised|points to uninitialised|Invalid (read|write|free)|lost in loss record|Mismatched free' \
| head -60
done
exit 1
fi
echo "valgrind clean: no unsuppressed errors"
- name: upload valgrind logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: valgrind-logs-${{ matrix.privilege }}-${{ matrix.transport }}
path: testtmp/**/valgrind.*.log
if-no-files-found: ignore
retention-days: 7
-13
View File
@@ -43,19 +43,8 @@ aclocal.m4
/testrun
/trimslash
/t_unsafe
/t_acl
/t_chmod_secure
/t_rename_secure
/t_secure_relpath
/t_symlink_secure
/t_hashtable_overflow
/t_iwildmatch
/t_clean_fname
/t_safe_arg
/simdtest
/wildtest
/getfsdev
/t_safe_arg_main
/rounding.h
/doc/rsync.pdf
/doc/rsync.ps
@@ -63,8 +52,6 @@ aclocal.m4
/testsuite/chown-fake.test
/testsuite/devices-fake.test
/testsuite/xattrs-hlink.test
/testsuite/fleettest.json
/fleettest-logs
/patches
/patches.gen
/build
-28
View File
@@ -7,34 +7,6 @@ option to use if you want to just skip that feature. What follows are various
support libraries that you may want to install to build rsync with the maximum
features (the impatient can skip down to the package summary):
## Ubuntu users: skip the build, use the PPA
If you are on a currently supported Ubuntu series (jammy 22.04 LTS, noble
24.04 LTS, questing 25.10, resolute 26.04 LTS) and just want the latest
upstream rsync, the rsync project maintains a Launchpad PPA that tracks
stable releases:
> sudo add-apt-repository ppa:rsyncproject/rsync
> sudo apt update && sudo apt install rsync
See [the PPA page][ppa] for current build status across architectures.
[ppa]: https://launchpad.net/~rsyncproject/+archive/ubuntu/rsync
To test the upcoming release instead, there is also a [`rsync-latest`
PPA][ppa-latest] that is rebuilt from the tip of the git master branch. These
are development snapshots whose version numbers (such as
`3.5.0~git20260601...`) deliberately sort below the matching stable release, so
the stable PPA above will never silently move you from a release onto a
snapshot. Use it for testing only -- it may contain unreleased changes:
> sudo add-apt-repository ppa:rsyncproject/rsync-latest
> sudo apt update && sudo apt install rsync
[ppa-latest]: https://launchpad.net/~rsyncproject/+archive/ubuntu/rsync-latest
The rest of this document covers building from source.
## The basic setup
You need to have a C compiler installed and optionally a C++ compiler in order
+31 -251
View File
@@ -18,9 +18,6 @@ CXXFLAGS=@CXXFLAGS@
EXEEXT=@EXEEXT@
LDFLAGS=@LDFLAGS@
LIBOBJDIR=lib/
AR=@AR@
ARFLAGS=cr
RANLIB=@RANLIB@
INSTALLCMD=@INSTALL@
INSTALLMAN=@INSTALL@
@@ -41,37 +38,32 @@ GENFILES=configure.sh aclocal.m4 config.h.in rsync.1 rsync.1.html \
rsync-ssl.1 rsync-ssl.1.html rsyncd.conf.5 rsyncd.conf.5.html \
@GEN_RRSYNC@
HEADERS=byteorder.h config.h errcode.h proto.h rsync.h ifuncs.h itypes.h inums.h \
lib/pool_alloc.h lib/mdigest.h lib/md-defines.h vfs/vfs.h
lib/pool_alloc.h lib/mdigest.h lib/md-defines.h
LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o \
lib/permstring.o lib/pool_alloc.o lib/sysacls.o lib/sysxattrs.o lib/acl.o @LIBOBJS@
lib/permstring.o lib/pool_alloc.o lib/sysacls.o lib/sysxattrs.o @LIBOBJS@
zlib_OBJS=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
OBJS1_NO_MAIN=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
util1.o util2.o checksum.o match.o log.o backup.o delete.o
OBJS1=$(OBJS1_NO_MAIN) main.o
OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
util1.o util2.o main.o checksum.o match.o syscall.o android.o log.o backup.o delete.o
OBJS2=options.o io.o compat.o hlink.o token.o uidlist.o socket.o hashtable.o \
usage.o fileio.o batch.o clientname.o chmod.o acls.o xattrs.o
OBJS3=progress.o pipe.o @MD5_ASM@ @ROLL_SIMD@ @ROLL_ASM@
DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o
popt_OBJS= popt/popt.o popt/poptconfig.o \
popt/popthelp.o popt/poptparse.o popt/poptint.o
VFS_OBJ=vfs/vfs.o vfs/dirstack.o vfs/secure_open.o vfs/owner_walk.o vfs/dircache.o vfs/stat.o vfs/rename.o vfs/unlink.o vfs/open.o vfs/chmod.o vfs/symlink.o vfs/link.o vfs/mkdir.o vfs/chown.o vfs/mknod.o vfs/times.o vfs/fileio.o vfs/make_path.o vfs/copy_file.o vfs/robust.o
OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@ libvfs.a
OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@
TLS_OBJ = tls.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@ libvfs.a
TLS_OBJ = tls.o syscall.o android.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
# Programs we must have to run the test cases
CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
testrun$(EXEEXT) trimslash$(EXEEXT) t_unsafe$(EXEEXT) t_chmod_secure$(EXEEXT) \
t_rename_secure$(EXEEXT) t_symlink_secure$(EXEEXT) t_secure_relpath$(EXEEXT) t_acl$(EXEEXT) t_hashtable_overflow$(EXEEXT) t_iwildmatch$(EXEEXT) t_clean_fname$(EXEEXT) t_safe_arg$(EXEEXT) wildtest$(EXEEXT) simdtest$(EXEEXT)
t_secure_relpath$(EXEEXT) wildtest$(EXEEXT) simdtest$(EXEEXT)
CHECK_SYMLINKS = testsuite/chown-fake_test.py testsuite/devices-fake_test.py \
testsuite/xattrs-hlink_test.py testsuite/exclude-lsh_test.py
CHECK_SYMLINKS = testsuite/chown-fake.test testsuite/devices-fake.test testsuite/xattrs-hlink.test
# Objects for CHECK_PROGS to clean
CHECK_OBJS=tls.o testrun.o getgroups.o getfsdev.o t_stub.o t_unsafe.o t_chmod_secure.o t_rename_secure.o t_symlink_secure.o t_secure_relpath.o t_acl.o t_hashtable_overflow.o t_iwildmatch.o t_clean_fname.o t_safe_arg.o trimslash.o wildtest.o
# Compile-only feature-shape checks.
CHECK_COMPILE_OBJS=vfs-no-at-fdcwd.o
CHECK_OBJS=tls.o testrun.o getgroups.o getfsdev.o t_stub.o t_unsafe.o t_chmod_secure.o t_secure_relpath.o trimslash.o wildtest.o
# note that the -I. is needed to handle config.h when using VPATH
.c.o:
@@ -84,21 +76,6 @@ CHECK_COMPILE_OBJS=vfs-no-at-fdcwd.o
all: Makefile rsync$(EXEEXT) stunnel-rsyncd.conf @MAKE_RRSYNC@ @MAKE_MAN@
.PHONY: all
# Compile-check the pre-*at() portability tier. syscall.c's *at wrappers were
# split into vfs/, so compile every vfs source with the AT_FDCWD primitives
# undefined (via vfs/vfs_internal.h's RSYNC_TEST_NO_AT_FDCWD block) and confirm
# the fallback arms still build. A shell loop keeps this portable (BSD/Solaris
# make have no pattern rules); the last object compiled is left as the target.
# $(VFS_OBJ:.o=.c) is POSIX suffix substitution, portable across makes.
vfs-no-at-fdcwd.o: $(VFS_OBJ:.o=.c) $(HEADERS) vfs/vfs.h vfs/vfs_internal.h
@rm -f $@ $@.tmp
@for f in $(VFS_OBJ:.o=.c); do \
echo " no-AT_FDCWD compile-check: $$f"; \
$(CC) -I. -I$(srcdir) $(CFLAGS) $(CPPFLAGS) \
-DRSYNC_TEST_NO_AT_FDCWD -c $(srcdir)/$$f -o $@.tmp || exit 1; \
done
@mv $@.tmp $@
.PHONY: install
install: all
-$(MKDIR_P) $(DESTDIR)$(bindir)
@@ -133,21 +110,6 @@ install-all: install install-ssl-daemon
install-strip:
$(MAKE) INSTALL_STRIP='-s' install
.PHONY: uninstall
uninstall:
rm -f $(DESTDIR)$(bindir)/rsync$(EXEEXT) $(DESTDIR)$(bindir)/rsync-ssl
rm -f $(DESTDIR)$(bindir)/rrsync
rm -f $(DESTDIR)$(mandir)/man1/rsync.1 $(DESTDIR)$(mandir)/man1/rsync-ssl.1
rm -f $(DESTDIR)$(mandir)/man1/rrsync.1
rm -f $(DESTDIR)$(mandir)/man5/rsyncd.conf.5
.PHONY: uninstall-ssl-daemon
uninstall-ssl-daemon:
rm -f $(DESTDIR)/etc/stunnel/rsyncd.conf
.PHONY: uninstall-all
uninstall-all: uninstall uninstall-ssl-daemon
rsync$(EXEEXT): $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
@@ -156,22 +118,11 @@ rrsync: support/rrsync
$(OBJS): $(HEADERS)
$(CHECK_OBJS): $(HEADERS)
$(VFS_OBJ): $(HEADERS)
$(VFS_OBJ): vfs/vfs_internal.h
tls.o xattrs.o: lib/sysxattrs.h
# The VFS layer is bundled into a static archive linked last on every target so
# that moving a filesystem family between files never breaks a test harness link
# (the linker pulls only the members each program references).
libvfs.a: $(VFS_OBJ)
rm -f $@
$(AR) $(ARFLAGS) $@ $(VFS_OBJ)
$(RANLIB) $@
usage.o: version.h latest-year.h help-rsync.h help-rsyncd.h git-version.h default-cvsignore.h
loadparm.o: default-dont-compress.h daemon-parm.h
flist.o: rounding.h
log.o: rounding.h
default-cvsignore.h default-dont-compress.h: rsync.1.md define-from-md.awk
$(AWK) -f $(srcdir)/define-from-md.awk -v hfile=$@ $(srcdir)/rsync.1.md
@@ -227,63 +178,22 @@ getgroups$(EXEEXT): getgroups.o
getfsdev$(EXEEXT): getfsdev.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
TRIMSLASH_OBJ = trimslash.o util2.o t_stub.o lib/compat.o lib/snprintf.o libvfs.a
TRIMSLASH_OBJ = trimslash.o syscall.o android.o util2.o t_stub.o lib/compat.o lib/snprintf.o
trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
T_UNSAFE_OBJ = t_unsafe.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o libvfs.a
T_UNSAFE_OBJ = t_unsafe.o syscall.o android.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS)
T_HASHTABLE_OVERFLOW_OBJ = t_hashtable_overflow.o hashtable.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o libvfs.a
t_hashtable_overflow$(EXEEXT): $(T_HASHTABLE_OVERFLOW_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_HASHTABLE_OVERFLOW_OBJ) $(LIBS)
T_IWILDMATCH_OBJ = t_iwildmatch.o lib/wildmatch.o
t_iwildmatch$(EXEEXT): $(T_IWILDMATCH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_IWILDMATCH_OBJ) $(LIBS)
T_CLEAN_FNAME_OBJ = t_clean_fname.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o libvfs.a
t_clean_fname$(EXEEXT): $(T_CLEAN_FNAME_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_CLEAN_FNAME_OBJ) $(LIBS)
# safe_arg lives in options.c alongside the whole option parser. Rather than
# rely on a non-portable linker --gc-sections to drop the parser (GNU ld only;
# macOS ld64 and the cygwin PE linker do not), link the real rsync objects so
# every dep resolves. t_safe_arg_main.o is main.c with main() renamed out, to
# supply main.c's globals while letting t_safe_arg.o provide the test's main().
# OBJS minus main.o is spelled out via OBJS1_NO_MAIN because $(filter-out) is
# GNU-make-only; BSD and Solaris make expand it to nothing.
t_safe_arg_main.o: main.c $(HEADERS)
$(CC) -I. -I$(srcdir) $(CFLAGS) $(CPPFLAGS) -Dmain=rsync_unused_main -c $(srcdir)/main.c -o t_safe_arg_main.o
T_SAFE_ARG_OBJ = t_safe_arg.o t_safe_arg_main.o $(OBJS1_NO_MAIN) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@ libvfs.a
t_safe_arg$(EXEEXT): $(T_SAFE_ARG_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_SAFE_ARG_OBJ) $(LIBS)
T_CHMOD_SECURE_OBJ = t_chmod_secure.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o libvfs.a
T_CHMOD_SECURE_OBJ = t_chmod_secure.o syscall.o android.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
t_chmod_secure$(EXEEXT): $(T_CHMOD_SECURE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_CHMOD_SECURE_OBJ) $(LIBS)
T_RENAME_SECURE_OBJ = t_rename_secure.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o libvfs.a
t_rename_secure$(EXEEXT): $(T_RENAME_SECURE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_RENAME_SECURE_OBJ) $(LIBS)
T_SYMLINK_SECURE_OBJ = t_symlink_secure.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o libvfs.a
t_symlink_secure$(EXEEXT): $(T_SYMLINK_SECURE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_SYMLINK_SECURE_OBJ) $(LIBS)
T_SECURE_RELPATH_OBJ = t_secure_relpath.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o libvfs.a
T_SECURE_RELPATH_OBJ = t_secure_relpath.o syscall.o android.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
t_secure_relpath$(EXEEXT): $(T_SECURE_RELPATH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_SECURE_RELPATH_OBJ) $(LIBS)
# Unit test for lib/acl.c: compares our fd/at ACL ops against the system libacl
# (linked via $(LIBS), which carries -lacl). lib/acl.o references no rsync
# globals, so this links with no stubs. Self-skips (exit 77) when built
# without SUPPORT_ACL_FD.
T_ACL_OBJ = t_acl.o lib/acl.o
t_acl$(EXEEXT): $(T_ACL_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_ACL_OBJ) $(LIBS)
.PHONY: conf
conf: configure.sh config.h.in
@@ -373,11 +283,9 @@ rrsync.1: support/rrsync.1.md md-convert Makefile
.PHONY: clean
clean: cleantests
rm -f *~ $(OBJS) $(VFS_OBJ) libvfs.a $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_COMPILE_OBJS) $(CHECK_COMPILE_OBJS:.o=.o.tmp) $(CHECK_SYMLINKS) @MAKE_RRSYNC@ \
rm -f *~ $(OBJS) $(CHECK_PROGS) $(CHECK_OBJS) $(CHECK_SYMLINKS) @MAKE_RRSYNC@ \
git-version.h rounding rounding.h *.old rsync*.1 rsync*.5 @MAKE_RRSYNC_1@ \
*.html daemon-parm.h help-*.h default-*.h proto.h proto.h-tstamp
rm -f *.gcno *.gcda lib/*.gcno lib/*.gcda zlib/*.gcno zlib/*.gcda popt/*.gcno popt/*.gcda vfs/*.gcno vfs/*.gcda
rm -rf coverage coverage-tcp coverage-all coverage-fallback
.PHONY: cleantests
cleantests:
@@ -418,148 +326,23 @@ test: check
# catch Bash-isms earlier even if we're running on GNU. Of course, we
# might lose in the future where POSIX diverges from old sh.
# `make check` runs tests in parallel by default. Override with
# `make check CHECK_J=1` (serial) or any other value.
CHECK_J = 8
# Parallelism for `make coverage`. Defaults to the same as CHECK_J: the
# coverage build sets -fprofile-update=atomic (atomic in-memory counters) and
# gcc's libgcov serializes the per-source .gcda read-modify-write merge with a
# file lock, so concurrent rsync processes (incl. the forked sender/generator/
# receiver) accumulate exactly -- verified by a count-linearity check (a hot
# line accumulates identically at -j1 and -P16). Override with
# `make coverage COVERAGE_J=1` if your libgcov does not lock .gcda merges.
COVERAGE_J = $(CHECK_J)
# Output directory and extra runtests.py flags for `make coverage`. The
# `coverage-tcp` target reuses the coverage recipe with --use-tcp (real
# loopback rsyncd, which exercises the TCP accept/auth path and the
# require_tcp-only tests) and a separate output directory.
COVERAGE_DIR = coverage
COVERAGE_RUNFLAGS =
# Excluded from the coverage report so the percentages reflect rsync's own
# runtime source. Three buckets:
# (1) Bundled third-party code rsync ships but does not own: zlib/, popt/, and
# the named lib/ imports (PostgreSQL getaddrinfo, ISC inet_ntop/inet_pton,
# standalone getpass). The other lib/*.c are rsync's own and stay in.
# (2) Test-helper / build-time programs that link against rsync objects but are
# not the rsync runtime: t_*.c, tls.c, wildtest.c, testrun.c, getgroups.c,
# getfsdev.c, trimslash.c, rounding.c. These have their own main() and are
# either driven directly by a test (counted there) or are configure-time
# probes; counting them as "rsync uncovered" is noise.
# (3) Compile-time-dead fallbacks under this build's config.h: lib/md5.c (the
# reference md5 -- openssl's EVP path is used when HAVE_OPENSSL) and
# lib/snprintf.c (only the #include line survives under
# HAVE_C99_VSNPRINTF). Covering these would mean a separate non-openssl /
# non-C99 build, which is out of scope for this report.
COVERAGE_EXCLUDE = -e '(^|/)zlib/' -e '(^|/)popt/' \
-e '(^|/)lib/(getaddrinfo|getpass|inet_ntop|inet_pton)\.' \
-e '(^|/)(t_[a-z_]+|tls|wildtest|testrun|getgroups|getfsdev|trimslash|rounding)\.c$$' \
-e '(^|/)lib/(md5|snprintf)\.c$$'
# Build everything the test suite needs (rsync + helper programs + symlinks)
# WITHOUT running it. Used by CI jobs that invoke runtests.py directly with
# custom options (e.g. the version-mix workflow's --rsync-bin2/--expect-result).
# Build the test-helper programs (CHECK_PROGS) without running the suite, so
# an external harness (e.g. fleettest.py) can invoke runtests.py with its own
# options.
.PHONY: check-progs
check-progs: all $(CHECK_PROGS) $(CHECK_COMPILE_OBJS) $(CHECK_SYMLINKS)
check-progs: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
.PHONY: check
check: all $(CHECK_PROGS) $(CHECK_COMPILE_OBJS) $(CHECK_SYMLINKS)
"$(srcdir)/runtests.py" --rsync-bin="`pwd`/rsync$(EXEEXT)" -j $(CHECK_J)
check: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT)
.PHONY: check29
check29: all $(CHECK_PROGS) $(CHECK_COMPILE_OBJS) $(CHECK_SYMLINKS)
"$(srcdir)/runtests.py" --rsync-bin="`pwd`/rsync$(EXEEXT)" -j $(CHECK_J) --protocol=29
check29: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) --protocol=29
.PHONY: check30
check30: all $(CHECK_PROGS) $(CHECK_COMPILE_OBJS) $(CHECK_SYMLINKS)
"$(srcdir)/runtests.py" --rsync-bin="`pwd`/rsync$(EXEEXT)" -j $(CHECK_J) --protocol=30
# Whole-suite gcov coverage report (HTML, with branch + decision coverage).
# Requires a build configured with --enable-coverage and the `gcovr` tool
# (pip install gcovr). Runs the suite in parallel (COVERAGE_J, default CHECK_J):
# this is safe because the coverage build uses -fprofile-update=atomic and
# libgcov locks the per-source .gcda during its merge, so concurrent rsync
# processes accumulate exactly (see COVERAGE_J above). Use COVERAGE_J=1 if your
# toolchain's libgcov does not lock .gcda merges.
.PHONY: coverage
coverage: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
@case '$(CFLAGS)' in *--coverage*) ;; \
*) echo "*** not a coverage build; reconfigure with --enable-coverage"; exit 1 ;; esac
@command -v gcovr >/dev/null 2>&1 || { echo "*** gcovr not found (pip install gcovr)"; exit 1; }
find . -name '*.gcda' -delete
@# Daemon modules with `uid = <non-root>` setuid the per-connection child
@# (and so the forked generator/receiver), which then cannot create or
@# merge .gcda files in a root-owned build dir -- silently dropping ALL
@# coverage from those processes. Make every .gcno's directory
@# world-writable so any uid can create the sibling .gcda, and set a
@# default ACL of o::rw so the .gcda are world-mergeable regardless of
@# the creator's umask (every test process resets umask to 022 via
@# rsyncfns.py, so a Makefile-level `umask 0` would not survive).
@find . -name '*.gcno' -printf '%h\n' 2>/dev/null | sort -u | \
while read d; do \
chmod a+rwx "$$d"; \
setfacl -m 'd:u::rwx,d:g::rwx,d:o::rwx' "$$d" 2>/dev/null || true; \
done
@rc=0; "$(srcdir)/runtests.py" --rsync-bin="`pwd`/rsync$(EXEEXT)" -j $(COVERAGE_J) $(COVERAGE_RUNFLAGS) || rc=$$?; \
rm -rf $(COVERAGE_DIR) && mkdir -p $(COVERAGE_DIR); \
gcovr --root $(srcdir) $(COVERAGE_EXCLUDE) --decisions --print-summary \
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file \
--html-details -o $(COVERAGE_DIR)/index.html . || exit $$?; \
echo "Coverage report written to $(COVERAGE_DIR)/index.html"; \
if test $$rc != 0; then \
echo "*** test suite FAILED (status $$rc) -- coverage report still written above"; \
fi; \
exit $$rc
# Same as `make coverage` but with the daemon tests run over a real loopback
# rsyncd (--use-tcp), into a separate report directory.
.PHONY: coverage-tcp
coverage-tcp:
$(MAKE) coverage COVERAGE_RUNFLAGS=--use-tcp COVERAGE_DIR=coverage-tcp
# Comprehensive single report: run the suite under several configurations,
# accumulating into the shared .gcda counters (NOT cleared between runs), then
# emit one merged, rsync-scoped report. Covers the default (pipe) transport, the
# protocol-29/30 compat branches, and the real-TCP daemon path (which also runs
# the require_tcp-only tests). Run under sudo to additionally cover root-only
# paths (devices, chown, use-chroot, protected-regular). Local target -- CI uses
# the plain `coverage`/`coverage-tcp` targets.
.PHONY: coverage-all
coverage-all: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
@case '$(CFLAGS)' in *--coverage*) ;; \
*) echo "*** not a coverage build; reconfigure with --enable-coverage"; exit 1 ;; esac
@command -v gcovr >/dev/null 2>&1 || { echo "*** gcovr not found (pip install gcovr)"; exit 1; }
find . -name '*.gcda' -delete
@# See the `coverage` target above for why: setuid'd daemon children must
@# be able to create/merge .gcda owned by a different uid.
@find . -name '*.gcno' -printf '%h\n' 2>/dev/null | sort -u | \
while read d; do \
chmod a+rwx "$$d"; \
setfacl -m 'd:u::rwx,d:g::rwx,d:o::rwx' "$$d" 2>/dev/null || true; \
done
@rc=0; \
for cfg in '' '--protocol=30' '--protocol=29' '--use-tcp'; do \
echo "===== coverage-all: runtests.py $$cfg ====="; \
"$(srcdir)/runtests.py" --rsync-bin="`pwd`/rsync$(EXEEXT)" -j $(COVERAGE_J) $$cfg || rc=$$?; \
done; \
rm -rf coverage-all && mkdir -p coverage-all; \
gcovr --root $(srcdir) $(COVERAGE_EXCLUDE) --decisions --print-summary \
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file \
--html-details -o coverage-all/index.html . || exit $$?; \
echo "Merged coverage report written to coverage-all/index.html"; \
if test $$rc != 0; then \
echo "*** some suite runs FAILED (status $$rc) -- report still written above"; \
fi; \
exit $$rc
# Coverage for the portable (non-openat2) resolver tier. Requires a SEPARATE
# build configured with --enable-coverage --disable-openat2: its .gcno differ
# from the openat2 build, so this report cannot be merged with the others.
.PHONY: coverage-fallback
coverage-fallback:
$(MAKE) coverage COVERAGE_DIR=coverage-fallback
check30: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
$(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) --protocol=30
wildtest.o: wildtest.c t_stub.o lib/wildmatch.c rsync.h config.h
wildtest$(EXEEXT): wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@
@@ -573,17 +356,14 @@ simdtest$(EXEEXT): simd-checksum-x86_64.cpp $(HEADERS)
touch $@; \
fi
testsuite/chown-fake_test.py:
ln -s chown_test.py $(srcdir)/testsuite/chown-fake_test.py
testsuite/chown-fake.test:
ln -s chown.test $(srcdir)/testsuite/chown-fake.test
testsuite/devices-fake_test.py:
ln -s devices_test.py $(srcdir)/testsuite/devices-fake_test.py
testsuite/devices-fake.test:
ln -s devices.test $(srcdir)/testsuite/devices-fake.test
testsuite/xattrs-hlink_test.py:
ln -s xattrs_test.py $(srcdir)/testsuite/xattrs-hlink_test.py
testsuite/exclude-lsh_test.py:
ln -s exclude_test.py $(srcdir)/testsuite/exclude-lsh_test.py
testsuite/xattrs-hlink.test:
ln -s xattrs.test $(srcdir)/testsuite/xattrs-hlink.test
# This does *not* depend on building or installing: you can use it to
# check a version installed from a binary or some other source tree,
@@ -591,7 +371,7 @@ testsuite/exclude-lsh_test.py:
.PHONY: installcheck
installcheck: $(CHECK_PROGS) $(CHECK_SYMLINKS)
"$(srcdir)/runtests.py" --rsync-bin="$(bindir)/rsync$(EXEEXT)" --srcdir="$(srcdir)" --tooldir="`pwd`" -j $(CHECK_J)
$(srcdir)/runtests.py --rsync-bin="$(bindir)/rsync$(EXEEXT)" --srcdir="$(srcdir)" --tooldir=`pwd`
# TODO: Add 'dist' target; need to know which files will be included
-460
View File
@@ -1,454 +1,3 @@
# NEWS for rsync 3.5.0 (13 Aug 2026)
## Changes in this version:
### Thanks!
This has been an extraordinary release developed over several months
and I'd like to thank everyone who has helped make it possible. The
volume of security issues we had to deal with would have been quite
overwhelming without the help that I've received.
I'm particularly grateful to Zen Dodd (Tao), Omar Elsayed (seks99x),
Will Sargeant, Paul Mackerras, Aleksa Sarai and Leonid Bugaev (buger)
who joined the rsync admins group helping to triage all the issues,
develop new tests, review PRs and helped develop the guidelines we used
for where to draw the line between a security issue and expected
behaviour (a surprisingly difficult thing to do in some cases). You've
all been a huge help and rsync is much better off for your assistance.
A big thank you also to Filipe Casal from Trail of Bits who worked with
us on the "Patch the Planet" program. Filipe provided a huge trove of
valuable tests and security reports.
Also a big thank you to Greg Kroah-Hartman for invaluable advice and
security reports and to Stuart Inglis for particularly high quality
bug reports and testing.
Many thanks to everyone who submitted bug reports, credits are listed
below against individual items.
Finally, thank you to everyone who joined in the discussion and
testing on the rsync-security mailing list, and to the rsync user
community for your patience in waiting for this release.
### SECURITY FIXES:
This release fixes 33 security issues found during a focused audit of rsync's
path handling and daemon protocol, a companion daemon-protocol fuzzing pass, and
reports from external researchers -- plus several robustness hardenings. CVE
IDs were assigned by VulnCheck (CNA); the precise "introduced in" version ranges
accompany each advisory, and many are much narrower than "everything before
3.5.0". Every fix ships with a regression test in the test suite that fails on
the unfixed tree. Many thanks to the external researchers credited below.
Link following (CWE-59/61) -- a local user who controls a path component plants
a symlink that a privileged rsync then follows:
- CVE-2026-53802 (HIGH): Arbitrary file read / transfer-shaping via symlinked
operator-supplied input files. rsync followed attacker-planted symlinks in
`--filter` merge files (including per-directory merges and `-C` `.cvsignore`),
`--files-from` / `--include-from` / `--exclude-from`, and the client
`--password-file` / daemon secrets file -- reading an arbitrary file as filter
rules, or sending a victim file's contents as the daemon authentication
response. Operator-supplied paths are now resolved component-by-component with
`openat(O_PATH|O_NOFOLLOW)`, allowing a symlink component only when it is owned
by uid 0 or the effective uid.
- CVE-2026-53803 (HIGH): Arbitrary file write / privilege escalation via
symlinked operator-supplied output paths -- `--log-file`,
`--write-batch`/`--read-batch`, and the daemon's motd / lock / early-input /
`--config` opens. A planted symlink (or parent component) could redirect the
write, e.g. append the log to `authorized_keys`; `--read-batch` could also feed
chosen bytes to the protocol parser. Same trusted-owner path walk, plus an
`S_ISREG` check on the `--read-batch` file.
- CVE-2026-53785 (HIGH): Under `--relative`, the receiver's implied-parent
creation (`make_path()`) built the parent chain with a plain `mkdir()` on the
full path, so a planted parent symlink placed the new directories and file
outside the destination tree. `make_path()` now creates each component through
the held-directory-fd primitive. Reported by Omar Elsayed (seks99x).
- CVE-2026-53784 (HIGH): Daemon module-root chdir escape under `use chroot =
no`: a plain `chdir()` followed a planted parent-component symlink, serving
files from outside the module. The module-root chdir now goes through the
secure resolver.
- CVE-2026-53793 (HIGH): Chroot `/./` inner-module escape -- a symlinked
parent component inside the inner module reached a sibling outside it (the
generator basis stat, the receiver write/finish path, the module chdir, and the
receiver's delta-basis open). The secure resolver is now engaged for all of
those paths.
- CVE-2026-53795 (HIGH): An absolute `--temp-dir` or `--link-dest` disabled
the receiver's rename/link confinement. `do_rename_at()`/`do_link_at()` bailed
to the unconfined path-based call whenever *either* path was absolute, so an
absolute source (the temp file, or the link-dest basis) let `finish_transfer()`'s
tmp->final rename -- or a hard-link create -- follow a destination parent
component an attacker flipped to a symlink mid-transfer, writing the file outside
the tree. Each side is now confined independently. Reported by Omar Elsayed
(seks99x).
- CVE-2026-53796 (MEDIUM): A non-daemon receiver's one-time `chdir()` into the
operator-named destination was not fully confined (a relative destination took a
plain `chdir()`), so an attacker who raced the named destination from a directory
to a symlink moved the receiver's CWD -- and every file it then created --
outside the tree. The destination chdir now uses the same ownership-checked
`O_NOFOLLOW` walk as the daemon module chdir (see BEHAVIOR CHANGES). Reported by
Omar Elsayed (seks99x).
- CVE-2026-53797 (MEDIUM): A non-daemon sender opened each transferred file's
content by path (leaf `O_NOFOLLOW` only), so a source parent component an
unprivileged user raced to a symlink after the file-list scan was followed --
reading a file from outside the source tree into an attacker-readable
destination. The content open is now anchored at the transfer root with
`secure_relative_open()`; `-L` / `--copy-unsafe-links` / `-k` still follow, and
`--insecure-links` restores the legacy open.
- CVE-2026-53799 (MEDIUM): Receiver ACL/xattr metadata application followed a
symlink race -> arbitrary ACL set (local privilege escalation). When preserving
metadata (`-A`/`--acls`, `-X`/`--xattrs`, or fake-super ACL-as-xattr), the
receiver applied each entry's ACL/xattrs by path via `acl_set_file()` /
`setxattr()`. A local user who raced a just-received entry (or a parent) into a
symlink before the apply could redirect an attacker-chosen ACL -- the bytes are
carried in the source entry -- onto a victim inode outside the destination tree,
granting rwx on a root-owned file. The apply now pins each entry's inode with an
`O_RDONLY|O_NOFOLLOW` fd and sets all metadata on the held inode (Linux 6.13+
`*xattrat` syscalls, or a patched libacl's `*_at` bindings, else the
`/proc/self/fd` compat path). Where neither primitive exists (the BSDs, Solaris,
macOS, or a `/proc`-less Linux container) it falls back to the path-based apply to
keep `--acls` functional -- a documented residual, refusable via `refuse options =
acls`.
- CVE-2026-53800 (MEDIUM): Sender `--remove-source-files` unlink followed a
parent-component symlink race -> arbitrary file deletion outside the source tree.
The post-send unlink and its same-file safety re-stat resolved by path relative to
the process CWD, so an unprivileged user who raced a source parent into a symlink
after the file was sent could make a higher-authority sender (a root
`--remove-source-files` run, or a daemon module not refusing the option) delete a
file outside the served tree. The removal is now resolved through the secure
held-dirfd walk anchored at the served module root (daemon) or transfer-root CWD
(local sender), the safety re-stat is confined likewise, and the per-file dev/ino
is only computed when `--remove-source-files` is in effect.
- CVE-2026-53801 (MEDIUM): Sender/daemon directory-scan enumeration escaped the
transfer root / module -> out-of-tree disclosure. The sender enumerated each
source directory with a plain `opendir()` on the accumulated path, not through the
secure resolver (the enumeration sibling of the previous item, which confined only
the content open). A parent component raced to a symlink between the file-list
scan and the recursive `opendir()` -- or, in daemon following mode
(`-L`/`--copy-dirlinks`/`--copy-unsafe-links`), an in-module symlinked directory
pointing outside the module -- let a higher-authority sender enumerate an
out-of-tree directory and copy its entry names, metadata and symlink targets. The
directory scan is now confined through a held `opendir` fd anchored at the transfer
root / module.
`support/rrsync` (the restricted SSH wrapper):
- CVE-2026-53783 (HIGH): rrsync restricted-directory escape. It validated each
argument with `realpath()` and then exec'd rsync against the same name (a
TOCTOU window), and left dangerous options enabled in a restricted subdir.
rrsync now inode-pins the validated path and roots the argument it hands rsync
at that pinned fd, denies `--copy-unsafe-links`, forces `--no-D`, and refuses a
symlinked `--log-file`. The pin relies on Linux's `/proc/self/fd` magic links
being bound to the open inode, so it is Linux-only; on the BSDs, macOS, Solaris
and Cygwin rrsync passes the `realpath()`-validated name as it always did.
Two limits are worth stating: under `--relative` only the anchor the
transmitted name starts from is pinned, so a component below it can still be
raced, and the final component of an ordinary sender argument is not pinned
either (rsync does not follow a symlink there, and the options that would
change that are refused in a restricted dir).
- A filter rule that failed to parse was echoed back verbatim, including when
the rule came from a merge file's contents. A per-directory merge rule names
a file the peer chooses and travels over the protocol rather than in an
argument, so this let a peer read back any line of any file the server process
could open that is not valid filter syntax -- through an `rrsync` restricted
account as well as a daemon module, since neither confined a merge open that
the wrapper never sees. A syntax error in a rule read from a file now reports
the file and line rather than the text; a rule given as an argument is still
shown. The `--debug=FILTER` traces print the same file-derived text, so
`rrsync` now refuses a peer-selected `--debug` (a stock client never sends
one). An operator who turns debugging on for their own server still sees the
rule text.
- Redacting those diagnostics did not close the merge route on its own, because
the worst shape produces no diagnostic at all: an exclude-only merge (the `-`
modifier) makes every line of the file a pattern, so nothing fails to parse
and the peer reads the contents off which of its own names went missing from
the file list. Through an `rrsync` restricted account that needs no
`--delete` and no verbosity on a pull. The open is now confined rather than
the disclosure suppressed: rsync gained `--confine-root=DIR`, which refuses an
operator- or peer-supplied path that resolves outside DIR, and `rrsync` passes
its restricted directory. A merge file inside that directory keeps working.
A daemon already had this through its module root and is unaffected.
Daemon protocol / identity:
- CVE-2026-53786 (MEDIUM): A client-supplied `--filter` merge file bypassed
the module filter list (it was checked against the module-prefixed path, which
never matched a module rule). The module-dir prefix is now stripped before the
check. Reported by Mitchell Benjamin (Revamp Studio).
- CVE-2026-53798 (MEDIUM): The daemon name converter mapped an unknown name to
uid/gid 0 (an empty response was read as `atol("") == 0`); with `fake super =
yes` the stored metadata became root-owned. An empty/non-numeric response is
now treated as a lookup failure. Reported by Mitchell Benjamin (Revamp
Studio).
- CVE-2026-53788 (MEDIUM): A peer-controlled name containing a newline/CR was
written verbatim into the name-converter line protocol, allowing request
injection. Converter tokens containing control characters are now rejected.
Reported by Mitchell Benjamin (Revamp Studio).
- CVE-2026-53789 (MEDIUM): A malicious daemon-sender could widen `--delete`
scope by omitting the "no content dir" flag on an implied parent, making the
receiver run `delete_in_dir()` on it. Implied-parent directories are now
forced non-content on the receiver. Reported by Mitchell Benjamin (Revamp
Studio).
- CVE-2026-53791 (CRITICAL): With `proxy protocol = true`, a client connecting
directly (not via the trusted proxy) could send a PROXY header to spoof its
source address and bypass host-based access control. A forwarded address is
now honoured only from a configured trusted-proxy peer.
Injection and memory safety:
- CVE-2026-53790 (HIGH): Command / argument injection via unquoted peer- or
host-controlled values -- the `RSYNC_CONNECT_PROG` `%H` host substitution, the
daemon exec-hook `%RSYNC_*%` expansions, rsync-ssl hostspecs, and a missing
newline/CR in remote-shell argument quoting. Each sink is now quoted or
validated (the hook escaping is confined to the shell-executed hooks, so
ordinary daemon string parameters such as `path` are unaffected).
- CVE-2026-53792 (MEDIUM): A malicious receiver sending a checksum header with a
block count > 0 but block length == 0 drove the sender's rolling-match
arithmetic negative. A zero block length is now rejected.
- CVE-2026-53794 (MEDIUM): `--max-alloc=0` disabled the per-allocation size
cap (the defense behind CVE-2024-12084) and could be forwarded on the wire to
an unpatched daemon. A zero max-alloc is now rejected at both the client and
the daemon. Reported by Azizcan Dastan (Milenium Security).
Peer-triggerable memory corruption in the daemon protocol, found by a
daemon-protocol fuzzing pass and reported by Greg Kroah-Hartman. Each is a
WRITE reachable from the wire, which is why these were split out from the
crash-only findings in the same pass:
- CVE-2026-70461 (HIGH): a one-byte heap out-of-bounds write in
`add_implied_include()`, driven by a peer-supplied filter rule whose trailing
backslash was not counted when sizing the copy.
- CVE-2026-70458 (HIGH): an out-of-bounds write from a file entry marked
`FLAG_HLINKED` that the receiver accepted even though `-H` was not in effect,
so the hard-link extra slots it then wrote were never allocated.
- CVE-2026-70456 (HIGH): an out-of-bounds heap write in `read_args()` when the
peer's argument count lands exactly on `maxargs` -- the trailing NULL went one
past the end of the array.
- CVE-2026-70457 (MEDIUM): an attacker-chosen-offset write in
`parse_size_arg()`'s error formatting, reachable through an over-large
`--max-size` / `--min-size` / `--max-alloc` forwarded to a daemon.
- CVE-2026-70459 (MEDIUM): a wild-pointer read crashing the per-connection
daemon child, from a crafted first incremental file list whose transfer root
is "." with a non-directory mode -- `parent_ndx` stayed 0 while `dir_flist`
was still empty, so the generator dereferenced a never-written slot.
Companion to CVE-2026-43620; reproduced on released 3.2.7, 3.4.0 and 3.4.1.
Daemon availability and access control:
- CVE-2026-70464 (HIGH): an unauthenticated peer could complete the `@RSYNCD`
greeting and then stall forever -- sending a line with no terminator, or
trickling NUL-terminated arguments into `read_args()` one byte at a time --
holding a per-connection child open past the module's `max connections`
limit. The `timeout` parameter did not cover it, because `set_io_timeout()`
ran after the `read_args()` calls that needed covering. A separate deadline
now spans both, and the early-protocol argument count is bounded. Reported
independently by Chamal De Silva and by Michal Ruprich (Red Hat QE).
- CVE-2026-70455 (HIGH): a daemon client could request an arbitrary Zstandard
worker count via `--compress-threads`; 256 was measured as 257 threads in a
single connection. Now capped at 8 on a daemon, while local and
remote-shell invocations keep the operator's value. Reported, fixed and
tested by Filipe Casal of Trail of Bits, in collaboration with OpenAI.
- CVE-2026-70453 (HIGH): quadratic CPU exhaustion in `hash_search()` from a
crafted chain of equal weak checksums. The chain walk is now bounded. First
reported as a performance problem in public rsync issue #217 by heyciao
(2021); recognised as a security issue, bounded and regression-tested by
Stuart Inglis. This one was already public and was not embargoed.
- CVE-2026-70452 (HIGH): `hosts deny` failed OPEN when a configured hostname
could not be resolved -- with `forward lookup` enabled, which is the default,
an unresolvable deny token admitted the host it was meant to block. It now
fails closed. Sibling of CVE-2026-43617. Reported by Leonid Bugaev.
- CVE-2026-70463 (HIGH): `auth users` ignored its documented comma-only
parsing. With a leading comma the split should be on commas alone, so that a
group name containing a space can be written; it split on whitespace too, so
a `deny` or `:ro` rule naming such a group was broken into two meaningless
tokens and never fired. Reported by Andres Berbescu.
- CVE-2026-70460 (HIGH): a peer-supplied `--partial-dir` or `--backup-dir` was
resolved by pathname, so an in-module symlink could redirect it and place
files outside the daemon's module root. Those paths are now confined.
Reported by Omar Elsayed (seks99x).
Client-side:
- CVE-2026-70462 (MEDIUM): a peer-supplied `MSG_IO_TIMEOUT` defeated the
client's own I/O timeout -- a large value overflowed signed arithmetic, and a
non-positive value disabled the timeout outright. The value is now capped on
receipt and the arithmetic made overflow-safe. Reported by Z3R0S! (z3r0s6);
the non-positive case was reported by Leonid Bugaev.
- CVE-2026-70454 (MEDIUM): `rsync-ssl` established an unauthenticated TLS
connection. In stunnel mode it neither required CA verification nor bound
the certificate to the requested hostname, so an active network attacker
could impersonate the server; the openssl backend had a matching hostname
gap in 3.2.0 through 3.2.3 (found and fixed in 2020 by Matt McCutchen).
stunnel mode now requires certificate verification and hostname binding
unless an explicit insecure opt-out is set, and the GnuTLS backend is
refused conservatively rather than used unverified (Greg Kroah-Hartman).
Robustness hardening (no CVE assigned): the `RSYNC_PROXY` CONNECT request and
proxy response headers are length-bounded, and peer-requested xattr expansion is
capped.
A second-pass source audit (reported by Leonid Bugaev) hardened several memory-
safety and robustness paths: the hashtable and file-list size computations are
guarded against a 32-bit integer overflow that a peer's entry count could
otherwise wrap into an under-allocation, and the
`SIGUSR2` handler is now async-signal-safe (it only sets a flag, deferring the
summary/close-out work to safe poll points). Separately, the xattr/ACL metadata
copy now reads the *source* through a held no-follow fd as well as writing the
destination through one -- closing a parent-symlink race on the `--copy-dest` and
backup source -- and the cross-tree operator-path metadata apply is now fd-pinned
under `--fake-super` too (previously it fell back to a path-based set for a
`fake super = yes` daemon staging through an absolute `--temp-dir`/`--backup-dir`).
### SECURITY RELATED:
- Mask a peer-supplied I/O-error value to the defined `IOERR_*` bits, both the
incoming `MSG_IO_ERROR` message (`io.c`) and the file-list trailer (`flist.c`),
so a malicious peer cannot set arbitrary (undefined) error flags that would be
stored in the local `io_error` and re-forwarded upstream. (Undefined bits
never reached the exit code, which maps only the defined bits.) Reported by
Leonid Bugaev.
- Escape control characters in filenames written to the log file (CWE-117 log
injection): a transferred name containing control bytes -- C0 (tab excepted)
and C1 `0x80`-`0x9f`, including CSI `0x9b` -- could otherwise inject terminal
escape sequences into an administrator's terminal when the log is viewed.
Reported by Leonid Bugaev.
- Stop `safe_arg()` leaking an uninitialized byte into a quoted filename. In
filename mode the writer suppresses the escaping backslash before a wildcard,
but the counter that sized the buffer reserved a slot for every backslash, so
the two disagreed and left an uninitialized heap byte in the returned string
-- which is handed to the remote shell when `--protect-args` is off. The
counter now mirrors the writer, and guarding the wildcard test with `f[1]`
also fixes a trailing backslash (previously `strchr()` matched the string
terminator, so the backslash was not doubled). Reported by Leonid Bugaev.
- Close a `--safe-links` bypass in `--backup`: when symlinks can be hard-linked,
`make_backup()`'s link/rename fast path hard-linked an unsafe (out-of-tree)
symlink into the backup area and skipped the `safe_symlinks` check the copy
path applies, silently preserving a link `--safe-links` was meant to drop. The
safe-links check now runs before the fast path, and a symlink whose target is
unreadable is failed closed rather than backed up unchecked. Reported by
Leonid Bugaev.
- Extend the operator-directory ownership walk to the backup leaf sinks:
`do_symlink_at()` (backing a symlink up into an operator `--backup-dir`) and
`do_rmdir_at()` (removing a pre-existing backup directory) now resolve their
parent through the same ownership walk, so a foreign-owned parent symlink no
longer redirects the backup symlink-create or directory-removal outside the
backup tree. `--insecure-links` (or a module's `insecure links = yes`) restores
the legacy follow. Reported by Omar Elsayed (seks99x).
- Confine an absolute operator source/destination through the ownership walk in
`robust_rename()`'s cross-filesystem (EXDEV) copy fallback, so a raced parent
symlink cannot redirect the fallback copy or its source unlink out of the tree.
Reported by Leonid Bugaev.
- Bound the number of equal-weak-checksum blocks examined per offset in
`hash_search()` (issue #217), so a crafted or degenerate checksum set with a
very long equal-checksum chain cannot drive the sender's per-offset
match-verify into a quadratic blow-up (CPU DoS). Fix by Stuart Inglis.
### BUG FIXES:
- Fix an off-by-one in `clean_fname()`'s `..`-collapse path normalization.
Reported by Leonid Bugaev.
- The AVX2 rolling-checksum assembly (`--enable-roll-asm`) read up to 64 bytes
past the end of the buffer it was given. The loop is software-pipelined and
preloaded the 64 bytes after the ones it was folding in, so its last iteration
always reached beyond the data -- the remainder is by construction under 64
bytes. It normally landed in slack inside rsync's map window and went
unnoticed; where the buffer ended at a page boundary it was a SIGSEGV mid
transfer, reported on macOS x86-64 by Roland Kletzing. Reported checksums are
unchanged.
- `--link-dest` no longer fails the transfer when the destination refuses to
hard-link a symlink, device node, FIFO or socket. Whether rsync hard-links
those at all was decided at build time, on whatever filesystem the source tree
happened to sit on, and one host can hold both answers -- macOS builds on
APFS, which can, and backs up to HFS+, which returns ENOTSUP. Such an entry
is now copied, exactly as it already is in a build that cannot link them and
as a regular file in the same position already was; the run used to exit 23
even though the entry was then created correctly. The fallback covers any
refusal, since the error does not identify one on its own: link(2) documents
EPERM both for a filesystem without hard links and for a permission refusal.
Still outstanding: under `-H`, a group of such entries hard-linked to each
other also needs a link within the destination, and where the destination
cannot hard-link the type at all, the members after the first are still lost.
- `--out-format` / `--log-file-format` now emit a literal `%` for `%%` instead of
mis-parsing the following character (added by Leonid Bugaev); a follow-up bounds
`log_format_has()`'s width-digit scan to match `log_formatted()`, closing a `%C`
read past the checksum field.
- A CVS `.cvsignore` (or `-C`) file containing a `!` clear-list token no longer
aborts with a spurious "rule has trailing characters" error. Reported by
Leonid Bugaev.
- `--chmod=a+s` now sets both the setuid and setgid bits, matching `chmod(1)`
(it previously set setuid only). Reported by Leonid Bugaev.
- Case-insensitive wildcard matching (used by daemon `hosts allow`/`hosts deny`
rules) now folds characters inside a `[...]` bracket expression, not just
literal pattern characters. Reported by Leonid Bugaev.
### BEHAVIOR CHANGES:
- A non-daemon receiver follows an operator-named symlinked destination directory
only when the symlink is owned by root or the running user (e.g. `rsync -a src/
/backup/` where `/backup -> /mnt/disk`); a destination symlinked by another uid
is now refused, closing a chdir TOCTOU where an attacker raced the named
destination into a symlink. `--insecure-links` restores the unconditional
follow.
- On platforms without a race-safe way to create a unix socket in a subdirectory
(the BSDs, macOS, Solaris, which lack `bindat()`), a nested socket transferred
under `--specials` is skipped with a warning instead of failing the whole
transfer. Top-level sockets are unaffected.
- `proxy protocol = true` with no `proxy protocol hosts` rejects all connections
(fail-closed); the daemon now warns about this at startup.
- `support/rrsync` in a restricted subdirectory forces `--no-D` (device/special
semantics are stripped, so a plain `rsync -a` still works) and denies
`--copy-unsafe-links`.
- The path resolver now follows in-tree directory symlinks uniformly on every
platform via a single race-free per-component `O_NOFOLLOW` walk, so `-K` /
`-L` / `-k` and `-R` through an in-tree symlinked parent behave the same
everywhere.
# NEWS for rsync 3.4.4 (8 Jun 2026)
## Changes in this version:
@@ -662,14 +211,6 @@ modtime.
### BUG FIXES:
- Fixed a bypass of `--safe-links` when `--backup` is also used on a system that supports hard-linking symlinks (Linux, macOS). An escaping symlink that should have been skipped was silently preserved in the backup area.
- Fixed a spurious abort when using `-C` (cvs-exclude) mode with a `.cvsignore` file that contained a `!` (clear-list) token.
- Updated the `--max-alloc` documentation to reflect that 0 is now rejected (CVE-2026-53794).
- Fixed the EXIT VALUES table: removed nonexistent code 6, added missing codes 15/16/19, corrected SIGUSR1 classification.
- Fixed a regression introduced by the 3.4.0 secure_relative_open()
CVE fix where legitimate directory symlinks on the receiver side
(e.g. when using `-K` / `--copy-dirlinks`) caused "failed
@@ -5720,7 +5261,6 @@ to develop and test fixes.
| RELEASE DATE | VER. | DATE OF COMMIT\* | PROTOCOL |
|--------------|--------|------------------|-------------|
| 13 Aug 2026 | 3.5.0 | | 32 |
| 08 Jun 2026 | 3.4.4 | | 32 |
| 20 May 2026 | 3.4.3 | | 32 |
| 28 Apr 2026 | 3.4.2 | | 32 |
-11
View File
@@ -93,15 +93,6 @@ details.
[3]: https://rsync.samba.org/lists.html
DISCORD
-------
There is also an rsync [Discord server][d] for real-time chat about rsync
and its development.
[d]: https://discord.gg/Avfvy9zhdp
BUG REPORTS
-----------
@@ -145,8 +136,6 @@ COPYRIGHT
Rsync was originally written by Andrew Tridgell and Paul Mackerras. Many
people from around the world have helped to maintain and improve it.
Special thanks go to Wayne Davison, who maintained rsync from 2004 to 2024.
Rsync may be used, modified and redistributed only under the terms of
the GNU General Public License, found in the file [COPYING][9] in this
distribution, or at [the Free Software Foundation][10].
-529
View File
@@ -11,532 +11,3 @@ Email your vulnerability information to rsync's maintainer:
Rsync Project <rsync.project@gmail.com>
## Approach to platform residuals
rsync hardens its security-sensitive operations — path resolution, metadata
application, file/socket creation — against local attacks such as parent-symlink
TOCTOU races. Some of these operations can only be made race-safe with a
primitive the underlying OS provides (an `*at()` syscall on a held directory fd,
an fdescfs-style `/proc/self/fd` magic symlink, `mknodat()`, the `*xattrat`
syscalls, and so on), and that primitive is not available on every supported
platform.
The guiding rule for those cases is:
> **On a modern Linux system every issue described in this document is fully
> addressed.** Where an operation *can* be secured on some platforms but *cannot*
> be secured on others, and the residual risk is a *local* privilege-escalation
> or data-disclosure class (an attacker who already has write access inside the
> transferred tree), rsync prefers keeping the operation functional on the
> platforms that lack the primitive over disabling a long-standing feature for
> everyone on those platforms.
So a hardened operation takes the race-safe path wherever the platform offers one
and falls back to the historical (path-based, unconfined) behaviour only where it
does not — rather than refusing the operation outright. Each such fallback is an
accepted residual, documented under "Known residuals" below, and on the daemon it
can be turned off per feature with `refuse options = ...`. The residuals are
therefore confined to non-Linux platforms (the BSDs, macOS, Solaris/illumos),
Cygwin, and — for a few features — pre-6.13 Linux kernels; a current, normally
configured Linux deployment carries none of them. (The `/proc/self/fd`-based
fallbacks assume a mounted `/proc`, which every standard Linux provides; a
deliberately `/proc`-less container is the one Linux case that can still hit a
residual.)
The one deliberate exception is an operation whose unconfined fallback would
*create a new filesystem object at an attacker-influenceable path* rather than set
metadata on the object rsync already transferred: the nested-socket `bind()` on
platforms without a race-safe socket-create (no `bindat()`). There the unsafe path
is an out-of-tree write/create primitive, not a same-object metadata race, and a
transferred socket inode is a worthless placeholder, so rsync refuses (skips) it
rather than keeping it functional. A leaf permission change is likewise failed
closed rather than applied through a raced symlink, but only as a rare backstop:
the common file/dir/FIFO case is secured on every platform via `fchmod` on a held
fd, so no real functionality is lost.
This trade-off applies only to these local-attacker residual classes. Remotely
reachable defects — memory safety, authentication bypass, protocol parsing, input
bounds — are fixed unconditionally on all platforms, never left as a residual.
## Robustness against malicious peers
rsync treats everything the peer sends — the file list, checksum headers,
multiplexed messages, forwarded daemon arguments, filter rules — as untrusted,
and bounds-checks it before use. A peer-triggerable crash of a connection's
worker process is treated as a defect to be fixed, even though the daemon's
fork-per-connection model confines such a fault to that one connection rather
than the whole service.
Alongside the issues enumerated elsewhere in this document, the code is hardened
continuously through protocol fuzzing (driving the daemon protocol against a
writable module) and static analysis, with a CI gate. This release closes a
batch of peer-triggerable faults found that way: NULL-dereference and
reachable-assert crashes from crafted file lists or indices, reads past a
file-list allocation (mostly bounded over-reads of an entry's extra slots),
unbounded merge-file and suffix-list recursion, and several bounded
out-of-bounds writes driven by peer-supplied lengths or option arguments. Each
is fixed at the root with a bounds or validity check plus a defence-in-depth
guard at the use site, and carries a regression test.
Two further peer-input hardenings in this release: a peer-supplied I/O-error
value (the `MSG_IO_ERROR` message and the file-list trailer) is masked to the
defined `IOERR_*` bits, so a peer cannot set arbitrary error flags in the local
`io_error` that would be stored and re-forwarded upstream; and control
characters in a (peer-controlled) filename written to the log file are escaped,
so a name carrying C0/C1 terminal-escape bytes cannot inject sequences into an
administrator's terminal when the log is viewed (CWE-117). The number of
equal-weak-checksum blocks `hash_search()` examines per offset is also bounded
(issue #217), so a crafted or degenerate checksum set with a very long
equal-checksum chain cannot drive the sender's per-offset match-verify into a
quadratic walk and pin one connection's CPU.
Contributors adding code that consumes peer input should validate it at the
point of receipt rather than relying on a downstream check.
## Symlink-race-safe path resolution
This section documents how rsync defends against parent-directory symlink races
(a TOCTOU / confused-deputy class) and the per-platform approach it takes, so
that contributors and automated agents extend the code consistently rather than
reintroducing the weakness.
### The threat
Many rsync operations resolve pathnames that an unprivileged party can partially
control: a receiver writing into a destination tree, a sender reading a source
tree, and temp and partial files, and so on. (The operator-chosen directory
paths — `--link-dest`/`--compare-dest`/`--copy-dest`/`--backup-dir`/`--temp-dir`/
`--partial-dir` — may legitimately point outside the tree, so they are resolved
by the ownership walk described under *Symlink defense for operator-supplied
paths* below rather than the strict transfer-path resolver here.) If someone who
can write inside that tree races a
parent directory component between a real directory and a symlink ("symlink
flipping"), a path-based syscall — `open`, `stat`, `chmod`, `chown`, `utimes`,
`rename`, `unlink`, `mkdir`, `mknod`, `symlink`, hard-link creation — can be
redirected to a target *outside* the intended tree. When rsync resolves that
path with more authority than the component's controller and without a
confinement boundary, this is a confused-deputy bug (e.g. a root nightly backup
capturing `/etc/shadow`, or a root receiver chmod/chown/unlink-ing a system
file).
`O_NOFOLLOW` on the final component is **not** sufficient: the *parent*
components must be resolved safely.
The boundary that matters is **authority plus confinement**, not "daemon vs
non-daemon". A non-chroot daemon module, a root-run local transfer, and a
two-user transfer are all unconfined privileged path resolvers. Where a real
confinement boundary already exists (e.g. a per-module `chroot`) that is the
strongest protection; otherwise rsync must resolve paths defensively.
A `chroot` is only a boundary for the *outer* path it confines. A daemon module
written as `path = /outer/./inner` (`use chroot = yes`) chroots to `/outer` but
treats `/inner` as the module root, so a symlink inside the module that points to
a sibling of `/inner` is still inside the chroot yet outside the module — the
inner module therefore needs the same defensive resolution as a non-chroot
module. The single gate that decides when hardened resolution applies is
"unconfined privileged resolver": `am_daemon && (!am_chrooted || module_dirlen)`
for the daemon (any non-chroot module, plus a `/./` inner-module chroot), and any
non-chroot receiver. The local sender's content open is confined the same way for
default symlink handling; only the symlink-following modes (`-L`/`--copy-links`/
`--copy-unsafe-links`/`-k`) and `--insecure-links` are excluded, so those keep
following symlinks by design.
### The mechanism
Resolution of attacker-influenceable paths goes through `secure_relative_open()`
and the `do_*_at()` wrappers in `syscall.c`, never a raw `open()`/`rename()`/
`chmod()` on a full path string. The principle is: **trust the operator-named
transfer root, and confine all resolution beneath it**, rejecting escapes via
`..` above the anchor, absolute symlinks, or out-of-tree symlinks.
`secure_relative_open()` resolves the parent directory by walking it one
component at a time on a stack of held directory fds, then operates on the final
component with an at-style call on the resulting directory fd.
For per-entry work the receiver and generator go one step further and hold the
parent directory open: `open_dir_secure()` resolves an entry's directory once
(via `secure_relative_open()`), `held_dfd_for()` caches that descriptor for the
duration of the entry, and every operation on the entry — `lstat`, the temp-file
`mkstemp`, the temp->final `rename`, `chmod`/`chown`/`utimes`, `mkdir`, special-
file and symlink creation, the delta-basis open, and the recursive delete — runs
through that one held fd via an `*at()` call (`do_*_atfd()`). Because the
descriptor is pinned to the directory inode, a parent component flipped to a
symlink *after* the open cannot redirect any of those operations. The alternate-
destination lookups are confined the same way (`basis_link_stat()` in
`generator.c` and `secure_basis_open()` in `receiver.c`), so a peer-chosen
`--link-dest`/`--compare-dest`/`--copy-dest` basis index cannot reach an
out-of-module file through a symlinked parent.
The sender's source-directory *enumeration* is confined the same way as its
content open. `send_directory()` opens each scanned directory through
`secure_opendir()` — which resolves it via `secure_relative_open()` /
`secure_relative_open_at()` and turns the held fd into the `DIR*` with
`fdopendir()` — so a parent component raced into a symlink, or (for a daemon
following mode) an in-module symlink pointing outside the module, cannot redirect
the scan to enumerate an out-of-tree directory and leak its entry names, metadata
and symlink targets. For a daemon, both the enumeration and the content open
anchor at the served module root **pinned by identity**: `module_dirfd` is opened
(`open(".")`) the moment the daemon `chdir`s into the module, while still
privileged, and module-relative paths resolve beneath that fd via
`secure_relative_open_at()`. Anchoring at the held fd rather than re-resolving the
absolute module path keeps the confinement working after the daemon drops to the
module uid even when the module sits under a directory that uid cannot traverse
(e.g. a `0700` home — re-resolving the absolute path would `EACCES`), and is
immune to the logical-path-versus-real-cwd skew a followed in-tree directory
symlink would otherwise introduce.
### Path resolution
`secure_relative_open()` resolves a path with a single portable mechanism on
every platform: a per-component walk on a stack of held directory fds. Each
component is opened relative to the held parent with `openat(parent_fd,
"component", O_NOFOLLOW)`; descending into a real subdirectory pushes its fd, a
`..` pops back to the already-held parent (a pop at the anchor is refused), and an
in-tree directory symlink is followed by reading its target and walking that off
the same stack (absolute targets refused, symlink hops bounded). The final
component is opened `O_NOFOLLOW`.
Because every component is opened relative to a *pinned* fd under `O_NOFOLLOW`,
and `..` is resolved by the held-fd stack rather than by the kernel, the walk is
race-free by construction: no rename or symlink swap of any path name can redirect
resolution outside the anchor subtree, and no kernel "beneath" primitive
(`openat2(RESOLVE_BENEATH)` / `openat(O_RESOLVE_BENEATH)`) is required. The
confinement is therefore uniform across Linux, the BSDs, macOS and
Solaris/illumos, on old and new kernels alike, with nothing to probe or fall back
to at runtime (and so no `openat2`/seccomp interaction to worry about in sandboxed
environments). Cygwin is the exception, because its directory descriptors and
symlink emulation do not give the held-fd walk the same inode pinning — see the
Cygwin residual below.
Legitimate *in-tree* directory symlinks are followed, so `--keep-dirlinks` /
`--copy-links` and a symlinked module path keep working. A relative alternate-dest
such as `--compare-dest=../01` may legitimately climb to a sibling still inside the
module; such a `..` path is re-anchored at the module root and its in-module climb
adjudicated by the walk (the `..` pops to the held parent), while escapes above the
anchor are still rejected.
### Leaf operations
The final operation is hardened as well, following `cp`: reads use `O_NOFOLLOW`
so a flipped leaf symlink is not followed, and new or destination files are
created with `O_CREAT|O_EXCL` (rsync's temporary files use `mkstemp`) so a
planted symlink at the target cannot be written through. A leaf `chmod` is the
one operation with no portable no-follow form: it is closed by opening the leaf
`O_RDONLY|O_NOFOLLOW` and `fchmod`-ing the held fd (refusing a symlink leaf with
`ELOOP`), falling back to `fchmodat(AT_SYMLINK_NOFOLLOW)` and then the
`fchmodat2()` syscall, and failing closed with a warning rather than ever
chmod-ing through a raced leaf symlink.
### Guidance for contributors
* When adding code that performs a path-based syscall on a path that can be
influenced by the remote peer or by another local user, use a `do_*_at()`
wrapper (or `secure_relative_open()`), not a raw full-path syscall.
* When introducing a new operation, add a matching `do_<op>_at()` wrapper that
resolves the parent with `secure_relative_open()` and acts via an at-style call
on the returned dirfd.
* Do not assume a non-daemon transfer is safe; the question is whether rsync has
more authority than whoever controls the path components.
* On platforms whose API lacks an at-style equivalent (e.g. `setattrlist()`),
follow the residuals policy at the top of this document: for a metadata
operation on the already-transferred object (ACLs, xattrs, crtimes, permissions)
fall back to the path-based call to keep the feature functional and document the
residual; but where the unsafe fallback would *create a new object on an
unconfined path* (the nested-socket `bind()` case), refuse it instead — that is
an out-of-tree write/create primitive, not a same-object metadata race, and the
lost functionality is negligible.
## Symlink defense for operator-supplied paths
rsync opens several operator-supplied paths during normal operation. These fall
into two groups, both governed by the same ownership-walk policy below:
* operator **files**: `--log-file`, `--password-file`, `--early-input` (a client
read whose contents are forwarded to the daemon's early-exec), `--files-from`,
`--include-from`, `--exclude-from`, `--filter=. file`, `--write-batch`,
`--read-batch`, per-directory filter merge files (`-C` / `-F` / `dir-merge`),
and on the daemon side `motd file =`, `secrets file =`, `lock file =`, and
`rsyncd.conf` itself.
* operator **directories**: `--backup-dir`, `--temp-dir`/`-T`, `--partial-dir`,
and the `--link-dest`/`--compare-dest`/`--copy-dest` basis lookup. These take
a directory the operator chose, which may legitimately point outside the
transfer tree (`--backup-dir=/var/backups`), so they are resolved with the
ownership walk rather than the strict transfer-path resolver.
The daemon module-root `chdir()` under
`use chroot = no` and the non-daemon receiver's `chdir()` into the
operator-named destination directory are in the same class: both follow
the operator's/root's own symlinked target (the `/backup -> /mnt/disk`
admin pattern) but refuse one an attacker raced in from another uid,
unless `--insecure-links` restores the legacy plain `chdir()`.
Each of these reads or writes a path the operator or sender chose, which
may transit attacker-influenceable parent directories (the `/tmp/somedir/`
class) or be planted directly (the `/home/$user/.cvsignore` class when
root runs `rsync -a /home /backup`).
rsync's defense, applied uniformly to all of the above, is a
component-by-component path walk (`open_no_attacker_symlinks` in
`util1.c`) that allows symlinks **only** when the symlink itself is owned
by uid 0 or the running process's effective uid. Symlinks owned by any
other uid are refused with `ELOOP` at any path component (parent or leaf).
Plain `O_NOFOLLOW` would be leaf-only and would not defend the
`/tmp/somedir/log` parent-component plant; this walk does.
The trust model preserves legitimate setups such as `/var/log -> /data/log`
(root-owned dir-symlink) and a non-root user's own `~/log -> /data/me`
symlink; it refuses an attacker's `/tmp/somedir -> /attack/path` plant.
For `--read-batch` an additional `fstat()` check refuses non-regular
files (FIFOs, devices) at the batch path, since the batch content drives
the receiver's protocol parser.
**Policy.** A symlink at **any** path component (parent or leaf) is **followed
iff it is owned by uid 0 or the process's effective uid, and refused (`ELOOP`)
otherwise**, identically for **absolute and relative** operator paths. The trust
signal is **authority (ownership)**, not **location**: an operator path may
legitimately point outside the transfer tree, so it cannot be confined by
location the way a transfer path is. This is deliberately distinct from the
transfer-path resolver `secure_relative_open()` (see *Symlink-race-safe path
resolution* above), which refuses **all** symlinks and anchors **beneath the
transfer root** — correct for peer-named paths, which never legitimately escape.
For the operator directory paths, a refused symlink simply makes the target look
absent (no backup/temp/basis is taken through it) and the transfer proceeds
normally; the operator's own symlinked target keeps working.
**The daemon `exclude`/`filter` chain is not a symlink boundary.** The daemon
filter chain (`exclude`, `exclude from`, `filter`, …) matches the *logical*
module-relative **name** of each item, not the physical file it resolves to. It
is a visibility/tamper filter — a peer cannot *name* a daemon-excluded path to
pull, push to, or delete it — but it is **not** a security boundary against
symlinks: an in-module symlink whose own name is not excluded can be followed to
an excluded target (the name the filter sees, e.g. `link`, is not the excluded
name, e.g. `secret`). This is by design and is the long-standing behaviour of
stock rsync; the defense for a writable module against symlink trickery is
`munge symlinks` (enabled by default for a writable, non-chrooted module), **not**
the filter. Do not rely on `exclude`/`filter` to confine a peer who can introduce
or traverse a symlink; see `rsyncd.conf(5)` ("filter" and "munge symlinks").
What *is* enforced for a *peer-supplied* operator path (`--partial-dir`,
`--backup-dir`, the alt-dest basis) is confinement to the **module root**: the
ownership walk refuses a foreign-uid symlink (the symlink-race defense) and
refuses a resolved target *outside* the module. That module-boundary confinement
is independent of `exclude`/`filter` — it holds whether or not the module sets an
exclude — and is what the operator-path tests cover.
**`--insecure-links`.** This flag is a **local** opt-out that restores the legacy
follow-any-symlink behaviour for the paths above. It is **not forwarded** to the
remote (a remote-shell peer that wants the opt-out must set it on its own side,
e.g. via `--rsync-path`), and a **daemon never honors it**: the opt-out predicate
reads the client-controllable flag only off a daemon, so a peer-forwarded or
`-M`-injected `--insecure-links` cannot weaken a daemon's confinement — the daemon
additionally hard-refuses it (drops the connection) via the refused-options path.
A daemon admin who wants the legacy behaviour for one isolated/trusted module
sets `insecure links = yes` in that module's `rsyncd.conf` stanza (see
`rsyncd.conf(5)`); this is admin-only and re-opens the symlink-escape
vulnerabilities for that module on purpose. The `operator-path-*` and
`insecure-links-*` tests enforce this consistency across every path-taking
option and across absolute/relative, leaf/parent, and same-uid/cross-uid plants.
For `support/rrsync` (the SSH-restricted-rsync wrapper), the same TOCTOU
class is closed in Python by opening each validated path component with
`O_RDONLY|O_NOFOLLOW`, verifying via `readlink('/proc/self/fd/N')` that the
pinned inode is still in-tree, and passing `/proc/self/fd/N` as the exec'd
rsync's argument (so the kernel routes the child's open through the pinned
inode rather than re-resolving the path). A receiver-side new destination
has no inode of its own yet, so its existing parent directory is pinned the
same way and the leaf is created at `/proc/self/fd/<parent>/<leaf>`. This pin
relies on an fdescfs-style magic symlink and is not available on every
platform -- see the rrsync residual below.
### Known residuals
The following are documented as out of scope for this release:
* The source-directory *enumeration* confinement needs `fdopendir()` (to form a
`DIR*` from the securely-resolved held fd) and `dirfd()`; on a platform lacking
either, `send_directory()` falls back to the legacy `opendir()` on the path, so
the scan is unconfined there — the same resolver-fallback shape as the other
`*at()`-less residuals. Every current target provides both; the per-entry
operations and the content open remain confined regardless.
* On **Cygwin**, the per-component held-fd walk does not provide the same
inode-pinning guarantee as on a POSIX kernel: Cygwin tracks a process's
current directory and resolves directory descriptors by path name rather than
by a pinned inode, and emulates symlinks as special files. Static out-of-tree
symlinks are still refused (the walk sees and rejects them), and a daemon
module path anchored at an absolute `module_dir` is confined; but an entry
whose parent component is *raced* from a directory to a symlink mid-resolution
can still slip past confinement that is anchored at the process CWD (e.g. the
sender's content open), because the descriptor is not bound to the original
inode. The parent-component symlink-race tests are therefore not enforced on
Cygwin (see `RSYNC_EXPECT_SKIPPED` in `.github/workflows/cygwin-build.yml` and
the Cygwin-only xfail in `symlink-race-source_test.py`). Cygwin is a
development/interoperability target, not a privilege boundary host, so this is
accepted for this release.
* On a platform with no `mknodat()` at all -- macOS before 13 is the
supported example, where `mknod()` and `mkfifo()` exist but neither
`mknodat()` nor `mkfifoat()` does -- creating a device node or FIFO
falls back to plain `do_mknod()`, which resolves the whole path by name.
What is lost is the *pinned parent*: the directory components are
re-resolved by the kernel at create time, so an attacker who can swap a
parent component races the create and can place the node outside the
transfer. The final component is not at risk -- `mknod()` and
`mkfifo()` do not follow a symlink at the leaf, they fail `EEXIST`.
Where `AT_FDCWD` exists -- which is every platform rsync 3.5.0 supports,
macOS 10.13 included -- fake-super placeholders still return through
`openat(..., O_NOFOLLOW)`, reached before either `*at` primitive is
tested, so ordinary in-tree placeholder creation stays confined;
fake-super loses parent confinement and the `O_NOFOLLOW` leaf only on the
paths that reach plain `do_mknod()` (the cache-declined/cross-tree
wrapper and the backup paths). On a build with no `AT_FDCWD` at all
there is no fd-relative primitive of any kind, so nothing above applies
and every special-file create, fake-super included, is unconfined. Transferring specials there (`--devices`, `--specials`) carries
the parent-component race. `symlink-mknod-fakesuper-symlink-race` skips
itself on such a build, since the property it asserts is one the build
deliberately does not have.
* On platforms where `mknod()`/`mknodat()` cannot create a socket inode
(the BSDs, macOS, Solaris), a transferred socket is recreated with
`socket()` + `unlink` + `bind(path)`, which cannot be confined (there is
no portable `bindat()`). Linux creates it race-safely with `mknodat()`
on a held dirfd; on the others a *nested* socket is skipped with a
warning rather than bound on an unconfined path, leaving only a
top-level, operator-named socket binding by path.
* `support/rrsync`'s race-free inode-pin -- of both existing path
components and a new destination's parent -- depends on materialising a
held fd as a path that the exec'd rsync re-resolves to the same inode.
rrsync validates and pins in its own process, but it then *exec*s a
separate rsync that re-resolves the paths from `argv`, so the confining
reference must be expressible as an argument. A held dirfd is not: it is
usable as a path only through an fdescfs-style magic symlink. rrsync
implements this for Linux only, via `/proc/self/fd/N`; it does not use the
`/dev/fd/N` equivalent that macOS/FreeBSD expose with `fdescfs` mounted. So on
every non-Linux platform (the BSDs, macOS, Solaris -- whose `/proc/self/fd`
entries are not magic symlinks -- and Cygwin), and on a `/proc`-less Linux
namespace, rrsync falls through to the realpath-validated path unpinned,
so a parent-component or between-pin-and-exec flip remains possible there;
a deeper `-R` new path whose parent does not exist yet is likewise
unpinned. The portable closure is an rsync-side fd-passing API -- rrsync
hands rsync the confined dirfd (inherited across `exec`) and rsync
resolves that argument relative to it with the same `secure_relative_open`
resolver the daemon uses, needing no magic-symlink filesystem -- a
protocol/CLI addition under discussion on the rsync-security list.
* The operator-directory ownership walk refuses a foreign-owned symlink on a
`--backup-dir`/`--temp-dir`/`--partial-dir`/`--link/compare/copy-dest` path, so
a *statically planted* symlink is rejected and the dependent operation does not
escape. Both the data writes and the *source-metadata reads* of those
operations are now confined to held no-follow fds: the `--copy-dest`
`copy_file()`/`copy_xattrs()` source read goes through the held basis content fd
(`sys_fgetxattr`), and `make_backup()` reads the backed-up file's ACL/xattrs
through a `backup_source_fd()`-pinned fd -- so a parent-component flip can no
longer redirect them to disclose an out-of-module value. The cross-tree
metadata *apply* on those leaves (the `%stat`/ACL/xattr write on a
`--temp-dir`/`--backup-dir` staging file) is fd-pinned the same way, now
including under `--fake-super`: the `set_file_attrs()` no-follow leaf fd was
previously opened only when `am_root >= 0`, so a `fake super = yes` daemon fell
back to a path-based `sys_lsetxattr()`/chmod a raced parent could redirect; the
pin is now opened for fake-super too (a raced leaf is refused, not redirected).
Two narrow follow-ons
re-resolve the (now-validated) operator path by name and remain a
*post-validation* parent-component race:
* the in-place backup (`--inplace --backup`) writes the backup file's data
through a confined create, but its `set_file_attrs()` metadata set
(chmod/chown/times) re-resolves the `--backup-dir` path by name afterwards
(it is not placed under operator mode, which would force the shared
`set_file_attrs()` path off its held-O_NOFOLLOW-fd xattr write and re-open
the very parent-symlink xattr race `copy-xattrs-symlink-race` pins closed); and
* the abbreviated-xattr optimisation reuses a basis xattr value for the
destination only when its checksum matches the digest the sender sent; that
basis read (`rsync_xal_set()`) re-resolves the basis path by name. This is a
*constrained checksum-oracle*, not a disclosure: it confirms that some raced
out-of-module xattr hashes to a value the sender already chose, rather than
copying an unknown value onto a readable file, and needs a colluding sender
plus a local racer.
An attacker who flips a parent component in the window *after* the confined data
write/stat can thus still affect those narrow metadata/oracle operations. This
is the same local-attacker post-confinement TOCTOU class as the ACL/crtimes
residuals below; the data-write and direct source-read escapes are closed, and
`--insecure-links` (or a module's `insecure links = yes`) is orthogonal to it.
* POSIX ACL application (`-A`/`--acls`) is race-safe on every Linux kernel —
6.13+ via the `*xattrat` syscalls (or a patched libacl's `*_at` bindings), and
older kernels via the `/proc/self/fd` compat that pins the same inode, provided
`procfs` is mounted — and a transferred file/dir/FIFO has its xattrs (`-X`)
applied through the held no-follow fd, so the apply cannot be redirected by a
raced parent component. Where neither primitive is available — the BSDs,
Solaris and macOS (no `*xattrat` syscalls and no `/proc/self/fd` magic
symlinks), plus the edge case of a Linux instance with no usable `/proc` (a
`/proc`-less container/namespace) — the ACL apply falls back to the path-based
`acl_set_file()` /
`sys_acl_*file()` calls — the long-standing 3.4.x behaviour — to keep `--acls`
functional rather than silently skipping it, so a parent-component flip can
have the received ACL written onto an object outside the module/destination
boundary (and, because the attacker controls the ACL bytes, granted to a chosen
uid). As with the macOS crtime tier below, this is an accepted residual under
the functionality-over-refusal policy; a daemon operator who does not want it
can disable the feature with `refuse options = acls`.
* macOS creation-time (`--crtimes`) preservation uses the path-based
`setattrlist()`/`getattrlist()` with `FSOPT_NOFOLLOW`, which protects only
the final component; there is no `setattrlistat()` targeting
`ATTR_CMN_CRTIME`. As with POSIX ACLs where the OS offers no race-safe
primitive, `--crtimes` is kept functional (daemon and non-daemon) and the
parent-component symlink race is an accepted residual: an attacker who
flips a parent component can have a crtime read/write target an object
outside the module/destination boundary. The mtime/atime path is *not*
affected -- `set_times()` resolves it race-safely through `utimensat()` on a
held dirfd in hardened mode. A daemon operator who does not want the crtime
residual can disable the feature with `refuse options = crtimes` in
`rsyncd.conf`.
* Pulling with `-o`/`-g` (or `-a`) **as root from an untrusted sender** is by
design a trust relationship, not a confinement boundary: the sender dictates
each received file's owner/group, including uid/gid 0. rsync maps the
sender's id/name pairs through the local id database; an empty or unknown
sender name falls back to the sender's numeric id (the value `--numeric-ids`
would use), and a sender can equally request root via the literal name
`root`. A root receiver must therefore only pull with `-o`/`-g` from a
trusted source (or use a non-root receiver / a uid-gid policy). The daemon
*name-converter* path is guarded separately — an unknown name there maps to
the sender's numeric id rather than 0 (see `clientserver.c`).
## Daemon authentication digest
Daemon authentication is a secret-prefix challenge-response: the client returns
`base64(H(secret || challenge))`, where `H` is a digest the two sides negotiate.
The negotiation is unauthenticated and ordered by the connecting side, and the
`md5`/`md4` digests remain available for backward compatibility, so a peer that
sends no digest list (any rsync before 3.2.0, including the openrsync that ships
with macOS) falls back to `md5` (or `md4` below protocol 30), and an on-path
attacker can rewrite the negotiation to force `md5`/`md4` even between two modern
peers. This is **not** an authentication bypass — `md4`/`md5` have no practical
preimage break — but a weak digest makes a *captured* `(challenge, response)`
pair far cheaper to brute-force offline, recovering a guessable shared secret.
The challenge itself is seeded from the kernel CSPRNG (`/dev/urandom`), so it is
an unpredictable per-connection nonce. An earlier time/pid-based challenge was
low-entropy enough (~35 bits) that recovering the `(sec, usec, pid)` tuple from
one observed challenge let an on-path observer predict every subsequent challenge
from that daemon process and pre-compute a dictionary against a captured
response. (If `/dev/urandom` is unavailable the daemon logs a warning and falls
back to the legacy time-based challenge rather than a constant.)
A daemon operator whose clients are all modern (rsync 3.2.7+ built with openssl,
when the SHA digests were added) can require a strong digest with the `auth
digest` module parameter, e.g. `auth digest = sha256`, which refuses any
connection that negotiates — or falls back to — a weaker digest (see
`rsyncd.conf`).
Residual: there is **no default floor**, because requiring one would break every
pre-3.2.0 client (notably the macOS-bundled openrsync, which authenticates only
with `md4`). An operator who cannot raise the floor should run the daemon behind
a verified TLS transport (`rsync-ssl`/stunnel) or over ssh — which removes the
on-path capture/downgrade vector at the transport layer — and should use a
high-entropy shared secret, which is infeasible to brute-force regardless of the
digest.
+11
View File
@@ -15,6 +15,7 @@ Create more granular verbosity 2003/05/15
DOCUMENTATION --------------------------------------------------------
Keep list of open issues and todos on the web site
Perhaps redo manual as SGML
LOGGING --------------------------------------------------------------
Memory accounting
@@ -212,6 +213,16 @@ DOCUMENTATION --------------------------------------------------------
Keep list of open issues and todos on the web site
-- --
Perhaps redo manual as SGML
The man page is getting rather large, and there is more information
that ought to be added.
TexInfo source is probably a dying format.
Linuxdoc looks like the most likely contender. I know DocBook is
favoured by some people, but it's so bloody verbose, even with emacs
support.
+7 -21
View File
@@ -28,7 +28,7 @@ static int allow_forward_dns;
extern const char undetermined_hostname[];
static int match_hostname(const char **host_ptr, const char *addr, const char *tok, int deny)
static int match_hostname(const char **host_ptr, const char *addr, const char *tok)
{
struct hostent *hp;
unsigned int i;
@@ -54,14 +54,8 @@ static int match_hostname(const char **host_ptr, const char *addr, const char *t
return 0;
/* Now try forward-DNS on the token (config-specified hostname) and see if the IP matches. */
if (!(hp = gethostbyname(tok))) {
/* A deny-list hostname token we cannot resolve must fail CLOSED:
* we can't prove the peer isn't the denied host, so treat the
* unresolvable token as a match (deny). Allow-list tokens keep
* failing as a non-match. Sibling of CVE-2026-43617, which fixed
* only the reverse-lookup path. */
return deny;
}
if (!(hp = gethostbyname(tok)))
return 0;
for (i = 0; hp->h_addr_list[i] != NULL; i++) {
if (strcmp(addr, inet_ntoa(*(struct in_addr*)(hp->h_addr_list[i]))) == 0) {
@@ -249,7 +243,7 @@ static int match_address(const char *addr, char *tok)
return ret;
}
static int access_match(const char *list, const char *addr, const char **host_ptr, int deny)
static int access_match(const char *list, const char *addr, const char **host_ptr)
{
char *tok;
char *list2 = strdup(list);
@@ -257,7 +251,7 @@ static int access_match(const char *list, const char *addr, const char **host_pt
strlower(list2);
for (tok = strtok(list2, " ,\t"); tok; tok = strtok(NULL, " ,\t")) {
if (match_hostname(host_ptr, addr, tok, deny) || match_address(addr, tok)) {
if (match_hostname(host_ptr, addr, tok) || match_address(addr, tok)) {
free(list2);
return 1;
}
@@ -281,7 +275,7 @@ int allow_access(const char *addr, const char **host_ptr, int i)
/* If we match an allow-list item, we always allow access. */
if (allow_list) {
if (access_match(allow_list, addr, host_ptr, 0))
if (access_match(allow_list, addr, host_ptr))
return 1;
/* For an allow-list w/o a deny-list, disallow non-matches. */
if (!deny_list)
@@ -290,17 +284,9 @@ int allow_access(const char *addr, const char **host_ptr, int i)
/* If we match a deny-list item (and got past any allow-list
* items), we always disallow access. */
if (deny_list && access_match(deny_list, addr, host_ptr, 1))
if (deny_list && access_match(deny_list, addr, host_ptr))
return 0;
/* Allow all other access. */
return 1;
}
int allow_proxy_protocol_peer(const char *list, const char *addr, const char **host_ptr)
{
if (!list || !*list)
return 0;
allow_forward_dns = 0;
return access_match(list, addr, host_ptr, 0);
}
+14 -383
View File
@@ -21,10 +21,6 @@
#include "rsync.h"
#include "lib/sysacls.h"
#include "lib/acl.h"
#ifdef HAVE_LIBACL_AT
#include <fcntl.h> /* AT_EMPTY_PATH / AT_SYMLINK_NOFOLLOW */
#endif
#ifdef SUPPORT_ACLS
@@ -473,129 +469,11 @@ static int find_matching_rsync_acl(const rsync_acl *racl, SMB_ACL_TYPE_T type,
return *match;
}
/* These two bridge lib/acl.c's neutral (tag,perm,id) entry array; with
* HAVE_LIBACL_AT the libacl *_at path uses unpack_smb_acl/pack_smb_acl directly,
* so they are unused there. */
#if defined(SUPPORT_ACL_FD) && !defined(HAVE_LIBACL_AT)
/* Convert a packed system ACL into the neutral (tag,perm,id) entry array that
* lib/acl.c serializes. Reuses pack_smb_acl()+change_sacl_perms() output so
* the bytes we write match exactly what acl_set_file() would have written.
* Returns the entry count and a malloc'd array in *ents_p, or -1 on error. */
static int sacl_to_entries(SMB_ACL_T sacl, rsync_acl_ent **ents_p)
{
static item_list ent_list = EMPTY_ITEM_LIST;
SMB_ACL_ENTRY_T entry;
rsync_acl_ent *out;
int rc;
ent_list.count = 0;
for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry); rc == 1;
rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) {
SMB_ACL_TAG_T tag_type;
uint32 access;
id_t g_u_id;
rsync_acl_ent *e;
uint16_t tag;
if ((rc = sys_acl_get_info(entry, &tag_type, &access, &g_u_id)) != 0)
break;
switch (tag_type) {
case SMB_ACL_USER_OBJ: tag = RACL_USER_OBJ; break;
case SMB_ACL_USER: tag = RACL_USER; break;
case SMB_ACL_GROUP_OBJ: tag = RACL_GROUP_OBJ; break;
case SMB_ACL_GROUP: tag = RACL_GROUP; break;
case SMB_ACL_MASK: tag = RACL_MASK; break;
case SMB_ACL_OTHER: tag = RACL_OTHER; break;
default: continue; /* skip an unrecognized tag */
}
e = EXPAND_ITEM_LIST(&ent_list, rsync_acl_ent, -10);
e->tag = tag;
e->perm = access & 7;
e->id = (tag == RACL_USER || tag == RACL_GROUP) ? (uint32_t)g_u_id : RACL_UNDEFINED_ID;
}
if (rc) {
rsyserr(FERROR_XFER, errno, "sacl_to_entries: sys_acl_get_entry/info()");
return -1;
}
out = new_array(rsync_acl_ent, ent_list.count ? ent_list.count : 1);
if (ent_list.count)
memcpy(out, ent_list.items, ent_list.count * sizeof (rsync_acl_ent));
*ents_p = out;
return ent_list.count;
}
/* Unpack a neutral entry array (from lib/acl.c) into an rsync_acl, mirroring
* unpack_smb_acl()'s tag handling. */
static BOOL unpack_acl_entries(const rsync_acl_ent *ents, int n, rsync_acl *racl)
{
static item_list temp_ida_list = EMPTY_ITEM_LIST;
int i;
temp_ida_list.count = 0;
for (i = 0; i < n; i++) {
uint32 access = ents[i].perm & 7;
id_access *ida;
switch (ents[i].tag) {
case RACL_USER_OBJ:
if (racl->user_obj == NO_ENTRY)
racl->user_obj = access;
continue;
case RACL_GROUP_OBJ:
if (racl->group_obj == NO_ENTRY)
racl->group_obj = access;
continue;
case RACL_MASK:
if (racl->mask_obj == NO_ENTRY)
racl->mask_obj = access;
continue;
case RACL_OTHER:
if (racl->other_obj == NO_ENTRY)
racl->other_obj = access;
continue;
case RACL_USER:
access |= NAME_IS_USER;
break;
case RACL_GROUP:
break;
default:
continue;
}
ida = EXPAND_ITEM_LIST(&temp_ida_list, id_access, -10);
ida->id = ents[i].id;
ida->access = access;
}
if (temp_ida_list.count) {
#ifdef SMB_ACL_NEED_SORT
if (temp_ida_list.count > 1)
qsort(temp_ida_list.items, temp_ida_list.count, sizeof (id_access), id_access_sorter);
#endif
racl->names.idas = new_array(id_access, temp_ida_list.count);
memcpy(racl->names.idas, temp_ida_list.items, temp_ida_list.count * sizeof (id_access));
} else
racl->names.idas = NULL;
racl->names.count = temp_ida_list.count;
temp_ida_list.count = 0;
return True;
}
#endif /* SUPPORT_ACL_FD */
static int get_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
rsync_acl *racl, SMB_ACL_TYPE_T type, mode_t mode)
static int get_rsync_acl(const char *fname, rsync_acl *racl,
SMB_ACL_TYPE_T type, mode_t mode)
{
SMB_ACL_T sacl;
#ifndef SUPPORT_ACL_FD
#ifndef HAVE_SOLARIS_ACLS
(void)fd; /* Solaris drives the ACL via facl(2) on fd but has no SUPPORT_ACL_FD. */
#endif
(void)dirfd;
(void)leaf;
#endif
#ifdef SUPPORT_XATTRS
/* --fake-super support: load ACLs from an xattr. */
if (am_root < 0) {
@@ -603,7 +481,7 @@ static int get_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
size_t len;
int cnt;
if ((buf = get_xattr_acl(fname, fd, type == SMB_ACL_TYPE_ACCESS, &len)) == NULL)
if ((buf = get_xattr_acl(fname, type == SMB_ACL_TYPE_ACCESS, &len)) == NULL)
return 0;
cnt = (len - 4*4) / (4+4);
if (len < 4*4 || len != (size_t)cnt*(4+4) + 4*4) {
@@ -636,107 +514,6 @@ static int get_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
}
#endif
#ifdef HAVE_SOLARIS_ACLS
/* Solaris has no libacl *_at; read the ACL through the held fd via facl(2)
* when we have one. With no held fd this branch is skipped and the path-based
* call below reads the ACL (acceptable: a read can't redirect a write out of
* the tree). */
if (fd >= 0) {
if ((sacl = sys_acl_get_fd_type(fd, type)) != 0) {
BOOL ok = unpack_smb_acl(sacl, racl);
sys_acl_free_acl(sacl);
if (!ok) {
rsyserr(FERROR_XFER, errno, "get_acl: unpack_smb_acl(%s)", fname);
return -1;
}
return 0;
}
if (no_acl_syscall_error(errno)) {
if (type == SMB_ACL_TYPE_ACCESS)
rsync_acl_fake_perms(racl, mode);
return 0;
}
rsyserr(FERROR_XFER, errno, "get_acl: sys_acl_get_fd_type(%s, %s)",
fname, str_acl_type(type));
return -1;
}
#endif
#ifdef SUPPORT_ACL_FD
#ifdef HAVE_LIBACL_AT
/* Read the ACL via the new libacl *_at calls; fd<0 && dirfd<0
* (e.g. a synthetic dir) falls through to the path-based call below. */
if (fd >= 0 || dirfd >= 0) {
if (fd >= 0)
sacl = sys_acl_get_file_at(fd, "", AT_EMPTY_PATH, type);
else
sacl = sys_acl_get_file_at(dirfd, leaf, AT_SYMLINK_NOFOLLOW, type);
if (sacl != 0) {
BOOL ok = unpack_smb_acl(sacl, racl);
sys_acl_free_acl(sacl);
if (!ok) {
rsyserr(FERROR_XFER, errno, "get_acl: unpack_smb_acl(%s)", fname);
return -1;
}
return 0;
}
if (no_acl_syscall_error(errno)) {
if (type == SMB_ACL_TYPE_ACCESS)
rsync_acl_fake_perms(racl, mode);
return 0;
}
rsyserr(FERROR_XFER, errno, "get_acl: acl_get_file_at(%s, %s)",
fname, str_acl_type(type));
return -1;
}
#else
/* Race-safe path: read the ACL through the held O_NOFOLLOW fd, or via
* setxattrat(AT_SYMLINK_NOFOLLOW) on dirfd+leaf, instead of re-resolving
* fname. Only for real-root ACLs (am_root >= 0; the fake-super branch
* above already returned). */
if (fd >= 0 || (dirfd >= 0 && xacl_at_available())) {
int is_def = type == SMB_ACL_TYPE_DEFAULT;
rsync_acl_ent *ents = NULL;
int n = 0, rc;
if (fd >= 0)
rc = xacl_get_fd(fd, is_def, &ents, &n);
else
rc = xacl_get_at(dirfd, leaf, is_def, &ents, &n);
if (rc < 0) {
if (no_acl_syscall_error(errno)) {
if (type == SMB_ACL_TYPE_ACCESS)
rsync_acl_fake_perms(racl, mode);
return 0;
}
rsyserr(FERROR_XFER, errno, "get_acl: xacl_get(%s, %s)",
fname, str_acl_type(type));
return -1;
}
if (n == 0) {
/* No explicit ACL: mirror libacl's mode-derived access ACL
* (an absent default ACL stays empty). */
if (type == SMB_ACL_TYPE_ACCESS)
rsync_acl_fake_perms(racl, mode);
} else if (!unpack_acl_entries(ents, n, racl)) {
if (ents)
free(ents);
rsyserr(FERROR_XFER, errno, "get_acl: unpack_acl_entries(%s)", fname);
return -1;
}
if (ents)
free(ents);
return 0;
}
/* Neither a held fd nor a usable dirfd path (xacl_at_available() covers the
* *xattrat syscalls AND the pre-6.13 /proc/self/fd compat, so this is the
* BSDs / a /proc-less namespace / an un-pinnable entry): read the real
* destination ACL via the path-based call rather than a mode-only fake, so
* --acls stays functional where the race-safe primitive is unavailable. */
#endif /* HAVE_LIBACL_AT */
#endif
if ((sacl = sys_acl_get_file(fname, type)) != 0) {
BOOL ok = unpack_smb_acl(sacl, racl);
@@ -758,10 +535,8 @@ static int get_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
return 0;
}
/* Return the Access Control List for the given filename. When a held
* O_NOFOLLOW fd (or a dirfd+leaf) is available, the ACL is read race-safely
* through it; otherwise (fd < 0 && dirfd < 0) the path-based fallback is used. */
int get_acl_fdat(int fd, int dirfd, const char *leaf, const char *fname, stat_x *sxp)
/* Return the Access Control List for the given filename. */
int get_acl(const char *fname, stat_x *sxp)
{
sxp->acc_acl = create_racl();
@@ -782,7 +557,7 @@ int get_acl_fdat(int fd, int dirfd, const char *leaf, const char *fname, stat_x
} else if (IS_MISSING_FILE(sxp->st))
return 0;
if (get_rsync_acl(fd, dirfd, leaf, fname, sxp->acc_acl, SMB_ACL_TYPE_ACCESS,
if (get_rsync_acl(fname, sxp->acc_acl, SMB_ACL_TYPE_ACCESS,
sxp->st.st_mode) < 0) {
free_acl(sxp);
return -1;
@@ -790,7 +565,7 @@ int get_acl_fdat(int fd, int dirfd, const char *leaf, const char *fname, stat_x
if (S_ISDIR(sxp->st.st_mode)) {
sxp->def_acl = create_racl();
if (get_rsync_acl(fd, dirfd, leaf, fname, sxp->def_acl, SMB_ACL_TYPE_DEFAULT,
if (get_rsync_acl(fname, sxp->def_acl, SMB_ACL_TYPE_DEFAULT,
sxp->st.st_mode) < 0) {
free_acl(sxp);
return -1;
@@ -800,11 +575,6 @@ int get_acl_fdat(int fd, int dirfd, const char *leaf, const char *fname, stat_x
return 0;
}
int get_acl(const char *fname, stat_x *sxp)
{
return get_acl_fdat(-1, -1, NULL, fname, sxp);
}
/* === Send functions === */
/* Send the ida list over the file descriptor. */
@@ -1163,60 +933,17 @@ static mode_t change_sacl_perms(SMB_ACL_T sacl, rsync_acl *racl, mode_t old_mode
}
#endif
static int set_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
acl_duo *duo_item, SMB_ACL_TYPE_T type, stat_x *sxp, mode_t mode)
static int set_rsync_acl(const char *fname, acl_duo *duo_item,
SMB_ACL_TYPE_T type, stat_x *sxp, mode_t mode)
{
#ifndef SUPPORT_ACL_FD
#ifndef HAVE_SOLARIS_ACLS
(void)fd; /* Solaris drives the ACL via facl(2) on fd but has no SUPPORT_ACL_FD. */
#endif
(void)dirfd;
(void)leaf;
#endif
if (type == SMB_ACL_TYPE_DEFAULT
&& duo_item->racl.user_obj == NO_ENTRY) {
int rc;
#ifdef SUPPORT_XATTRS
/* --fake-super support: delete default ACL from xattrs. */
if (am_root < 0)
rc = del_def_xattr_acl(fd, fname);
rc = del_def_xattr_acl(fname);
else
#endif
#ifdef SUPPORT_ACL_FD
#ifdef HAVE_LIBACL_AT
/* Race-safe default-ACL delete via the new libacl *_at
* calls (held fd via AT_EMPTY_PATH, dirfd+leaf via AT_SYMLINK_NOFOLLOW)
* -- race-safe on every Linux kernel. fd<0 && dirfd<0 falls to path. */
if (fd >= 0)
rc = sys_acl_delete_def_file_at(fd, "", AT_EMPTY_PATH);
else if (dirfd >= 0)
rc = sys_acl_delete_def_file_at(dirfd, leaf, AT_SYMLINK_NOFOLLOW);
else
#else
/* Race-safe default-ACL delete via the held fd or dirfd+leaf. Where
* neither is available (xacl_at_available() is false -- the BSDs, a
* /proc-less namespace, an un-pinnable entry; every Linux with procfs
* takes the dirfd path via *xattrat or the /proc/self/fd compat) -- fall
* back to the path-based call, preferring the documented --acls behaviour
* over refusing it where the race-safe primitive is unavailable. */
if (fd >= 0)
rc = xacl_del_default_fd(fd);
else if (dirfd >= 0 && xacl_at_available())
rc = xacl_del_default_at(dirfd, leaf);
else
#endif /* HAVE_LIBACL_AT */
#endif
#ifdef HAVE_SOLARIS_ACLS
/* Solaris: delete the default ACL through the held fd via facl(2). For a
* root receiver a missing held fd means the leaf was raced, so refuse rather
* than let the path-based delete follow it; a plain non-root receiver keeps
* the legacy path fallback (op_pin am_root != 0 rule). */
if (fd >= 0)
rc = sys_acl_delete_def_fd(fd);
else if (vfs_relpath_active() && am_root) {
errno = ELOOP;
rc = -1;
} else
#endif
rc = sys_acl_delete_def_file(fname);
if (rc < 0) {
@@ -1245,7 +972,7 @@ static int set_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
SIVAL(bp, 4, ida->access);
}
}
rc = set_xattr_acl(fd, fname, type == SMB_ACL_TYPE_ACCESS, buf, len);
rc = set_xattr_acl(fname, type == SMB_ACL_TYPE_ACCESS, buf, len);
free(buf);
return rc;
#endif
@@ -1262,92 +989,6 @@ static int set_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
if (cur_mode == (mode_t)-1)
return 0;
}
#endif
#ifdef SUPPORT_ACL_FD
#ifdef HAVE_LIBACL_AT
/* Apply the packed/perm-reconciled ACL (duo_item->sacl)
* through the new libacl *_at calls -- held fd via AT_EMPTY_PATH,
* dirfd+leaf via AT_SYMLINK_NOFOLLOW -- race-safe on every Linux kernel,
* and byte-identical to the path-based sys_acl_set_file() below. */
if (fd >= 0 || dirfd >= 0) {
int rc;
if (fd >= 0)
rc = sys_acl_set_file_at(fd, "", AT_EMPTY_PATH, type, duo_item->sacl);
else
rc = sys_acl_set_file_at(dirfd, leaf, AT_SYMLINK_NOFOLLOW, type, duo_item->sacl);
if (rc < 0) {
rsyserr(FERROR_XFER, errno, "set_acl: acl_set_file_at(%s, %s)",
fname, str_acl_type(type));
return -1;
}
if (type == SMB_ACL_TYPE_ACCESS)
sxp->st.st_mode = cur_mode;
return 0;
}
#else
/* Race-safe write: serialize the packed (and perm-reconciled)
* system ACL to the kernel xattr format and apply it through the
* held fd or dirfd+leaf -- never re-resolving fname. This matches
* exactly what sys_acl_set_file() would have written. */
if (fd >= 0 || (dirfd >= 0 && xacl_at_available())) {
int is_def = type == SMB_ACL_TYPE_DEFAULT;
rsync_acl_ent *ents;
int n = sacl_to_entries(duo_item->sacl, &ents);
int rc;
if (n < 0)
return -1;
if (fd >= 0)
rc = xacl_set_fd(fd, is_def, ents, n);
else
rc = xacl_set_at(dirfd, leaf, is_def, ents, n);
free(ents);
if (rc < 0) {
rsyserr(FERROR_XFER, errno, "set_acl: xacl_set(%s, %s)",
fname, str_acl_type(type));
return -1;
}
if (type == SMB_ACL_TYPE_ACCESS)
sxp->st.st_mode = cur_mode;
return 0;
}
/* No held fd and no usable dirfd path (xacl_at_available() is false --
* the BSDs, a /proc-less namespace, an un-pinnable entry; every Linux
* with procfs took xacl_set_at() above via *xattrat or the /proc/self/fd
* compat): prefer the documented --acls behaviour over refusing it and
* fall back to the path-based set. This re-resolves fname, so it still
* carries the parent-symlink-race exposure on those remaining platforms;
* it is the only way to honour --acls where no race-safe primitive
* exists. */
#endif /* HAVE_LIBACL_AT */
#endif
#ifdef HAVE_SOLARIS_ACLS
/* Solaris: apply the ACL through the held fd via facl(2). */
if (fd >= 0) {
if (sys_acl_set_fd_type(fd, type, duo_item->sacl) < 0) {
rsyserr(FERROR_XFER, errno, "set_acl: sys_acl_set_fd_type(%s, %s)",
fname, str_acl_type(type));
return -1;
}
if (type == SMB_ACL_TYPE_ACCESS)
sxp->st.st_mode = cur_mode;
return 0;
}
if (vfs_relpath_active() && am_root) {
/* Real root always can open its own freshly-staged reg/dir/fifo leaf,
* so a missing held fd on a confined receiver means the leaf was raced
* to a symlink; sys_acl_set_file() follows the leaf, so refuse rather
* than write the attacker-supplied ACL onto a redirected inode (covers
* the top-level no-slash entry the caller's slashed-path xattr_refuse
* gate misses). A plain non-root receiver keeps the path-based fallback
* for a legitimately un-pinnable owned leaf (e.g. a 0300 dir), matching
* the operator-path op_pin rule (am_root != 0). */
errno = ELOOP;
rsyserr(FERROR_XFER, errno, "set_acl: refusing path-based ACL on %s (no held fd)",
fname);
return -1;
}
#endif
if (sys_acl_set_file(fname, type, duo_item->sacl) < 0) {
rsyserr(FERROR_XFER, errno, "set_acl: sys_acl_set_file(%s, %s)",
@@ -1365,16 +1006,11 @@ static int set_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
* dir), and the regular mode bits on the file. Call this with fname set to
* NULL to just check if the ACL is different.
*
* When a held O_NOFOLLOW fd (or a dirfd+leaf) is supplied, the ACL is applied
* race-safely through it; otherwise (fd < 0 && dirfd < 0) the path-based
* fallback is used.
*
* If the ACL operation has a side-effect of changing the file's mode, the
* sxp->st.st_mode value will be changed to match.
*
* Returns 0 for an unchanged ACL, 1 for changed, -1 for failed. */
int set_acl_fdat(int fd, int dirfd, const char *leaf, const char *fname,
const struct file_struct *file, stat_x *sxp, mode_t new_mode)
int set_acl(const char *fname, const struct file_struct *file, stat_x *sxp, mode_t new_mode)
{
int changed = 0;
int32 ndx;
@@ -1394,7 +1030,7 @@ int set_acl_fdat(int fd, int dirfd, const char *leaf, const char *fname,
if (!eq) {
changed = 1;
if (!dry_run && fname
&& set_rsync_acl(fd, dirfd, leaf, fname, duo_item, SMB_ACL_TYPE_ACCESS,
&& set_rsync_acl(fname, duo_item, SMB_ACL_TYPE_ACCESS,
sxp, new_mode) < 0)
return -1;
}
@@ -1411,7 +1047,7 @@ int set_acl_fdat(int fd, int dirfd, const char *leaf, const char *fname,
if (!eq) {
changed = 1;
if (!dry_run && fname
&& set_rsync_acl(fd, dirfd, leaf, fname, duo_item, SMB_ACL_TYPE_DEFAULT,
&& set_rsync_acl(fname, duo_item, SMB_ACL_TYPE_DEFAULT,
sxp, new_mode) < 0)
return -1;
}
@@ -1420,11 +1056,6 @@ int set_acl_fdat(int fd, int dirfd, const char *leaf, const char *fname,
return changed;
}
int set_acl(const char *fname, const struct file_struct *file, stat_x *sxp, mode_t new_mode)
{
return set_acl_fdat(-1, -1, NULL, fname, file, sxp, new_mode);
}
/* Non-incremental recursion needs to convert all the received IDs.
* This is done in a single pass after receiving the whole file-list. */
static void match_racl_ids(const item_list *racl_list)
+82
View File
@@ -0,0 +1,82 @@
/*
* Android-specific helpers.
*
* openat2() usability probe
* -------------------------
* openat2(2) is invoked directly via syscall() because the C library lacked a
* wrapper for it for years. Under a seccomp filter that uses
* SECCOMP_RET_TRAP -- as the Android application sandbox does -- a disallowed
* syscall raises SIGSYS and *kills the process* rather than failing with
* ENOSYS, so inspecting errno after the call is too late. We therefore probe
* openat2() once, behind a temporary SIGSYS handler, so a trapped syscall is
* caught and secure_relative_open_linux() can fall back to the portable
* per-component O_NOFOLLOW resolver instead of the whole process dying.
*
* This is only needed on Android, so the probe body is compiled only there.
* __ANDROID__ is defined by Bionic's headers and reflects the *target*, not
* the build host: it is set both for NDK cross-compiles (from a Linux/macOS
* host) and for native Termux builds, and is unset on every other platform.
* That makes it a reliable compile-time switch for cross builds -- there is
* nothing to detect in configure. Everywhere else openat2() is never
* seccomp-trapped to SIGSYS (a missing syscall simply returns ENOSYS), so
* openat2_usable() collapses to a constant 1 with no run-time cost.
*/
#include "rsync.h"
#if defined(__ANDROID__) && defined(HAVE_OPENAT2)
#include <setjmp.h>
#include <sys/syscall.h>
#include <linux/openat2.h>
static sigjmp_buf openat2_probe_env;
static void openat2_probe_handler(int signo)
{
(void)signo;
siglongjmp(openat2_probe_env, 1);
}
#endif
int openat2_usable(void)
{
#if defined(__ANDROID__) && defined(HAVE_OPENAT2)
static int cached = -1;
struct sigaction sa, old_sa;
if (cached >= 0)
return cached;
memset(&sa, 0, sizeof sa);
sa.sa_handler = openat2_probe_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSYS, &sa, &old_sa) != 0)
return cached = 0;
if (sigsetjmp(openat2_probe_env, 1) != 0) {
/* SIGSYS delivered: openat2 is blocked by a seccomp filter. */
cached = 0;
} else {
struct open_how how;
int fd;
memset(&how, 0, sizeof how);
how.flags = O_RDONLY | O_DIRECTORY;
how.resolve = RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS;
fd = syscall(SYS_openat2, AT_FDCWD, ".", &how, sizeof how);
if (fd >= 0)
close(fd);
/* Usable only if the probe actually succeeded. Any failure --
* ENOSYS (kernel < 5.6), a seccomp SECCOMP_RET_ERRNO denial
* (EPERM/EACCES), or EINVAL (RESOLVE_BENEATH unsupported) --
* means we must fall back to the portable O_NOFOLLOW walk. */
cached = fd >= 0;
}
sigaction(SIGSYS, &old_sa, NULL);
return cached;
#else
return 1;
#endif
}
+6 -104
View File
@@ -22,13 +22,6 @@
#include "itypes.h"
#include "ifuncs.h"
/* O_CLOEXEC is absent on some still-supported targets. The random-source fd
* is read and closed synchronously, so the established zero-value fallback is
* sufficient without adding a configure dependency. */
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
extern int read_only;
extern char *password_file;
extern struct name_num_obj valid_auth_checksums;
@@ -64,31 +57,10 @@ void base64_encode(const char *buf, int len, char *out, int pad)
out[i] = '\0';
}
/* Fill buf with len bytes from the kernel CSPRNG. Returns 1 on success.
* We read /dev/urandom directly rather than depending on getrandom()/
* arc4random_buf() availability so this works on every platform rsync
* targets without new configure probes. */
static int get_random_bytes(char *buf, int len)
{
int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
int got = 0;
if (fd < 0)
return 0;
while (got < len) {
int n = read(fd, buf + got, len - got);
if (n <= 0)
break;
got += n;
}
close(fd);
return got == len;
}
/* Generate a challenge buffer and return it base64-encoded. */
static void gen_challenge(const char *addr, char *challenge)
{
char input[32];
char rnd[32];
char digest[MAX_DIGEST_LEN];
struct timeval tv;
int len;
@@ -102,16 +74,6 @@ static void gen_challenge(const char *addr, char *challenge)
SIVAL(input, 24, getpid());
len = sum_init(valid_auth_checksums.negotiated_nni, 0);
/* The challenge must be unpredictable to a network observer; addr+time
* +pid alone is ~35 bits and lets an attacker enumerate the preimage
* offline. Hash 32 bytes from the kernel RNG first so the digest
* carries full entropy, keeping the legacy inputs as a mix-in so a
* urandom failure degrades to (never below) the old behaviour. */
if (get_random_bytes(rnd, sizeof rnd))
sum_update(rnd, sizeof rnd);
else
rprintf(FWARNING, "gen_challenge: /dev/urandom unavailable, "
"falling back to time-based challenge\n");
sum_update(input, sizeof input);
sum_end(digest);
@@ -148,25 +110,10 @@ static const char *check_secret(int module, const char *user, const char *group,
char *err;
FILE *fh;
/* Daemon 'secrets file = PATH' open. A planted symlink would be
* followed and the strict-modes fstat() check below runs on the target
* inode, so a symlink to /etc/shadow (0640 root:shadow) would pass and
* the daemon would auth against shadow hashes. Refuse symlinks not
* owned by uid 0 or our euid. */
if (!fname || !*fname)
if (!fname || !*fname || (fh = fopen(fname, "r")) == NULL)
return "no secrets file";
{
int fd = vfs_open_owner_walk(fname, O_RDONLY, 0, 0);
if (fd < 0)
return "no secrets file";
fh = fdopen(fd, "r");
if (!fh) {
close(fd);
return "no secrets file";
}
}
if (vfs_fstat(fileno(fh), &st) == -1) {
if (do_fstat(fileno(fh), &st) == -1) {
rsyserr(FLOG, errno, "fstat(%s)", fname);
ok = 0;
} else if (lp_strict_modes(module)) {
@@ -237,23 +184,13 @@ static const char *getpassf(const char *filename)
} else {
int fd;
/* --password-file=PATH client open. Its first line is sent as the
* auth response, so a planted symlink leaks the target's content
* (e.g. shadow hashes) to a malicious daemon; the vfs_stat()
* other-access check runs on the target mode and passes 0640
* root:shadow. Refuse symlinks not owned by uid 0 or our euid. */
if ((fd = vfs_open_owner_walk(filename, O_RDONLY, 0, 0)) < 0) {
if ((fd = open(filename,O_RDONLY)) < 0) {
rsyserr(FERROR, errno, "could not open password file %s", filename);
exit_cleanup(RERR_SYNTAX);
}
/* fstat the opened fd, not the pathname: a same-object check
* (matching check_secret() above) so an attacker who swaps the
* path between open and check can't make the owner/mode test
* validate a different inode than the one we read the password
* from. */
if (vfs_fstat(fd, &st) == -1) {
rsyserr(FERROR, errno, "fstat(%s)", filename);
if (do_stat(filename, &st) == -1) {
rsyserr(FERROR, errno, "stat(%s)", filename);
exit_cleanup(RERR_SYNTAX);
}
if ((st.st_mode & 06) != 0) {
@@ -303,35 +240,6 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
return "";
negotiate_daemon_auth(f_out, 0);
/* Enforce a configured minimum auth digest (default: none). This refuses
* a peer that negotiated -- or, via an omitted digest list / old protocol,
* fell back to -- a digest weaker than the operator-required floor, e.g. a
* client downgraded to md5/md4. Lower rank == stronger (the auth list is
* ordered strongest-first), so a higher rank than the floor is too weak. */
{
const char *min_digest = lp_auth_digest(module);
if (min_digest && *min_digest) {
int floor_rank = auth_digest_rank(min_digest);
int got_rank = auth_digest_rank(valid_auth_checksums.negotiated_nni->name);
if (floor_rank < 0) {
rprintf(FLOG, "auth failed on module %s from %s (%s): the "
"configured 'auth digest = %s' is not a supported digest "
"on this build\n",
lp_name(module), host, addr, min_digest);
return NULL;
}
if (got_rank < 0 || got_rank > floor_rank) {
rprintf(FLOG, "auth failed on module %s from %s (%s): negotiated "
"auth digest %s is weaker than the required "
"'auth digest = %s'\n",
lp_name(module), host, addr,
valid_auth_checksums.negotiated_nni->name, min_digest);
return NULL;
}
}
}
gen_challenge(addr, challenge);
io_printf(f_out, "%s%s\n", leader, challenge);
@@ -347,13 +255,7 @@ char *auth_server(int f_in, int f_out, int module, const char *host,
users = strdup(users);
/* conf_strtok() honours the documented leading-comma form: a value that
* starts with a comma splits on commas ALONE, so an entry may contain
* spaces -- which is how a group name with a space is written. Splitting
* on whitespace here tore such an entry apart, so the rule the admin wrote
* never matched and a rule they never wrote appeared from its tail. The
* daemon's gid field already uses this parser (clientserver.c). */
for (tok = conf_strtok(users); tok; tok = conf_strtok(NULL)) {
for (tok = strtok(users, " ,\t"); tok; tok = strtok(NULL, " ,\t")) {
char *opts;
/* See if the user appended :deny, :ro, or :rw. */
if ((opts = strchr(tok, ':')) != NULL) {
+42 -133
View File
@@ -34,33 +34,12 @@ extern char backup_dir_buf[MAXPATHLEN];
extern char *backup_suffix;
extern char *backup_dir;
/* Pin a backup SOURCE leaf with a confined O_NOFOLLOW fd (via the operator
* owner-walk resolver, like set_file_attrs's op_leaf_fd) so the ACL/xattr the
* backup caches off it are read through the held fd -- a parent-symlink race
* can't redirect the read out of the module. Returns -1 for a non-hardened
* receiver (caller path-reads) or for a raced/absent leaf on a hardened one
* (caller skips the cache rather than read through a flippable path; use
* backup_metadata_hardened() to tell the two -1 cases apart). */
int backup_metadata_hardened(void)
{
return vfs_relpath_active() && !vfs_symlink_optout_allowed();
}
int backup_source_fd(const char *path)
{
#if defined AT_FDCWD && defined O_NOFOLLOW
if (backup_metadata_hardened() && path && *path)
return vfs_open_at(path, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_CLOEXEC, 0, VFS_OPERATOR_PATH);
#endif
return -1;
}
/* Returns -1 on error, 0 on missing dir, and 1 on present dir. */
static int validate_backup_dir(void)
{
STRUCT_STAT st;
if (vfs_lstat(VFS_AT_FDCWD, backup_dir_buf, &st, VFS_OPERATOR_PATH) < 0) {
if (do_lstat_at(backup_dir_buf, &st) < 0) {
if (errno == ENOENT)
return 0;
rsyserr(FERROR, errno, "backup lstat %s failed", backup_dir_buf);
@@ -119,7 +98,7 @@ static BOOL copy_valid_path(const char *fname)
for ( ; b; name = b + 1, b = strchr(name, '/')) {
*b = '\0';
while (vfs_mkdir(VFS_AT_FDCWD, backup_dir_buf, ACCESSPERMS, VFS_OPERATOR_PATH) < 0) {
while (do_mkdir_at(backup_dir_buf, ACCESSPERMS) < 0) {
if (errno == EEXIST) {
val = validate_backup_dir();
if (val > 0)
@@ -135,36 +114,27 @@ static BOOL copy_valid_path(const char *fname)
/* Try to transfer the directory settings of the actual dir
* that the files are coming from. */
if (x_stat(rel, &sx.st, NULL, VFS_OPERATOR_PATH) < 0)
if (x_stat(rel, &sx.st, NULL) < 0)
rsyserr(FERROR, errno, "backup stat %s failed", full_fname(rel));
else {
struct file_struct *file;
if (!(file = make_file(rel, NULL, NULL, 0, NO_FILTERS)))
continue;
#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS
{ /* read the source dir's ACL/xattr through a confined fd */
int bfd = backup_source_fd(rel);
if (!backup_metadata_hardened() || bfd >= 0) {
# ifdef SUPPORT_ACLS
if (preserve_acls && !S_ISLNK(file->mode)) {
get_acl_fdat(bfd, -1, NULL, rel, &sx);
cache_tmp_acl(file, &sx);
free_acl(&sx);
}
# endif
# ifdef SUPPORT_XATTRS
if (preserve_xattrs) {
get_xattr(rel, bfd, &sx);
cache_tmp_xattr(file, &sx);
free_xattr(&sx);
}
# endif
}
if (bfd >= 0)
close(bfd);
#ifdef SUPPORT_ACLS
if (preserve_acls && !S_ISLNK(file->mode)) {
get_acl(rel, &sx);
cache_tmp_acl(file, &sx);
free_acl(&sx);
}
#endif
set_file_attrs(backup_dir_buf, file, NULL, NULL, ATTRS_OPERATOR_PATH);
#ifdef SUPPORT_XATTRS
if (preserve_xattrs) {
get_xattr(rel, &sx);
cache_tmp_xattr(file, &sx);
free_xattr(&sx);
}
#endif
set_file_attrs(backup_dir_buf, file, NULL, NULL, 0);
unmake_file(file);
}
@@ -189,15 +159,12 @@ char *get_backup_name(const char *fname)
if (backup_dir) {
static int initialized = 0;
if (!initialized) {
char dirbuf[MAXPATHLEN];
int ret;
if (strlcpy(dirbuf, backup_dir_buf, sizeof dirbuf) >= sizeof dirbuf) {
errno = ENAMETOOLONG;
return NULL;
}
if (backup_dir_len > 1)
dirbuf[backup_dir_len-1] = '\0';
ret = vfs_make_path(dirbuf, 0, VFS_OPERATOR_PATH);
backup_dir_buf[backup_dir_len-1] = '\0';
ret = make_path(backup_dir_buf, 0);
if (backup_dir_len > 1)
backup_dir_buf[backup_dir_len-1] = '/';
if (ret < 0)
return NULL;
initialized = 1;
@@ -230,11 +197,7 @@ static inline int link_or_rename(const char *from, const char *to,
if (IS_SPECIAL(stp->st_mode) || IS_DEVICE(stp->st_mode))
return 0; /* Use copy code. */
#endif
/* from = the live dest file being backed up (a transfer path); to = the
* --backup-dir path (operator). Per-operand policy keeps the transfer
* source under the secure receiver resolve and only owner-walks the
* operator backup parent. */
if (vfs_link_at(from, to, 0, VFS_OPERATOR_PATH) == 0) {
if (do_link_at(from, to) == 0) {
if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: HLINK %s successful.\n", from);
return 2;
@@ -244,12 +207,11 @@ static inline int link_or_rename(const char *from, const char *to,
return 0;
}
#endif
if (vfs_rename_at(from, to, 0, VFS_OPERATOR_PATH) == 0) {
if (do_rename_at(from, to) == 0) {
if (stp->st_nlink > 1 && !S_ISDIR(stp->st_mode)) {
/* If someone has hard-linked the file into the backup
* dir, rename() might return success but do nothing! from is the
* transfer-side source, so unlink it under the secure resolve (0). */
robust_unlink(from, 0); /* Just in case... */
* dir, rename() might return success but do nothing! */
robust_unlink(from); /* Just in case... */
}
if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: RENAME %s successful.\n", from);
@@ -261,7 +223,7 @@ static inline int link_or_rename(const char *from, const char *to,
/* Hard-link, rename, or copy an item to the backup name. Returns 0 for
* failure, 1 if item was moved, 2 if item was duplicated or hard linked
* into backup area, or 3 if item doesn't exist or isn't a regular file. */
static int make_backup_inner(const char *fname, BOOL prefer_rename)
int make_backup(const char *fname, BOOL prefer_rename)
{
stat_x sx;
struct file_struct *file;
@@ -271,44 +233,12 @@ static int make_backup_inner(const char *fname, BOOL prefer_rename)
init_stat_x(&sx);
/* Return success if no file to keep. */
if (x_lstat(fname, &sx.st, NULL, VFS_OPERATOR_PATH) < 0)
if (x_lstat(fname, &sx.st, NULL) < 0)
return 3;
if (!(buf = get_backup_name(fname)))
return 0;
#ifdef SUPPORT_LINKS
/* Honor --safe-links BEFORE the hard-link / rename fast path. When
* CAN_HARDLINK_SYMLINK is defined, link_or_rename() would otherwise
* hard-link an escaping symlink (e.g. ../../etc/passwd) into the backup
* area and "goto success", skipping the safe_symlinks check in the
* copy-fallback path below -- silently preserving an unsafe link that
* --safe-links was meant to drop. Match the copy path: don't back up an
* unsafe symlink. */
if (preserve_links && S_ISLNK(sx.st.st_mode) && safe_symlinks) {
char lnkbuf[MAXPATHLEN];
int llen = vfs_readlink(fname, lnkbuf, MAXPATHLEN - 1);
/* A failed readlink means we can't verify the target, so fail
* closed: skip the backup rather than let the hard-link fast path
* preserve a possibly-unsafe symlink unchecked. */
if (llen <= 0) {
if (INFO_GTE(SYMSAFE, 1))
rprintf(FINFO, "not backing up symlink with unreadable target \"%s\"\n", fname);
ret = 2;
goto success;
}
lnkbuf[llen] = '\0';
if (unsafe_symlink(lnkbuf, fname)) {
if (INFO_GTE(SYMSAFE, 1)) {
rprintf(FINFO, "not backing up unsafe symlink \"%s\" -> \"%s\"\n",
fname, lnkbuf);
}
ret = 2;
goto success;
}
}
#endif
/* Try a hard-link or a rename first. Using rename is not atomic, but
* is more efficient than forcing a copy for larger files when no hard-
* linking is possible. */
@@ -316,7 +246,7 @@ static int make_backup_inner(const char *fname, BOOL prefer_rename)
goto success;
if (errno == EEXIST || errno == EISDIR) {
STRUCT_STAT bakst;
if (vfs_lstat(VFS_AT_FDCWD, buf, &bakst, VFS_OPERATOR_PATH) == 0) {
if (do_lstat_at(buf, &bakst) == 0) {
int flags = get_del_for_flag(bakst.st_mode) | DEL_FOR_BACKUP | DEL_RECURSE;
if (delete_item(buf, bakst.st_mode, flags) != 0)
return 0;
@@ -329,34 +259,25 @@ static int make_backup_inner(const char *fname, BOOL prefer_rename)
if (!(file = make_file(fname, NULL, &sx.st, 0, NO_FILTERS)))
return 3; /* the file could have disappeared */
#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS
{ /* read the source file's ACL/xattr through a confined fd */
int bfd = backup_source_fd(fname);
if (!backup_metadata_hardened() || bfd >= 0) {
# ifdef SUPPORT_ACLS
if (preserve_acls && !S_ISLNK(file->mode)) {
get_acl_fdat(bfd, -1, NULL, fname, &sx);
cache_tmp_acl(file, &sx);
free_acl(&sx);
}
# endif
# ifdef SUPPORT_XATTRS
if (preserve_xattrs) {
get_xattr(fname, bfd, &sx);
cache_tmp_xattr(file, &sx);
free_xattr(&sx);
}
# endif
#ifdef SUPPORT_ACLS
if (preserve_acls && !S_ISLNK(file->mode)) {
get_acl(fname, &sx);
cache_tmp_acl(file, &sx);
free_acl(&sx);
}
if (bfd >= 0)
close(bfd);
#endif
#ifdef SUPPORT_XATTRS
if (preserve_xattrs) {
get_xattr(fname, &sx);
cache_tmp_xattr(file, &sx);
free_xattr(&sx);
}
#endif
/* Check to see if this is a device file, or link */
if ((am_root && preserve_devices && IS_DEVICE(file->mode))
|| (preserve_specials && IS_SPECIAL(file->mode))) {
if (vfs_mknod(VFS_AT_FDCWD, buf, file->mode, sx.st.st_rdev, VFS_OPERATOR_PATH) < 0)
if (do_mknod_at(buf, file->mode, sx.st.st_rdev) < 0)
rsyserr(FERROR, errno, "mknod %s failed", full_fname(buf));
else if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: DEVICE %s successful.\n", fname);
@@ -373,7 +294,7 @@ static int make_backup_inner(const char *fname, BOOL prefer_rename)
}
ret = 2;
} else {
if (vfs_symlink(sl, VFS_AT_FDCWD, buf, VFS_OPERATOR_PATH) < 0)
if (do_symlink_at(sl, buf) < 0)
rsyserr(FERROR, errno, "link %s -> \"%s\"", full_fname(buf), sl);
else if (DEBUG_GTE(BACKUP, 1))
rprintf(FINFO, "make_backup: SYMLINK %s successful.\n", fname);
@@ -397,7 +318,7 @@ static int make_backup_inner(const char *fname, BOOL prefer_rename)
/* Copy to backup tree if a file. */
if (!ret) {
if (copy_file(fname, buf, -1, file->mode, VFS_OPERATOR_PATH) < 0) {
if (copy_file(fname, buf, -1, file->mode) < 0) {
rsyserr(FERROR, errno, "keep_backup failed: %s -> \"%s\"",
full_fname(fname), buf);
unmake_file(file);
@@ -416,7 +337,7 @@ static int make_backup_inner(const char *fname, BOOL prefer_rename)
save_preserve_xattrs = preserve_xattrs;
preserve_xattrs = 0;
set_file_attrs(buf, file, NULL, fname, ATTRS_OPERATOR_PATH | ATTRS_ACCURATE_TIME);
set_file_attrs(buf, file, NULL, fname, ATTRS_ACCURATE_TIME);
preserve_xattrs = save_preserve_xattrs;
unmake_file(file);
@@ -432,15 +353,3 @@ static int make_backup_inner(const char *fname, BOOL prefer_rename)
rprintf(FINFO, "backed up %s to %s\n", fname, buf);
return ret;
}
int make_backup(const char *fname, BOOL prefer_rename)
{
int ret;
/* The --backup-dir is an operator-supplied path: resolve it (and the
* tail/rename beneath it) with the ownership walk so a foreign-owned
* symlink component is refused while the operator's own is followed --
* absolute and relative alike. --insecure-links / "insecure links ="
* restores legacy following. */
ret = make_backup_inner(fname, prefer_rename);
return ret;
}
+19 -55
View File
@@ -166,33 +166,25 @@ static int write_arg(const char *arg)
const char *x, *s;
int len, err = 0;
/* Emit a "--opt=" prefix unquoted only when it is a plain option token;
* a metacharacter before '=' (an attacker-shaped arg) must be quoted
* along with the rest, or it would run raw in the replay script. */
if (*arg == '-' && (x = strchr(arg, '=')) != NULL) {
const char *p = arg;
while (p < x && (*p == '-' || *p == '_'
|| (*p >= '0' && *p <= '9')
|| (*p >= 'A' && *p <= 'Z')
|| (*p >= 'a' && *p <= 'z')))
p++;
if (p == x) {
err |= write(batch_sh_fd, arg, x - arg + 1) != x - arg + 1;
arg += x - arg + 1;
}
err |= write(batch_sh_fd, arg, x - arg + 1) != x - arg + 1;
arg += x - arg + 1;
}
/* Single-quote unconditionally so every shell metacharacter (backtick,
* newline, redirection, ...) stays literal in the replay script. An
* embedded ' is emitted as the '\'' close/escape/reopen sequence. */
err |= write(batch_sh_fd, "'", 1) != 1;
for (s = arg; (x = strchr(s, '\'')) != NULL; s = x + 1) {
err |= write(batch_sh_fd, s, x - s) != x - s;
err |= write(batch_sh_fd, "'\\''", 4) != 4;
if (strpbrk(arg, " \"'&;|[]()$#!*?^\\") != NULL) {
err |= write(batch_sh_fd, "'", 1) != 1;
for (s = arg; (x = strchr(s, '\'')) != NULL; s = x + 1) {
err |= write(batch_sh_fd, s, x - s + 1) != x - s + 1;
err |= write(batch_sh_fd, "'", 1) != 1;
}
len = strlen(s);
err |= write(batch_sh_fd, s, len) != len;
err |= write(batch_sh_fd, "'", 1) != 1;
return err;
}
len = strlen(s);
err |= write(batch_sh_fd, s, len) != len;
err |= write(batch_sh_fd, "'", 1) != 1;
len = strlen(arg);
err |= write(batch_sh_fd, arg, len) != len;
return err;
}
@@ -202,7 +194,7 @@ static int write_opt(const char *opt, const char *arg)
{
int len = strlen(opt);
int err = write(batch_sh_fd, " ", 1) != 1;
err |= write(batch_sh_fd, opt, len) != len;
err = write(batch_sh_fd, opt, len) != len ? 1 : 0;
if (arg) {
err |= write(batch_sh_fd, "=", 1) != 1;
err |= write_arg(arg);
@@ -218,16 +210,6 @@ static void write_filter_rules(int fd)
for (ent = filter_list.head; ent; ent = ent->next) {
unsigned int plen;
char *p = get_rule_prefix(ent, "- ", 0, &plen);
/* A filter pattern is one here-doc line; an embedded newline would let
* a crafted pattern (e.g. from a dir-merge/--exclude-from file in an
* untrusted tree) forge the "#E#" terminator on its own line and inject
* shell commands into the generated replay script. Such a pattern also
* can't round-trip the line-delimited here-doc, so refuse it fail-closed
* rather than emit an injectable script. */
if (ent->pattern && strchr(ent->pattern, '\n')) {
rprintf(FERROR, "cannot write a filter rule containing a newline to the batch replay script\n");
exit_cleanup(RERR_SYNTAX);
}
write_buf(fd, p, plen);
write_sbuf(fd, ent->pattern);
if (ent->rflags & FILTRULE_DIRECTORY)
@@ -242,45 +224,27 @@ static void write_filter_rules(int fd)
/* This sets batch_fd and (for --write-batch) batch_sh_fd. */
void open_batch_files(void)
{
/* --write-batch/--read-batch are operator-supplied; a planted symlink
* could truncate+overwrite an arbitrary file (write side) or stream
* attacker bytes into the protocol parser (read side). Refuse symlinks
* not owned by uid 0 or our euid anywhere in the path. */
if (write_batch) {
char filename[MAXPATHLEN];
stringjoin(filename, sizeof filename, batch_name, ".sh", NULL);
batch_sh_fd = vfs_open_owner_walk(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR | S_IXUSR, 0);
batch_sh_fd = do_open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR);
if (batch_sh_fd < 0) {
rsyserr(FERROR, errno, "Batch file %s open error", full_fname(filename));
exit_cleanup(RERR_FILESELECT);
}
/* O_BINARY: the batch stream is binary protocol data; without it
* Cygwin et al apply CRLF translation and corrupt it. Unlike
* vfs_open(), vfs_open_owner_walk passes flags verbatim. */
batch_fd = vfs_open_owner_walk(batch_name, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IRUSR | S_IWUSR, 0);
batch_fd = do_open(batch_name, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
} else if (strcmp(batch_name, "-") == 0)
batch_fd = STDIN_FILENO;
else
batch_fd = vfs_open_owner_walk(batch_name, O_RDONLY | O_BINARY, S_IRUSR | S_IWUSR, 0);
batch_fd = do_open(batch_name, O_RDONLY, S_IRUSR | S_IWUSR);
if (batch_fd < 0) {
rsyserr(FERROR, errno, "Batch file %s open error", full_fname(batch_name));
exit_cleanup(RERR_FILEIO);
}
/* --read-batch: the file's bytes drive the protocol parser, so refuse
* non-regular files (FIFO, device, socket) at the batch path. */
if (!write_batch && batch_fd != STDIN_FILENO) {
STRUCT_STAT st;
if (vfs_fstat(batch_fd, &st) == 0 && !S_ISREG(st.st_mode)) {
rprintf(FERROR, "Batch file %s is not a regular file\n",
full_fname(batch_name));
exit_cleanup(RERR_FILEIO);
}
}
}
/* This routine tries to write out an equivalent --read-batch command
-31
View File
@@ -68,26 +68,10 @@ SIVAL64(char *buf, int pos, int64 val)
#else /* !CAREFUL_ALIGNMENT */
/* We don't want false positives about alignment from UBSAN, see:
https://github.com/WayneD/rsync/issues/427#issuecomment-1375132291
*/
/* From https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html */
#ifndef GCC_VERSION
#define GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \
+ __GNUC_PATCHLEVEL__)
#endif
/* This handles things for architectures like the 386 that can handle alignment errors.
* WARNING: This section is dependent on the length of an int32 (and thus a uint32)
* being correct (4 bytes)! Set CAREFUL_ALIGNMENT if it is not. */
#ifdef __clang__
__attribute__((no_sanitize("undefined")))
#elif GCC_VERSION >= 409
__attribute__((no_sanitize_undefined))
#endif
static inline uint32
IVALu(const uchar *buf, int pos)
{
@@ -99,11 +83,6 @@ IVALu(const uchar *buf, int pos)
return *u.num;
}
#ifdef __clang__
__attribute__((no_sanitize("undefined")))
#elif GCC_VERSION >= 409
__attribute__((no_sanitize_undefined))
#endif
static inline void
SIVALu(uchar *buf, int pos, uint32 val)
{
@@ -115,11 +94,6 @@ SIVALu(uchar *buf, int pos, uint32 val)
*u.num = val;
}
#ifdef __clang__
__attribute__((no_sanitize("undefined")))
#elif GCC_VERSION >= 409
__attribute__((no_sanitize_undefined))
#endif
static inline int64
IVAL64(const char *buf, int pos)
{
@@ -131,11 +105,6 @@ IVAL64(const char *buf, int pos)
return *u.num;
}
#ifdef __clang__
__attribute__((no_sanitize("undefined")))
#elif GCC_VERSION >= 409
__attribute__((no_sanitize_undefined))
#endif
static inline void
SIVAL64(char *buf, int pos, int64 val)
{
+1 -19
View File
@@ -87,24 +87,6 @@ struct name_num_obj valid_auth_checksums = {
"daemon auth checksum", NULL, 0, 0, valid_auth_checksums_items
};
/* Return the strength rank (0 = strongest) of a daemon-auth digest by name in
* valid_auth_checksums_items[], which is listed strongest-first; -1 if the name
* is not a supported auth digest on this build. Used by the daemon's
* "auth digest" floor to compare the negotiated digest against the minimum. */
int auth_digest_rank(const char *name)
{
struct name_num_item *nni;
int rank = 0;
if (!name || !*name)
return -1;
for (nni = valid_auth_checksums_items; nni->name; nni++, rank++) {
if (strcasecmp(nni->name, name) == 0)
return rank;
}
return -1;
}
/* These cannot make use of openssl, so they're marked just as built-in */
struct name_num_item implied_checksum_md4 =
{ CSUM_MD4, NNI_BUILTIN, "md4", NULL };
@@ -423,7 +405,7 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
int32 remainder;
int fd;
fd = vfs_open_checklinks(fname);
fd = do_open_checklinks(fname);
if (fd == -1) {
memset(sum, 0, file_sum_len);
return;
+7 -89
View File
@@ -29,7 +29,7 @@ extern mode_t orig_umask;
struct chmod_mode_struct {
struct chmod_mode_struct *next;
int ModeAND, ModeOR, ModeCOPY_SRC, ModeCOPY_DST, ModeCOPY_AND, ModeOP;
int ModeAND, ModeOR;
char flags;
};
@@ -43,20 +43,6 @@ struct chmod_mode_struct {
#define STATE_2ND_HALF 2
#define STATE_OCTAL_NUM 3
static int mode_dest_special_bits(int where)
{
int bits = 0;
if (where & 0100)
bits |= S_ISUID;
if (where & 0010)
bits |= S_ISGID;
if (where & 0001)
bits |= S_ISVTX;
return bits;
}
/* Parse a chmod-style argument, and break it down into one or more AND/OR
* pairs in a linked list. We return a pointer to new items on success
* (appending the items to the specified list), or NULL on error. */
@@ -64,13 +50,13 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
struct chmod_mode_struct **root_mode_ptr)
{
int state = STATE_1ST_HALF;
int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0, copybits = 0;
int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0;
struct chmod_mode_struct *first_mode = NULL, *curr_mode = NULL,
*prev_mode = NULL;
while (state != STATE_ERROR) {
if (!*modestr || *modestr == ',') {
int bits, where_specified;
int bits;
if (!op) {
state = STATE_ERROR;
@@ -84,10 +70,9 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
first_mode = curr_mode;
curr_mode->next = NULL;
where_specified = where;
if (where) {
if (where)
bits = where * what;
} else {
else {
where = 0111;
bits = (where * what) & ~orig_umask;
}
@@ -96,35 +81,18 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
case CHMOD_ADD:
curr_mode->ModeAND = CHMOD_BITS;
curr_mode->ModeOR = bits + topoct;
curr_mode->ModeCOPY_SRC = copybits;
curr_mode->ModeCOPY_DST = where;
curr_mode->ModeCOPY_AND = where_specified ? CHMOD_BITS : ~orig_umask;
curr_mode->ModeOP = op;
break;
case CHMOD_SUB:
curr_mode->ModeAND = CHMOD_BITS - bits - topoct;
curr_mode->ModeOR = 0;
curr_mode->ModeCOPY_SRC = copybits;
curr_mode->ModeCOPY_DST = where;
curr_mode->ModeCOPY_AND = where_specified ? CHMOD_BITS : ~orig_umask;
curr_mode->ModeOP = op;
break;
case CHMOD_EQ:
curr_mode->ModeAND = CHMOD_BITS - (where * 7) - (topoct ? topbits : 0)
- (copybits ? mode_dest_special_bits(where) : 0);
curr_mode->ModeAND = CHMOD_BITS - (where * 7) - (topoct ? topbits : 0);
curr_mode->ModeOR = bits + topoct;
curr_mode->ModeCOPY_SRC = copybits;
curr_mode->ModeCOPY_DST = where;
curr_mode->ModeCOPY_AND = where_specified ? CHMOD_BITS : ~orig_umask;
curr_mode->ModeOP = op;
break;
case CHMOD_SET:
curr_mode->ModeAND = 0;
curr_mode->ModeOR = bits;
curr_mode->ModeCOPY_SRC = 0;
curr_mode->ModeCOPY_DST = 0;
curr_mode->ModeCOPY_AND = CHMOD_BITS;
curr_mode->ModeOP = op;
break;
}
@@ -135,7 +103,7 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
modestr++;
state = STATE_1ST_HALF;
where = what = op = topoct = topbits = flags = copybits = 0;
where = what = op = topoct = topbits = flags = 0;
}
switch (state) {
@@ -164,7 +132,6 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
break;
case 'a':
where |= 0111;
topbits |= 06000; /* a+s sets BOTH setuid and setgid (like chmod(1)) */
break;
case '+':
op = CHMOD_ADD;
@@ -192,53 +159,26 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
case STATE_2ND_HALF:
switch (*modestr) {
case 'r':
if (copybits)
state = STATE_ERROR;
what |= 4;
break;
case 'w':
if (copybits)
state = STATE_ERROR;
what |= 2;
break;
case 'X':
if (copybits)
state = STATE_ERROR;
flags |= FLAG_X_KEEP;
/* FALL THROUGH */
case 'x':
if (copybits)
state = STATE_ERROR;
what |= 1;
break;
case 's':
if (copybits)
state = STATE_ERROR;
if (topbits)
topoct |= topbits;
else
topoct = 04000;
break;
case 't':
if (copybits)
state = STATE_ERROR;
topoct |= 01000;
break;
case 'u':
if (what || topoct || copybits)
state = STATE_ERROR;
copybits = 0100;
break;
case 'g':
if (what || topoct || copybits)
state = STATE_ERROR;
copybits = 0010;
break;
case 'o':
if (what || topoct || copybits)
state = STATE_ERROR;
copybits = 0001;
break;
default:
state = STATE_ERROR;
break;
@@ -272,20 +212,6 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
return first_mode;
}
static int mode_copy_bits(int mode, int copy_src, int copy_dst, int copy_and)
{
int copy_bits = 0;
if (copy_src & 0100)
copy_bits |= (mode >> 6) & 7;
if (copy_src & 0010)
copy_bits |= (mode >> 3) & 7;
if (copy_src & 0001)
copy_bits |= mode & 7;
return (copy_dst * copy_bits) & copy_and;
}
/* Takes an existing file permission and a list of AND/OR changes, and
* create a new permissions. */
@@ -293,25 +219,17 @@ int tweak_mode(int mode, struct chmod_mode_struct *chmod_modes)
{
int IsX = mode & 0111;
int NonPerm = mode & ~CHMOD_BITS;
int copy_bits;
for ( ; chmod_modes; chmod_modes = chmod_modes->next) {
if ((chmod_modes->flags & FLAG_DIRS_ONLY) && !S_ISDIR(NonPerm))
continue;
if ((chmod_modes->flags & FLAG_FILES_ONLY) && S_ISDIR(NonPerm))
continue;
copy_bits = mode_copy_bits(mode, chmod_modes->ModeCOPY_SRC,
chmod_modes->ModeCOPY_DST,
chmod_modes->ModeCOPY_AND);
mode &= chmod_modes->ModeAND;
if ((chmod_modes->flags & FLAG_X_KEEP) && !IsX && !S_ISDIR(NonPerm))
mode |= chmod_modes->ModeOR & ~0111;
else
mode |= chmod_modes->ModeOR;
if (chmod_modes->ModeOP == CHMOD_SUB)
mode &= CHMOD_BITS - copy_bits;
else
mode |= copy_bits;
}
return mode | NonPerm;
+3 -11
View File
@@ -58,7 +58,7 @@ void close_all(void)
max_fd = sysconf(_SC_OPEN_MAX) - 1;
for (fd = max_fd; fd >= 0; fd--) {
if ((ret = vfs_fstat(fd, &st)) == 0) {
if ((ret = do_fstat(fd, &st)) == 0) {
if (is_a_socket(fd))
ret = shutdown(fd, 2);
ret = close(fd);
@@ -198,7 +198,7 @@ NORETURN void _exit_cleanup(int code, const char *file, int line)
switch_step++;
if (cleanup_fname)
vfs_unlink(VFS_AT_FDCWD, cleanup_fname, 0);
do_unlink_at(cleanup_fname);
if (exit_code)
kill_all(SIGUSR1);
if (cleanup_pid && cleanup_pid == getpid()) {
@@ -269,16 +269,8 @@ NORETURN void _exit_cleanup(int code, const char *file, int line)
break;
}
if (called_from_signal_handler) {
#ifdef GCOV_COVERAGE
/* _exit() bypasses the gcov atexit flush; rsync's generator (and
* other processes) normally finish via the signal handler, so
* without this they would write no .gcda. Harmless otherwise. */
extern void __gcov_dump(void);
__gcov_dump();
#endif
if (called_from_signal_handler)
_exit(exit_code);
}
exit(exit_code);
}
+29 -227
View File
@@ -42,7 +42,6 @@ extern int munge_symlinks;
extern int use_secure_symlinks;
extern int open_noatime;
extern int sanitize_paths;
extern int daemon_config_filter_file;
extern int numeric_ids;
extern int filesfrom_fd;
extern int remote_protocol;
@@ -71,8 +70,6 @@ extern gid_t our_gid;
char *auth_user;
char *daemon_auth_choices;
/* read_args() enforces MAX_DAEMON_ARGS and reports "too many daemon arguments"
* before a daemon client can grow argv without bound. */
int read_only = 0;
int module_id = -1;
int pid_file_fd = -1;
@@ -84,32 +81,11 @@ struct chmod_mode_struct *daemon_chmod_modes;
#define EARLY_INPUT_CMD "#early_input="
#define EARLY_INPUT_CMDLEN (sizeof EARLY_INPUT_CMD - 1)
/* Fallback bound on each peer-driven daemon handshake phase when no positive
* "timeout" is configured. A module value can shorten the pre-auth and
* argument-read phases, but cannot extend either beyond this limit. */
#define DAEMON_HANDSHAKE_TIMEOUT 60
static int daemon_handshake_timeout(int module)
{
int timeout = lp_timeout(module);
/* "timeout" is parsed with atoi(), so negative values are possible. */
if (timeout <= 0 || timeout > DAEMON_HANDSHAKE_TIMEOUT)
timeout = DAEMON_HANDSHAKE_TIMEOUT;
return timeout;
}
/* module_dirlen is the length of the module_dir string when in daemon
* mode and module_dir is not "/"; otherwise 0. (Note that a chroot-
* enabled module can have a non-"/" module_dir these days.) */
char *module_dir = NULL;
unsigned int module_dirlen = 0;
/* An fd held open on the served module root, captured while the daemon is still
* positioned there (and privileged) -- so the sender's directory scan can be
* confined beneath the module by resolving module-relative paths against this fd,
* without re-walking (and re-permission-checking) the absolute module path as the
* dropped-privilege module uid. -1 when not a daemon or not yet captured. */
int module_dirfd = -1;
char *full_module_path;
@@ -182,12 +158,7 @@ static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int
if (!am_client) {
char *motd = lp_motd_file();
if (motd && *motd) {
/* 'motd file = PATH': motd content is sent to every client, so
* a planted symlink would leak the target's bytes. Refuse
* symlinks not owned by uid 0 or our euid. */
int motd_fd = vfs_open_owner_walk(motd, O_RDONLY, 0, 0);
FILE *f = motd_fd >= 0 ? fdopen(motd_fd, "r") : NULL;
if (!f && motd_fd >= 0) close(motd_fd);
FILE *f = fopen(motd, "r");
while (f && !feof(f)) {
int len = fread(buf, 1, bufsiz - 1, f);
if (len > 0)
@@ -291,30 +262,19 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char
if (!user)
user = getenv("LOGNAME");
if (exchange_protocols(f_in, f_out, line, sizeof line, 1) < 0) {
free(modname);
if (exchange_protocols(f_in, f_out, line, sizeof line, 1) < 0)
return -1;
}
if (early_input_file) {
STRUCT_STAT st;
/* --early-input-file=PATH: refuse symlinks not owned by uid 0 or
* our euid anywhere in the path. */
int ei_fd = vfs_open_owner_walk(early_input_file, O_RDONLY, 0, 0);
FILE *f = ei_fd >= 0 ? fdopen(ei_fd, "rb") : NULL;
if (!f && ei_fd >= 0) close(ei_fd);
if (!f || vfs_fstat(fileno(f), &st) < 0) {
FILE *f = fopen(early_input_file, "rb");
if (!f || do_fstat(fileno(f), &st) < 0) {
rsyserr(FERROR, errno, "failed to open %s", early_input_file);
if (f)
fclose(f);
free(modname);
return -1;
}
early_input_len = st.st_size;
if (early_input_len > (int)sizeof line) {
rprintf(FERROR, "%s is > %d bytes.\n", early_input_file, (int)sizeof line);
fclose(f);
free(modname);
return -1;
}
if (early_input_len > 0) {
@@ -323,8 +283,6 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char
int len;
if (feof(f)) {
rprintf(FERROR, "Early EOF in %s\n", early_input_file);
fclose(f);
free(modname);
return -1;
}
len = fread(line, 1, early_input_len, f);
@@ -401,7 +359,6 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char
while (1) {
if (!read_line_old(f_in, line, sizeof line, 0)) {
rprintf(FERROR, "rsync: didn't get server startup line\n");
free(modname);
return -1;
}
@@ -425,7 +382,6 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char
rprintf(FERROR, "%s\n", line);
/* This is always fatal; the server will now
* close the socket. */
free(modname);
return -1;
}
@@ -587,7 +543,6 @@ static pid_t start_pre_exec(const char *cmd, int *arg_fd_ptr, int *error_fd_ptr)
status = shell_exec(cmd);
gcov_flush();
if (!WIFEXITED(status))
_exit(1);
_exit(WEXITSTATUS(status));
@@ -803,9 +758,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
}
read_only = lp_read_only(i); /* may also be overridden by auth_server() */
/* The module is now known, so its local timeout policy can tighten the
* absolute deadline while the claimed slot is awaiting authentication. */
set_daemon_handshake_timeout(daemon_handshake_timeout(i));
auth_user = auth_server(f_in, f_out, i, host, addr, "@RSYNCD: AUTHREQD ");
if (!auth_user) {
@@ -813,10 +765,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
return -1;
}
set_env_str("RSYNC_USER_NAME", auth_user);
/* Do not count local setup or operator hooks against a peer's read time.
* In particular, the post-xfer parent and pre-xfer/name-converter children
* are forked below and must never inherit an armed asynchronous deadline. */
set_daemon_handshake_timeout(0);
module_id = i;
@@ -925,17 +873,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
} else
set_filter_dir(module_dir, module_dirlen);
/* Snapshot the module root for the VFS confinement checks now that the
* path is final. The root dirfd is pinned later (below); this first call
* must precede any VFS open of an operator-supplied path -- the filter/
* include files just below, and the log file -- so they see the boundary. */
vfs_set_module_root(module_dir, module_dirlen, -1);
/* Everything loaded from here to the end of the exclude block is the
* operator's own configuration, so it keeps the ownership walk without the
* module-confinement parse_filter_file() applies to peer-driven merges. */
daemon_config_filter_file = 1;
p = lp_filter(module_id);
parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT),
XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3);
@@ -957,8 +894,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
parse_filter_str(&daemon_filter_list, p, rule_template(FILTRULE_WORD_SPLIT),
XFLG_ABS_IF_SLASH | XFLG_DIR2WILD3 | XFLG_OLD_PREFIXES);
daemon_config_filter_file = 0;
log_init(1);
#if defined HAVE_SETENV || defined HAVE_PUTENV
@@ -992,7 +927,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
set_env_num("RSYNC_EXIT_STATUS", status);
if (shell_exec(lp_postxfer_exec(module_id)) < 0)
status = -1;
gcov_flush();
_exit(status);
}
}
@@ -1046,13 +980,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
if (use_chroot) {
/* Cache timezone data before chroot makes /etc/localtime inaccessible */
tzset();
/* Flush gcov counters now: after chroot the build-tree .gcda
* paths are unreachable, so everything this child has executed
* so far (the whole rsync_module() pre-chroot path) would
* otherwise be lost. Post-chroot coverage from this child is
* still unrecordable -- accepted, documented in
* testsuite/COVERAGE.md. */
gcov_flush();
if (chroot(module_chdir)) {
rsyserr(FLOG, errno, "chroot(\"%s\") failed", module_chdir);
io_printf(f_out, "@ERROR: chroot failed\n");
@@ -1064,14 +991,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
if (!change_dir(module_chdir, CD_NORMAL))
return path_failure(f_out, module_chdir, True);
/* Pin the module root by identity now -- cwd is the served root and we are
* still privileged -- so the sender's later directory scans resolve against
* this fd rather than re-walking the absolute module path post-setuid. */
#if defined HAVE_FDOPENDIR && defined O_DIRECTORY
module_dirfd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
#endif
/* Update the VFS snapshot with the now-pinned root dirfd. */
vfs_set_module_root(module_dir, module_dirlen, module_dirfd);
if (module_dirlen)
sanitize_paths = 1;
@@ -1081,7 +1000,7 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
STRUCT_STAT st;
char prefix[SYMLINK_PREFIX_LEN]; /* NOT +1 ! */
strlcpy(prefix, SYMLINK_PREFIX, sizeof prefix); /* trim the trailing slash */
if (vfs_stat(VFS_AT_FDCWD, prefix, &st, VFS_ALLOW_SYMLINK) == 0 && S_ISDIR(st.st_mode)) {
if (do_stat(prefix, &st) == 0 && S_ISDIR(st.st_mode)) {
rprintf(FLOG, "Symlink munging is unsafe when a %s directory exists.\n",
prefix);
io_printf(f_out, "@ERROR: daemon security issue -- contact admin\n", name);
@@ -1089,17 +1008,14 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
}
}
/* Enable secure symlink handling for any non-chrooted daemon module, and
* for a chroot module with a /./ inner boundary (module_dirlen) -- there
* the kernel chroot confines the outer path but not the inner module, so
* the receiver finish/rename path must still resolve beneath the module
* root. This prevents TOCTOU race attacks where an attacker could switch a
* directory to a symlink between path validation and file open. Match the
* gate in vfs_relpath_active() (syscall.c) -- the protection has nothing
* to do with symlink munging, so a module configured with "munge symlinks =
* false" must still get the secure-open path. */
use_secure_symlinks = am_daemon && (!am_chrooted || module_dirlen)
&& !vfs_symlink_optout_allowed();
/* Enable secure symlink handling for any non-chrooted daemon module.
* This prevents TOCTOU race attacks where an attacker could switch a
* directory to a symlink between path validation and file open.
* Match the gate used by the do_*_at() wrappers in syscall.c
* (am_daemon && !am_chrooted) -- the protection has nothing to do
* with symlink munging, so a module configured with
* "munge symlinks = false" must still get the secure-open path. */
use_secure_symlinks = am_daemon && !am_chrooted;
if (gid_list.count) {
gid_t *gid_array = gid_list.items;
@@ -1152,11 +1068,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
}
}
/* This deadline is checked only in the read path, so the preceding local
* setup and hooks can take as long as necessary. Keep one absolute bound
* across both read_args() calls: anonymous modules must not be able to pin
* a max-connections slot by trickling an unterminated argument forever. */
set_daemon_handshake_timeout(daemon_handshake_timeout(module_id));
io_printf(f_out, "@RSYNCD: OK\n");
read_args(f_in, name, line, sizeof line, rl_nulls, 1, &argv, &argc, &request);
@@ -1174,7 +1085,6 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
ret = parse_arguments(&argc, (const char ***) &argv);
} else
orig_early_argv = NULL;
set_daemon_handshake_timeout(0);
/* The default is to use the user's setting unless the module sets True or False. */
if (lp_open_noatime(module_id) >= 0)
@@ -1314,20 +1224,14 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
return 0;
}
static BOOL namecvt_safe_token(const char *s);
BOOL namecvt_call(const char *cmd, const char **name_p, id_t *id_p)
{
char buf[1024];
int got, len;
if (*name_p) {
if (!namecvt_safe_token(*name_p)) {
rprintf(FERROR, "invalid name-converter token: %s\n", *name_p);
return False;
}
if (*name_p)
len = snprintf(buf, sizeof buf, "%s %s\n", cmd, *name_p);
} else
else
len = snprintf(buf, sizeof buf, "%s %ld\n", cmd, (long)*id_p);
if (len >= (int)sizeof buf) {
rprintf(FERROR, "namecvt_call() request was too large.\n");
@@ -1344,39 +1248,14 @@ BOOL namecvt_call(const char *cmd, const char **name_p, id_t *id_p)
if (!read_line_old(namecvt_fd_ans, buf, sizeof buf, 0))
return False;
if (*name_p) {
/* Name-to-id: an unknown name returns an empty line and atol("")=0
* would map it to root, so validate strictly below (all digits, no
* ERANGE, fits id_t). */
const char *p;
unsigned long v;
if (!*buf)
return False;
for (p = buf; *p; p++) {
if (*p < '0' || *p > '9')
return False;
}
errno = 0;
v = strtoul(buf, NULL, 10);
if (errno == ERANGE || v > (unsigned long)(id_t)-1)
return False;
*id_p = (id_t)v;
} else
if (*name_p)
*id_p = (id_t)atol(buf);
else
*name_p = strdup(buf);
return True;
}
static BOOL namecvt_safe_token(const char *s)
{
for (; *s; s++) {
unsigned char ch = (unsigned char)*s;
if (ch < ' ' || ch == 0x7f)
return False;
}
return True;
}
/* send a list of available modules to the client. Don't list those
with "list = False". */
static void send_listing(int fd)
@@ -1393,18 +1272,6 @@ static void send_listing(int fd)
io_printf(fd,"@RSYNCD: EXIT\n");
}
static int proxy_peer_allowed(int fd)
{
const char *host = undetermined_hostname;
const char *addr = client_addr(fd);
if (!allow_proxy_protocol_peer(lp_proxy_protocol_hosts(), addr, &host)) {
rprintf(FLOG, "proxy protocol rejected from untrusted peer %s (%s)\n", host, addr);
return 0;
}
return 1;
}
static int load_config(int globals_only)
{
if (!config_file) {
@@ -1442,16 +1309,8 @@ int start_daemon(int f_in, int f_out)
if (!load_config(0))
exit_cleanup(RERR_SYNTAX);
/* Bound the handshake before ANY peer input is read -- the PROXY-protocol
* header below is peer-supplied too, and was previously unbounded. An
* rsh-run daemon is not a listener and has no shared slot to exhaust. */
if (am_daemon > 0)
set_daemon_handshake_timeout(daemon_handshake_timeout(-1));
if (lp_proxy_protocol()) {
if (!proxy_peer_allowed(f_in) || !read_proxy_protocol_header(f_in))
return -1;
}
if (lp_proxy_protocol() && !read_proxy_protocol_header(f_in))
return -1;
/* Do reverse DNS lookup before chroot/setuid. The result is cached,
* so the later client_name() call will use this cached value. This
@@ -1485,7 +1344,7 @@ int start_daemon(int f_in, int f_out)
}
/* Deliberately do NOT set am_chrooted here. am_chrooted
* gates the per-module symlink-race defenses
* (vfs_resolve_open() and the do_*_at() wrappers in
* (secure_relative_open() and the do_*_at() wrappers in
* syscall.c) and means "the kernel is enforcing path
* confinement at the module boundary". The daemon chroot
* confines path resolution to the daemon-chroot directory,
@@ -1494,7 +1353,7 @@ int start_daemon(int f_in, int f_out)
* subtrees and a sender-controlled symlink in module A
* could redirect a syscall to module B (or to other files
* inside the daemon chroot) without the per-module
* defenses. Leave am_chrooted=0 here so vfs_resolve_open()
* defenses. Leave am_chrooted=0 here so secure_relative_open()
* still fires for "use chroot = no" modules. */
if (chdir("/") < 0) {
rsyserr(FLOG, errno, "daemon chdir(\"/\") failed");
@@ -1538,7 +1397,6 @@ int start_daemon(int f_in, int f_out)
set_nonblocking(f_in);
}
if (exchange_protocols(f_in, f_out, line, sizeof line, 0) < 0)
return -1;
@@ -1593,73 +1451,36 @@ static void create_pid_file(void)
char pidbuf[32];
STRUCT_STAT st1, st2;
char *fail = NULL;
const char *base = pid_file;
int pdfd = -1;
if (!pid_file || !*pid_file)
return;
#ifdef O_NOFOLLOW
#define SAFE_NOFOLLOW O_NOFOLLOW
#define SAFE_OPEN_FLAGS (O_CREAT|O_NOFOLLOW)
#else
#define SAFE_NOFOLLOW 0
#endif
#ifdef AT_FDCWD
/* Pin the parent directory so the existence check, open and re-stat below
* all resolve the leaf against one stable directory inode, removing the
* lstat->open path race. The parent is operator-configured and trusted, so
* it is opened following symlinks (e.g. a /var/run -> /run); only the leaf
* is opened/checked O_NOFOLLOW (the do_*_atfd wrappers force that). */
{
const char *slash = strrchr(pid_file, '/');
char dirbuf[MAXPATHLEN];
const char *dir = ".";
if (slash) {
size_t dlen = slash == pid_file ? 1 : (size_t)(slash - pid_file);
if (dlen >= sizeof dirbuf) {
rprintf(FLOG, "pid file path is too long: %s\n", pid_file);
exit_cleanup(RERR_FILEIO);
}
memcpy(dirbuf, pid_file, dlen);
dirbuf[dlen] = '\0';
dir = dirbuf;
base = slash + 1;
}
if ((pdfd = vfs_open(dir, O_RDONLY|O_DIRECTORY, 0)) < 0) {
rsyserr(FLOG, errno, "failed to open pid-file directory \"%s\"", dir);
exit_cleanup(RERR_FILEIO);
}
}
#define PID_LSTAT(stp) vfs_lstat(pdfd, base, stp, 0)
#define PID_UNLINK() vfs_unlink(pdfd, base, 0)
#define PID_OPEN() vfs_open_atfd(pdfd, base, O_RDWR|O_CREAT, 0664)
#else
#define PID_LSTAT(stp) vfs_lstat(VFS_AT_FDCWD, base, stp, VFS_ALLOW_SYMLINK)
#define PID_UNLINK() unlink(base)
#define PID_OPEN() vfs_open(base, O_RDWR|O_CREAT|SAFE_NOFOLLOW, 0664)
#define SAFE_OPEN_FLAGS (O_CREAT)
#endif
/* These tests make sure that a temp-style lock dir is handled safely. */
st1.st_mode = 0;
if (PID_LSTAT(&st1) == 0 && !S_ISREG(st1.st_mode) && PID_UNLINK() < 0)
if (do_lstat(pid_file, &st1) == 0 && !S_ISREG(st1.st_mode) && unlink(pid_file) < 0)
fail = "unlink";
else if ((pid_file_fd = PID_OPEN()) < 0)
else if ((pid_file_fd = do_open(pid_file, O_RDWR|SAFE_OPEN_FLAGS, 0664)) < 0)
fail = S_ISREG(st1.st_mode) ? "open" : "create";
else if (!lock_range(pid_file_fd, 0, 4))
fail = "lock";
else if (vfs_fstat(pid_file_fd, &st1) < 0)
else if (do_fstat(pid_file_fd, &st1) < 0)
fail = "fstat opened";
else if (st1.st_size > (int)sizeof pidbuf)
fail = "find small";
else if (PID_LSTAT(&st2) < 0)
else if (do_lstat(pid_file, &st2) < 0)
fail = "lstat";
else if (!S_ISREG(st1.st_mode))
fail = "avoid file overwrite race for";
else if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino)
fail = "verify stat info for";
#ifdef HAVE_FTRUNCATE
else if (vfs_ftruncate(pid_file_fd, 0) < 0)
else if (do_ftruncate(pid_file_fd, 0) < 0)
fail = "truncate";
#endif
else {
@@ -1676,13 +1497,6 @@ static void create_pid_file(void)
cleanup_set_pid(pid); /* Mark the file for removal on exit, even if the write failed. */
}
#undef PID_LSTAT
#undef PID_UNLINK
#undef PID_OPEN
#undef SAFE_NOFOLLOW
if (pdfd >= 0)
close(pdfd);
if (fail) {
char msg[1024];
snprintf(msg, sizeof msg, "failed to %s pid file %s: %s\n",
@@ -1706,7 +1520,6 @@ static void become_daemon(void)
fprintf(stderr, "failed to fork: %s\n", strerror(errno));
exit_cleanup(RERR_FILEIO);
}
gcov_flush();
_exit(0);
}
@@ -1752,17 +1565,6 @@ int daemon_main(void)
}
set_dparams(0);
/* "proxy protocol = true" with no trusted-proxy list rejects every
* connection as an untrusted proxy peer (fail-closed). That is intended,
* but silent at startup, so warn the operator while stderr is still open. */
if (lp_proxy_protocol()
&& (!lp_proxy_protocol_hosts() || !*lp_proxy_protocol_hosts())) {
rprintf(FWARNING,
"\"proxy protocol = true\" but \"proxy protocol hosts\" is unset:"
" all connections will be rejected as untrusted proxy peers."
" Set \"proxy protocol hosts\" to your trusted proxy's address.\n");
}
if (no_detach)
create_pid_file();
else
+11 -22
View File
@@ -351,7 +351,7 @@ static int parse_negotiate_str(struct name_num_obj *nno, char *tmpbuf)
continue;
ret = nni;
best = nno->saw[nni->num];
if (best == 1) /* Can't improve on our own #1 preference */
if (best == 1 || am_server) /* The server side stops at the first acceptable client choice */
break;
}
if (ret) {
@@ -526,11 +526,8 @@ static void send_negotiate_str(int f_out, struct name_num_obj *nno, int ntype)
rprintf(FINFO, "Client %s list (on client): %s\n", nno->type, tmpbuf);
}
/* Each side sends their list of valid names to the other side and then each
* side picks its own most-preferred name that also appears in the peer's
* list. Honest peers emit their list in table (strongest-first) order via
* get_default_nno_list(), so both sides converge on the strongest mutual
* choice; a peer that front-loads a weaker name only desyncs itself. */
/* Each side sends their list of valid names to the other side and then both sides
* pick the first name in the client's list that is also in the server's list. */
if (do_negotiated_strings)
write_vstring(f_out, tmpbuf, len);
}
@@ -588,13 +585,14 @@ void setup_protocol(int f_out,int f_in)
pathname_ndx = (file_extra_cnt += PTR_EXTRA_CNT);
else
depth_ndx = ++file_extra_cnt;
/* uid_ndx/gid_ndx/acls_ndx/xattrs_ndx are assigned AFTER
* check_batch_flags() below: a batch file's stream-flags can flip
* preserve_uid/gid/acls/xattrs on, and computing the *_ndx slots
* before that leaves e.g. preserve_xattrs=1 with xattrs_ndx=0 -- so
* F_XATTR(file) (= REQ_EXTRA(file, 0)) writes at offset 0 of every
* file_struct, clobbering file->dirname. Nothing between here and
* check_batch_flags() reads file_extra_cnt or the *_ndx values. */
if (preserve_uid)
uid_ndx = ++file_extra_cnt;
if (preserve_gid)
gid_ndx = ++file_extra_cnt;
if (preserve_acls && !am_sender)
acls_ndx = ++file_extra_cnt;
if (preserve_xattrs)
xattrs_ndx = ++file_extra_cnt;
if (am_server)
set_allow_inc_recurse();
@@ -641,15 +639,6 @@ void setup_protocol(int f_out,int f_in)
if (read_batch)
check_batch_flags();
if (preserve_uid)
uid_ndx = ++file_extra_cnt;
if (preserve_gid)
gid_ndx = ++file_extra_cnt;
if (preserve_acls && !am_sender)
acls_ndx = ++file_extra_cnt;
if (preserve_xattrs)
xattrs_ndx = ++file_extra_cnt;
if (!saw_stderr_opt && protocol_version <= 28 && am_server)
msgs2stderr = 0; /* The client side may not have stderr setup for us. */
+54 -143
View File
@@ -5,7 +5,7 @@ AC_INIT([rsync],[ ],[https://rsync.samba.org/bug-tracking.html])
AC_C_BIGENDIAN
AC_HEADER_DIRENT
AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS(poll.h sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \
AC_CHECK_HEADERS(sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \
unistd.h utime.h compat.h sys/param.h ctype.h sys/wait.h sys/stat.h \
sys/ioctl.h sys/filio.h string.h stdlib.h sys/socket.h sys/mode.h grp.h \
sys/un.h sys/attr.h arpa/inet.h arpa/nameser.h locale.h sys/types.h \
@@ -13,7 +13,7 @@ AC_CHECK_HEADERS(poll.h sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h
sys/acl.h acl/libacl.h attr/xattr.h sys/xattr.h sys/extattr.h dl.h \
popt.h popt/popt.h linux/falloc.h netinet/in_systm.h netgroup.h \
zlib.h xxhash.h openssl/md4.h openssl/md5.h zstd.h lz4.h sys/file.h \
sys/resource.h bsd/string.h)
bsd/string.h)
AC_CHECK_HEADERS([netinet/ip.h], [], [], [[#include <netinet/in.h>]])
AC_HEADER_MAJOR_FIXED
@@ -60,8 +60,6 @@ AC_PROG_AWK
AC_PROG_EGREP
AC_PROG_INSTALL
AC_PROG_MKDIR_P
AC_CHECK_TOOL([AR], [ar], [ar])
AC_PROG_RANLIB
AC_SUBST(SHELL)
AC_PATH_PROG([PERL], [perl])
AC_PATH_PROG([PYTHON3], [python3])
@@ -84,34 +82,6 @@ if test x"$enable_profile" = x"yes"; then
CFLAGS="$CFLAGS -pg"
fi
dnl Coverage build (gcov) for `make coverage`. NOTE: --enable-profile above is
dnl gprof (-pg) and is NOT coverage. -O0 keeps branch coverage meaningful;
dnl -fprofile-update=atomic keeps the shared .gcda counters correct while the
dnl suite runs many rsync processes in parallel.
AC_ARG_ENABLE(coverage,
AS_HELP_STRING([--enable-coverage],[build with gcov instrumentation for `make coverage`]))
if test x"$enable_coverage" = x"yes"; then
CFLAGS="$CFLAGS --coverage -fprofile-update=atomic -O0"
CXXFLAGS="$CXXFLAGS --coverage -fprofile-update=atomic -O0"
LDFLAGS="$LDFLAGS --coverage"
AC_DEFINE([GCOV_COVERAGE], 1,
[Flush gcov counters at exit_cleanup: rsync's children exit via _exit(), which bypasses the gcov atexit handler, so without this no .gcda is written for the receiver/generator/daemon-worker processes.])
fi
dnl openat2(RESOLVE_BENEATH) is used on Linux 5.6+ for the secure resolver.
dnl --disable-openat2 forces the portable per-component O_NOFOLLOW fallback to
dnl run as the primary resolver on ordinary Linux, so that tier is exercised
dnl (and coverage-counted) without needing a pre-5.6 kernel. Behaviour-neutral
dnl by default (the knob only REMOVES a tier when explicitly disabled).
AC_ARG_ENABLE(openat2,
AS_HELP_STRING([--disable-openat2],[do not use Linux openat2(RESOLVE_BENEATH); force the portable resolver (for exercising the fallback tier)]))
AC_ARG_ENABLE(strict-confinement,
AS_HELP_STRING([--enable-strict-confinement],[abort if a confined receiver ever does a raw path-based metadata op (a CI/dev hardening check; no effect on a normal build)]))
if test x"$enable_strict_confinement" = x"yes"; then
AC_DEFINE(STRICT_CONFINEMENT, 1, [Define to abort on a confined-regime raw path-based metadata op (CI hardening check)])
fi
AC_MSG_CHECKING([if md2man can create manpages])
if test x"$ac_cv_path_PYTHON3" = x; then
AC_MSG_RESULT(no - python3 not found)
@@ -361,8 +331,10 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]], [[return 0;]])],
CFLAGS="$OLD_CFLAGS"
AC_SUBST(NOEXECSTACK)
dnl We need both the SYS_openat2 syscall number and <linux/openat2.h> (for
dnl struct open_how / RESOLVE_BENEATH); some setups have one without the other.
dnl Only define HAVE_OPENAT2 when both the <linux/openat2.h> header and the
dnl SYS_openat2 syscall number are present. syscall.c uses openat2(RESOLVE_BENEATH)
dnl for the secure resolver on Linux 5.6+; defining it unconditionally broke the
dnl build on older kernels/headers that lack the header (#924, #905, #900).
AC_CACHE_CHECK([for openat2],rsync_cv_HAVE_OPENAT2,[
AC_COMPILE_IFELSE([
AC_LANG_PROGRAM([[
@@ -376,11 +348,9 @@ return SYS_openat2 + (int)how.resolve;
],
[rsync_cv_HAVE_OPENAT2=yes], [rsync_cv_HAVE_OPENAT2=no])
])
if test x"$enable_openat2" != x"no"; then
if test x"$rsync_cv_HAVE_OPENAT2" = x"yes"; then
AC_DEFINE([HAVE_OPENAT2], 1,
[Define to use Linux openat2(RESOLVE_BENEATH) in vfs_resolve_open where available.])
fi
if test x"$rsync_cv_HAVE_OPENAT2" = x"yes"; then
AC_DEFINE([HAVE_OPENAT2], 1,
[Define to use Linux openat2(RESOLVE_BENEATH) in secure_relative_open where available.])
fi
# arrgh. libc in some old debian version screwed up the largefile
@@ -440,17 +410,21 @@ AS_HELP_STRING([--disable-ipv6],[disable to omit ipv6 support]),
;;
esac ],
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
AC_RUN_IFELSE([AC_LANG_SOURCE([[ /* AF_INET6 availability check */
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
]], [[
struct sockaddr_in6 sa6;
(void)sa6;
(void)AF_INET6;
int main()
{
if (socket(AF_INET6, SOCK_STREAM, 0) < 0)
exit(1);
else
exit(0);
}
]])],
[AC_MSG_RESULT(yes)
AC_DEFINE(INET6, 1, [true if you have IPv6])],
AC_DEFINE(INET6, 1, true if you have IPv6)],
[AC_MSG_RESULT(no)],
[AC_MSG_RESULT(no)]
))
@@ -917,19 +891,6 @@ AC_HAVE_TYPE([struct stat64], [#include <stdio.h>
# if we can't find strcasecmp, look in -lresolv (for Unixware at least)
#
dnl rsync's I/O readiness loops use poll() rather than select() so that a
dnl file descriptor at or above FD_SETSIZE cannot overflow an fd_set (which
dnl is undefined behaviour and could hang the transfer). poll() is in
dnl POSIX.1-2001; fail early and clearly if this target lacks it.
dnl
dnl io.c and socket.c include <poll.h> unconditionally, so the HEADER has to
dnl be required too: a system that exposes poll() through some other header
dnl would otherwise pass configure and then fail to compile.
AC_CHECK_FUNCS([poll], , [AC_MSG_ERROR([rsync requires poll(); please report the platform to the rsync developers])])
if test x"$ac_cv_header_poll_h" != x"yes"; then
AC_MSG_ERROR([rsync requires <poll.h>; please report the platform to the rsync developers])
fi
AC_CHECK_FUNCS(strcasecmp)
if test x"$ac_cv_func_strcasecmp" = x"no"; then
AC_CHECK_LIB(resolv, strcasecmp)
@@ -947,8 +908,7 @@ dnl AC_FUNC_MEMCMP
AC_FUNC_UTIME_NULL
AC_FUNC_ALLOCA
AC_CHECK_FUNCS(waitpid wait4 getcwd chown chmod lchmod mknod mkfifo fdopendir \
getrlimit setrlimit \
AC_CHECK_FUNCS(waitpid wait4 getcwd chown chmod lchmod mknod mkfifo \
fchmod fstat ftruncate strchr readlink link utime utimes lutimes strftime \
chflags getattrlist mktime innetgr linkat mknodat mkfifoat \
memmove lchown vsnprintf snprintf vasprintf asprintf setsid strpbrk \
@@ -956,21 +916,9 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd chown chmod lchmod mknod mkfifo fdopendir \
setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
seteuid strerror putenv iconv_open locale_charset nl_langinfo getxattr \
extattr_get_link sigaction sigprocmask setattrlist getgrouplist \
initgroups utimensat futimens posix_fallocate attropen setvbuf nanosleep usleep \
initgroups utimensat posix_fallocate attropen setvbuf nanosleep usleep \
setenv unsetenv)
dnl dirfd() is a macro or static inline on several systems (the BSDs), so the
dnl default AC_CHECK_FUNCS link probe -- which declares `char dirfd(void);` and
dnl links against a bare symbol -- gives a false negative there. Probe it with a
dnl real compile+link that includes <dirent.h> and actually calls dirfd().
AC_CACHE_CHECK([for dirfd], rsync_cv_HAVE_DIRFD,
[AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <dirent.h>]],
[[DIR *d = opendir("."); return d ? dirfd(d) < -1 : 0;]])],
[rsync_cv_HAVE_DIRFD=yes], [rsync_cv_HAVE_DIRFD=no])])
if test x"$rsync_cv_HAVE_DIRFD" = x"yes"; then
AC_DEFINE([HAVE_DIRFD], 1, [Define to 1 if you have a working dirfd() (function or macro).])
fi
dnl cygwin iconv.h defines iconv_open as libiconv_open
if test x"$ac_cv_func_iconv_open" != x"yes"; then
AC_CHECK_FUNC(libiconv_open, [ac_cv_func_iconv_open=yes; AC_DEFINE(HAVE_ICONV_OPEN, 1)])
@@ -1292,14 +1240,37 @@ if test x"$rsync_cv_HAVE_SECURE_MKSTEMP" = x"yes"; then
fi
# Whether mknod()/mknodat() can create a FIFO or a unix-domain socket is a
# property of the target filesystem, not a build-time constant -- e.g. mknod
# makes sockets on Linux but not the BSDs/macOS/Solaris, and a single transfer
# can write to filesystems with different capabilities. So rsync no longer
# probes this at configure time (a run-test that also misfired when cross-
# compiling); do_mknod*() just try mknod[at]() and, on failure, fall back to
# mkfifo[at]()/socket+bind() per call. We only need the libc symbols, checked
# above via AC_CHECK_FUNCS (mknod mknodat mkfifo mkfifoat) -- all link tests.
AC_CACHE_CHECK([if mknod creates FIFOs],rsync_cv_MKNOD_CREATES_FIFOS,[
AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
int main(void) { int rc, ec; char *fn = "fifo-test";
unlink(fn); rc = mknod(fn,S_IFIFO,0600); ec = errno; unlink(fn);
if (rc) {printf("(%d %d) ",rc,ec); return ec;}
return 0;}]])],[rsync_cv_MKNOD_CREATES_FIFOS=yes],[rsync_cv_MKNOD_CREATES_FIFOS=no],[rsync_cv_MKNOD_CREATES_FIFOS=cross])])
if test x"$rsync_cv_MKNOD_CREATES_FIFOS" = x"yes"; then
AC_DEFINE(MKNOD_CREATES_FIFOS, 1, [Define to 1 if mknod() can create FIFOs.])
fi
AC_CACHE_CHECK([if mknod creates sockets],rsync_cv_MKNOD_CREATES_SOCKETS,[
AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
int main(void) { int rc, ec; char *fn = "sock-test";
unlink(fn); rc = mknod(fn,S_IFSOCK,0600); ec = errno; unlink(fn);
if (rc) {printf("(%d %d) ",rc,ec); return ec;}
return 0;}]])],[rsync_cv_MKNOD_CREATES_SOCKETS=yes],[rsync_cv_MKNOD_CREATES_SOCKETS=no],[rsync_cv_MKNOD_CREATES_SOCKETS=cross])])
if test x"$rsync_cv_MKNOD_CREATES_SOCKETS" = x"yes"; then
AC_DEFINE(MKNOD_CREATES_SOCKETS, 1, [Define to 1 if mknod() can create sockets.])
fi
#
# The following test was mostly taken from the tcl/tk plus patches
@@ -1473,66 +1444,6 @@ else
esac
fi
#################################################
# On Linux, POSIX ACLs are stored as the "system.posix_acl_{access,default}"
# xattrs, so we can get/set them through a held O_NOFOLLOW fd (fsetxattr) or a
# dirfd+leaf (setxattrat, AT_SYMLINK_NOFOLLOW) instead of the path-based libacl
# acl_*_file() calls -- making the operation safe against a parent-symlink race.
# This needs POSIX ACLs and the f/at xattr syscalls, which on Linux are
# available whenever <sys/xattr.h> (or <attr/xattr.h>) is -- independent of the
# -X feature (--disable-xattr-support), so we gate on the header, not
# enable_xattr_support.
AH_TEMPLATE([SUPPORT_ACL_FD],
[Define to 1 to do POSIX ACL ops via fd/at xattr syscalls (lib/acl.c)])
AH_TEMPLATE([HAVE_XATTRAT_SYSCALLS],
[Define to 1 if the setxattrat/getxattrat/removexattrat syscalls are available])
if test x"$samba_cv_HAVE_POSIX_ACLS" = x"yes" \
&& { test x"$ac_cv_header_sys_xattr_h" = x"yes" || test x"$ac_cv_header_attr_xattr_h" = x"yes"; }; then
case "$host_os" in
*linux*)
AC_DEFINE(SUPPORT_ACL_FD, 1)
AC_CACHE_CHECK([for SYS_setxattrat],rsync_cv_have_sys_setxattrat,[
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/syscall.h>
#include <stdint.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
struct xattr_args { uint64_t value; uint32_t size; uint32_t flags; };]],
[[struct xattr_args a; a.value = 0; a.size = 0; a.flags = 0;
syscall(SYS_setxattrat, 0, ".", 0, "n", &a, sizeof a);
syscall(SYS_getxattrat, 0, ".", 0, "n", &a, sizeof a);
syscall(SYS_removexattrat, 0, ".", 0, "n");]])],[rsync_cv_have_sys_setxattrat=yes],[rsync_cv_have_sys_setxattrat=no])])
if test x"$rsync_cv_have_sys_setxattrat" = x"yes"; then
AC_DEFINE(HAVE_XATTRAT_SYSCALLS, 1)
fi
;;
esac
fi
#################################################
# Detect a patched libacl providing the race-safe
# *_at ACL entry points (acl_get_file_at/acl_set_file_at/acl_delete_def_file_at,
# ACL_1.3, unreleased upstream). When present we route the race-safe ACL get/
# set/delete through them on Linux -- race-safe on every kernel (6.13+ uses
# *xattrat; older uses libacl's /proc/self/fd compat). A stock -lacl lacks these
# symbols, so this stays undefined and the build falls back to lib/acl.c;
# detection must therefore run against the patched lib (CPPFLAGS/LDFLAGS).
AH_TEMPLATE([HAVE_LIBACL_AT],
[Define to 1 if libacl provides acl_get_file_at/acl_set_file_at/acl_delete_def_file_at])
if test x"$samba_cv_HAVE_POSIX_ACLS" = x"yes"; then
case "$host_os" in
*linux*)
AC_CHECK_LIB(acl, acl_get_file_at, [rsync_have_libacl_at=yes], [rsync_have_libacl_at=no])
if test x"$rsync_have_libacl_at" = x"yes"; then
AC_CHECK_FUNCS([acl_set_file_at acl_delete_def_file_at], [], [rsync_have_libacl_at=no])
fi
if test x"$rsync_have_libacl_at" = x"yes"; then
AC_DEFINE(HAVE_LIBACL_AT, 1)
fi
;;
esac
fi
if test x"$enable_acl_support" = x"no" || test x"$enable_xattr_support" = x"no" || test x"$enable_iconv" = x"no"; then
AC_MSG_CHECKING([whether $CC supports -Wno-unused-parameter])
OLD_CFLAGS="$CFLAGS"
@@ -1550,7 +1461,7 @@ case "$CC" in
;;
esac
AC_CONFIG_FILES([Makefile lib/dummy zlib/dummy popt/dummy vfs/dummy shconfig])
AC_CONFIG_FILES([Makefile lib/dummy zlib/dummy popt/dummy shconfig])
AC_OUTPUT
AC_MSG_RESULT()
+1 -3
View File
@@ -30,9 +30,7 @@ int claim_connection(char *fname, int max_connections)
if (max_connections == 0)
return 1;
/* 'lock file = PATH': refuse symlinks not owned by uid 0 or our euid so
* a planted parent can't redirect the root daemon's O_CREAT open. */
if ((fd = vfs_open_owner_walk(fname, O_RDWR|O_CREAT, 0600, 0)) < 0)
if ((fd = open(fname, O_RDWR|O_CREAT, 0600)) < 0)
return 0;
/* Find a free spot. */
+1 -8
View File
@@ -84,14 +84,7 @@ BEGIN {
defines = defines "\t" vtype " " name ";\n"
values = values "\t" $0 ", /* " name " */\n"
parms = parms " {\"" pubname "\", P_" ptype psect name ", " enum ", 0},\n"
# The shell-executed hook params (whose %RSYNC_*% expansion is fed to
# /bin/sh) use the _SHELL accessor, which single-quotes peer-controlled
# values to prevent injection. Ordinary string params must NOT quote --
# it would corrupt a documented `path = /home/%RSYNC_USER_NAME%` etc.
if (atype == "STRING" && (name == "early_exec" || name == "prexfer_exec" || name == "postxfer_exec" || name == "name_converter"))
accessors = accessors "FN_" sect "_STRING_SHELL(lp_" name ", " name ")\n"
else
accessors = accessors "FN_" sect "_" atype "(lp_" name ", " name ")\n"
accessors = accessors "FN_" sect "_" atype "(lp_" name ", " name ")\n"
if (vtype == "char*") {
exps = exps "\tBOOL " name "_EXP;\n"
-3
View File
@@ -6,7 +6,6 @@ STRING daemon_gid NULL
STRING daemon_uid NULL
STRING motd_file NULL
STRING pid_file NULL
STRING proxy_protocol_hosts NULL
STRING socket_options NULL
INTEGER listen_backlog 5
@@ -16,7 +15,6 @@ BOOL proxy_protocol False
Locals: =================================================================
STRING auth_digest NULL
STRING auth_users NULL
STRING charset NULL
STRING comment NULL
@@ -57,7 +55,6 @@ BOOL fake_super False
BOOL forward_lookup True
BOOL ignore_errors False
BOOL ignore_nonreadable False
BOOL insecure_links False
BOOL list True
BOOL read_only True
BOOL reverse_lookup True
+5 -75
View File
@@ -34,54 +34,6 @@ int ignore_perishable = 0;
int non_perishable_cnt = 0;
int skipped_deletes = 0;
/* Held fd of the directory whose contents delete_dir_contents() is currently
* removing, so delete_item()'s per-entry rmdir/unlink/chmod go through it
* instead of re-resolving the full path for every entry. Set (with save/
* restore across the recursion) around the delete loop; -1 outside a recursive
* delete or when the secure resolver is gated off (chroot / non-receiver) or
* the path doesn't live directly in that dir. */
static int del_dirfd = -1;
static const char *del_dir_prefix;
static int del_dir_prefix_len;
/* If `path` is a single component directly inside the dir being deleted,
* point *leaf at its basename and return the held dir fd; else return -1. */
static int del_held_dfd(const char *path, const char **leaf)
{
if (del_dirfd >= 0
&& strncmp(path, del_dir_prefix, del_dir_prefix_len) == 0
&& path[del_dir_prefix_len] == '/'
&& strchr(path + del_dir_prefix_len + 1, '/') == NULL) {
*leaf = path + del_dir_prefix_len + 1;
return del_dirfd;
}
return -1;
}
static void del_chmod(const char *fbuf, mode_t mode)
{
const char *leaf;
int dfd = del_held_dfd(fbuf, &leaf);
if (dfd >= 0)
vfs_chmod(dfd, leaf, mode, 0);
else
vfs_chmod(VFS_AT_FDCWD, fbuf, mode, 0);
}
/* vfs_flags carries VFS_OPERATOR_PATH for a backup-tree delete (DEL_FOR_BACKUP):
* the path-based fallback then resolves the leaf's parent via the ownership walk,
* matching the confinement the base gives this unlink under make_backup() (where
* the held dirfd is absent for a cross-tree --backup-dir leaf). A held-dirfd
* delete is already confined, so it ignores the flag. */
static int del_unlink(const char *fbuf, int vfs_flags)
{
const char *leaf;
int dfd = del_held_dfd(fbuf, &leaf);
if (dfd >= 0 && vfs_unlink(dfd, leaf, 0) == 0)
return 0;
return robust_unlink(fbuf, vfs_flags); /* fall back (ETXTBSY retry, or not held) */
}
static inline int is_backup_file(char *fn)
{
int k = strlen(fn) - backup_suffix_len;
@@ -131,18 +83,6 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
flags = (flags & ~(DEL_RECURSE|DEL_MAKE_ROOM|DEL_NO_UID_WRITE))
| DEL_DIR_IS_EMPTY;
/* Hold this dir open so the per-entry chmod/rmdir/unlink below (and in
* delete_item) become *at() calls against it rather than re-resolving the
* full path for every entry. Save/restore around the recursion. */
int save_del_dirfd = del_dirfd;
const char *save_del_prefix = del_dir_prefix;
int save_del_prefix_len = del_dir_prefix_len;
fname[dlen] = '\0';
del_dirfd = vfs_opendir(fname);
fname[dlen] = '/';
del_dir_prefix = fname;
del_dir_prefix_len = dlen;
for (j = dirlist->used; j--; ) {
struct file_struct *fp = dirlist->files[j];
@@ -158,7 +98,7 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
strlcpy(p, fp->basename, remainder);
if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
del_chmod(fname, fp->mode | S_IWUSR);
do_chmod_at(fname, fp->mode | S_IWUSR);
/* Save stack by recursing to ourself directly. */
if (S_ISDIR(fp->mode)) {
if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
@@ -168,12 +108,6 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
ret = DR_NOT_EMPTY;
}
if (del_dirfd >= 0)
close(del_dirfd);
del_dirfd = save_del_dirfd;
del_dir_prefix = save_del_prefix;
del_dir_prefix_len = save_del_prefix_len;
fname[dlen] = '\0';
done:
@@ -205,7 +139,7 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
}
if (flags & DEL_NO_UID_WRITE)
del_chmod(fbuf, mode | S_IWUSR);
do_chmod_at(fbuf, mode | S_IWUSR);
if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
/* This only happens on the first call to delete_item() since
@@ -225,23 +159,19 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
}
if (S_ISDIR(mode)) {
const char *leaf;
int dfd = del_held_dfd(fbuf, &leaf);
what = "rmdir";
ok = (dfd >= 0 ? vfs_unlink(dfd, leaf, VFS_REMOVEDIR)
: vfs_unlink(VFS_AT_FDCWD, fbuf,
VFS_REMOVEDIR | ((flags & DEL_FOR_BACKUP) ? VFS_OPERATOR_PATH : 0))) == 0;
ok = do_rmdir_at(fbuf) == 0;
} else {
if (make_backups > 0 && !(flags & DEL_FOR_BACKUP) && (backup_dir || !is_backup_file(fbuf))) {
what = "make_backup";
ok = make_backup(fbuf, True);
if (ok == 2) {
what = "unlink";
ok = del_unlink(fbuf, (flags & DEL_FOR_BACKUP) ? VFS_OPERATOR_PATH : 0) == 0;
ok = robust_unlink(fbuf) == 0;
}
} else {
what = "unlink";
ok = del_unlink(fbuf, (flags & DEL_FOR_BACKUP) ? VFS_OPERATOR_PATH : 0) == 0;
ok = robust_unlink(fbuf) == 0;
}
}
+20
View File
@@ -0,0 +1,20 @@
Handling the rsync SGML documentation
rsync documentation is now primarily in Docbook format. Docbook is an
SGML/XML documentation format that is becoming standard on free
operating systems. It's also used for Samba documentation.
The SGML files are source code that can be translated into various
useful output formats, primarily PDF, HTML, Postscript and plain text.
To do this transformation on Debian, you should install the
docbook-utils package. Having done that, you can say
docbook2pdf rsync.sgml
and so on.
On other systems you probably need James Clark's "sp" and "JadeTeX"
packages. Work it out for yourself and send a note to the mailing
list.
+42
View File
@@ -0,0 +1,42 @@
Notes on rsync profiling
strlcpy is hot:
0.00 0.00 1/7735635 push_dir [68]
0.00 0.00 1/7735635 pop_dir [71]
0.00 0.00 1/7735635 send_file_list [15]
0.01 0.00 18857/7735635 send_files [4]
0.04 0.00 129260/7735635 send_file_entry [18]
0.04 0.00 129260/7735635 make_file [20]
0.04 0.00 141666/7735635 send_directory <cycle 1> [36]
2.29 0.00 7316589/7735635 f_name [13]
[14] 11.7 2.42 0.00 7735635 strlcpy [14]
Here's the top few functions:
46.23 9.57 9.57 13160929 0.00 0.00 mdfour64
14.78 12.63 3.06 13160929 0.00 0.00 copy64
11.69 15.05 2.42 7735635 0.00 0.00 strlcpy
10.05 17.13 2.08 41438 0.05 0.38 sum_update
4.11 17.98 0.85 13159996 0.00 0.00 mdfour_update
1.50 18.29 0.31 file_compare
1.45 18.59 0.30 129261 0.00 0.01 send_file_entry
1.23 18.84 0.26 2557585 0.00 0.00 f_name
1.11 19.07 0.23 1483750 0.00 0.00 u_strcmp
1.11 19.30 0.23 118129 0.00 0.00 writefd_unbuffered
0.92 19.50 0.19 1085011 0.00 0.00 writefd
0.43 19.59 0.09 156987 0.00 0.00 read_timeout
0.43 19.68 0.09 129261 0.00 0.00 clean_fname
0.39 19.75 0.08 32887 0.00 0.38 matched
0.34 19.82 0.07 1 70.00 16293.92 send_files
0.29 19.89 0.06 129260 0.00 0.00 make_file
0.29 19.95 0.06 75430 0.00 0.00 read_unbuffered
mdfour could perhaps be made faster:
/* NOTE: This code makes no attempt to be fast! */
There might be an optimized version somewhere that we can borrow.
+351
View File
@@ -0,0 +1,351 @@
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
<book id="rsync">
<bookinfo>
<title>rsync</title>
<copyright>
<year>1996 -- 2002</year>
<holder>Martin Pool</holder>
<holder>Andrew Tridgell</holder>
</copyright>
<author>
<firstname>Martin</firstname>
<surname>Pool</surname>
</author>
</bookinfo>
<chapter>
<title>Introduction</title>
<para>rsync is a flexible program for efficiently copying files or
directory trees.
<para>rsync has many options to select which files will be copied
and how they are to be transferred. It may be used as an
alternative to ftp, http, scp or rcp.
<para>The rsync remote-update protocol allows rsync to transfer just
the differences between two sets of files across the network link,
using an efficient checksum-search algorithm described in the
technical report that accompanies this package.</para>
<para>Some of the additional features of rsync are:</para>
<itemizedlist>
<listitem>
<para>support for copying links, devices, owners, groups and
permissions
</para>
</listitem>
<listitem>
<para>
exclude and exclude-from options similar to GNU tar
</para>
</listitem>
<listitem>
<para>
a CVS exclude mode for ignoring the same files that CVS would ignore
</listitem>
<listitem>
<para>
can use any transparent remote shell, including rsh or ssh
</listitem>
<listitem>
<para>
does not require root privileges
</listitem>
<listitem>
<para>
pipelining of file transfers to minimize latency costs
</listitem>
<listitem>
<para>
support for anonymous or authenticated rsync servers (ideal for
mirroring)
</para>
</listitem>
</itemizedlist>
</chapter>
<chapter>
<title>Using rsync</title>
<section>
<title>
Introductory example
</title>
<para>
Probably the most common case of rsync usage is to copy files
to or from a remote machine using
<application>ssh</application> as a network transport. In
this situation rsync is a good alternative to
<application>scp</application>.
</para>
<para>
The most commonly used arguments for rsync are
</para>
<variablelist>
<varlistentry>
<term><option>-v</option></term>
<listitem>
<para>Be verbose. Primarily, display the name of each file as it is copied.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-a</option></term>
<listitem>
<para>
Reproduce the structure and attributes of the origin files as exactly
as possible: this includes copying subdirectories, symlinks, special
files, ownership and permissions. (@xref{Attributes to
copy}.)
</para>
</listitem>
</varlistentry>
</variablelist>
<para><option>-v </option>
<para><option>-z</option>
Compress network traffic, using a modified version of the
@command{zlib} library.</para>
<para><option>-P</option>
Display a progress indicator while files are transferred. This should
normally be omitted if rsync is not run on a terminal.
</para>
</section>
<section>
<title>Local and remote</title>
<para>There are six different ways of using rsync. They
are:</para>
<!-- one of (CALLOUTLIST GLOSSLIST ITEMIZEDLIST ORDEREDLIST SEGMENTEDLIST SIMPLELIST VARIABLELIST CAUTION IMPORTANT NOTE TIP WARNING LITERALLAYOUT PROGRAMLISTING PROGRAMLISTINGCO SCREEN SCREENCO SCREENSHOT SYNOPSIS CMDSYNOPSIS FUNCSYNOPSIS CLASSSYNOPSIS FIELDSYNOPSIS CONSTRUCTORSYNOPSIS DESTRUCTORSYNOPSIS METHODSYNOPSIS FORMALPARA PARA SIMPARA ADDRESS BLOCKQUOTE GRAPHIC GRAPHICCO MEDIAOBJECT MEDIAOBJECTCO INFORMALEQUATION INFORMALEXAMPLE INFORMALFIGURE INFORMALTABLE EQUATION EXAMPLE FIGURE TABLE MSGSET PROCEDURE SIDEBAR QANDASET ANCHOR BRIDGEHEAD REMARK HIGHLIGHTS ABSTRACT AUTHORBLURB EPIGRAPH INDEXTERM REFENTRY SECTION) -->
<orderedlist>
<listitem>
<para>
for copying local files. This is invoked when neither
source nor destination path contains a @code{:} separator
<listitem>
<para>
for copying from the local machine to a remote machine using
a remote shell program as the transport (such as rsh or
ssh). This is invoked when the destination path contains a
single @code{:} separator.
<listitem>
<para>
for copying from a remote machine to the local machine
using a remote shell program. This is invoked when the source
contains a @code{:} separator.
<listitem>
<para>
for copying from a remote rsync server to the local
machine. This is invoked when the source path contains a @code{::}
separator or a @code{rsync://} URL.
<listitem>
<para>
for copying from the local machine to a remote rsync
server. This is invoked when the destination path contains a @code{::}
separator.
<listitem>
<para>
for listing files on a remote machine. This is done the
same way as rsync transfers except that you leave off the
local destination.
</listitem>
</orderedlist>
<para>
Note that in all cases (other than listing) at least one of the source
and destination paths must be local.
<para>
Any one invocation of rsync makes a copy in a single direction. rsync
currently has no equivalent of @command{ftp}'s interactive mode.
@cindex @sc{nfs}
@cindex network filesystems
@cindex remote filesystems
<para>
rsync's network protocol is generally faster at copying files than
network filesystems such as @sc{nfs} or @sc{cifs}. It is better to
run rsync on the file server either as a daemon or over ssh than
running rsync giving the network directory.
</para>
</section>
</chapter>
<chapter>
<title>Frequently asked questions</title>
<!-- one of (CALLOUTLIST GLOSSLIST ITEMIZEDLIST ORDEREDLIST SEGMENTEDLIST SIMPLELIST VARIABLELIST CAUTION IMPORTANT NOTE TIP WARNING LITERALLAYOUT PROGRAMLISTING PROGRAMLISTINGCO SCREEN SCREENCO SCREENSHOT SYNOPSIS CMDSYNOPSIS FUNCSYNOPSIS CLASSSYNOPSIS FIELDSYNOPSIS CONSTRUCTORSYNOPSIS DESTRUCTORSYNOPSIS METHODSYNOPSIS FORMALPARA PARA SIMPARA ADDRESS BLOCKQUOTE GRAPHIC GRAPHICCO MEDIAOBJECT MEDIAOBJECTCO INFORMALEQUATION INFORMALEXAMPLE INFORMALFIGURE INFORMALTABLE EQUATION EXAMPLE FIGURE TABLE MSGSET PROCEDURE SIDEBAR QANDASET ANCHOR BRIDGEHEAD REMARK HIGHLIGHTS ABSTRACT AUTHORBLURB EPIGRAPH INDEXTERM SECTION SIMPLESECT REFENTRY SECT1) -->
<qandaset>
<!-- one of (QANDADIV QANDAENTRY) -->
<qandaentry>
<question>
<!-- one of (CALLOUTLIST GLOSSLIST ITEMIZEDLIST ORDEREDLIST
SEGMENTEDLIST SIMPLELIST VARIABLELIST CAUTION IMPORTANT NOTE
TIP WARNING LITERALLAYOUT PROGRAMLISTING PROGRAMLISTINGCO
SCREEN SCREENCO SCREENSHOT SYNOPSIS CMDSYNOPSIS FUNCSYNOPSIS
CLASSSYNOPSIS FIELDSYNOPSIS CONSTRUCTORSYNOPSIS
DESTRUCTORSYNOPSIS METHODSYNOPSIS FORMALPARA PARA SIMPARA
ADDRESS BLOCKQUOTE GRAPHIC GRAPHICCO MEDIAOBJECT
MEDIAOBJECTCO INFORMALEQUATION INFORMALEXAMPLE
INFORMALFIGURE INFORMALTABLE EQUATION EXAMPLE FIGURE TABLE
PROCEDURE ANCHOR BRIDGEHEAD REMARK HIGHLIGHTS INDEXTERM) -->
<para>Are there mailing lists for rsync?
</question>
<answer>
<para>Yes, and you can subscribe and unsubscribe through a
web interface at
<ulink
url="http://lists.samba.org/">http://lists.samba.org/</ulink>
</para>
<para>
If you are having trouble with the mailing list, please
send mail to the administrator
<email>rsync-admin@lists.samba.org</email>
not to the list itself.
</para>
<para>
The mailing list archives are searchable. Use
<ulink url="http://google.com/">Google</ulink> and prepend
the search with <userinput>site:lists.samba.org
rsync</userinput>, plus relevant keywords.
</para>
</answer>
</qandaentry>
<qandaentry>
<question>
<para>
Why is rsync so much bigger when I build it with
<command>gcc</command>?
</para>
</question>
<answer>
<para>
On gcc, rsync builds by default with debug symbols
included. If you strip both executables, they should end
up about the same size. (Use <command>make
install-strip</command>.)
</para>
</answer>
</qandaentry>
<qandaentry>
<question>
<para>Is rsync useful for a single large file like an ISO image?</para>
</question>
<answer>
<para>
Yes, but note the following:
<para>
Background: A common use of rsync is to update a file (or set of files) in one location from a more
correct or up-to-date copy in another location, taking advantage of portions of the files that are
identical to speed up the process. (Note that rsync will transfer a file in its entirety if no copy
exists at the destination.)
<para>
(This discussion is written in terms of updating a local copy of a file from a correct file in a
remote location, although rsync can work in either direction.)
<para>
The file to be updated (the local file) must be in a destination directory that has enough space for
two copies of the file. (In addition, keep an extra copy of the file to be updated in a different
location for safety -- see the discussion (below) about rsync's behavior when the rsync process is
interrupted before completion.)
<para>
The local file must have the same name as the remote file being sync'd to (I think?). If you are
trying to upgrade an iso from, for example, beta1 to beta2, rename the local file to the same name
as the beta2 file. *(This is a useful thing to do -- only the changed portions will be
transmitted.)*
<para>
The extra copy of the local file kept in a different location is because of rsync's behavior if
interrupted before completion:
<para>
* If you specify the --partial option and rsync is interrupted, rsync will save the partially
rsync'd file and throw away the original local copy. (The partially rsync'd file is correct but
truncated.) If rsync is restarted, it will not have a local copy of the file to check for duplicate
blocks beyond the section of the file that has already been rsync'd, thus the remainder of the rsync
process will be a "pure transfer" of the file rather than taking advantage of the rsync algorithm.
<para>
* If you don't specify the --partial option and rsync is interrupted, rsync will throw away the
partially rsync'd file, and, when rsync is restarted starts the rsync process over from the
beginning.
<para>
Which of these is most desirable depends on the degree of commonality between the local and remote
copies of the file *and how much progress was made before the interruption*.
<para>
The ideal approach after an interruption would be to create a new file by taking the original file
and deleting a portion equal in size to the portion already rsync'd and then appending *the
remaining* portion to the portion of the file that has already been rsync'd. (There has been some
discussion about creating an option to do this automatically.)
The --compare-dest option is useful when transferring multiple files, but is of no benefit in
transferring a single file. (AFAIK)
*Other potentially useful information can be found at:
-[3]http://twiki.org/cgi-bin/view/Wikilearn/RsyncingALargeFile
This answer, formatted with "real" bullets, can be found at:
-[4]http://twiki.org/cgi-bin/view/Wikilearn/RsyncingALargeFileFAQ*
</para>
</answer>
</qandaentry>
</qandaset>
</chapter>
<appendix>
<title>Other Resources</title>
<para><ulink url="http://www.ccp14.ac.uk/ccp14admin/rsync/"></ulink></para>
</appendix>
</book>
+58 -348
View File
@@ -42,99 +42,8 @@ extern int protocol_version;
extern int trust_sender_args;
extern int module_id;
/* Set while the daemon loads its own filter parameters; see parse_filter_file(). */
int daemon_config_filter_file = 0;
/* Where the rule text now being parsed came from, when that is a file's
* CONTENTS rather than an argument. A rule that fails to parse used to be
* echoed back verbatim, and the peer chooses which file gets merged (a
* per-directory merge rule travels over the protocol, so no argument of ours
* ever names it), which made the filter parser a read-any-line oracle: any
* line that is not valid filter syntax came straight back in the error.
* Report where the bad rule is, not what it says. */
static int rule_src_in_file = 0; /* parsing a file's contents right now */
static const char *rule_src_file = NULL; /* ...and its name is safe to show */
static int rule_src_line = 0;
/* Where a file whose own name we must NOT print was named, which is a location
* we CAN print: it keeps the diagnostic useful without echoing the pathname a
* merge rule supplied. */
static const char *rule_src_named_at = NULL;
/* True while the text we are handling came out of a file's contents: either we
* are parsing that file right now, or this is a deferred per-dir merge whose
* NAME came from one and which carries the provenance on the rule. */
#define TEXT_FROM_FILE(template) \
(rule_src_in_file \
|| ((template) && (template)->rflags & FILTRULE_FROM_FILE))
/* "FILE line N", or just "FILE" when the count is not a line count. */
static const char *rule_src_where(void)
{
static char buf[MAXPATHLEN + 32];
if (!rule_src_file) {
if (!rule_src_named_at)
return "a file read earlier"; /* origin not retained */
snprintf(buf, sizeof buf, "a file named at %s", rule_src_named_at);
return buf;
}
if (rule_src_line < 0)
return rule_src_file;
snprintf(buf, sizeof buf, "%s line %d", rule_src_file, rule_src_line);
return buf;
}
/* THE chokepoint. Every diagnostic string that is, or is built from, a filter
* rule's own text -- a pattern, a merge-file name, a path composed from one --
* must be passed through rule_text() on its way to rprintf(). When the rule
* came from an argument the text is returned unchanged, because it is the
* user's own and hiding it only makes typos harder to fix. When it came from
* a FILE's contents it is replaced by a description of where it came from,
* because the peer chooses which file gets merged and any line of it that
* reaches a message is a line the peer can read back.
*
* Doing it here rather than at each site is the point: a message added later
* cannot reintroduce the leak by forgetting to check, and there is one place
* to audit. `template' is the rule the text belongs to, or NULL when the only
* thing that matters is whether we are parsing a file right now.
*
* The returned buffer is rotated, so two calls in one rprintf() are safe. */
static const char *rule_text_len(const filter_rule *template,
const char *text, int len)
{
static char buf[2][BIGPATHBUFLEN];
static int which = 0;
char *b = buf[which];
which ^= 1;
if (!TEXT_FROM_FILE(template)) {
if (len < 0)
return text;
snprintf(b, sizeof buf[0], "%.*s", len, text);
return b;
}
snprintf(b, sizeof buf[0], "<rule from %s>", rule_src_where());
return b;
}
static const char *rule_text(const filter_rule *template, const char *text)
{
return rule_text_len(template, text, -1);
}
/* For the extra detail some messages add ABOUT the text -- a character of it,
* an offset into it. Dropped along with the text it describes. */
static const char *rule_detail(const filter_rule *template, const char *detail)
{
return TEXT_FROM_FILE(template) ? "" : detail;
}
static void filter_rule_err(const char *msg, const char *rulestr)
{
rprintf(FERROR, "%s: %s\n", msg, rule_text(NULL, rulestr));
exit_cleanup(RERR_SYNTAX);
}
extern char curr_dir[MAXPATHLEN];
extern unsigned int curr_dir_len;
extern unsigned int module_dirlen;
filter_rule_list filter_list = { .debug_type = "" };
@@ -152,7 +61,7 @@ int trust_sender_filter = 0;
#define SLASH_WILD3_SUFFIX "/***"
/* The dirbuf is set by push_local_filters() to the current subdirectory
* relative to vfs.curr_dir that is being processed. The path always has a
* relative to curr_dir that is being processed. The path always has a
* trailing slash appended, and the variable dirbuf_len contains the length
* of this path prefix. The path is always absolute. */
static char dirbuf[MAXPATHLEN+1];
@@ -162,9 +71,6 @@ static int dirbuf_depth;
/* This is True when we're scanning parent dirs for per-dir merge-files. */
static BOOL parent_dirscan = False;
#define MAX_MERGE_DEPTH 32
static int merge_depth = 0;
/* This array contains a list of all the currently active per-dir merge
* files. This makes it easier to save the appropriate values when we
* "push" down into each subdirectory. */
@@ -265,10 +171,10 @@ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_
else
mention_rule_suffix = DEBUG_GTE(FILTER, 2) ? "" : NULL;
if (mention_rule_suffix) {
rprintf(FINFO, "[%s] add_rule(%s%s)%s%s\n",
who_am_i(), rule_detail(rule, get_rule_prefix(rule, pat, 0, NULL)),
rule_text_len(rule, pat, (int)pat_len),
listp->debug_type, rule_detail(rule, mention_rule_suffix));
rprintf(FINFO, "[%s] add_rule(%s%.*s%s)%s%s\n",
who_am_i(), get_rule_prefix(rule, pat, 0, NULL),
(int)pat_len, pat, (rule->rflags & FILTRULE_DIRECTORY) ? "/" : "",
listp->debug_type, mention_rule_suffix);
}
/* These flags also indicate that we're reading a list that
@@ -373,7 +279,7 @@ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_
}
lp = new_array0(filter_rule_list, 1);
if (asprintf(&lp->debug_type, " [per-dir %s]", rule_text(rule, cp)) < 0)
if (asprintf(&lp->debug_type, " [per-dir %s]", cp) < 0)
out_of_memory("add_rule");
rule->u.mergelist = lp;
@@ -521,7 +427,7 @@ void add_implied_include(const char *arg, int skip_daemon_module)
if (cp[1] == ']') {
if (!saw_wild)
cp++; /* A \] in a non-wild filter causes a problem, so drop the \ . */
} else if (!cp[1] || !strchr("*[?", cp[1])) {
} else if (!strchr("*[?", cp[1])) {
backslash_cnt++;
if (saw_wild)
*p++ = '\\';
@@ -690,8 +596,7 @@ static void pop_filter_list(filter_rule_list *listp)
* value and will be updated with the length of the resulting name. We
* always return a name that is null terminated, even if the merge_file
* name was not. */
static char *parse_merge_name(const filter_rule *template,
const char *merge_file, unsigned int *len_ptr,
static char *parse_merge_name(const char *merge_file, unsigned int *len_ptr,
unsigned int prefix_skip)
{
static char buf[MAXPATHLEN];
@@ -722,7 +627,7 @@ static char *parse_merge_name(const filter_rule *template,
}
if (!sanitize_path(fn, merge_file, r, dirbuf_depth, SP_DEFAULT)) {
rprintf(FERROR, "merge-file name overflows: %s\n",
rule_text(template, merge_file));
merge_file);
return NULL;
}
fn_len = strlen(fn);
@@ -735,8 +640,7 @@ static char *parse_merge_name(const filter_rule *template,
if (fn != buf) {
int d_len = dirbuf_len - prefix_skip;
if (d_len + fn_len >= MAXPATHLEN) {
rprintf(FERROR, "merge-file name overflows: %s\n",
rule_text(template, fn));
rprintf(FERROR, "merge-file name overflows: %s\n", fn);
return NULL;
}
memcpy(buf, dirbuf + prefix_skip, d_len);
@@ -754,9 +658,9 @@ void set_filter_dir(const char *dir, unsigned int dirlen)
{
unsigned int len;
if (*dir != '/') {
memcpy(dirbuf, vfs.curr_dir, vfs.curr_dir_len);
dirbuf[vfs.curr_dir_len] = '/';
len = vfs.curr_dir_len + 1;
memcpy(dirbuf, curr_dir, curr_dir_len);
dirbuf[curr_dir_len] = '/';
len = curr_dir_len + 1;
if (len + dirlen >= MAXPATHLEN)
dirlen = 0;
} else
@@ -786,7 +690,7 @@ static BOOL setup_merge_file(int mergelist_num, filter_rule *ex,
char *x, *y, *pat = ex->pattern;
unsigned int len;
if (!(x = parse_merge_name(ex, pat, NULL, 0)) || *x != '/')
if (!(x = parse_merge_name(pat, NULL, 0)) || *x != '/')
return 0;
if (DEBUG_GTE(FILTER, 2)) {
@@ -850,7 +754,7 @@ struct local_filter_state {
/* Each time rsync changes to a new directory it call this function to
* handle all the per-dir merge-files. The "dir" value is the current path
* relative to vfs.curr_dir (which might not be null-terminated). We copy it
* relative to curr_dir (which might not be null-terminated). We copy it
* into dirbuf so that we can easily append a file name on the end. */
void *push_local_filters(const char *dir, unsigned int dirlen)
{
@@ -912,7 +816,7 @@ void *push_local_filters(const char *dir, unsigned int dirlen)
io_error |= IOERR_GENERAL;
rprintf(FERROR,
"cannot add local filter rules in long-named directory: %s\n",
rule_text(ex, full_fname(dirbuf)));
full_fname(dirbuf));
}
dirbuf[dirbuf_len] = '\0';
}
@@ -1017,10 +921,10 @@ static int rule_matches(const char *fname, filter_rule *ex, int name_flags)
if ((p = strrchr(name,'/')) != NULL)
name = p+1;
} else if (ex->rflags & FILTRULE_ABS_PATH && *fname != '/'
&& vfs.curr_dir_len > module_dirlen + 1) {
&& curr_dir_len > module_dirlen + 1) {
/* If we're matching against an absolute-path pattern,
* we need to prepend our full path info. */
strings[str_cnt++] = vfs.curr_dir + module_dirlen + 1;
strings[str_cnt++] = curr_dir + module_dirlen + 1;
strings[str_cnt++] = "/";
} else if (ex->rflags & FILTRULE_WILD2_PREFIX && *fname != '/') {
/* Allow "**"+"/" to match at the start of the string. */
@@ -1095,8 +999,8 @@ static void report_filter_result(enum logcode code, char const *name,
: "file";
rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n",
w, actions[*w=='g'][!(ent->rflags & FILTRULE_INCLUDE)],
t, name, rule_text(ent, ent->pattern),
rule_detail(ent, ent->rflags & FILTRULE_DIRECTORY ? "/" : ""), type);
t, name, ent->pattern,
ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type);
}
}
@@ -1129,56 +1033,6 @@ int check_server_filter(filter_rule_list *listp, enum logcode code, const char *
return ret;
}
/* Returns 1 if `name` matches an implied-parent rule (a directory component
* seeded by add_implied_include() with FILTRULE_DIRECTORY) but not a leaf
* rule -- i.e. the client asked for something under the dir, never the dir
* itself as content.
*
* The receiver uses this to refuse a malicious sender that sets XMIT_TOP_DIR
* without XMIT_NO_CONTENT_DIR on such a dir: the honest encoding is both flags
* (flist.c send path), so otherwise the receiver would set FLAG_CONTENT_DIR
* and delete_in_dir() could sweep pre-existing siblings under --delete. */
int is_implied_parent_dir(const char *name)
{
filter_rule *ent;
int parent_match = 0;
if (!implied_filter_list.head)
return 0;
/* The receiver exempts its synthetic transfer-root entry from the
* requested-name filter. Treat it as parent-only unless an empty/root
* source argument added the root-content rule. */
if ((name[0] == '.' && name[1] == '\0')
|| (name[0] == '/' && name[1] == '.' && name[2] == '\0')) {
for (ent = implied_filter_list.head; ent; ent = ent->next) {
if (!(ent->rflags & FILTRULE_INCLUDE))
continue;
if (strcmp(ent->pattern, "/**") == 0
|| strcmp(ent->pattern, "/*") == 0)
return 0;
}
return 1;
}
for (ent = implied_filter_list.head; ent; ent = ent->next) {
if (ent->rflags & (FILTRULE_PERDIR_MERGE | FILTRULE_CVS_IGNORE))
continue;
if (!rule_matches(name, ent, NAME_IS_DIR))
continue;
if (!(ent->rflags & FILTRULE_INCLUDE))
continue;
if (ent->rflags & FILTRULE_DIRECTORY) {
parent_match = 1;
continue;
}
/* A non-DIRECTORY include rule = a leaf the client asked for, so
* the dir is legitimately in the list, not parent-only. */
return 0;
}
return parent_match;
}
/* Return -1 if file "name" is defined to be excluded by the specified
* exclude list, 1 if it is included, and 0 if it was not matched. */
int check_filter(filter_rule_list *listp, enum logcode code,
@@ -1258,8 +1112,6 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
/* Inherit from the template. Don't inherit FILTRULES_SIDES; we check
* that later. */
rule->rflags = template->rflags & FILTRULES_FROM_CONTAINER;
if (rule_src_in_file)
rule->rflags |= FILTRULE_FROM_FILE; /* before parse_merge_name() */
/* Figure out what kind of a filter rule "s" is pointing at. Note
* that if FILTRULE_NO_PREFIXES is set, the rule is either an include
@@ -1357,7 +1209,8 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
rule->rflags |= FILTRULE_CLEAR_LIST;
break;
default:
filter_rule_err("Unknown filter rule", *rulestr_ptr);
rprintf(FERROR, "Unknown filter rule: `%s'\n", *rulestr_ptr);
exit_cleanup(RERR_SYNTAX);
}
while (ch != '!' && *++s && *s != ' ' && *s != '_') {
if (template->rflags & FILTRULE_WORD_SPLIT && isspace(*s)) {
@@ -1366,15 +1219,11 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
}
switch (*s) {
default:
invalid: {
char where[32];
snprintf(where, sizeof where, " '%c' at position %d",
*s, (int)(s - (const uchar *)*rulestr_ptr));
rprintf(FERROR, "invalid modifier%s in filter rule: %s\n",
rule_detail(NULL, where),
rule_text(NULL, *rulestr_ptr));
invalid:
rprintf(FERROR,
"invalid modifier '%c' at position %d in filter rule: %s\n",
*s, (int)(s - (const uchar *)*rulestr_ptr), *rulestr_ptr);
exit_cleanup(RERR_SYNTAX);
}
case '-':
if (!BITS_SETnUNSET(rule->rflags, FILTRULE_MERGE_FILE, FILTRULE_NO_PREFIXES))
goto invalid;
@@ -1446,8 +1295,10 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
/* The filter and template both specify side(s). This
* is dodgy (and won't work correctly if the template is
* a one-sided per-dir merge rule), so reject it. */
filter_rule_err("specified-side merge file contains specified-side filter",
*rulestr_ptr);
rprintf(FERROR,
"specified-side merge file contains specified-side filter: %s\n",
*rulestr_ptr);
exit_cleanup(RERR_SYNTAX);
}
rule->rflags |= template->rflags & FILTRULES_SIDES;
}
@@ -1462,14 +1313,17 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
len = strlen((char*)s);
if (rule->rflags & FILTRULE_CLEAR_LIST) {
if (!(template->rflags & FILTRULE_NO_PREFIXES)
if (!(rule->rflags & FILTRULE_NO_PREFIXES)
&& !(xflags & XFLG_OLD_PREFIXES) && len) {
filter_rule_err("'!' rule has trailing characters", *rulestr_ptr);
rprintf(FERROR,
"'!' rule has trailing characters: %s\n", *rulestr_ptr);
exit_cleanup(RERR_SYNTAX);
}
if (len > 1)
rule->rflags &= ~FILTRULE_CLEAR_LIST;
} else if (!len && !(rule->rflags & FILTRULE_CVS_IGNORE)) {
filter_rule_err("unexpected end of filter rule", *rulestr_ptr);
rprintf(FERROR, "unexpected end of filter rule: %s\n", *rulestr_ptr);
exit_cleanup(RERR_SYNTAX);
}
/* --delete-excluded turns an un-modified include/exclude into a sender-side rule. */
@@ -1528,8 +1382,8 @@ void parse_filter_str(filter_rule_list *listp, const char *rulestr,
break;
if (pat_len >= MAXPATHLEN) {
rprintf(FERROR, "discarding over-long filter: %s\n",
rule_text_len(NULL, pat, (int)pat_len));
rprintf(FERROR, "discarding over-long filter: %.*s\n",
(int)pat_len, pat);
free_continue:
free_filter(rule);
continue;
@@ -1557,11 +1411,6 @@ void parse_filter_str(filter_rule_list *listp, const char *rulestr,
filter_rule *excl_self;
excl_self = new0(filter_rule);
/* The pattern below is the merge rule's own text, so it
* inherits that rule's provenance. Built by hand, this
* rule looked argument-origin once parsing finished and
* the match trace echoed a merge file's contents at -vv. */
excl_self->rflags = rule->rflags & FILTRULE_FROM_FILE;
/* Find the beginning of the basename and add an exclude for it. */
for (name = pat + pat_len; name > pat && name[-1] != '/'; name--) {}
add_rule(listp, name, (pat + pat_len) - name, excl_self, 0);
@@ -1571,7 +1420,7 @@ void parse_filter_str(filter_rule_list *listp, const char *rulestr,
if (parent_dirscan) {
const char *p;
unsigned int len = pat_len;
if ((p = parse_merge_name(rule, pat, &len, module_dirlen)))
if ((p = parse_merge_name(pat, &len, module_dirlen)))
add_rule(listp, p, len, rule, 0);
else
free_filter(rule);
@@ -1580,7 +1429,7 @@ void parse_filter_str(filter_rule_list *listp, const char *rulestr,
} else {
const char *p;
unsigned int len = pat_len;
if ((p = parse_merge_name(rule, pat, &len, 0)))
if ((p = parse_merge_name(pat, &len, 0)))
parse_filter_file(listp, p, rule, XFLG_FATAL_ERRORS);
free_filter(rule);
continue;
@@ -1601,159 +1450,46 @@ void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_
char line[BIGPATHBUFLEN];
char *eob = line + sizeof line - 1;
BOOL word_split = (template->rflags & FILTRULE_WORD_SPLIT) != 0;
const char *save_src_file, *save_src_named_at;
int save_src_line, save_src_in_file;
int named_by_file;
int pending = EOF;
char named_at[MAXPATHLEN + 32];
/* Our own copy: fname may point into parse_merge_name()'s static buffer,
* which a merge rule inside THIS file overwrites while we still need it. */
char src_name[MAXPATHLEN];
if (!fname || !*fname)
return;
if (merge_depth >= MAX_MERGE_DEPTH) {
rprintf(FERROR,
"[%s] merge-file include depth limit (%d) exceeded at %s\n",
who_am_i(), MAX_MERGE_DEPTH, rule_text(template, fname));
/* Match the failed-open path below: abort under a fatal
* (operator-supplied) merge, otherwise drop the rule. */
if (xflags & XFLG_FATAL_ERRORS)
exit_cleanup(RERR_FILEIO);
return;
}
merge_depth++;
if (*fname != '-' || fname[1] || am_server) {
/* This path is operator- and (via per-directory merge files like
* .cvsignore) sender-controlled: a planted symlink could leak a
* root-readable file through the filter parser, or redirect an
* --exclude-from open via a planted parent. Refuse symlinks not
* owned by uid 0 or our euid. */
const char *open_path;
int fd;
if (daemon_filter_list.head) {
char *dir;
strlcpy(line, fname, sizeof line);
/* parse_merge_name() prepends module_dir for absolute paths,
* so strip module_dirlen back off before the check or the
* anchored module-relative daemon rule won't match (as
* options.c does for --exclude-from/--include-from). The
* original absolute path is still used for the open below. */
dir = line + (*line == '/' ? module_dirlen : 0);
clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS);
if (check_filter(&daemon_filter_list, FLOG, dir, 0) < 0) {
/* Hidden by the daemon filter: treat the merge file as
* non-existent rather than tripping XFLG_FATAL_ERRORS
* below, so it neither errors out nor leaks a
* fatal-vs-silent oracle. */
if (DEBUG_GTE(FILTER, 2)) {
/* Same rule as everywhere else: the name is
* file content when a rule we read named it,
* and so is "the daemon filter hides it". */
rprintf(FINFO, "[%s] parse_filter_file(%s)%s\n",
who_am_i(), rule_text(template, fname),
rule_detail(template, " hidden by daemon filter"));
}
merge_depth--;
return;
}
open_path = line;
clean_fname(line, CFN_COLLAPSE_DOT_DOT_DIRS);
if (check_filter(&daemon_filter_list, FLOG, line, 0) < 0)
fp = NULL;
else
fp = fopen(line, "rb");
} else
open_path = fname;
/* Confine the open to the module root. The ownership walk on its own
* is not enough for a peer-driven merge file: a non-chrooted daemon
* writes --backup-dir entries as root, so a raced backup symlink is
* ROOT-owned -- exactly what the ownership walk treats as trusted --
* and naming it in a dir-merge rule would read an out-of-module file
* in as filter rules (their text comes back to the peer in "Unknown
* filter rule" errors).
*
* The daemon's own "filter"/"include from"/"exclude from" parameters
* are exempt: those are operator-configured and legitimately live
* outside the module (/etc/rsync/excludes and the like). */
fd = vfs_open_owner_walk(open_path, O_RDONLY, 0, !daemon_config_filter_file);
if (fd < 0)
fp = NULL;
else if (!(fp = fdopen(fd, "rb")))
close(fd);
fp = fopen(fname, "rb");
} else
fp = stdin;
if (DEBUG_GTE(FILTER, 2)) {
/* The name is file CONTENT when a rule we read named it, and a
* word-split per-dir merge turns every word of a file into one
* of these -- so the trace would echo what the syntax errors no
* longer do. Say where it came from instead. */
rprintf(FINFO, "[%s] parse_filter_file(%s,%x,%x)%s\n",
who_am_i(), rule_text(template, fname), template->rflags, xflags,
rule_detail(template, fp ? "" : " [not found]"));
who_am_i(), fname, template->rflags, xflags,
fp ? "" : " [not found]");
}
if (!fp) {
if (xflags & XFLG_FATAL_ERRORS) {
/* rule_src_file is still the PARENT's context here: when it
* is set, this name came out of a file we read, so neither
* the name nor errno (an existence oracle) may be shown. */
if (TEXT_FROM_FILE(template)) {
/* errno too: it answers "does this path exist". */
rprintf(FERROR, "failed to open %sclude file %s\n",
template->rflags & FILTRULE_INCLUDE ? "in" : "ex",
rule_text(template, fname));
} else {
rsyserr(FERROR, errno,
"failed to open %sclude file %s",
template->rflags & FILTRULE_INCLUDE ? "in" : "ex",
fname);
}
rsyserr(FERROR, errno,
"failed to open %sclude file %s",
template->rflags & FILTRULE_INCLUDE ? "in" : "ex",
fname);
exit_cleanup(RERR_FILEIO);
}
merge_depth--;
return;
}
/* Before dirbuf is cut back: a per-directory fname points INTO dirbuf,
* so truncating first leaves only the directory and the location we
* report loses the filename. */
strlcpy(src_name, fname, sizeof src_name);
dirbuf[dirbuf_len] = '\0';
/* Rule text from here on is this file's contents, not an argument, so
* a syntax error must not echo it. Saved and restored because a merge
* rule inside this file can bring us back in for another file. */
save_src_in_file = rule_src_in_file;
save_src_file = rule_src_file;
save_src_line = rule_src_line;
/* If a rule we read named THIS file, our own path is file content too:
* track the location for provenance but do not put it in a message. */
named_by_file = TEXT_FROM_FILE(template);
save_src_named_at = rule_src_named_at;
if (named_by_file) {
/* Snapshot where we were told to merge this, before that state
* is replaced below (rule_src_where returns a static buffer).
* A DEFERRED merge has no live location to point at -- the file
* that named it was read and finished long ago -- so leave the
* generic description rather than nesting two vague ones. */
if (rule_src_in_file) {
strlcpy(named_at, rule_src_where(), sizeof named_at);
rule_src_named_at = named_at;
} else
rule_src_named_at = NULL;
}
rule_src_in_file = 1;
rule_src_file = named_by_file ? NULL : src_name;
rule_src_line = word_split ? -1 : 0; /* -1: tokens, not lines */
while (1) {
char *s = line;
int ch, overflow = 0;
if (rule_src_line >= 0)
rule_src_line++;
while (1) {
if (pending != EOF) { /* a CR lookahead we could not push back */
ch = pending;
pending = EOF;
} else if ((ch = getc(fp)) == EOF) {
if ((ch = getc(fp)) == EOF) {
if (ferror(fp) && errno == EINTR) {
clearerr(fp);
continue;
@@ -1762,51 +1498,25 @@ void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_
}
if (word_split && isspace(ch))
break;
if (eol_nulls? !ch : (ch == '\n' || ch == '\r')) {
if (ch == '\r') { /* CRLF is one line, not two */
int nxt;
while ((nxt = getc(fp)) == EOF
&& ferror(fp) && errno == EINTR)
clearerr(fp);
if (nxt == EOF) {
if (!ferror(fp))
ch = EOF; /* real end of file */
} else if (nxt != '\n' && ungetc(nxt, fp) == EOF) {
/* Pushback failed: hand it to the
* NEXT rule, where it belongs --
* appending it here would both
* corrupt this rule and skip the
* s < eob bound below. */
pending = nxt;
}
}
if (eol_nulls? !ch : (ch == '\n' || ch == '\r'))
break;
}
if (s < eob)
*s++ = ch;
else
overflow = 1;
}
if (overflow) {
rprintf(FERROR, "discarding over-long filter: %s\n",
rule_text_len(NULL, line, 0));
rprintf(FERROR, "discarding over-long filter: %s...\n", line);
s = line;
}
*s = '\0';
/* Skip an empty token and (when line parsing) comments. */
if (*line && (word_split || (*line != ';' && *line != '#'))) {
rule_src_file = named_by_file ? NULL : src_name;
if (*line && (word_split || (*line != ';' && *line != '#')))
parse_filter_str(listp, line, template, xflags);
}
if (ch == EOF)
break;
}
rule_src_in_file = save_src_in_file;
rule_src_file = save_src_file;
rule_src_line = save_src_line;
rule_src_named_at = save_src_named_at;
fclose(fp);
merge_depth--;
}
/* If the "for_xfer" flag is set, the prefix is made compatible with the
+35 -95
View File
@@ -45,17 +45,17 @@ int sparse_end(int f, OFF_T size, int updating_basis_or_equiv)
int ret = 0;
if (updating_basis_or_equiv) {
if (sparse_seek && vfs_punch_hole(f, sparse_past_write, sparse_seek) < 0)
if (sparse_seek && do_punch_hole(f, sparse_past_write, sparse_seek) < 0)
ret = -1;
#ifdef HAVE_FTRUNCATE /* A compilation formality -- in-place requires ftruncate() */
else /* Just in case the original file was longer */
ret = vfs_ftruncate(f, size);
ret = do_ftruncate(f, size);
#endif
} else if (sparse_seek) {
#ifdef HAVE_FTRUNCATE
ret = vfs_ftruncate(f, size);
ret = do_ftruncate(f, size);
#else
if (vfs_lseek(f, sparse_seek-1, SEEK_CUR) != size-1)
if (do_lseek(f, sparse_seek-1, SEEK_CUR) != size-1)
ret = -1;
else {
do {
@@ -75,60 +75,11 @@ int sparse_end(int f, OFF_T size, int updating_basis_or_equiv)
/* Note that the offset is just the caller letting us know where
* the current file position is in the file. The use_seek arg tells
* us that we should seek over matching data instead of writing it. */
/* Flush any deferred run of zero bytes as a hole, advancing the file
* position past it (both vfs_lseek() and vfs_punch_hole() move the offset). */
static int flush_sparse_hole(int f)
{
if (!sparse_seek)
return 0;
if (sparse_past_write >= preallocated_len) {
if (vfs_lseek(f, sparse_seek, SEEK_CUR) < 0) {
sparse_seek = 0;
return -1;
}
} else if (vfs_punch_hole(f, sparse_past_write, sparse_seek) < 0) {
sparse_seek = 0;
return -1;
}
sparse_seek = 0;
return 0;
}
static int full_sparse_write(int f, const char *buf, int len)
{
while (len > 0) {
int ret = write(f, buf, len);
if (ret <= 0) {
if (ret < 0 && errno == EINTR)
continue;
sparse_seek = 0;
return -1;
}
buf += ret;
len -= ret;
}
return 0;
}
/* Emit one span of data that is not being turned into a hole. For an in-place
* update (use_seek) the bytes on disk already match, so we only need to move
* past them; otherwise we write them out. Either way a deferred hole is
* flushed first so that the span lands at the right offset. */
static int emit_sparse_span(int f, int use_seek, const char *buf, int len)
{
if (flush_sparse_hole(f) < 0)
return -1;
if (use_seek)
return vfs_lseek(f, len, SEEK_CUR) < 0 ? -1 : 0;
return full_sparse_write(f, buf, len);
}
static int write_sparse(int f, int use_seek, OFF_T offset, const char *buf, int len)
{
int l1, l2, i, start, end;
int l1 = 0, l2 = 0;
int ret;
/* Always treat a leading and trailing run of zeros as a (deferred)
* hole, since they may merge with holes in the adjacent write calls. */
for (l1 = 0; l1 < len && buf[l1] == 0; l1++) {}
for (l2 = 0; l2 < len-l1 && buf[len-(l2+1)] == 0; l2++) {}
@@ -137,46 +88,37 @@ static int write_sparse(int f, int use_seek, OFF_T offset, const char *buf, int
if (l1 == len)
return len;
/* Scan the middle [l1, len-l2) for interior runs of zeros that are at
* least SPARSE_WRITE_SIZE long (the hole granularity rsync has always
* used) and defer those as holes. Everything in between -- which may
* include shorter zero runs not worth a hole -- is emitted in one go,
* rather than being chopped into SPARSE_WRITE_SIZE-byte pieces, which
* made copying a large non-sparse file cost ~one write() per KiB.
*
* The matched (use_seek) case runs through the same scan: its interior
* zero runs still have to be punched out, which is what --inplace
* --sparse relies on to keep a hole-y basis file sparse. */
start = l1;
end = len - l2;
for (i = l1; i < end; ) {
int z;
if (buf[i] != 0) {
i++;
continue;
}
for (z = 1; i + z < end && buf[i+z] == 0; z++) {}
if (z < SPARSE_WRITE_SIZE) {
i += z;
continue;
}
if (i > start) {
if (emit_sparse_span(f, use_seek, buf + start, i - start) < 0)
if (sparse_seek) {
if (sparse_past_write >= preallocated_len) {
if (do_lseek(f, sparse_seek, SEEK_CUR) < 0)
return -1;
sparse_past_write = offset + i;
}
sparse_seek += z;
i += z;
start = i;
}
if (end > start) {
if (emit_sparse_span(f, use_seek, buf + start, end - start) < 0)
} else if (do_punch_hole(f, sparse_past_write, sparse_seek) < 0) {
sparse_seek = 0;
return -1;
}
}
sparse_seek = l2;
sparse_past_write = offset + len - l2;
if (use_seek) {
/* The in-place data already matches. */
if (do_lseek(f, len - (l1+l2), SEEK_CUR) < 0)
return -1;
return len;
}
while ((ret = write(f, buf + l1, len - (l1+l2))) <= 0) {
if (ret < 0 && errno == EINTR)
continue;
sparse_seek = 0;
return ret;
}
if (ret != (int)(len - (l1+l2))) {
sparse_seek = 0;
return l1+ret;
}
return len;
}
@@ -211,10 +153,8 @@ int write_file(int f, int use_seek, OFF_T offset, const char *buf, int len)
while (len > 0) {
int r1;
if (sparse_files > 0) {
/* write_sparse() handles the whole span itself, scanning
* for holes and coalescing the non-zero data into large
* write()s instead of SPARSE_WRITE_SIZE-byte dribbles. */
r1 = write_sparse(f, use_seek, offset, buf, len);
int len1 = MIN(len, SPARSE_WRITE_SIZE);
r1 = write_sparse(f, use_seek, offset, buf, len1);
offset += r1;
} else {
if (!wf_writeBuf) {
@@ -262,7 +202,7 @@ int skip_matched(int fd, OFF_T offset, const char *buf, int len)
if (flush_write_file(fd) < 0)
return -1;
if ((pos = vfs_lseek(fd, len, SEEK_CUR)) != offset + len) {
if ((pos = do_lseek(fd, len, SEEK_CUR)) != offset + len) {
rsyserr(FERROR_XFER, errno, "lseek returned %s, not %s",
big_num(pos), big_num(offset));
return -1;
@@ -345,7 +285,7 @@ char *map_ptr(struct map_struct *map, OFF_T offset, int32 len)
}
if (map->p_fd_offset != read_start) {
OFF_T ret = vfs_lseek(map->fd, read_start, SEEK_SET);
OFF_T ret = do_lseek(map->fd, read_start, SEEK_SET);
if (ret != read_start) {
rsyserr(FERROR, errno, "lseek returned %s, not %s",
big_num(ret), big_num(read_start));
+35 -335
View File
@@ -29,10 +29,6 @@
extern int am_root;
extern int am_server;
extern int am_daemon;
extern int am_chrooted;
extern char *module_dir;
extern unsigned int module_dirlen;
extern int module_dirfd;
extern int am_sender;
extern int am_generator;
extern int inc_recurse;
@@ -68,7 +64,6 @@ extern int non_perishable_cnt;
extern int prune_empty_dirs;
extern int copy_links;
extern int copy_unsafe_links;
extern int insecure_links;
extern int protocol_version;
extern int sanitize_paths;
extern int munge_symlinks;
@@ -86,6 +81,7 @@ extern char *usermap, *groupmap;
extern struct name_num_item *file_sum_nni;
extern char curr_dir[MAXPATHLEN];
extern struct chmod_mode_struct *chmod_modes;
@@ -218,47 +214,13 @@ void show_flist_stats(void)
*
* The stat structure pointed to by stp will contain information about the
* link or the referent as appropriate, if they exist. */
/* Set by send_directory() to the fd of the directory it is currently scanning
* (and that dir's path prefix), so the per-entry stat can go through the
* already-open dir fd instead of re-resolving the full path for every entry.
* Pure performance and sender-side only -- the scanned dir is already open, so
* fstatat(scan_dirfd, basename) is identical to lstat(scandir/basename); no
* confinement is implied or needed. */
static int scan_dirfd = -1;
static const char *scan_dir_prefix;
static int scan_dir_prefix_len;
static int scan_link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks)
{
/* Use the held scan fd only for a single component directly inside the
* scanned dir, and only when am_root >= 0 (link_stat_at folds in no
* fake-super %stat xattr; link_stat does so via get_stat_xattr, a no-op
* once am_root >= 0). */
if (scan_dirfd >= 0 && am_root >= 0
&& strncmp(path, scan_dir_prefix, scan_dir_prefix_len) == 0
&& path[scan_dir_prefix_len] == '/'
&& strchr(path + scan_dir_prefix_len + 1, '/') == NULL)
return link_stat_at(scan_dirfd, path + scan_dir_prefix_len + 1, stp, follow_dirlinks);
return link_stat(path, stp, follow_dirlinks);
}
static int scan_readlink(const char *path, char *linkbuf, size_t bufsiz)
{
if (scan_dirfd >= 0 && am_root >= 0
&& strncmp(path, scan_dir_prefix, scan_dir_prefix_len) == 0
&& path[scan_dir_prefix_len] == '/'
&& strchr(path + scan_dir_prefix_len + 1, '/') == NULL)
return vfs_readlink_atfd(scan_dirfd, path + scan_dir_prefix_len + 1, linkbuf, bufsiz);
return vfs_readlink(path, linkbuf, bufsiz);
}
static int readlink_stat(const char *path, STRUCT_STAT *stp, char *linkbuf)
{
#ifdef SUPPORT_LINKS
if (scan_link_stat(path, stp, copy_dirlinks) < 0)
if (link_stat(path, stp, copy_dirlinks) < 0)
return -1;
if (S_ISLNK(stp->st_mode)) {
int llen = scan_readlink(path, linkbuf, MAXPATHLEN - 1);
int llen = do_readlink(path, linkbuf, MAXPATHLEN - 1);
if (llen < 0)
return -1;
linkbuf[llen] = '\0';
@@ -267,7 +229,7 @@ static int readlink_stat(const char *path, STRUCT_STAT *stp, char *linkbuf)
rprintf(FINFO,"copying unsafe symlink \"%s\" -> \"%s\"\n",
path, linkbuf);
}
return x_stat(path, stp, NULL, 0);
return x_stat(path, stp, NULL);
}
if (munge_symlinks && am_sender && llen > SYMLINK_PREFIX_LEN
&& strncmp(linkbuf, SYMLINK_PREFIX, SYMLINK_PREFIX_LEN) == 0) {
@@ -277,7 +239,7 @@ static int readlink_stat(const char *path, STRUCT_STAT *stp, char *linkbuf)
}
return 0;
#else
return x_stat(path, stp, NULL, 0);
return x_stat(path, stp, NULL);
#endif
}
@@ -285,41 +247,17 @@ int link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks)
{
#ifdef SUPPORT_LINKS
if (copy_links)
return x_stat(path, stp, NULL, 0);
if (x_lstat(path, stp, NULL, 0) < 0)
return x_stat(path, stp, NULL);
if (x_lstat(path, stp, NULL) < 0)
return -1;
if (follow_dirlinks && S_ISLNK(stp->st_mode)) {
STRUCT_STAT st;
if (x_stat(path, &st, NULL, 0) == 0 && S_ISDIR(st.st_mode))
if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode))
*stp = st;
}
return 0;
#else
return x_stat(path, stp, NULL, 0);
#endif
}
/* Held-dirfd variant of link_stat(): stat single-component `name` relative to
* directory fd `dfd`, instead of re-resolving a full path. Equivalent to
* link_stat() only when NOT in --fake-super mode -- x_stat/x_lstat fold the
* fake-super %stat xattr into the result via get_stat_xattr(), which is a
* path-based no-op once am_root >= 0. Callers therefore use this only when
* am_root >= 0 (and a valid dfd), falling back to link_stat() otherwise. */
int link_stat_at(int dfd, const char *name, STRUCT_STAT *stp, int follow_dirlinks)
{
#ifdef SUPPORT_LINKS
if (copy_links)
return vfs_stat(dfd, name, stp, 0);
if (vfs_lstat(dfd, name, stp, 0) < 0)
return -1;
if (follow_dirlinks && S_ISLNK(stp->st_mode)) {
STRUCT_STAT st;
if (vfs_stat(dfd, name, &st, 0) == 0 && S_ISDIR(st.st_mode))
*stp = st;
}
return 0;
#else
return vfs_stat(dfd, name, stp, 0);
return x_stat(path, stp, NULL);
#endif
}
@@ -365,31 +303,17 @@ static void flist_expand(struct file_list *flist, int extra)
{
struct file_struct **new_ptr;
/* Refuse BEFORE any int arithmetic below can overflow: used+extra (computed
* in the early-return and the cap below) and the malloced growth math. Only
* reachable past INT_MAX entries (my_alloc's --max-alloc cap normally stops
* the list growing anywhere near there). */
if (extra < 0 || flist->used < 0 || flist->used > INT_MAX - extra)
goto too_large;
if (flist->used + extra <= flist->malloced)
return;
if (flist->malloced < FLIST_START)
flist->malloced = FLIST_START;
else if (flist->malloced >= FLIST_LINEAR) {
if (flist->malloced > INT_MAX - FLIST_LINEAR)
goto too_large;
else if (flist->malloced >= FLIST_LINEAR)
flist->malloced += FLIST_LINEAR;
} else if (flist->malloced < FLIST_START_LARGE/16) {
if (flist->malloced > INT_MAX/4)
goto too_large;
else if (flist->malloced < FLIST_START_LARGE/16)
flist->malloced *= 4;
} else {
if (flist->malloced > INT_MAX/2)
goto too_large;
else
flist->malloced *= 2;
}
/* In case count jumped or we are starting the list
* with a known size just set it. */
@@ -406,11 +330,6 @@ static void flist_expand(struct file_list *flist, int extra)
}
flist->files = new_ptr;
return;
too_large:
rprintf(FERROR, "[%s] file list has grown too large to expand\n", who_am_i());
exit_cleanup(RERR_MALLOC);
}
static void flist_done_allocating(struct file_list *flist)
@@ -857,7 +776,7 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
if ((basename = strrchr(thisname, '/')) != NULL) {
int len = basename++ - thisname;
if (len != lastdir_len || !lastdir || memcmp(thisname, lastdir, len) != 0) {
if (len != lastdir_len || memcmp(thisname, lastdir, len) != 0) {
lastdir = new_array(char, len + 1);
memcpy(lastdir, thisname, len);
lastdir[len] = '\0';
@@ -906,17 +825,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
rdev_major = DEV_MAJOR(devp);
rdev = MAKEDEV(rdev_major, DEV_MINOR(devp));
extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
} else if (IS_DEVICE(mode)) {
/* Abbrev-branch counterpart to the !preserve_devices
* stub-alloc below: zeroed F_RDEV_P slots. */
extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
}
if (preserve_links && S_ISLNK(mode))
linkname_len = strlen(F_SYMLINK(first)) + 1;
else if (S_ISLNK(mode))
/* Abbrev-branch counterpart to the !preserve_links
* stub-alloc below: empty linkname. */
linkname_len = 1;
else
linkname_len = 0;
real_ISREG_entry = S_ISREG(mode) ? 1 : 0;
@@ -1038,15 +949,6 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
if (IS_DEVICE(mode))
extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
file_length = 0;
} else if (IS_DEVICE(mode)) {
/* Peer/batch sent an S_IFCHR/S_IFBLK entry but we are not
* preserving devices. A cooperating sender wouldn't do this;
* a crafted batch can. Allocate (and zero, via the memset
* below) the DEV_EXTRA_CNT slots so F_RDEV_P() callers
* (set_stat_xattr under --fake-super, generator IS_DEVICE
* paths) read {0,0} instead of the previous pool slot. */
extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
file_length = 0;
} else if (protocol_version < 28)
rdev = MAKEDEV(0, 0);
@@ -1067,14 +969,6 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
#endif
if (munge_symlinks)
linkname_len += SYMLINK_PREFIX_LEN;
} else if (S_ISLNK(mode)) {
/* Peer/batch sent an S_IFLNK entry but we are not preserving
* links (no -l, and the batch stream-flags didn't set it). A
* cooperating sender wouldn't do this; a crafted batch can.
* Allocate one byte for an empty linkname so F_SYMLINK()
* callers (log.c %L, generator.c) read a valid "" instead of
* the next pool slot's redzone. */
linkname_len = 1;
}
else
#endif
@@ -1122,15 +1016,6 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
exit_cleanup(RERR_UNSUPPORTED);
}
/* "." is the synthetic transfer root. Reinterpreting it as a file lets
* --force recursively remove the real destination directory before the
* receiver creates that file. */
if ((!strcmp(thisname, ".") || !strcmp(thisname, "/.")) && !S_ISDIR(mode)) {
rprintf(FERROR, "ERROR: rejecting non-directory transfer-root entry: %s\n",
thisname);
exit_cleanup(RERR_PROTOCOL);
}
if (*thisname == '/' ? thisname[1] != '.' || thisname[2] != '\0' : *thisname != '.' || thisname[1] != '\0') {
int filt_flags = S_ISDIR(mode) ? NAME_IS_DIR : NAME_IS_FILE;
if (!trust_sender_filter /* a per-dir filter rule means we must trust the sender's filtering */
@@ -1170,8 +1055,7 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
memcpy(bp, basename, basename_len);
#ifdef SUPPORT_HARD_LINKS
if (preserve_hard_links && xflags & XMIT_HLINKED
&& !S_ISDIR(mode)
if (xflags & XMIT_HLINKED
#ifndef CAN_HARDLINK_SYMLINK
&& !S_ISLNK(mode)
#endif
@@ -1226,26 +1110,6 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
if (basename_len == 1+1 && *basename == '.') /* +1 for '\0' */
F_DEPTH(file)--;
if (protocol_version >= 30) {
/* Stop a malicious sender expanding --delete scope by flagging
* an implied parent as a content dir: if we only allowed this
* entry as a parent of the requested leaf, force the flags back
* to the honest implied-parent encoding (XMIT_TOP_DIR |
* XMIT_NO_CONTENT_DIR) so it lands in FLAG_IMPLIED_DIR, not
* FLAG_CONTENT_DIR, and delete_in_dir() can't sweep siblings.
* Not gated on trust_sender_filter: implied_filter_list is
* receiver-owned state, so a per-dir filter must not be able to
* downgrade this defense. */
if (implied_filter_list.head
&& is_implied_parent_dir(thisname)
&& (!(xflags & XMIT_NO_CONTENT_DIR) || !(xflags & XMIT_TOP_DIR))) {
if (DEBUG_GTE(FILTER, 1)) {
rprintf(FINFO,
"[%s] receiver downgraded implied-parent dir %s "
"to non-content (sender xflags=0x%x)\n",
who_am_i(), thisname, xflags);
}
xflags |= XMIT_NO_CONTENT_DIR | XMIT_TOP_DIR;
}
if (!(xflags & XMIT_NO_CONTENT_DIR)) {
if (xflags & XMIT_TOP_DIR)
file->flags |= FLAG_TOP_DIR;
@@ -1253,17 +1117,13 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
} else if (xflags & XMIT_TOP_DIR)
file->flags |= FLAG_IMPLIED_DIR;
} else if (xflags & XMIT_TOP_DIR) {
if (implied_filter_list.head && is_implied_parent_dir(thisname))
file->flags |= FLAG_IMPLIED_DIR;
else {
in_del_hier = recurse;
del_hier_name_len = F_DEPTH(file) == 0 ? 0 : l1 + l2;
if (relative_paths && del_hier_name_len > 2
&& lastname[del_hier_name_len-1] == '.'
&& lastname[del_hier_name_len-2] == '/')
del_hier_name_len -= 2;
file->flags |= FLAG_TOP_DIR | FLAG_CONTENT_DIR;
}
in_del_hier = recurse;
del_hier_name_len = F_DEPTH(file) == 0 ? 0 : l1 + l2;
if (relative_paths && del_hier_name_len > 2
&& lastname[del_hier_name_len-1] == '.'
&& lastname[del_hier_name_len-2] == '/')
del_hier_name_len -= 2;
file->flags |= FLAG_TOP_DIR | FLAG_CONTENT_DIR;
} else if (in_del_hier) {
if (!relative_paths || !del_hier_name_len
|| (l1 >= del_hier_name_len
@@ -1283,11 +1143,7 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
#ifdef SUPPORT_LINKS
if (linkname_len) {
bp += basename_len;
if (!preserve_links) {
/* The empty-linkname case allocated above; nothing on
* the wire to read. Just terminate it. */
*bp = '\0';
} else if (first_hlink_ndx >= flist->ndx_start) {
if (first_hlink_ndx >= flist->ndx_start) {
struct file_struct *first = flist->files[first_hlink_ndx - flist->ndx_start];
memcpy(bp, F_SYMLINK(first), linkname_len);
} else {
@@ -1447,7 +1303,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
* options was specified, so there's no need for the
* extra lstat() if one of these options isn't on. */
if ((copy_links || copy_unsafe_links || copy_dirlinks)
&& x_lstat(thisname, &st, NULL, 0) == 0
&& x_lstat(thisname, &st, NULL) == 0
&& S_ISLNK(st.st_mode)) {
io_error |= IOERR_GENERAL;
rprintf(FERROR_XFER, "symlink has no referent: %s\n",
@@ -1544,7 +1400,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
if ((basename = strrchr(thisname, '/')) != NULL) {
int len = basename++ - thisname;
if (len != lastdir_len || !lastdir || memcmp(thisname, lastdir, len) != 0) {
if (len != lastdir_len || memcmp(thisname, lastdir, len) != 0) {
lastdir = new_array(char, len + 1);
memcpy(lastdir, thisname, len);
lastdir[len] = '\0';
@@ -1562,7 +1418,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
if (copy_devices && am_sender && IS_DEVICE(st.st_mode)) {
if (st.st_size == 0) {
int fd = vfs_open_checklinks(fname);
int fd = do_open_checklinks(fname);
if (fd >= 0) {
st.st_size = get_device_size(fd, fname);
close(fd);
@@ -1591,18 +1447,6 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
extra_len += SUM_EXTRA_CNT * EXTRA_LEN;
}
#ifdef HAVE_STRUCT_STAT_ST_RDEV
/* The sender path historically passes rdev via the tmp_rdev static
* (read by send_file_entry()), so make_file() never reserved
* DEV_EXTRA_CNT in the file_struct itself. But receiver-side callers
* (recv_generator's --inplace --backup back_file, backup.c make_backup)
* hand this struct to set_file_attrs() -> set_stat_xattr(), which reads
* F_RDEV_P(file) under --fake-super. Reserve and populate the slots
* so the struct is self-contained, matching recv_file_entry(). */
if (IS_DEVICE(st.st_mode))
extra_len += DEV_EXTRA_CNT * EXTRA_LEN;
#endif
#if EXTRA_ROUNDING > 0
if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
@@ -1636,10 +1480,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
#ifdef HAVE_STRUCT_STAT_ST_RDEV
if (IS_DEVICE(st.st_mode)) {
uint32 *devp = F_RDEV_P(file);
tmp_rdev = st.st_rdev;
DEV_MAJOR(devp) = major(st.st_rdev);
DEV_MINOR(devp) = minor(st.st_rdev);
st.st_size = 0;
} else if (IS_SPECIAL(st.st_mode))
st.st_size = 0;
@@ -1675,7 +1516,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
F_ATIME(file) = st.st_atime;
#ifdef SUPPORT_CRTIMES
if (crtimes_ndx)
F_CRTIME(file) = vfs_get_create_time(fname, &st);
F_CRTIME(file) = get_create_time(fname, &st);
#endif
if (basename != thisname)
@@ -1824,7 +1665,6 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
sx.st.st_mode = file->mode;
if (get_acl(fname, &sx) < 0) {
io_error |= IOERR_GENERAL;
free_acl(&sx);
return NULL;
}
}
@@ -1832,11 +1672,8 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
#ifdef SUPPORT_XATTRS
if (preserve_xattrs) {
sx.st.st_mode = file->mode;
if (get_xattr(fname, -1, &sx) < 0) {
if (get_xattr(fname, &sx) < 0) {
io_error |= IOERR_GENERAL;
#ifdef SUPPORT_ACLS
free_acl(&sx); /* get_acl() above may have loaded one */
#endif
return NULL;
}
}
@@ -2011,77 +1848,6 @@ static void interpret_stat_error(const char *fname, int is_dir)
}
}
#if defined HAVE_FDOPENDIR && defined HAVE_DIRFD
/* Open a source directory for scanning confined beneath the transfer root.
* vfs_resolve_open() does a per-component O_NOFOLLOW walk that refuses a
* parent component raced into a symlink pointing out of the tree; fdopendir()
* then turns the held fd into the DIR* the scan reads. This mirrors the
* sender's confined content open (sender.c): the directory enumeration must be
* confined the same way, or a parent-symlink race (or, for a daemon following
* mode, an in-module symlink to outside) lets the scan enumerate an out-of-tree
* directory and leak its names/metadata/symlink targets. O_DIRECTORY without
* O_NOFOLLOW makes vfs_resolve_open() follow in-tree directory symlinks
* beneath the anchor and refuse escapes, so this serves both the default
* no-follow scan and a daemon's symlink-following scan (see the caller).
* Returns NULL with errno set on failure, like opendir(). */
static DIR *secure_opendir(const char *fbuf)
{
int dfd, fl;
DIR *d;
if (am_daemon && (!am_chrooted || module_dirlen)
&& module_dir && module_dir[0] == '/' && *fbuf != '/' && module_dirfd >= 0
&& vfs.curr_dir_len >= module_dirlen
&& strncmp(vfs.curr_dir, module_dir, module_dirlen) == 0
&& (vfs.curr_dir[module_dirlen] == '\0' || vfs.curr_dir[module_dirlen] == '/')) {
/* Daemon: anchor the confined scan at the module root pinned by identity
* at module setup (module_dirfd, opened while the daemon was positioned
* there and still privileged), and walk the module-relative path of the
* scan target beneath it. This re-follows the same in-module path -- so a
* legitimate in-module ".." climb (sub/climb -> ../sibling) or an in-module
* directory symlink is followed, and an escape refused -- without
* re-walking the absolute module path as the dropped uid (the privilege-
* drop EACCES), and without assuming the lexical vfs.curr_dir depth matches the
* real cwd (a followed in-module symlink can desync them; anchoring at the
* pinned module root and walking down the logical path is correct either
* way). */
const char *p = vfs.curr_dir + module_dirlen;
char modrel[MAXPATHLEN];
while (*p == '/')
p++;
if ((size_t)snprintf(modrel, sizeof modrel, "%s%s%s",
p, *p ? "/" : "", fbuf) >= sizeof modrel) {
errno = ENAMETOOLONG;
return NULL;
}
dfd = vfs_resolve_open_at(module_dirfd, *modrel ? modrel : ".",
O_RDONLY | O_DIRECTORY, 0);
} else if (*fbuf == '/') {
/* An absolute scan path (an absolute --relative / --files-from name, or a
* "/" transfer root): anchor at "/" -- operator-named, trusted. */
const char *relp = fbuf;
while (*relp == '/')
relp++;
dfd = vfs_resolve_open("/", relp, O_RDONLY | O_DIRECTORY, 0);
} else {
/* Non-daemon (or chrooted) sender: confine beneath the cwd the sender
* chdir'd into (the transfer root). */
dfd = vfs_resolve_open(NULL, fbuf, O_RDONLY | O_DIRECTORY, 0);
}
if (dfd < 0)
return NULL;
if ((fl = fcntl(dfd, F_GETFD)) >= 0)
fcntl(dfd, F_SETFD, fl | FD_CLOEXEC);
if (!(d = fdopendir(dfd))) {
int save = errno;
close(dfd);
errno = save;
}
return d;
}
#endif
/* This function is normally called by the sender, but the receiving side also
* calls it from get_dirlist() with f set to -1 so that we just construct the
* file list in memory without sending it over the wire. Also, get_dirlist()
@@ -2100,31 +1866,7 @@ static void send_directory(int f, struct file_list *flist, char *fbuf, int len,
assert(flist != NULL);
#if defined HAVE_FDOPENDIR && defined HAVE_DIRFD
/* Confine the enumeration beneath the transfer root. secure_opendir()
* follows in-tree directory symlinks (RESOLVE_BENEATH) and refuses one that
* escapes, so it serves both modes:
* - a daemon/hardened sender (vfs_relpath_active()) is confined to the
* module in EVERY mode -- including -L/--copy-dirlinks/--copy-unsafe-
* links, matching the content open (sender_open_copylinks_confined) --
* so a following mode cannot be lured to enumerate outside the module;
* - a non-daemon sender is confined in the default no-follow mode; its
* symlink-following modes intentionally dereference out of the
* operator's own tree, so they keep the legacy opendir().
* f >= 0 is the sender's outgoing scan; get_dirlist() passes f < 0 and keeps
* the legacy opendir(). A module opted out of confinement ("insecure links =
* yes", admin-only) -- or a non-daemon --insecure-links -- uses the legacy
* opendir() too, restoring the pre-hardening enumeration (re-opening the
* escape; documented). */
if (f >= 0 && !vfs_symlink_optout_allowed() && (vfs_relpath_active()
|| !(copy_links || copy_unsafe_links || copy_dirlinks || insecure_links)))
d = secure_opendir(fbuf);
else
d = opendir(fbuf);
#else
d = opendir(fbuf);
#endif
if (!d) {
if (!(d = opendir(fbuf))) {
if (errno == ENOENT) {
if (am_sender) /* Can abuse this for vanished error w/ENOENT: */
interpret_stat_error(fbuf, True);
@@ -2147,14 +1889,6 @@ static void send_directory(int f, struct file_list *flist, char *fbuf, int len,
} else
remainder = 0;
#ifdef HAVE_DIRFD
/* Let the per-entry stat (readlink_stat -> scan_link_stat) go through the
* already-open directory fd instead of re-resolving fbuf for each name. */
scan_dirfd = dirfd(d);
scan_dir_prefix = fbuf;
scan_dir_prefix_len = len;
#endif
for (errno = 0, di = readdir(d); di; errno = 0, di = readdir(d)) {
unsigned name_len;
char *dname = d_name(di);
@@ -2183,9 +1917,6 @@ static void send_directory(int f, struct file_list *flist, char *fbuf, int len,
send_file_name(f, flist, fbuf, NULL, flags, filter_level);
}
scan_dirfd = -1; /* fbuf is about to be reused / d closed */
scan_dir_prefix = NULL; /* and don't leave the global pointing into fbuf */
scan_dir_prefix_len = 0;
fbuf[len] = '\0';
if (errno) {
@@ -2319,8 +2050,7 @@ static void send1extra(int f, struct file_struct *file, struct file_list *flist)
int len, dlen, flags = FLAG_DIVERT_DIRS | FLAG_CONTENT_DIR;
size_t j;
if (!f_name(file, fbuf))
return;
f_name(file, fbuf);
dlen = strlen(fbuf);
if (!change_pathname(file, NULL, 0))
@@ -2558,7 +2288,7 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
}
if (!orig_dir)
orig_dir = strdup(vfs.curr_dir);
orig_dir = strdup(curr_dir);
while (1) {
char fbuf[MAXPATHLEN], *fn, name_type;
@@ -2890,33 +2620,11 @@ struct file_list *recv_file_list(int f, int dir_ndx)
#endif
if (inc_recurse && dir_ndx >= 0) {
if (!first_flist) {
/* All flists have already been freed via the NDX_DONE
* chain, so dir_flist is stale: its files[] entries
* point into a destroyed pool. A sub-flist marker now
* is a protocol violation (and would otherwise UAF the
* stale dir entry below, then deref an uninitialised
* slot in the freshly reset dir_flist further down). */
rprintf(FERROR_XFER,
"rsync: refusing sub-flist after final flist was freed\n");
exit_cleanup(RERR_PROTOCOL);
}
if (dir_ndx >= dir_flist->used) {
rprintf(FERROR_XFER, "rsync: refusing invalid dir_ndx %u >= %u\n", dir_ndx, dir_flist->used);
exit_cleanup(RERR_PROTOCOL);
}
struct file_struct *file = dir_flist->files[dir_ndx];
if (!F_IS_ACTIVE(file)) {
/* flist_sort_and_clean() can clear_file() a directory
* entry that was a duplicate or otherwise pruned, but
* the cleared file_struct stays in dir_flist. A peer
* that then sends a sub-flist for that slot would make
* f_name() return NULL into the dirname strcmp() below. */
rprintf(FERROR_XFER,
"rsync: refusing flist for cleared dir_ndx %d\n",
dir_ndx);
exit_cleanup(RERR_PROTOCOL);
}
if (file->flags & FLAG_GOT_DIR_FLIST) {
rprintf(FERROR_XFER, "rsync: refusing malicious duplicate flist for dir %d\n", dir_ndx);
exit_cleanup(RERR_PROTOCOL);
@@ -2945,7 +2653,7 @@ struct file_list *recv_file_list(int f, int dir_ndx)
if ((flags = read_varint(f)) == 0) {
int err = read_varint(f);
if (!ignore_errors)
io_error |= err & IOERR_VALID_MASK;
io_error |= err;
break;
}
} else {
@@ -2963,7 +2671,7 @@ struct file_list *recv_file_list(int f, int dir_ndx)
}
err = read_varint(f);
if (!ignore_errors)
io_error |= err & IOERR_VALID_MASK;
io_error |= err;
break;
}
}
@@ -2978,7 +2686,7 @@ struct file_list *recv_file_list(int f, int dir_ndx)
cur_dir++;
if (cur_dir != good_dirname) {
const char *d = dir_ndx >= 0 ? f_name(dir_flist->files[dir_ndx], NULL) : empty_dir;
if (!d || strcmp(cur_dir, d) != 0) {
if (strcmp(cur_dir, d) != 0) {
rprintf(FERROR,
"ABORTING due to invalid path from sender: %s/%s\n",
cur_dir, file->basename);
@@ -3066,17 +2774,9 @@ struct file_list *recv_file_list(int f, int dir_ndx)
/* Recv the io_error flag */
int err = read_int(f);
if (!ignore_errors)
io_error |= err & IOERR_VALID_MASK;
io_error |= err;
} else if (inc_recurse && flist->ndx_start == 1) {
/* The first inc_recurse flist has no parent in dir_flist; a
* malicious peer can send a "." entry whose mode is not a
* directory, so it never lands in dir_flist (used stays 0) yet
* the basename test below still passes. That left parent_ndx at
* its default 0 and the consumers dereferenced dir_flist->files[0]
* = uninitialised heap. Require dir_flist to actually hold an
* entry before trusting index 0. */
if (!file_total || !dir_flist->used
|| strcmp(flist->sorted[flist->low]->basename, ".") != 0)
if (!file_total || strcmp(flist->sorted[flist->low]->basename, ".") != 0)
flist->parent_ndx = -1;
}
+66 -531
View File
@@ -41,7 +41,6 @@ extern int preserve_xattrs;
extern int preserve_links;
extern int preserve_devices;
extern int preserve_specials;
extern int drop_devices;
extern int preserve_hard_links;
extern int preserve_executability;
extern int preserve_perms;
@@ -132,7 +131,7 @@ static int start_delete_delay_temp(void)
dry_run = 0;
if (!get_tmpname(fnametmp, "deldelay", False)
|| (deldelay_fd = vfs_mkstemp(fnametmp, 0600)) < 0) {
|| (deldelay_fd = do_mkstemp(fnametmp, 0600)) < 0) {
rprintf(FINFO, "NOTE: Unable to create delete-delay temp file%s.\n",
inc_recurse ? "" : " -- switching to --delete-after");
delete_during = 0;
@@ -238,11 +237,7 @@ static int read_delay_line(char *buf, int *flags_p)
goto invalid_data;
}
past_space++;
/* Name length + NUL. Computed from past_space directly: the old
* `j - read_pos - (past_space - bp)` form was off by +1 when a '!'
* prefix had advanced bp past read_pos, over-reading deldelay_buf
* by one byte on a buffer-filling final entry. */
len = (deldelay_buf + j) - past_space + 1;
len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */
read_pos = j + 1;
if (len > MAXPATHLEN) {
@@ -413,7 +408,7 @@ static inline int any_time_differs(stat_x *sxp, struct file_struct *file, UNUSED
#ifdef SUPPORT_CRTIMES
if (!differs && crtimes_ndx) {
if (sxp->crtime == 0)
sxp->crtime = vfs_get_create_time(fname, &sxp->st);
sxp->crtime = get_create_time(fname, &sxp->st);
differs = !same_time(sxp->crtime, 0, F_CRTIME(file), 0);
}
#endif
@@ -461,7 +456,7 @@ static inline int xattrs_differ(const char *fname, struct file_struct *file, sta
{
if (preserve_xattrs) {
if (!XATTR_READY(*sxp))
get_xattr(fname, -1, sxp);
get_xattr(fname, sxp);
if (xattr_diff(file, sxp, 0))
return 1;
}
@@ -539,7 +534,7 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
#ifdef SUPPORT_CRTIMES
if (crtimes_ndx) {
if (sxp->crtime == 0)
sxp->crtime = vfs_get_create_time(fnamecmp, &sxp->st);
sxp->crtime = get_create_time(fnamecmp, &sxp->st);
if (!same_time(sxp->crtime, 0, F_CRTIME(file), 0))
iflags |= ITEM_REPORT_CRTIME;
}
@@ -570,7 +565,7 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
#ifdef SUPPORT_XATTRS
if (preserve_xattrs) {
if (!XATTR_READY(*sxp))
get_xattr(fnamecmp, -1, sxp);
get_xattr(fnamecmp, sxp);
if (xattr_diff(file, sxp, 1))
iflags |= ITEM_REPORT_XATTR;
}
@@ -655,7 +650,7 @@ int quick_check_ok(enum filetype ftype, const char *fn, struct file_struct *file
case FT_SYMLINK: {
#ifdef SUPPORT_LINKS
char lnk[MAXPATHLEN];
int len = vfs_readlink(fn, lnk, MAXPATHLEN-1);
int len = do_readlink(fn, lnk, MAXPATHLEN-1);
if (len <= 0)
return 0;
lnk[len] = '\0';
@@ -723,7 +718,8 @@ static void sum_sizes_sqroot(struct sum_struct *sum, int64 len)
else {
int32 max_blength = protocol_version < 30 ? OLD_MAX_BLOCK_SIZE : MAX_BLOCK_SIZE;
int32 c;
for (c = 1, l = len; l >>= 2; c <<= 1) {}
int cnt;
for (c = 1, l = len, cnt = 0; l >>= 2; c <<= 1, cnt++) {}
if (c < 0 || c >= max_blength)
blength = max_blength;
else {
@@ -931,15 +927,13 @@ static int copy_altdest_file(const char *src, const char *dest, struct file_stru
copy_to = buf;
}
cleanup_set(copy_to, NULL, NULL, -1, -1);
if (copy_file(src, copy_to, fd_w, file->mode, 0) < 0) {
if (copy_file(src, copy_to, fd_w, file->mode) < 0) {
if (INFO_GTE(COPY, 1)) {
rsyserr(FINFO, errno, "copy_file %s => %s",
full_fname(src), copy_to);
}
/* Try to clean up. copy_to's parent components are peer-named
* and can be raced to a symlink, so resolve each with O_NOFOLLOW
* via vfs_unlink_at() like the other generator-side unlinks. */
vfs_unlink(VFS_AT_FDCWD, copy_to, 0);
/* Try to clean up. */
unlink(copy_to);
cleanup_disable();
return -1;
}
@@ -951,117 +945,6 @@ static int copy_altdest_file(const char *src, const char *dest, struct file_stru
return ok ? 0 : -1;
}
/* Stat an alternate-basis candidate (basis_dir[j]/fname) for a daemon /./
* inner-module chroot through the secure resolver, so a --compare/copy/link-dest
* basis can't reach outside the inner module via a symlinked parent (the kernel
* chroot confines only the outer path). vfs_resolve_open() refuses a parent
* that escapes beneath the module root. Plain link_stat() everywhere else --
* the non-chroot daemon sanitizes basis paths already, and a local receiver must
* still follow an operator's --link-dest=../backup. */
static int basis_link_stat(const char *path, STRUCT_STAT *stp)
{
extern int am_chrooted;
extern unsigned int module_dirlen;
#if defined AT_FDCWD && defined O_NOFOLLOW && defined O_DIRECTORY
/* The basis dir (--link-dest/--compare-dest/--copy-dest) is an operator-
* supplied path. For a non-daemon receiver, resolve it with the ownership
* walk: a symlink component owned by uid 0 or the euid (the operator's own
* basis dir, e.g. --link-dest=/var/backups) is followed, a foreign-owned one
* is refused -- absolute and relative alike. Refusing it makes the basis
* look absent, so the file transfers normally instead of being read/linked/
* skipped through an attacker's symlink. --insecure-links restores legacy
* following; a daemon keeps its stronger confinement (chroot / secure
* resolver) below. Only when am_root >= 0: link_stat_at() omits the
* fake-super %stat xattr that link_stat() folds in, so --fake-super keeps
* the plain path (a lower-severity, non-root basis lookup). */
if (!am_daemon && am_root >= 0 && !vfs_symlink_optout_allowed()) {
const char *leaf;
/* non-daemon path: is_operator only gates the daemon module-confinement
* (a no-op here), so the ownership walk is identical either way. */
int dfd = vfs_owner_walk_parent(path, &leaf, 0);
int r, e;
if (dfd < 0)
return -1;
r = link_stat_at(dfd, leaf, stp, 0);
e = errno;
close(dfd);
errno = e;
return r;
}
/* A non-chroot daemon serving an operator/peer alt-dest basis: resolve through
* the ownership walk with module-ROOT confinement (is_operator=1) so an
* in-module symlink whose target lands OUTSIDE the module is refused -- the
* basis then looks absent and the file transfers normally instead of being
* stat'd/read/linked through the link (closes the --compare-dest=/E read
* oracle). "insecure links = yes" falls through to the legacy link_stat()
* below, restoring 3.2.7 following. Only an ABSOLUTE basis (rooted under the
* module by check_alt_basis_dirs, so it can reach an in-module symlink) is
* confined here; a RELATIVE basis (--link-dest=../01) is a dest-relative
* sibling whose "../" is already clamped to the module root by sanitize_path,
* and must keep the plain link_stat below (#915/#930). The leaf is taken
* under the confined parent with O_NOFOLLOW/AT_SYMLINK_NOFOLLOW, so
* --copy-links can't follow a leaf symlink out of the module. */
if (am_daemon && !am_chrooted && path[0] == '/' && !vfs_symlink_optout_allowed()) {
const char *leaf;
int dfd, e;
dfd = vfs_owner_walk_parent(path, &leaf, 1);
if (dfd < 0)
return -1;
if (am_root >= 0) {
int r = vfs_lstat(dfd, leaf, stp, 0);
e = errno;
close(dfd);
errno = e;
return r;
}
#ifdef SUPPORT_XATTRS
{
/* --fake-super: O_NOFOLLOW-open the held leaf (the daemon owns its
* fake-super files) so the %stat xattr link_stat() would fold is
* preserved while a leaf symlink is still refused. */
int lfd = vfs_open_atfd(dfd, leaf, O_RDONLY | O_NOFOLLOW | O_NONBLOCK, 0);
STRUCT_STAT xst;
e = errno;
close(dfd);
if (lfd < 0) { errno = e; return -1; }
if (vfs_fstat(lfd, stp) < 0) { e = errno; close(lfd); errno = e; return -1; }
if (get_stat_xattr(NULL, lfd, stp, &xst) == 0)
*stp = xst;
close(lfd);
return 0;
}
#else
{
int r = vfs_lstat(dfd, leaf, stp, 0);
e = errno;
close(dfd);
errno = e;
return r;
}
#endif
}
#endif
if (am_daemon && am_chrooted && module_dirlen && path[0] != '/' && !vfs_symlink_optout_allowed()) {
const char *slash = strrchr(path, '/');
if (slash) {
char dir[MAXPATHLEN];
size_t dlen = (size_t)(slash - path);
int dfd, r, e;
if (dlen >= sizeof dir) { errno = ENAMETOOLONG; return -1; }
memcpy(dir, path, dlen);
dir[dlen] = '\0';
if ((dfd = vfs_resolve_open(NULL, dir, O_RDONLY | O_DIRECTORY, 0)) < 0)
return -1;
r = link_stat_at(dfd, slash + 1, stp, 0);
e = errno;
close(dfd);
errno = e;
return r;
}
}
return link_stat(path, stp, 0);
}
/* This is only called for regular files. We return -2 if we've finished
* handling the file, -1 if no dest-linking occurred, or a non-negative
* value if we found an alternate basis file. If we're called with the
@@ -1079,7 +962,7 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
do {
pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
if (basis_link_stat(cmpbuf, &sxp->st) < 0 || !S_ISREG(sxp->st.st_mode))
if (link_stat(cmpbuf, &sxp->st, 0) < 0 || !S_ISREG(sxp->st.st_mode))
continue;
if (match_level == 0) {
best_match = j;
@@ -1105,7 +988,7 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
if (j != best_match) {
j = best_match;
pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
if (basis_link_stat(cmpbuf, &sxp->st) < 0)
if (link_stat(cmpbuf, &sxp->st, 0) < 0)
goto got_nothing_for_ya;
}
@@ -1113,21 +996,12 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
if (find_exact_for_existing) {
if (alt_dest_type == LINK_DEST && real_st.st_dev == sxp->st.st_dev && real_st.st_ino == sxp->st.st_ino)
return -1;
if (vfs_unlink(VFS_AT_FDCWD, fname, 0) < 0 && errno != ENOENT)
if (do_unlink_at(fname) < 0 && errno != ENOENT)
goto got_nothing_for_ya;
}
#ifdef SUPPORT_HARD_LINKS
if (alt_dest_type == LINK_DEST) {
/* For a NON-daemon receiver the basis dir is an operator path:
* resolve the link source via the ownership walk so a foreign-owned
* symlink raced in after the basis_link_stat() check is still
* refused (matching basis_link_stat's !am_daemon gate). A daemon
* keeps its stronger module-anchored confinement (vfs_link_at's
* vfs_relpath_active path) -- the ownership walk would follow an
* operator-owned symlink out of the module. */
int hlok = hard_link_one(file, fname, cmpbuf, 1,
!am_daemon ? VFS_OPERATOR_PATH : 0);
if (!hlok)
if (!hard_link_one(file, fname, cmpbuf, 1))
goto try_a_copy;
if (atimes_ndx)
set_file_attrs(fname, file, sxp, NULL, 0);
@@ -1156,14 +1030,6 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
#ifdef SUPPORT_HARD_LINKS
try_a_copy: /* Copy the file locally. */
#endif
/* NB: the copy-dest basis read is deliberately NOT routed through the
* ownership walk: copy_altdest_file()->copy_file() also opens the dest
* and copies xattrs through a held O_NOFOLLOW fd, and passing
* VFS_OPERATOR_PATH across that re-opens the copy_xattrs parent-
* symlink race (copy-xattrs-symlink-race) -- so copy_file gets flags 0.
* basis_link_stat() already
* refuses a foreign-owned basis symlink, closing the static escape; the
* post-stat race on an absolute copy-dest basis is a documented residual. */
if (!dry_run && copy_altdest_file(cmpbuf, fname, file) < 0) {
if (find_exact_for_existing) /* Can get here via hard-link failure */
goto got_nothing_for_ya;
@@ -1193,10 +1059,8 @@ got_nothing_for_ya:
}
/* This is only called for non-regular files. We return -2 if we've finished
* handling the file, -3 if we matched one but the destination refused to
* hard-link it (the caller creates it instead, and must not report it again),
* or -1 if no dest-linking occurred, or a non-negative value if we found an
* alternate basis file. */
* handling the file, or -1 if no dest-linking occurred, or a non-negative
* value if we found an alternate basis file. */
static int try_dests_non(struct file_struct *file, char *fname, int ndx,
char *cmpbuf, stat_x *sxp, int itemizing,
enum logcode code)
@@ -1219,7 +1083,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
do {
pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
if (basis_link_stat(cmpbuf, &sxp->st) < 0)
if (link_stat(cmpbuf, &sxp->st, 0) < 0)
continue;
if (ftype != get_file_type(sxp->st.st_mode))
continue;
@@ -1246,12 +1110,11 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
if (j != best_match) {
j = best_match;
pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
if (basis_link_stat(cmpbuf, &sxp->st) < 0)
if (link_stat(cmpbuf, &sxp->st, 0) < 0)
return -1;
}
if (match_level == 3) {
int cannot_hardlink = 0;
#ifdef SUPPORT_HARD_LINKS
if (alt_dest_type == LINK_DEST
#ifndef CAN_HARDLINK_SYMLINK
@@ -1261,34 +1124,13 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
&& !IS_SPECIAL(file->mode) && !IS_DEVICE(file->mode)
#endif
&& !S_ISDIR(file->mode)) {
/* cmpbuf is the alt-dest (--link-dest) basis: for a non-daemon
* receiver it is an operator path (owner walk; matches the
* hard_link_one() path above and basis_link_stat's !am_daemon gate).
* fname is the transfer destination (secure receiver resolve). */
if (vfs_link_at(cmpbuf, fname, !am_daemon ? VFS_OPERATOR_PATH : 0, 0) < 0) {
/* CAN_HARDLINK_SYMLINK/_SPECIAL answer for whatever
* filesystem the build tree sat on; the destination is
* free to disagree, and one host can hold both (macOS
* builds on APFS, backs up to HFS+). A refusal here is
* that same answer arriving late, so fall back to a copy
* as a build without the macro does -- the caller creates
* the entry either way, so failing the transfer only cost
* the exit status.
*
* Every errno, as the regular-file path next door already
* does (try_dests_reg -> hard_link_one -> try_a_copy).
* Picking out the "cannot" errnos is not possible anyway:
* link(2) documents EPERM both for a filesystem with no
* hard-link support and for an ordinary permission
* refusal, and FUSE reports ENOSYS for the same thing.
*
* The rest report themselves: ENOSPC/EDQUOT/EROFS fail the
* copy too, EMLINK and EXDEV mean it was never linkable.
* EIO alone goes unremarked, deliberately -- a diagnostic
* here lands in --link-dest's itemised output. */
cannot_hardlink = 1;
match_level = 2;
} else if (preserve_hard_links && F_IS_HLINKED(file))
if (do_link_at(cmpbuf, fname) < 0) {
rsyserr(FERROR_XFER, errno,
"failed to hard-link %s with %s",
cmpbuf, fname);
return j;
}
if (preserve_hard_links && F_IS_HLINKED(file))
finish_hard_link(file, fname, ndx, NULL, itemizing, code, -1);
} else
#endif
@@ -1304,11 +1146,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
rprintf(FCLIENT, "%s%s is uptodate\n",
fname, ftype == FT_DIR ? "/" : "");
}
/* -2 tells the caller the entry is already up to date, which for
* --link-dest means "skip it". We could not link it, so say -3
* instead: no caller claims that, and the fall-through creates the
* entry -- the same place a build without the macro ends up. */
return cannot_hardlink ? -3 : -2;
return -2;
}
return j;
@@ -1378,237 +1216,6 @@ static BOOL is_below(struct file_struct *file, struct file_struct *subtree)
*
* Note that f_out is set to -1 when doing final directory-permission and
* modification-time repair. */
/* Held-dirfd helpers for the per-entry ops below: when the secure resolver is
* active they act on the entry's basename relative to its cached directory fd
* (vfs_cached_dirfd, keyed on file->dirname), else fall back to the full-path
* do_*_at wrappers (behaviour-identical). vfs_cached_dirfd() declines when fname
* isn't in file->dirname (e.g. the single-file local_name dest), and the leaf
* is derived from fname, not file->basename. */
static int gen_entry_stat(const char *fname, struct file_struct *file,
STRUCT_STAT *stp, int follow_dirlinks)
{
int dfd;
/* link_stat_at folds in no fake-super xattr, so only use it when
* am_root >= 0 (where link_stat's get_stat_xattr is a no-op anyway). */
if (am_root >= 0 && (dfd = vfs_cached_dirfd(fname, file)) >= 0) {
const char *slash = strrchr(fname, '/');
return link_stat_at(dfd, slash ? slash + 1 : fname, stp, follow_dirlinks);
}
return link_stat(fname, stp, follow_dirlinks);
}
static int gen_entry_mkdir(char *fname, struct file_struct *file, mode_t mode)
{
int dfd = vfs_cached_dirfd(fname, file);
if (dfd >= 0) {
char *slash = strrchr(fname, '/');
return vfs_mkdir(dfd, slash ? slash + 1 : fname, mode, 0);
}
return vfs_mkdir(VFS_AT_FDCWD, fname, mode, 0);
}
static int gen_entry_chmod(const char *fname, struct file_struct *file, mode_t mode)
{
int dfd = vfs_cached_dirfd(fname, file);
if (dfd >= 0) {
const char *slash = strrchr(fname, '/');
return vfs_chmod(dfd, slash ? slash + 1 : fname, mode, 0);
}
return vfs_chmod(VFS_AT_FDCWD, fname, mode, 0);
}
static void gen_entry_set_times(const char *fname, struct file_struct *file, STRUCT_STAT *stp)
{
int dfd = vfs_cached_dirfd(fname, file);
if (dfd >= 0) {
const char *slash = strrchr(fname, '/');
if (set_times_at(dfd, slash ? slash + 1 : fname, stp) != -2)
return; /* handled (success or error) by the at-on-dfd tier */
}
set_times(fname, stp);
}
static int gen_entry_symlink(const char *slnk, const char *path, struct file_struct *file)
{
int dfd = vfs_cached_dirfd(path, file);
if (dfd >= 0) {
const char *slash = strrchr(path, '/');
return vfs_symlink(slnk, dfd, slash ? slash + 1 : path, 0);
}
return vfs_symlink(slnk, VFS_AT_FDCWD, path, 0);
}
/* True when this build compiled no fd-relative primitive able to create this
* kind of node. That is a property of the build, not of the call, so it is
* decided here rather than inferred from an errno. */
static int no_atfd_mknod_primitive(mode_t mode)
{
#ifndef AT_FDCWD
(void)mode;
return 1;
#else
/* --fake-super creates the placeholder with openat(), which exists
* wherever AT_FDCWD does, so a failure there is a real failure and is
* deliberately NOT retried unconfined -- even though a runtime denial
* (a seccomp policy permitting open() but not openat()) would then fail
* a create that the errno-based test used to let through. */
if (am_root < 0)
return 0;
# ifdef HAVE_MKNODAT
(void)mode;
return 0;
# elif defined(HAVE_MKFIFOAT)
return !S_ISFIFO(mode); /* FIFOs are covered; device nodes are not */
# else
(void)mode;
return 1;
# endif
#endif
}
static int gen_entry_mknod(const char *path, struct file_struct *file, mode_t mode, dev_t rdev)
{
int dfd;
/* vfs_mknod_atfd can't create a socket (no portable bindat); fall back. */
if (!S_ISSOCK(mode) && (dfd = vfs_cached_dirfd(path, file)) >= 0) {
const char *slash = strrchr(path, '/');
int ret = vfs_mknod(dfd, slash ? slash + 1 : path, mode, rdev, 0);
/* Fall through to the unconfined path-based create only where this
* build compiled no fd-relative primitive for this kind of node --
* SECURITY.md's rule for a platform that cannot be secure at all.
* Testing errno == ENOSYS is not that test: a live mknodat() or
* mkfifoat() can return ENOSYS too (an unimplemented FUSE mknod,
* or seccomp), which would drop confinement on a platform that
* does have the secure primitive. */
if (ret == 0 || !no_atfd_mknod_primitive(mode))
return ret;
}
return vfs_mknod(VFS_AT_FDCWD, path, mode, rdev, 0);
}
static int gen_entry_unlink(const char *path, struct file_struct *file)
{
int dfd = vfs_cached_dirfd(path, file);
if (dfd >= 0) {
const char *slash = strrchr(path, '/');
return vfs_unlink(dfd, slash ? slash + 1 : path, 0);
}
return vfs_unlink(VFS_AT_FDCWD, path, 0);
}
/* opath and npath are both expected to live in the entry's directory (the
* tmp -> final rename); when both resolve to the held dir fd the rename is a
* single renameat() within it, else fall back to the full-path wrapper. */
static int gen_entry_rename(const char *opath, const char *npath, struct file_struct *file)
{
int odfd = vfs_cached_dirfd(opath, file);
int ndfd = vfs_cached_dirfd(npath, file);
if (odfd >= 0 && ndfd >= 0) {
const char *os = strrchr(opath, '/');
const char *ns = strrchr(npath, '/');
return vfs_rename_atfd(odfd, os ? os + 1 : opath, ndfd, ns ? ns + 1 : npath);
}
return vfs_rename_at(opath, npath, 0, 0); /* both live in the entry's dir (transfer) */
}
#ifdef SUPPORT_XATTRS
/* Copy xattrs from src onto fname through a held, O_NOFOLLOW-opened fd so a
* parent-symlink race can't redirect the setxattr. A hardened receiver always
* uses a confined fd -- via the cached dir fd, or a secure re-pin when that
* misses -- and refuses rather than path-write if it can't pin; only a
* non-hardened receiver falls back to the path-based copy (matches
* set_file_attrs' held-fd handling). */
static int gen_entry_copy_xattrs(const char *src, const char *fname, struct file_struct *file)
{
int dfd = vfs_cached_dirfd(fname, file);
int xfd = -1, sfd = -1, ret;
if (dfd >= 0) {
const char *slash = strrchr(fname, '/');
xfd = openat(dfd, slash ? slash + 1 : fname,
O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_NOCTTY | O_CLOEXEC);
if (xfd < 0) {
/* We hold a confined parent dirfd but couldn't pin the
* leaf (e.g. it was raced to a symlink) -- refuse rather
* than fall back to a path-based set that would follow the
* parent components. */
rsyserr(FERROR_XFER, errno,
"gen_entry_copy_xattrs: openat(%s) failed",
full_fname(fname));
return -1;
}
}
#if defined AT_FDCWD && defined O_NOFOLLOW
else if (vfs_relpath_active()) {
/* No cached parent dirfd (e.g. a path deeper than the dirfd cache, or a
* raced parent) but we must confine: re-pin the dest leaf through the
* secure resolver so copy_xattrs uses fsetxattr, not a path-based
* lsetxattr a flipped parent could redirect out of tree. A raced
* parent/leaf makes this fail -> refuse rather than path-write. */
int odir = 0;
# ifdef O_DIRECTORY
if (S_ISDIR(file->mode))
odir = O_DIRECTORY;
# endif
xfd = vfs_resolve_open(NULL, fname,
O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_NOCTTY | O_CLOEXEC | odir, 0);
if (xfd < 0) {
rsyserr(FERROR_XFER, errno,
"gen_entry_copy_xattrs: secure open of %s failed",
full_fname(fname));
return -1;
}
}
#endif
/* Pin the SOURCE (alt-dest basis) leaf too so the xattr READ can't be raced
* out of tree (copy_file does the same for its content+xattr source). A
* relative basis goes through the RESOLVE_BENEATH resolver; an absolute one
* through the operator ownership walk. Refuse (don't path-read) when we are
* meant to confine but can't pin; a non-hardened receiver path-reads (sfd<0). */
#if defined AT_FDCWD && defined O_NOFOLLOW
if (vfs_relpath_active() && src && *src && !vfs_symlink_optout_allowed()) {
int odir = 0;
#ifdef O_DIRECTORY
if (S_ISDIR(file->mode)) /* vfs_resolve_open rejects a dir leaf without this */
odir = O_DIRECTORY;
#endif
if (src[0] != '/')
sfd = vfs_resolve_open(NULL, src, O_RDONLY | O_NOFOLLOW | odir, 0);
else {
int sdfd, e;
const char *leaf;
sdfd = vfs_owner_walk_parent(src, &leaf, 1);
if (sdfd >= 0) {
sfd = openat(sdfd, leaf, O_RDONLY | O_NOFOLLOW | odir | O_NONBLOCK | O_NOCTTY | O_CLOEXEC);
e = errno; close(sdfd); errno = e;
}
}
if (sfd < 0) {
rsyserr(FERROR_XFER, errno,
"gen_entry_copy_xattrs: secure open of basis %s failed",
full_fname(src));
if (xfd >= 0)
close(xfd);
return -1;
}
}
#endif
#ifdef STRICT_CONFINEMENT
/* In the confined regime the dfd/re-pin paths above yield xfd >= 0 or already
* returned -1; reaching copy_xattrs with xfd < 0 while confined would let it
* path-write the dest xattrs (the copy-xattrs fallback class) -- abort. */
if (xfd < 0 && vfs_must_be_confined(fname, 0))
vfs_strict_confine_fail(fname, "gen_entry_copy_xattrs dest");
#endif
ret = copy_xattrs(src, sfd, fname, xfd);
if (sfd >= 0)
close(sfd);
if (xfd >= 0)
close(xfd);
return ret;
}
#endif
static void recv_generator(char *fname, struct file_struct *file, int ndx,
int itemizing, enum logcode code, int f_out)
{
@@ -1623,7 +1230,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
static int need_fuzzy_dirlist = 0;
struct file_struct *fuzzy_file = NULL;
int fd = -1, f_copy = -1;
stat_x sx = {0}, real_sx = {0};
stat_x sx, real_sx;
STRUCT_STAT partial_st;
struct file_struct *back_file = NULL;
int statret, real_ret, stat_errno;
@@ -1720,10 +1327,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
}
if (relative_paths && !implied_dirs && file->mode != 0
&& vfs_stat(VFS_AT_FDCWD, dn, &sx.st, 0) < 0) {
&& do_stat_at(dn, &sx.st) < 0) {
if (dry_run)
goto parent_is_dry_missing;
if (vfs_make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH, 0) < 0) {
if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
rsyserr(FERROR_XFER, errno,
"recv_generator: mkdir %s failed",
full_fname(dn));
@@ -1746,7 +1353,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
parent_dirname = dn;
statret = gen_entry_stat(fname, file, &sx.st, keep_dirlinks && is_dir);
statret = link_stat(fname, &sx.st, keep_dirlinks && is_dir);
stat_errno = errno;
}
@@ -1832,7 +1439,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
&& (stype == FT_DIR
|| delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_DIR) != 0))
goto cleanup; /* Any errors get reported later. */
if (gen_entry_mkdir(fname, file, (file->mode|added_perms) & 0700) == 0)
if (do_mkdir_at(fname, (file->mode|added_perms) & 0700) == 0)
file->flags |= FLAG_DIR_CREATED;
goto cleanup;
}
@@ -1874,13 +1481,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
itemize(fnamecmp, file, ndx, statret, &sx,
statret ? ITEM_LOCAL_CHANGE : 0, 0, NULL);
}
if (real_ret != 0 && gen_entry_mkdir(fname, file, file->mode|added_perms) < 0 && errno != EEXIST) {
/* The parent may have just been created by make_path(), so
* drop any cached (failed) dir fd before the retry. */
vfs_dircache_reset();
if (real_ret != 0 && do_mkdir_at(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
if (!relative_paths || errno != ENOENT
|| vfs_make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH, 0) < 0
|| (gen_entry_mkdir(fname, file, file->mode|added_perms) < 0 && errno != EEXIST)) {
|| make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
|| (do_mkdir_at(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
rsyserr(FERROR_XFER, errno,
"recv_generator: mkdir %s failed",
full_fname(fname));
@@ -1894,7 +1498,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
#ifdef SUPPORT_XATTRS
if (preserve_xattrs && statret == 1)
gen_entry_copy_xattrs(fnamecmpbuf, fname, file);
copy_xattrs(fnamecmpbuf, fname);
#endif
if (set_file_attrs(fname, file, real_ret ? NULL : &real_sx, NULL, 0)
&& INFO_GTE(NAME, 1) && code != FNONE && f_out != -1)
@@ -1907,7 +1511,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
#ifdef HAVE_CHMOD
if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
mode_t mode = file->mode | S_IRWXU;
if (gen_entry_chmod(fname, file, mode) < 0) {
if (do_chmod_at(fname, mode) < 0) {
rsyserr(FERROR_XFER, errno,
"failed to modify permissions on %s",
full_fname(fname));
@@ -1981,14 +1585,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
} else if (basis_dir[0] != NULL) {
int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
if (j == -3) {
/* The destination cannot hard-link this type. Land exactly
* where a build without CAN_HARDLINK_SYMLINK lands: create
* the entry, but leave the reporting to the itemisation
* try_dests_non() already emitted. */
itemizing = 0;
code = FNONE;
} else if (j == -2) {
if (j == -2) {
#ifndef CAN_HARDLINK_SYMLINK
if (alt_dest_type == LINK_DEST) {
/* Resort to --copy-dest behavior. */
@@ -2027,20 +1624,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
goto cleanup;
}
/* --drop-D refuses to CREATE devices/specials without touching
* preserve_devices/preserve_specials, which also frame the file list's
* rdev fields -- clearing those on one end of a connection alone
* desynchronises it. Falls through to the "skipping non-regular file"
* path below, exactly as --no-D reaches it. */
if (!drop_devices
&& ((am_root && preserve_devices && ftype == FT_DEVICE)
|| (preserve_specials && ftype == FT_SPECIAL))) {
if ((am_root && preserve_devices && ftype == FT_DEVICE)
|| (preserve_specials && ftype == FT_SPECIAL)) {
dev_t rdev;
int del_for_flag;
/* Whether the dest existed, captured before the type-mismatch
* flip below clears statret -- so atomic_create() gets a delete
* flag (and reads sx.st) only when sx.st was actually stat'd. */
int dest_existed = (statret == 0);
if (ftype == FT_DEVICE) {
uint32 *devp = F_RDEV_P(file);
rdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp));
@@ -2067,12 +1654,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
} else if (basis_dir[0] != NULL) {
int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
if (j == -3) {
/* As above: a destination that cannot hard-link this type
* behaves like a build without CAN_HARDLINK_SPECIAL. */
itemizing = 0;
code = FNONE;
} else if (j == -2) {
if (j == -2) {
#ifndef CAN_HARDLINK_SPECIAL
if (alt_dest_type == LINK_DEST) {
/* Resort to --copy-dest behavior. */
@@ -2092,7 +1674,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
fname, (int)file->mode,
(long)major(rdev), (long)minor(rdev));
}
if (atomic_create(file, fname, NULL, NULL, rdev, &sx, dest_existed ? del_for_flag : 0)) {
if (atomic_create(file, fname, NULL, NULL, rdev, &sx, del_for_flag)) {
set_file_attrs(fname, file, NULL, NULL, 0);
if (itemizing) {
itemize(fnamecmp, file, ndx, statret, &sx,
@@ -2229,7 +1811,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
if (write_devices && IS_DEVICE(sx.st.st_mode) && sx.st.st_size == 0) {
/* This early open into fd skips the regular open below. */
if ((fd = vfs_open_nofollow(fnamecmp, O_RDONLY)) >= 0)
if ((fd = do_open_nofollow(fnamecmp, O_RDONLY)) >= 0)
real_sx.st.st_size = sx.st.st_size = get_device_size(fd, fnamecmp);
}
@@ -2239,10 +1821,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
;
else if (quick_check_ok(FT_REG, fnamecmp, file, &sx.st)) {
if (partialptr) {
/* The --partial-dir basis is an operator/peer path: unlink it
* through the exclude-aware ownership walk so a symlinked
* partial-dir can't delete a file in an excluded subtree. */
vfs_unlink(VFS_AT_FDCWD, partialptr, VFS_OPERATOR_PATH);
do_unlink_at(partialptr);
handle_partial_dir(partialptr, PDIR_DELETE);
}
set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT | maybe_ATTRS_ACCURATE_TIME);
@@ -2281,17 +1860,11 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
if (read_batch || whole_file) {
if (inplace && make_backups > 0 && fnamecmp_type == FNAMECMP_FNAME) {
/* The --backup-dir (backupptr) is an operator path; this in-place
* backup bypasses make_backup(), so get_backup_name() (make_path) and
* copy_file() below are passed VFS_OPERATOR_PATH to resolve it with the
* ownership walk instead of following any symlink. */
if (!(backupptr = get_backup_name(fname))) {
if (!(backupptr = get_backup_name(fname)))
goto cleanup;
}
if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) {
if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS)))
goto pretend_missing;
}
if (copy_file(fname, backupptr, -1, back_file->mode, VFS_OPERATOR_PATH) < 0) {
if (copy_file(fname, backupptr, -1, back_file->mode) < 0) {
unmake_file(back_file);
back_file = NULL;
goto cleanup;
@@ -2307,7 +1880,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
/* open the file */
if (fd < 0 && (fd = vfs_open_checklinks(fnamecmp)) < 0) {
if (fd < 0 && (fd = do_open_checklinks(fnamecmp)) < 0) {
rsyserr(FERROR, errno, "failed to open %s, continuing",
full_fname(fnamecmp));
pretend_missing:
@@ -2323,22 +1896,20 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
if (inplace && make_backups > 0 && fnamecmp_type == FNAMECMP_FNAME) {
/* Operator --backup-dir, bypassing make_backup(): resolve get_backup_name()
* (make_path), the unlink and the create with the ownership walk. */
if (!(backupptr = get_backup_name(fname))) {
goto cleanup;
}
if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) {
goto pretend_missing;
}
if (robust_unlink(backupptr, VFS_OPERATOR_PATH) && errno != ENOENT) {
if (robust_unlink(backupptr) && errno != ENOENT) {
rsyserr(FERROR_XFER, errno, "unlink %s",
full_fname(backupptr));
unmake_file(back_file);
back_file = NULL;
goto cleanup;
}
if ((f_copy = vfs_open_at(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600, VFS_OPERATOR_PATH)) < 0) {
if ((f_copy = do_open_at(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) {
rsyserr(FERROR_XFER, errno, "open %s", full_fname(backupptr));
unmake_file(back_file);
back_file = NULL;
@@ -2406,34 +1977,14 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
close(fd);
if (back_file) {
int save_preserve_xattrs = preserve_xattrs;
if (f_copy >= 0)
close(f_copy);
#ifdef SUPPORT_XATTRS
/* The delta-backup path wrote backupptr via the held f_copy, so
* copy its xattrs through that fd here. The whole-file/inplace
* path (f_copy < 0) backed it up via copy_file(), which already
* copied the xattrs through its own held fd -- don't repeat it
* with a path-based set a parent-symlink race could redirect. */
if (preserve_xattrs && f_copy >= 0) {
/* Read fname's xattrs through a confined fd so the copy onto the
* held backup fd can't be fed an out-of-module source by a raced
* parent symlink; a hardened race skips rather than path-reads. */
int bfd = backup_source_fd(fname);
if (!backup_metadata_hardened() || bfd >= 0)
copy_xattrs(fname, bfd, backupptr, f_copy);
if (bfd >= 0)
close(bfd);
if (preserve_xattrs) {
copy_xattrs(fname, backupptr);
preserve_xattrs = 0;
}
#endif
if (f_copy >= 0)
close(f_copy);
/* backupptr's data/xattrs were written safely (confined create under
* VFS_OPERATOR_PATH, held-fd xattr copy above). This metadata set
* re-resolves backupptr by path and is NOT wrapped in operator mode:
* set_file_attrs() also drives the path-based xattr set whose held-fd
* race-fix operator mode would defeat (cf. the copy-dest note in
* try_dests_reg). The static --backup-dir escape is already closed; a
* parent-symlink flipped in after the confined create races only these
* chmod/chown/times -- a documented residual. */
set_file_attrs(backupptr, back_file, NULL, NULL, 0);
preserve_xattrs = save_preserve_xattrs;
if (INFO_GTE(BACKUP, 1)) {
@@ -2444,7 +1995,6 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
free_stat_x(&sx);
free_stat_x(&real_sx);
}
/* If we are replacing an existing hard link, symlink, device, or special file,
@@ -2479,7 +2029,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
if (slnk) {
#ifdef SUPPORT_LINKS
if (gen_entry_symlink(slnk, create_name, file) < 0) {
if (do_symlink_at(slnk, create_name) < 0) {
rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed",
full_fname(create_name), slnk);
return 0;
@@ -2489,41 +2039,28 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
#endif
} else if (hlnk) {
#ifdef SUPPORT_HARD_LINKS
if (!hard_link_one(file, create_name, hlnk, 0, 0))
if (!hard_link_one(file, create_name, hlnk, 0))
return 0;
#else
return 0;
#endif
} else {
if (gen_entry_mknod(create_name, file, file->mode, rdev) < 0) {
int e = errno;
/* A nested socket can't be created race-safely where there is no
* bindat() (the BSDs, macOS, Solaris): syscall.c returns EOPNOTSUPP
* rather than re-resolving an unsafe parent. A socket inode is only
* a placeholder -- a live socket isn't usefully transferred -- so
* skip it with a warning instead of failing the whole transfer
* (got_xfer_error -> exit 23). Top-level sockets still create via
* the path-based bind() fallback. */
if (S_ISSOCK(file->mode) && (e == EOPNOTSUPP || e == ENOSYS)) {
rprintf(FWARNING, "skipping socket (creation unsupported here): %s\n",
full_fname(create_name));
return 0;
}
rsyserr(FERROR_XFER, e, "mknod %s failed",
if (do_mknod_at(create_name, file->mode, rdev) < 0) {
rsyserr(FERROR_XFER, errno, "mknod %s failed",
full_fname(create_name));
return 0;
}
}
if (!skip_atomic) {
if (gen_entry_rename(tmpname, fname, file) < 0) {
if (do_rename_at(tmpname, fname) < 0) {
char *full_tmpname = strdup(full_fname(tmpname));
if (full_tmpname == NULL)
out_of_memory("atomic_create");
rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\" failed",
full_tmpname, full_fname(fname));
free(full_tmpname);
gen_entry_unlink(tmpname, file);
do_unlink_at(tmpname);
return 0;
}
}
@@ -2587,15 +2124,15 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
continue;
fname = f_name(file, NULL);
if (fix_dir_perms)
gen_entry_chmod(fname, file, file->mode);
do_chmod_at(fname, file->mode);
if (need_retouch_dir_times) {
STRUCT_STAT st;
if (gen_entry_stat(fname, file, &st, 0) == 0 && mtime_differs(&st, file)) {
if (link_stat(fname, &st, 0) == 0 && mtime_differs(&st, file)) {
st.st_mtime = file->modtime;
#ifdef ST_MTIME_NSEC
st.ST_MTIME_NSEC = F_MOD_NSEC_or_0(file);
#endif
gen_entry_set_times(fname, file, &st);
set_times(fname, &st);
}
}
if (counter >= loopchk_limit) {
@@ -2696,8 +2233,7 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
if (delete_during == 2 || !dir_tweaking) {
/* Skip directory touch-up. */
} else if (first_flist->parent_ndx >= 0
&& first_flist->parent_ndx < dir_flist->used)
} else if (first_flist->parent_ndx >= 0)
touch_up_dirs(dir_flist, first_flist->parent_ndx);
flist_free(first_flist); /* updates first_flist */
@@ -2768,8 +2304,7 @@ void generate_files(int f_out, const char *local_name)
}
#endif
if (inc_recurse && cur_flist->parent_ndx >= 0
&& cur_flist->parent_ndx < dir_flist->used) {
if (inc_recurse && cur_flist->parent_ndx >= 0) {
struct file_struct *fp = dir_flist->files[cur_flist->parent_ndx];
if (solo_file)
strlcpy(fbuf, solo_file, sizeof fbuf);
-1
View File
@@ -57,6 +57,5 @@
printf("%lu", (unsigned long)gid);
printf("\n");
free(list);
return 0;
}
+10 -25
View File
@@ -19,7 +19,7 @@
#include "rsync.h"
#define HASH_LOAD_LIMIT(size) ((size)/4*3) /* /4 first: never overflows int */
#define HASH_LOAD_LIMIT(size) ((size)*3/4)
struct hashtable *hashtable_create(int size, int key64)
{
@@ -28,25 +28,15 @@ struct hashtable *hashtable_create(int size, int key64)
int node_size = key64 ? sizeof (struct ht_int64_node)
: sizeof (struct ht_int32_node);
/* Pick a power of 2 that can hold the requested size. Test size < 16 first
* so a negative/zero req short-circuits before the size-1 (INT_MIN is UB). */
if (size < 16 || (size & (size-1))) {
/* Pick a power of 2 that can hold the requested size. */
if (size & (size-1) || size < 16) {
size = 16;
while (size < req) {
if (size > INT_MAX/2) { /* the next doubling would overflow int */
rprintf(FERROR, "[%s] hashtable_create: requested size %d is too large\n",
who_am_i(), req);
exit_cleanup(RERR_MALLOC);
}
while (size < req)
size *= 2;
}
}
tbl = new(struct hashtable);
/* Pass size and node_size as SEPARATE factors so my_alloc's overflow /
* --max-alloc guard sees both; computing size*node_size as int would wrap to
* a tiny count and under-allocate (heap overflow on later node access). */
tbl->nodes = my_alloc(do_calloc, size, node_size, __FILE__, __LINE__);
tbl->nodes = new_array0(char, size * node_size);
tbl->size = size;
tbl->entries = 0;
tbl->node_size = node_size;
@@ -100,15 +90,10 @@ void *hashtable_find(struct hashtable *tbl, int64 key, void *data_when_new)
if (data_when_new && tbl->entries > HASH_LOAD_LIMIT(tbl->size)) {
void *old_nodes = tbl->nodes;
int size, i;
int size = tbl->size * 2;
int i;
if (tbl->size > INT_MAX/2) { /* doubling would overflow int */
rprintf(FERROR, "[%s] hashtable grow: size overflow\n", who_am_i());
exit_cleanup(RERR_MALLOC);
}
size = tbl->size * 2;
/* Separate factors so my_alloc's guard sees both (see hashtable_create). */
tbl->nodes = my_alloc(do_calloc, size, tbl->node_size, __FILE__, __LINE__);
tbl->nodes = new_array0(char, size * tbl->node_size);
tbl->size = size;
tbl->entries = 0;
@@ -135,7 +120,7 @@ void *hashtable_find(struct hashtable *tbl, int64 key, void *data_when_new)
if (!key64) {
/* Based on Jenkins One-at-a-time hash. */
uchar buf[4] = {0}, *keyp = buf; /* {0} only to satisfy the analyzer (SIVALu fills buf) */
uchar buf[4], *keyp = buf;
int i;
SIVALu(buf, 0, key);
@@ -366,7 +351,7 @@ void *hashtable_find(struct hashtable *tbl, int64 key, void *data_when_new)
*/
#define NON_ZERO_32(x) ((x) ? (x) : (uint32_t)1)
#define NON_ZERO_64(x, y) ((x) || (y) ? (y) | (uint64_t)(x) << 32 | (y) : (int64)1)
#define NON_ZERO_64(x, y) ((x) || (y) ? (y) | (int64)(x) << 32 | (y) : (int64)1)
uint32_t hashlittle(const void *key, size_t length)
{
+5 -29
View File
@@ -125,22 +125,8 @@ static void match_gnums(int32 *ndx_list, int ndx_count)
if (inc_recurse) {
node = hashtable_find(prior_hlinks, gnum, data_when_new);
if (node->data == data_when_new) {
if (gnum < hlink_flist->ndx_start) {
/* A non-first hard-link entry whose
* gnum points before this flist's
* ndx_start should already have been
* recorded in prior_hlinks by an
* earlier flist. A peer that sends
* such a back-reference on the first
* flist (or to a gnum that was never
* declared XMIT_HLINK_FIRST) is
* misbehaving. */
rprintf(FERROR,
"hard-link gnum %d precedes flist start %d\n",
(int)gnum, (int)hlink_flist->ndx_start);
exit_cleanup(RERR_PROTOCOL);
}
node->data = new_array0(char, 5);
assert(gnum >= hlink_flist->ndx_start);
file->flags |= FLAG_HLINK_FIRST;
prev = -1;
} else if (CVAL(node->data, 0) == 0) {
@@ -420,14 +406,7 @@ int hard_link_check(struct file_struct *file, int ndx, char *fname,
}
break;
}
/* Content-based basis match only applies to regular
* files: for a hard-linked symlink/device/special the
* exact-inode check above is the only meaningful test,
* and quick_check_ok(FT_REG, ...) would read F_SUM()
* on a file_struct that has no SUM_EXTRA_CNT space
* (recv_file_entry only allocates it for S_ISREG). */
if (!S_ISREG(file->mode)
|| !quick_check_ok(FT_REG, cmpbuf, file, &alt_sx.st))
if (!quick_check_ok(FT_REG, cmpbuf, file, &alt_sx.st))
continue;
statret = 1;
if (unchanged_attrs(cmpbuf, file, &alt_sx))
@@ -451,7 +430,7 @@ int hard_link_check(struct file_struct *file, int ndx, char *fname,
if (preserve_xattrs) {
free_xattr(sxp);
if (!XATTR_READY(alt_sx))
get_xattr(cmpbuf, -1, sxp);
get_xattr(cmpbuf, sxp);
else {
sxp->xattr = alt_sx.xattr;
alt_sx.xattr = NULL;
@@ -473,12 +452,9 @@ int hard_link_check(struct file_struct *file, int ndx, char *fname,
}
int hard_link_one(struct file_struct *file, const char *fname,
const char *oldname, int terse, int vfs_flags)
const char *oldname, int terse)
{
/* oldname is the link source (vfs_flags carries its policy -- VFS_OPERATOR_PATH
* for an alt-dest basis on a non-daemon receiver, else 0); fname is the
* transfer destination, always under the secure receiver resolve. */
if (vfs_link_at(oldname, fname, vfs_flags, 0) < 0) {
if (do_link_at(oldname, fname) < 0) {
enum logcode code;
if (terse) {
if (!INFO_GTE(NAME, 1))
+65 -260
View File
@@ -31,15 +31,7 @@
#include "ifuncs.h"
#include "inums.h"
#include <poll.h>
/* Readiness bits we act on. poll() can report POLLERR/POLLHUP/POLLNVAL even
* when they were not requested, and POLLPRI stands in for select()'s old
* exception set. */
#define POLL_RD_BITS (POLLIN | POLLPRI | POLLERR | POLLHUP)
#define POLL_WR_BITS (POLLOUT | POLLERR | POLLHUP)
/** If no timeout is specified then use a 60 second I/O timeout */
/** If no timeout is specified then use a 60 second select timeout */
#define SELECT_TIMEOUT 60
extern int bwlimit;
@@ -67,7 +59,6 @@ extern int xfer_sum_len;
extern int daemon_connection;
extern int protocol_version;
extern int remove_source_files;
extern int write_batch;
extern int preserve_hard_links;
extern BOOL extra_flist_sending_enabled;
extern BOOL flush_ok_after_signal;
@@ -88,7 +79,6 @@ BOOL flist_receiving_enabled = False;
/* Ignore an EOF error if non-zero. See whine_about_eof(). */
int kluge_around_eof = 0;
int got_kill_signal = -1; /* is set to 0 only after multiplexed I/O starts */
volatile sig_atomic_t got_sigusr2 = 0; /* set by the async-signal-safe SIGUSR2 handler */
int sock_f_in = -1;
int sock_f_out = -1;
@@ -112,11 +102,6 @@ static struct {
static time_t last_io_in;
static time_t last_io_out;
/* Absolute wall-clock bound for peer-controlled daemon handshake reads.
* This is deliberately separate from io_timeout: the latter is an idle
* transfer timeout and may be supplied by the module or client. */
static time_t daemon_handshake_deadline;
static int write_batch_monitor_in = -1;
static int write_batch_monitor_out = -1;
@@ -128,38 +113,6 @@ static xbuf ff_xb = EMPTY_XBUF;
static xbuf iconv_buf = EMPTY_XBUF;
#endif
static int select_timeout = SELECT_TIMEOUT;
/* Turn select_timeout (in seconds) into a poll() millisecond count, keeping it
* positive and bounded. A negative count means "wait forever" to poll(), which
* would bypass our keepalives and timeout enforcement entirely. */
static int poll_timeout_ms(void)
{
int secs = select_timeout;
if (secs <= 0 || secs > SELECT_TIMEOUT)
secs = SELECT_TIMEOUT;
return secs * 1000;
}
static int handshake_poll_timeout_ms(void)
{
time_t now, left;
int timeout = poll_timeout_ms();
if (!daemon_handshake_deadline)
return timeout;
now = time(NULL);
left = daemon_handshake_deadline - now;
if (left <= 0) {
rprintf(FERROR, "[%s] daemon handshake timeout -- exiting\n", who_am_i());
exit_cleanup(RERR_TIMEOUT);
}
if (left <= INT_MAX / 1000 && left * 1000 < timeout)
timeout = (int)left * 1000;
return timeout;
}
static int active_filecnt = 0;
static OFF_T active_bytecnt = 0;
static int first_message = 1;
@@ -267,15 +220,9 @@ static NORETURN void whine_about_eof(BOOL allow_kluge)
int i;
if (kluge_around_eof > 0)
exit_cleanup(0);
/* The receiver is waiting here for the generator's SIGUSR2; act on it
* (exit cleanly) the moment it arrives rather than sleeping the full
* 10s and then erroring. The async-signal-safe handler only sets the
* flag, so this loop must poll it. */
for (i = 10*1000/20; i--; ) {
if (got_sigusr2)
receive_sigusr2();
/* If we're still here after 10 seconds, exit with an error. */
for (i = 10*1000/20; i--; )
msleep(20);
}
}
rprintf(FERROR, RSYNC_NAME ": connection unexpectedly closed "
@@ -296,35 +243,31 @@ static size_t safe_read(int fd, char *buf, size_t len)
assert(fd != iobuf.in_fd);
while (1) {
struct pollfd pfd;
struct timeval tv;
fd_set r_fds, e_fds;
int cnt;
if (got_sigusr2) /* receiver told to wrap up (e.g. a --read-batch fd) */
receive_sigusr2();
FD_ZERO(&r_fds);
FD_SET(fd, &r_fds);
FD_ZERO(&e_fds);
FD_SET(fd, &e_fds);
tv.tv_sec = select_timeout;
tv.tv_usec = 0;
/* We use poll() rather than select() so that a high-numbered fd
* (>= FD_SETSIZE) cannot overflow an fd_set bitmap. */
pfd.fd = fd;
pfd.events = POLLIN | POLLPRI;
pfd.revents = 0;
cnt = poll(&pfd, 1, handshake_poll_timeout_ms());
cnt = select(fd+1, &r_fds, NULL, &e_fds, &tv);
if (cnt <= 0) {
if (cnt < 0 && errno != EINTR && errno != EAGAIN) {
rsyserr(FERROR, errno, "safe_read poll failed");
if (cnt < 0 && errno == EBADF) {
rsyserr(FERROR, errno, "safe_read select failed");
exit_cleanup(RERR_FILEIO);
}
check_timeout(1, MSK_ALLOW_FLUSH);
continue;
}
/* An invalid fd is reported here rather than via poll()'s return. */
if (pfd.revents & POLLNVAL) {
rsyserr(FERROR, EBADF, "safe_read poll failed");
exit_cleanup(RERR_FILEIO);
}
/*if (FD_ISSET(fd, &e_fds))
rprintf(FINFO, "select exception on fd %d\n", fd); */
if (pfd.revents & POLL_RD_BITS) {
if (FD_ISSET(fd, &r_fds)) {
ssize_t n = read(fd, buf + got, len - got);
if (DEBUG_GTE(IO, 2)) {
rprintf(FINFO, "[%s] safe_read(%d)=%" SIZE_T_FMT_MOD "d\n",
@@ -372,9 +315,6 @@ static void safe_write(int fd, const char *buf, size_t len)
assert(fd != iobuf.out_fd);
if (got_sigusr2) /* receiver told to wrap up before this (batch) write */
receive_sigusr2();
n = write(fd, buf, len);
if ((size_t)n == len)
return;
@@ -392,21 +332,19 @@ static void safe_write(int fd, const char *buf, size_t len)
}
while (len) {
struct pollfd pfd;
struct timeval tv;
fd_set w_fds;
int cnt;
if (got_sigusr2) /* receiver told to wrap up (e.g. a --write-batch fd) */
receive_sigusr2();
FD_ZERO(&w_fds);
FD_SET(fd, &w_fds);
tv.tv_sec = select_timeout;
tv.tv_usec = 0;
/* poll() avoids the FD_SETSIZE limit that select() imposes. */
pfd.fd = fd;
pfd.events = POLLOUT;
pfd.revents = 0;
cnt = poll(&pfd, 1, poll_timeout_ms());
cnt = select(fd + 1, NULL, &w_fds, NULL, &tv);
if (cnt <= 0) {
if (cnt < 0 && errno != EINTR && errno != EAGAIN) {
rsyserr(FERROR, errno, "safe_write poll failed on %s", what_fd_is(fd));
if (cnt < 0 && errno == EBADF) {
rsyserr(FERROR, errno, "safe_write select failed on %s", what_fd_is(fd));
exit_cleanup(RERR_FILEIO);
}
if (io_timeout)
@@ -414,12 +352,7 @@ static void safe_write(int fd, const char *buf, size_t len)
continue;
}
if (pfd.revents & POLLNVAL) {
rsyserr(FERROR, EBADF, "safe_write poll failed on %s", what_fd_is(fd));
exit_cleanup(RERR_FILEIO);
}
if (pfd.revents & POLL_WR_BITS) {
if (FD_ISSET(fd, &w_fds)) {
n = write(fd, buf, len);
if (n < 0) {
if (errno == EINTR)
@@ -628,8 +561,9 @@ static void handle_kill_signal(BOOL flush_ok)
* unused raw data in the buf would prevent the reading of socket data. */
static char *perform_io(size_t needed, int flags)
{
struct pollfd pfds[3];
int cnt, max_fd, npfds, poll_timeout, in_pollpos, out_pollpos, ff_pollpos;
fd_set r_fds, e_fds, w_fds;
struct timeval tv;
int cnt, max_fd;
size_t empty_buf_len = 0;
xbuf *out;
char *data;
@@ -722,15 +656,13 @@ static char *perform_io(size_t needed, int flags)
}
max_fd = -1;
npfds = 0;
in_pollpos = out_pollpos = ff_pollpos = -1;
FD_ZERO(&r_fds);
FD_ZERO(&e_fds);
if (iobuf.in_fd >= 0 && iobuf.in.size - iobuf.in.len) {
if (!read_batch || batch_fd >= 0) {
pfds[npfds].fd = iobuf.in_fd;
pfds[npfds].events = POLLIN | POLLPRI;
pfds[npfds].revents = 0;
in_pollpos = npfds++;
FD_SET(iobuf.in_fd, &r_fds);
FD_SET(iobuf.in_fd, &e_fds);
}
if (iobuf.in_fd > max_fd)
max_fd = iobuf.in_fd;
@@ -738,14 +670,12 @@ static char *perform_io(size_t needed, int flags)
/* Only do more filesfrom processing if there is enough room in the out buffer. */
if (ff_forward_fd >= 0 && iobuf.out.size - iobuf.out.len > FILESFROM_BUFLEN*2) {
pfds[npfds].fd = ff_forward_fd;
pfds[npfds].events = POLLIN;
pfds[npfds].revents = 0;
ff_pollpos = npfds++;
FD_SET(ff_forward_fd, &r_fds);
if (ff_forward_fd > max_fd)
max_fd = ff_forward_fd;
}
FD_ZERO(&w_fds);
if (iobuf.out_fd >= 0) {
if (iobuf.raw_flushing_ends_before
|| (!iobuf.msg.len && iobuf.out.len > iobuf.out_empty_len && !(flags & PIO_NEED_MSGROOM))) {
@@ -785,18 +715,7 @@ static char *perform_io(size_t needed, int flags)
} else
out = NULL;
if (out) {
/* A direct daemon connection uses one fd for both
* directions; give it a single row with both events
* rather than two rows carrying different masks. */
if (in_pollpos >= 0 && iobuf.out_fd == iobuf.in_fd) {
pfds[in_pollpos].events |= POLLOUT;
out_pollpos = in_pollpos;
} else {
pfds[npfds].fd = iobuf.out_fd;
pfds[npfds].events = POLLOUT;
pfds[npfds].revents = 0;
out_pollpos = npfds++;
}
FD_SET(iobuf.out_fd, &w_fds);
if (iobuf.out_fd > max_fd)
max_fd = iobuf.out_fd;
}
@@ -830,20 +749,19 @@ static char *perform_io(size_t needed, int flags)
if (got_kill_signal > 0)
handle_kill_signal(True);
if (got_sigusr2)
receive_sigusr2();
if (extra_flist_sending_enabled) {
if (file_total - file_old_total < MAX_FILECNT_LOOKAHEAD && IN_MULTIPLEXED_AND_READY)
poll_timeout = 0;
tv.tv_sec = 0;
else {
extra_flist_sending_enabled = False;
poll_timeout = poll_timeout_ms();
tv.tv_sec = select_timeout;
}
} else
poll_timeout = poll_timeout_ms();
tv.tv_sec = select_timeout;
tv.tv_usec = 0;
cnt = poll(pfds, npfds, poll_timeout);
cnt = select(max_fd + 1, &r_fds, &w_fds, &e_fds, &tv);
if (cnt <= 0) {
if (cnt < 0 && errno == EBADF) {
@@ -856,29 +774,11 @@ static char *perform_io(size_t needed, int flags)
extra_flist_sending_enabled = !flist_eof;
} else
check_timeout((flags & PIO_NEED_INPUT) != 0, 0);
/* Just in case... */
if (in_pollpos >= 0)
pfds[in_pollpos].revents = 0;
if (ff_pollpos >= 0)
pfds[ff_pollpos].revents = 0;
if (out_pollpos >= 0)
pfds[out_pollpos].revents = 0;
FD_ZERO(&r_fds); /* Just in case... */
FD_ZERO(&w_fds);
}
if (cnt > 0) {
/* poll() reports a bad fd here, not via its return value. */
int p;
for (p = 0; p < npfds; p++) {
if (pfds[p].revents & POLLNVAL) {
msgs2stderr = 1;
rsyserr(FERROR, EBADF, "perform_io: poll reported an invalid fd");
exit_cleanup(RERR_SOCKETIO);
}
}
}
if (iobuf.in_fd >= 0 && in_pollpos >= 0
&& pfds[in_pollpos].revents & POLL_RD_BITS) {
if (iobuf.in_fd >= 0 && FD_ISSET(iobuf.in_fd, &r_fds)) {
size_t len, pos = iobuf.in.pos + iobuf.in.len;
ssize_t n;
if (pos >= iobuf.in.size) {
@@ -927,7 +827,7 @@ static char *perform_io(size_t needed, int flags)
exit_cleanup(RERR_TIMEOUT);
}
if (out && out_pollpos >= 0 && pfds[out_pollpos].revents & POLL_WR_BITS) {
if (out && FD_ISSET(iobuf.out_fd, &w_fds)) {
size_t len = iobuf.raw_flushing_ends_before ? iobuf.raw_flushing_ends_before - out->pos : out->len;
ssize_t n;
@@ -978,8 +878,6 @@ static char *perform_io(size_t needed, int flags)
if (got_kill_signal > 0)
handle_kill_signal(True);
if (got_sigusr2)
receive_sigusr2();
/* We need to help prevent deadlock by doing what reading
* we can whenever we are here trying to write. */
@@ -990,8 +888,7 @@ static char *perform_io(size_t needed, int flags)
wait_for_receiver(); /* generator only */
}
if (ff_forward_fd >= 0 && ff_pollpos >= 0
&& pfds[ff_pollpos].revents & POLL_RD_BITS) {
if (ff_forward_fd >= 0 && FD_ISSET(ff_forward_fd, &r_fds)) {
/* This can potentially flush all output and enable
* multiplexed output, so keep this last in the loop
* and be sure to not cache anything that would break
@@ -1003,8 +900,6 @@ static char *perform_io(size_t needed, int flags)
if (got_kill_signal > 0)
handle_kill_signal(True);
if (got_sigusr2)
receive_sigusr2();
data = iobuf.in.buf + iobuf.in.pos;
@@ -1175,30 +1070,17 @@ void send_msg_int(enum msgcode code, int num)
void send_msg_success(const char *fname, int num)
{
/* Batch-only mode has not duplicated anything on the receiving side yet.
* The receiver still reports success to the generator for file-list and
* hard-link bookkeeping, but the generator must not turn that status into
* sender-side removal. */
if (am_generator && write_batch < 0 && remove_source_files)
return;
if (local_server) {
STRUCT_STAT st;
if (DEBUG_GTE(IO, 1))
rprintf(FINFO, "[%s] send_msg_success(%d)\n", who_am_i(), num);
/* The dev/ino is consumed only by the sender's --remove-source-files
* same-file safety check (successful_send), so skip the per-file
* stat entirely otherwise -- it's sent but never read. */
if (remove_source_files && stat(fname, &st) == 0) {
SIVAL64(num_dev_ino_buf, 4, st.st_dev);
SIVAL64(num_dev_ino_buf, 4+8, st.st_ino);
} else {
SIVAL64(num_dev_ino_buf, 4, 0);
SIVAL64(num_dev_ino_buf, 4+8, 0);
}
if (stat(fname, &st) < 0)
memset(&st, 0, sizeof (STRUCT_STAT));
SIVAL(num_dev_ino_buf, 0, num);
SIVAL64(num_dev_ino_buf, 4, st.st_dev);
SIVAL64(num_dev_ino_buf, 4+8, st.st_ino);
send_msg(MSG_SUCCESS, num_dev_ino_buf, sizeof num_dev_ino_buf, -1);
} else
send_msg_int(MSG_SUCCESS, num);
@@ -1221,7 +1103,7 @@ static void got_flist_entry_status(enum festatus status, int ndx)
switch (status) {
case FES_SUCCESS:
if (remove_source_files && write_batch >= 0) {
if (remove_source_files) {
if (local_server)
send_msg(MSG_SUCCESS, num_dev_ino_buf, sizeof num_dev_ino_buf, -1);
else
@@ -1265,26 +1147,8 @@ void io_set_sock_fds(int f_in, int f_out)
void set_io_timeout(int secs)
{
/* A negative timeout is meaningless; treat it as "no timeout" rather than
* letting it drive allowed_lull / select_timeout negative (a tight loop).
* (--timeout is parsed by options.c as a plain int, so it can be negative.) */
if (secs < 0)
secs = 0;
io_timeout = secs;
/* Compute ceil(io_timeout/2) in a wider type: io_timeout can be INT_MAX
* (a peer's MSG_IO_TIMEOUT -- now capped in read_a_msg() -- or an operator
* --timeout, which options.c parses unbounded), and a plain "io_timeout + 1"
* would overflow to a negative allowed_lull / select_timeout. poll() now
* takes a millisecond count where negative means "wait forever", so this
* would hang the process rather than spin it -- and it still fires a
* keepalive flood. poll_timeout_ms() clamps as well; keep both. */
allowed_lull = (int)(((int64)io_timeout + 1) / 2);
/* The generator and sender derive an int loop-check limit as
* allowed_lull * 5; keep allowed_lull small enough that that product can't
* overflow either. The cap is invisible to real use -- allowed_lull is the
* keep-alive half-interval and INT_MAX/5 seconds is over 13 years. */
if (allowed_lull > INT_MAX / 5)
allowed_lull = INT_MAX / 5;
allowed_lull = (io_timeout + 1) / 2;
if (!io_timeout || allowed_lull > SELECT_TIMEOUT)
select_timeout = SELECT_TIMEOUT;
@@ -1295,14 +1159,6 @@ void set_io_timeout(int secs)
allowed_lull = 0;
}
void set_daemon_handshake_timeout(int secs)
{
if (secs > 0)
daemon_handshake_deadline = time(NULL) + secs;
else
daemon_handshake_deadline = 0;
}
static void check_for_d_option_error(const char *msg)
{
static const char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
@@ -1449,8 +1305,6 @@ static void unbackslash_arg(char *s)
*t = '\0';
}
#define MAX_DAEMON_ARGS (MAX_ARGS * 16)
void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls,
int unescape, char ***argv_p, int *argc_p, char **request_p)
{
@@ -1474,11 +1328,6 @@ void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls,
if (read_line(f_in, buf, bufsiz, rl_flags) == 0)
break;
if (mod_name && argc >= MAX_DAEMON_ARGS - 1) {
rprintf(FERROR, "too many daemon arguments\n");
exit_cleanup(RERR_PROTOCOL);
}
if (argc == maxargs-1) {
maxargs += MAX_ARGS;
argv = realloc_array(argv, char *, maxargs);
@@ -1509,13 +1358,6 @@ void read_args(int f_in, char *mod_name, char *buf, size_t bufsiz, int rl_nulls,
dot_pos = argc;
}
}
/* glob_expand()/glob_match() reserve glob.argc+1 slots -- room for the
* entry being added but not for this trailing NULL. A post-dot line
* whose " mod/" splits land argc on exactly maxargs (or any later
* ENSURE_MEMSPACE doubling boundary) would otherwise make the next
* store an 8-byte NULL write one slot past the argv allocation. */
if (argc >= maxargs)
argv = realloc_array(argv, char *, maxargs = argc + 1);
argv[argc] = NULL;
glob_expand(NULL, NULL, NULL, NULL);
@@ -1532,9 +1374,8 @@ BOOL io_start_buffering_out(int f_out)
if (iobuf.out.buf) {
if (iobuf.out_fd == -1)
iobuf.out_fd = f_out;
else if (iobuf.out_fd >= 0)
else
assert(f_out == iobuf.out_fd);
/* else out_fd == -2: peer already gone; leave it dead. */
return False;
}
@@ -1552,9 +1393,8 @@ BOOL io_start_buffering_in(int f_in)
if (iobuf.in.buf) {
if (iobuf.in_fd == -1)
iobuf.in_fd = f_in;
else if (iobuf.in_fd >= 0)
else
assert(f_in == iobuf.in_fd);
/* else in_fd == -2: peer already EOF'd; leave it dead. */
return False;
}
@@ -1703,26 +1543,16 @@ static void read_a_msg(void)
if (msg_bytes != 4)
goto invalid_msg;
val = raw_read_int();
val &= IOERR_VALID_MASK;
iobuf.in_multiplexed = 1;
io_error |= val;
if (am_receiver)
send_msg_int(MSG_IO_ERROR, val);
iobuf.in_multiplexed = 1;
break;
case MSG_IO_TIMEOUT:
if (msg_bytes != 4 || am_server || am_generator)
goto invalid_msg;
val = raw_read_int();
iobuf.in_multiplexed = 1;
/* The peer may only ask us to use a SHORTER timeout (a stricter cap); a
* non-positive value would disable our --timeout entirely, letting a
* malicious server hang the client indefinitely, so ignore it. A very
* large value (near INT_MAX) would overflow the (io_timeout + 1) / 2
* computation in set_io_timeout(), wrapping allowed_lull and
* select_timeout negative -- which poll() reads as "wait forever",
* hanging the client. Cap at 24 hours. */
if (val <= 0 || val > 86400)
break;
if (!io_timeout || io_timeout > val) {
if (INFO_GTE(MISC, 2))
rprintf(FINFO, "Setting --timeout=%d to match server\n", val);
@@ -1733,17 +1563,17 @@ static void read_a_msg(void)
/* Support protocol-30 keep-alive method. */
if (msg_bytes != 0)
goto invalid_msg;
iobuf.in_multiplexed = 1;
if (am_sender)
maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
iobuf.in_multiplexed = 1;
break;
case MSG_DELETED:
if (msg_bytes >= sizeof data)
goto overflow;
if (am_generator) {
raw_read_buf(data, msg_bytes);
send_msg(MSG_DELETED, data, msg_bytes, 1);
iobuf.in_multiplexed = 1;
send_msg(MSG_DELETED, data, msg_bytes, 1);
break;
}
#ifdef ICONV_OPTION
@@ -1781,6 +1611,7 @@ static void read_a_msg(void)
} else
#endif
raw_read_buf(data, msg_bytes);
iobuf.in_multiplexed = 1;
/* A directory name was sent with the trailing null */
if (msg_bytes > 0 && !data[msg_bytes-1])
log_delete(data, S_IFDIR);
@@ -1788,7 +1619,6 @@ static void read_a_msg(void)
data[msg_bytes] = '\0';
log_delete(data, S_IFREG);
}
iobuf.in_multiplexed = 1;
break;
case MSG_SUCCESS:
if (msg_bytes != (local_server ? 4+8+8 : 4)) {
@@ -1810,11 +1640,11 @@ static void read_a_msg(void)
if (msg_bytes != 4)
goto invalid_msg;
val = raw_read_int();
iobuf.in_multiplexed = 1;
if (am_generator)
got_flist_entry_status(FES_NO_SEND, val);
else
send_msg_int(MSG_NO_SEND, val);
iobuf.in_multiplexed = 1;
break;
case MSG_ERROR_SOCKET:
case MSG_ERROR_UTF8:
@@ -2222,21 +2052,6 @@ void read_sum_head(int f, struct sum_struct *sum)
(long)sum->blength, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
if (sum->count && sum->blength == 0) {
rprintf(FERROR, "Invalid zero block length [%s]\n",
who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
#if SIZEOF_CAPITAL_OFF_T < 8
/* The append-mode callers compute (OFF_T)count * blength; on a 32-bit
* OFF_T that product can wrap even though both factors are individually
* in range, corrupting the lseek/loop bounds. Reject it early. */
if (sum->blength > 0 && sum->count > MAX_INT32 / sum->blength) {
rprintf(FERROR, "checksum count*blength overflows OFF_T [%s]\n",
who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
#endif
sum->s2length = protocol_version < 27 ? csum_length : (int)read_int(f);
if (sum->s2length < 0 || sum->s2length > xfer_sum_len) {
rprintf(FERROR, "Invalid checksum length %d [%s]\n",
@@ -2348,7 +2163,7 @@ void write_int(int f, int32 x)
void write_varint(int f, int32 x)
{
char b[5] = {0}; /* {0} only to satisfy the analyzer: it doesn't model SIVAL initialising b[1..4] */
char b[5];
uchar bit;
int cnt;
@@ -2370,7 +2185,7 @@ void write_varint(int f, int32 x)
void write_varlong(int f, int64 x, uchar min_bytes)
{
char b[9] = {0}; /* {0} only to satisfy the analyzer: it doesn't model SIVAL64 initialising b[1..8] */
char b[9];
uchar bit;
int cnt = 8;
@@ -2551,7 +2366,6 @@ int32 read_ndx(int f)
{
static int32 prev_positive = -1, prev_negative = 1;
int32 *prev_ptr, num;
uint32 unum;
char b[4];
if (protocol_version < 30)
@@ -2571,20 +2385,11 @@ int32 read_ndx(int f)
b[3] = CVAL(b, 0) & ~0x80;
b[0] = b[1];
read_buf(f, b+1, 2);
unum = IVAL(b, 0);
num = IVAL(b, 0);
} else
unum = (UVAL(b,0)<<8) + UVAL(b,1) + (uint32)*prev_ptr;
num = (UVAL(b,0)<<8) + UVAL(b,1) + *prev_ptr;
} else
unum = UVAL(b, 0) + (uint32)*prev_ptr;
/* A peer-supplied index that overflows a signed int32 (used unchecked as a
* file-list index) is a protocol violation -- reject it here rather than
* relying on every downstream consumer to bounds-check. */
if (unum > (uint32)MAX_INT32) {
rprintf(FERROR, "Invalid file index: %lu [%s]\n",
(unsigned long)unum, who_am_i());
exit_cleanup(RERR_PROTOCOL);
}
num = (int32)unum;
num = UVAL(b, 0) + *prev_ptr;
*prev_ptr = num;
if (prev_ptr == &prev_negative)
num = -num;
-451
View File
@@ -1,451 +0,0 @@
/*
* POSIX ACL get/set/delete via the generic xattr syscalls.
*
* POSIX ACLs are stored by the kernel as the "system.posix_acl_access" and
* "system.posix_acl_default" extended attributes, in a fixed little-endian
* wire format (see include/acl_ea.h in the acl package). By serializing that
* format ourselves and using fgetxattr/fsetxattr on a held O_NOFOLLOW fd -- or
* getxattrat/setxattrat(AT_SYMLINK_NOFOLLOW) on a dirfd+leaf -- we get a
* symlink-race-safe ACL primitive that also covers the *default* ACL, which
* libacl's fd API (acl_get_fd/acl_set_fd, access-only) cannot.
*
* This file knows nothing about rsync's globals or its internal ACL form: it
* speaks a neutral (tag, perm, id) entry array, which makes it directly
* comparable against the system libacl in the t_acl unit test.
*
* Copyright (C) 2026 Wayne Davison & the rsync project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, visit the http://fsf.org website.
*/
#include "rsync.h"
#include "acl.h"
#ifdef SUPPORT_ACL_FD
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h> /* AT_SYMLINK_NOFOLLOW */
#if defined HAVE_SYS_XATTR_H
#include <sys/xattr.h>
#elif defined HAVE_ATTR_XATTR_H
#include <attr/xattr.h>
#endif
#ifdef HAVE_XATTRAT_SYSCALLS
#include <sys/syscall.h>
/* Self-contained copy of the kernel's struct xattr_args (stable ABI: an
* 8-byte-aligned u64 pointer, then two u32s). Defined locally to avoid
* pulling <linux/xattr.h>, whose XATTR_* macros clash with <sys/xattr.h>. */
struct rsync_xattr_args {
uint64_t value __attribute__((aligned(8)));
uint32_t size;
uint32_t flags;
};
#endif
/* Linux 2.4 didn't have a distinct ENOATTR. */
#ifndef ENOATTR
#define ENOATTR ENODATA
#endif
#define ACL_XATTR_ACCESS "system.posix_acl_access"
#define ACL_XATTR_DEFAULT "system.posix_acl_default"
/* On-disk layout: a 4-byte LE version header followed by 8-byte LE entries. */
#define ACL_EA_VERSION 0x0002
#define ACL_EA_HDR_LEN 4
#define ACL_EA_ENT_LEN 8
/* === little-endian (de)serialization (host-endianness independent) === */
static void put_le16(unsigned char *p, uint16_t v)
{
p[0] = (unsigned char)(v & 0xff);
p[1] = (unsigned char)((v >> 8) & 0xff);
}
static void put_le32(unsigned char *p, uint32_t v)
{
p[0] = (unsigned char)(v & 0xff);
p[1] = (unsigned char)((v >> 8) & 0xff);
p[2] = (unsigned char)((v >> 16) & 0xff);
p[3] = (unsigned char)((v >> 24) & 0xff);
}
static uint16_t get_le16(const unsigned char *p)
{
return (uint16_t)(p[0] | (p[1] << 8));
}
static uint32_t get_le32(const unsigned char *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static int is_named_tag(uint16_t tag)
{
return tag == RACL_USER || tag == RACL_GROUP;
}
/* Canonical order: tag ascending, then id ascending within a tag. This is
* the order libacl's __acl_reorder_obj_p() produces and what the kernel's
* validator expects (USER_OBJ, USER*, GROUP_OBJ, GROUP*, MASK, OTHER). */
static int ent_compare(const void *a, const void *b)
{
const rsync_acl_ent *x = a, *y = b;
if (x->tag != y->tag)
return x->tag < y->tag ? -1 : 1;
if (x->id != y->id)
return x->id < y->id ? -1 : 1;
return 0;
}
/* Serialize entries into a freshly-malloc'd xattr buffer (canonical order). */
static unsigned char *acl_to_xattr(const rsync_acl_ent *ents, int count, size_t *len_out)
{
size_t len = ACL_EA_HDR_LEN + (size_t)count * ACL_EA_ENT_LEN;
unsigned char *buf = malloc(len);
rsync_acl_ent *sorted = NULL;
unsigned char *p;
int i;
if (!buf)
return NULL;
if (count > 1) {
sorted = malloc((size_t)count * sizeof sorted[0]);
if (!sorted) {
free(buf);
return NULL;
}
memcpy(sorted, ents, (size_t)count * sizeof sorted[0]);
qsort(sorted, count, sizeof sorted[0], ent_compare);
ents = sorted;
}
put_le32(buf, ACL_EA_VERSION);
p = buf + ACL_EA_HDR_LEN;
for (i = 0; i < count; i++, p += ACL_EA_ENT_LEN) {
put_le16(p, ents[i].tag);
put_le16(p + 2, ents[i].perm);
put_le32(p + 4, is_named_tag(ents[i].tag) ? ents[i].id : RACL_UNDEFINED_ID);
}
if (sorted)
free(sorted);
*len_out = len;
return buf;
}
/* Parse an xattr buffer into a malloc'd entry array (canonical order). */
static int xattr_to_acl(const unsigned char *buf, size_t len,
rsync_acl_ent **out, int *count_out)
{
rsync_acl_ent *ents;
const unsigned char *p;
int n, i;
if (len < ACL_EA_HDR_LEN || (len - ACL_EA_HDR_LEN) % ACL_EA_ENT_LEN != 0
|| get_le32(buf) != ACL_EA_VERSION) {
errno = EINVAL;
return -1;
}
n = (int)((len - ACL_EA_HDR_LEN) / ACL_EA_ENT_LEN);
ents = n ? malloc((size_t)n * sizeof ents[0]) : NULL;
if (n && !ents)
return -1;
p = buf + ACL_EA_HDR_LEN;
for (i = 0; i < n; i++, p += ACL_EA_ENT_LEN) {
ents[i].tag = get_le16(p);
ents[i].perm = get_le16(p + 2);
ents[i].id = is_named_tag(ents[i].tag) ? get_le32(p + 4) : RACL_UNDEFINED_ID;
}
if (n > 1)
qsort(ents, n, sizeof ents[0], ent_compare);
*out = ents;
*count_out = n;
return 0;
}
/* === syscall dispatchers (fd-variant vs at-variant) === */
/* Pre-6.13 fallback for the dirfd+leaf at-variants: address the leaf as
* /proc/self/fd/<dirfd>/<leaf> and use the l*xattr (no-follow-leaf) calls. The
* /proc/self/fd/<dirfd> magic symlink resolves to the pinned parent inode -- a
* raced parent symlink cannot redirect it -- and l*xattr does not follow a raced
* leaf symlink, so this is race-safe without the Linux 6.13 *xattrat syscalls, as
* long as procfs is mounted. (`leaf` is a single component, <= NAME_MAX.)
* Returns 0 and fills `buf`, or -1 with ENAMETOOLONG. */
static int proc_fd_leaf_path(char *buf, size_t buflen, int dirfd, const char *leaf)
{
int n = snprintf(buf, buflen, "/proc/self/fd/%d/%s", dirfd, leaf);
if (n < 0 || (size_t)n >= buflen) {
errno = ENAMETOOLONG;
return -1;
}
return 0;
}
static ssize_t do_getxattr(int fd, int dirfd, const char *leaf,
const char *name, void *val, size_t size)
{
char p[MAXPATHLEN];
if (fd >= 0)
return fgetxattr(fd, name, val, size);
#ifdef HAVE_XATTRAT_SYSCALLS
{
struct rsync_xattr_args args;
ssize_t ret;
args.value = (uint64_t)(uintptr_t)val;
args.size = (uint32_t)size;
args.flags = 0;
ret = syscall(SYS_getxattrat, dirfd, leaf, AT_SYMLINK_NOFOLLOW,
name, &args, sizeof args);
if (ret != -1 || errno != ENOSYS)
return ret;
/* ENOSYS: kernel < 6.13 -- fall through to the /proc compat. */
}
#endif
if (proc_fd_leaf_path(p, sizeof p, dirfd, leaf) < 0)
return -1;
return lgetxattr(p, name, val, size);
}
static int do_setxattr(int fd, int dirfd, const char *leaf,
const char *name, const void *val, size_t size)
{
char p[MAXPATHLEN];
if (fd >= 0)
return fsetxattr(fd, name, val, size, 0);
#ifdef HAVE_XATTRAT_SYSCALLS
{
struct rsync_xattr_args args;
int ret;
args.value = (uint64_t)(uintptr_t)val;
args.size = (uint32_t)size;
args.flags = 0; /* replace */
ret = syscall(SYS_setxattrat, dirfd, leaf, AT_SYMLINK_NOFOLLOW,
name, &args, sizeof args);
if (ret != -1 || errno != ENOSYS)
return ret;
}
#endif
if (proc_fd_leaf_path(p, sizeof p, dirfd, leaf) < 0)
return -1;
return lsetxattr(p, name, val, size, 0);
}
static int do_removexattr(int fd, int dirfd, const char *leaf, const char *name)
{
char p[MAXPATHLEN];
if (fd >= 0)
return fremovexattr(fd, name);
#ifdef HAVE_XATTRAT_SYSCALLS
{
int ret = syscall(SYS_removexattrat, dirfd, leaf, AT_SYMLINK_NOFOLLOW, name);
if (ret != -1 || errno != ENOSYS)
return ret;
}
#endif
if (proc_fd_leaf_path(p, sizeof p, dirfd, leaf) < 0)
return -1;
return lremovexattr(p, name);
}
/* Read the whole named xattr into a malloc'd buffer, growing as needed. */
static int read_full_xattr(int fd, int dirfd, const char *leaf,
const char *name, unsigned char **buf_out, size_t *len_out)
{
unsigned char *buf = NULL;
size_t size = 0;
int tries;
for (tries = 0; tries < 8; tries++) {
ssize_t n = do_getxattr(fd, dirfd, leaf, name, size ? buf : NULL, size);
if (n >= 0) {
if (size == 0) {
/* First call just learned the length. */
size = n ? (size_t)n : 1;
buf = malloc(size);
if (!buf)
return -1;
continue;
}
*buf_out = buf;
*len_out = (size_t)n;
return 0;
}
if (errno == ERANGE) { /* grew under us: re-probe the size */
if (buf)
free(buf);
buf = NULL;
size = 0;
continue;
}
if (buf)
free(buf);
return -1; /* ENODATA / EOPNOTSUPP / ENOSYS / ... in errno */
}
if (buf)
free(buf);
errno = ERANGE;
return -1;
}
/* === public API === */
static int acl_get_common(int fd, int dirfd, const char *leaf,
int want_default, rsync_acl_ent **entries, int *count)
{
const char *name = want_default ? ACL_XATTR_DEFAULT : ACL_XATTR_ACCESS;
unsigned char *buf;
size_t len;
int rc;
*entries = NULL;
*count = 0;
if (read_full_xattr(fd, dirfd, leaf, name, &buf, &len) < 0) {
if (errno == ENODATA || errno == ENOATTR)
return 0; /* no explicit ACL present */
return -1; /* EOPNOTSUPP / ENOSYS / real error */
}
rc = xattr_to_acl(buf, len, entries, count);
free(buf);
return rc;
}
int xacl_get_fd(int fd, int want_default, rsync_acl_ent **entries, int *count)
{
return acl_get_common(fd, -1, NULL, want_default, entries, count);
}
int xacl_get_at(int dirfd, const char *leaf, int want_default,
rsync_acl_ent **entries, int *count)
{
return acl_get_common(-1, dirfd, leaf, want_default, entries, count);
}
static int acl_set_common(int fd, int dirfd, const char *leaf,
int want_default, const rsync_acl_ent *ents, int count)
{
const char *name = want_default ? ACL_XATTR_DEFAULT : ACL_XATTR_ACCESS;
unsigned char *buf;
size_t len;
int rc, save_errno;
buf = acl_to_xattr(ents, count, &len);
if (!buf) {
errno = ENOMEM;
return -1;
}
rc = do_setxattr(fd, dirfd, leaf, name, buf, len);
save_errno = errno;
free(buf);
errno = save_errno;
return rc < 0 ? -1 : 0;
}
int xacl_set_fd(int fd, int want_default, const rsync_acl_ent *ents, int count)
{
return acl_set_common(fd, -1, NULL, want_default, ents, count);
}
int xacl_set_at(int dirfd, const char *leaf, int want_default,
const rsync_acl_ent *ents, int count)
{
return acl_set_common(-1, dirfd, leaf, want_default, ents, count);
}
static int acl_del_default_common(int fd, int dirfd, const char *leaf)
{
if (do_removexattr(fd, dirfd, leaf, ACL_XATTR_DEFAULT) < 0) {
if (errno == ENODATA || errno == ENOATTR)
return 0; /* already absent: success, like acl_delete_def_file */
return -1;
}
return 0;
}
int xacl_del_default_fd(int fd)
{
return acl_del_default_common(fd, -1, NULL);
}
int xacl_del_default_at(int dirfd, const char *leaf)
{
return acl_del_default_common(-1, dirfd, leaf);
}
/* True iff /proc/self/fd magic symlinks are usable, so the dirfd+leaf at-variants
* work race-safely via the /proc compat on a pre-6.13 kernel. */
static int proc_self_fd_usable(void)
{
int dfd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
char p[64];
int usable = 0;
if (dfd < 0)
return 0;
if (snprintf(p, sizeof p, "/proc/self/fd/%d/.", dfd) < (int)sizeof p) {
/* The probe attr is absent; the path resolving (any errno but
* ENOENT/ENOTDIR -- e.g. ENODATA/ENOTSUP/EACCES) means procfs gives us
* the magic fd-symlink we need. */
errno = 0;
lgetxattr(p, "user.rsync_acl_probe", NULL, 0);
usable = !(errno == ENOENT || errno == ENOTDIR);
}
close(dfd);
return usable;
}
int xacl_at_available(void)
{
static int avail = -1;
if (avail < 0) {
#ifdef HAVE_XATTRAT_SYSCALLS
/* Probe the *xattrat syscall directly (not via do_getxattr's /proc
* fallback): any errno other than ENOSYS means it is present (6.13+). */
struct rsync_xattr_args args;
args.value = 0;
args.size = 0;
args.flags = 0;
errno = 0;
syscall(SYS_getxattrat, AT_FDCWD, ".", AT_SYMLINK_NOFOLLOW,
"user.rsync_acl_probe", &args, sizeof args);
if (errno != ENOSYS) {
avail = 1;
return avail;
}
#endif
/* No *xattrat syscalls (pre-6.13, or a kernel built without them): the dirfd+leaf ACL ops
* are still race-safe via /proc/self/fd if procfs is mounted, closing
* the parent-symlink-race gap that otherwise forces the path-based set. */
avail = proc_self_fd_usable();
}
return avail;
}
#endif /* SUPPORT_ACL_FD */
-74
View File
@@ -1,74 +0,0 @@
/*
* POSIX ACL get/set/delete via the generic xattr syscalls, addressing the
* kernel "system.posix_acl_{access,default}" attributes directly so that the
* operation can be confined to a held O_NOFOLLOW fd (fsetxattr) or a
* dirfd+leaf with AT_SYMLINK_NOFOLLOW (setxattrat). This replaces the path-
* based libacl acl_*_file() calls on Linux, where those would re-resolve the
* path and could be redirected by a parent-component symlink race.
*
* Copyright (C) 2026 Wayne Davison & the rsync project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, visit the http://fsf.org website.
*/
#ifdef SUPPORT_ACL_FD
#include <stdint.h>
/* A single logical POSIX ACL entry in host-native form. The tag values are
* the stable kernel ABI numbers (== the libacl ACL_* constants), so they map
* straight onto the on-disk e_tag without translation. */
typedef struct {
uint16_t tag; /* RACL_USER_OBJ / USER / GROUP_OBJ / GROUP / MASK / OTHER */
uint16_t perm; /* permission bits: read=4, write=2, execute=1 */
uint32_t id; /* uid/gid for USER/GROUP entries; RACL_UNDEFINED_ID otherwise */
} rsync_acl_ent;
#define RACL_USER_OBJ 0x01
#define RACL_USER 0x02
#define RACL_GROUP_OBJ 0x04
#define RACL_GROUP 0x08
#define RACL_MASK 0x10
#define RACL_OTHER 0x20
#define RACL_UNDEFINED_ID ((uint32_t)-1)
/* Read the access (want_default==0) or default (want_default!=0) ACL.
*
* On success returns 0 and sets *entries to a malloc()ed array of *count
* entries (the caller frees it with free(); *entries may be NULL when
* *count==0, which means "no explicit ACL present" -- e.g. ENODATA).
*
* On failure returns -1 with errno set. Callers distinguish:
* ENOTSUP/EOPNOTSUPP - this filesystem has no ACL support (may differ per fs)
* ENOSYS - the at-variant syscalls are unavailable on this kernel
* The fd-variant operates on a held, already-NOFOLLOW-opened descriptor. The
* at-variant resolves leaf relative to dirfd and never follows a leaf symlink. */
int xacl_get_fd(int fd, int want_default, rsync_acl_ent **entries, int *count);
int xacl_get_at(int dirfd, const char *leaf, int want_default, rsync_acl_ent **entries, int *count);
/* Write the given entries as the access/default ACL. The entries are emitted
* in canonical order; the kernel validates them (a malformed set -> EINVAL). */
int xacl_set_fd(int fd, int want_default, const rsync_acl_ent *entries, int count);
int xacl_set_at(int dirfd, const char *leaf, int want_default, const rsync_acl_ent *entries, int count);
/* Delete a directory's default ACL. A missing default ACL is success. */
int xacl_del_default_fd(int fd);
int xacl_del_default_at(int dirfd, const char *leaf);
/* Cached runtime probe: are the *xattrat syscalls usable on this kernel?
* Returns 0 when they are absent (so callers can fall back) or unbuilt. */
int xacl_at_available(void);
#endif /* SUPPORT_ACL_FD */
+1 -3
View File
@@ -34,9 +34,7 @@
#endif
.text
/* .balign = N bytes everywhere; bare .align means 2^N on Mach-O (would ask
* for 64KB alignment and trip a macOS linker warning). */
.balign 16
.align 16
.globl md5_process_asm
md5_process_asm:
+2 -2
View File
@@ -89,8 +89,8 @@ static void copy64(uint32 *M, const uchar *in)
int i;
for (i = 0; i < MD4_DIGEST_LEN; i++) {
M[i] = ((uint32)in[i*4+3] << 24) | ((uint32)in[i*4+2] << 16)
| ((uint32)in[i*4+1] << 8) | ((uint32)in[i*4+0] << 0);
M[i] = (in[i*4+3] << 24) | (in[i*4+2] << 16)
| (in[i*4+1] << 8) | (in[i*4+0] << 0);
}
}
+1 -44
View File
@@ -44,32 +44,6 @@ struct align_test {
#define PTR_ADD(b,o) ( (void*) ((char*)(b) + (o)) )
#define PTR_SUB(b,o) ( (void*) ((char*)(b) - (o)) )
/* Under AddressSanitizer, fence each pool_alloc() chunk with a poisoned
* redzone just below it (allocations grow downward from the top of an extent).
* A bump allocator hands out chunks from one big malloc, so ASan cannot see a
* write that underflows one chunk into its neighbour -- e.g. a miscomputed
* F_SUM() reaching before a file_struct's extras. The redzone turns that into
* a hard ASan report. We unpoison a whole extent whenever its space is reused
* (reset/reclaim), so legitimate later allocations never trip over old
* redzones; ASan unpoisons freed extents itself via free(). */
#if defined(__SANITIZE_ADDRESS__)
# define POOL_ASAN 1
#elif defined(__has_feature)
# if __has_feature(address_sanitizer)
# define POOL_ASAN 1
# endif
#endif
#ifdef POOL_ASAN
# include <sanitizer/asan_interface.h>
# define POOL_REDZONE 16 /* >= the largest pool-relative underflow we guard */
# define POOL_POISON(p,n) ASAN_POISON_MEMORY_REGION((p), (n))
# define POOL_UNPOISON(p,n) ASAN_UNPOISON_MEMORY_REGION((p), (n))
#else
# define POOL_POISON(p,n) ((void)0)
# define POOL_UNPOISON(p,n) ((void)0)
#endif
alloc_pool_t
pool_create(size_t size, size_t quantum, void (*bomb)(const char*, const char*, int), int flags)
{
@@ -191,18 +165,7 @@ pool_alloc(alloc_pool_t p, size_t len, const char *bomb_msg)
pool->extents->free -= len;
{
void *ret = PTR_ADD(pool->extents->start, pool->extents->free);
#ifdef POOL_ASAN
size_t rz = pool->extents->free < POOL_REDZONE
? pool->extents->free : POOL_REDZONE;
if (rz) {
pool->extents->free -= rz;
POOL_POISON(PTR_ADD(pool->extents->start, pool->extents->free), rz);
}
#endif
return ret;
}
return PTR_ADD(pool->extents->start, pool->extents->free);
bomb_out:
if (pool->bomb)
@@ -252,10 +215,6 @@ pool_free(alloc_pool_t p, size_t len, void *addr)
if (!cur)
return;
/* This extent's space may be reused (and POOL_CLEAR may memset it)
* below, so drop any redzones in it first. */
POOL_UNPOISON(cur->start, pool->size);
if (!prev) {
/* The "live" extent is kept ready for more allocations. */
if (cur->free + cur->bound + len >= pool->size) {
@@ -313,8 +272,6 @@ pool_free_old(alloc_pool_t p, void *addr)
if (!cur)
return;
POOL_UNPOISON(cur->start, pool->size);
if (addr == PTR_ADD(cur->start, cur->free)) {
if (prev) {
prev->next = NULL;
+7 -151
View File
@@ -180,26 +180,6 @@ int sys_acl_free_acl(SMB_ACL_T the_acl)
return acl_free(the_acl);
}
#ifdef HAVE_LIBACL_AT
/* Dirfd/AT-flag ACL ops via the new libacl,
* race-safe on every Linux kernel. at_flags is AT_SYMLINK_NOFOLLOW (dirfd+leaf)
* or AT_EMPTY_PATH (operate on an open fd passed as dirfd, path ""). */
SMB_ACL_T sys_acl_get_file_at(int dirfd, const char *path_p, int at_flags, SMB_ACL_TYPE_T type)
{
return acl_get_file_at(dirfd, path_p, at_flags, type);
}
int sys_acl_set_file_at(int dirfd, const char *path_p, int at_flags, SMB_ACL_TYPE_T type, SMB_ACL_T theacl)
{
return acl_set_file_at(dirfd, path_p, at_flags, type, theacl);
}
int sys_acl_delete_def_file_at(int dirfd, const char *path_p, int at_flags)
{
return acl_delete_def_file_at(dirfd, path_p, at_flags);
}
#endif /* HAVE_LIBACL_AT */
#elif defined(HAVE_TRU64_ACLS) /*--------------------------------------------*/
/*
* The interface to DEC/Compaq Tru64 UNIX ACLs
@@ -499,20 +479,12 @@ SMB_ACL_T sys_acl_get_file(const char *path_p, SMB_ACL_TYPE_T type)
return acl_d;
}
#ifdef HAVE_SOLARIS_ACLS
/* facl(2)-based ACL read on a held fd (no path re-resolution). Solaris stores
* the access and default ACLs as one combined ACL; split out the requested half. */
SMB_ACL_T sys_acl_get_fd_type(int fd, SMB_ACL_TYPE_T type)
#if 0
SMB_ACL_T sys_acl_get_fd(int fd)
{
SMB_ACL_T acl_d;
int count; /* # of ACL entries allocated */
int naccess; /* # of access ACL entries */
int ndefault; /* # of default ACL entries */
if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) {
errno = EINVAL;
return NULL;
}
count = INITIAL_ACL_SIZE;
if ((acl_d = sys_acl_init(count)) == NULL) {
@@ -539,39 +511,17 @@ SMB_ACL_T sys_acl_get_fd_type(int fd, SMB_ACL_TYPE_T type)
}
/*
* calculate the number of access and default ACL entries
* calculate the number of access ACL entries
*/
for (naccess = 0; naccess < count; naccess++) {
if (acl_d->acl[naccess].a_type & ACL_DEFAULT)
break;
}
ndefault = count - naccess;
if (type == SMB_ACL_TYPE_DEFAULT) {
int i, j;
/*
* Default ACL entries follow the access entries in the combined
* Solaris ACL; move them to the front of the wrapper and clear
* ACL_DEFAULT so the caller sees a plain default ACL.
*/
for (i = 0, j = naccess; i < ndefault; i++, j++) {
acl_d->acl[i] = acl_d->acl[j];
acl_d->acl[i].a_type &= ~ACL_DEFAULT;
}
acl_d->count = ndefault;
} else {
acl_d->count = naccess;
}
acl_d->count = naccess;
return acl_d;
}
SMB_ACL_T sys_acl_get_fd(int fd)
{
return sys_acl_get_fd_type(fd, SMB_ACL_TYPE_ACCESS);
}
#endif
int sys_acl_get_info(SMB_ACL_ENTRY_T entry, SMB_ACL_TAG_T *tag_type_p, uint32 *bits_p, id_t *u_g_id_p)
@@ -778,108 +728,14 @@ int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T type, SMB_ACL_T acl_d)
return ret;
}
#ifdef HAVE_SOLARIS_ACLS
/* facl(2)-based ACL write on a held fd (no path re-resolution). Setting an ACL
* on a directory replaces the combined access+default set, so for a dir read the
* other half through the fd, merge, and write the combined ACL back. Mirrors the
* path-based sys_acl_set_file() below. */
int sys_acl_set_fd_type(int fd, SMB_ACL_TYPE_T type, SMB_ACL_T acl_d)
#if 0
int sys_acl_set_fd(int fd, SMB_ACL_T acl_d)
{
struct stat s;
struct acl *acl_p;
int acl_count;
struct acl *acl_buf = NULL;
int ret;
if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) {
errno = EINVAL;
return -1;
}
if (acl_sort(acl_d) != 0) {
return -1;
}
acl_p = &acl_d->acl[0];
acl_count = acl_d->count;
if (fstat(fd, &s) != 0) {
return -1;
}
if (S_ISDIR(s.st_mode)) {
SMB_ACL_T acc_acl;
SMB_ACL_T def_acl;
SMB_ACL_T tmp_acl;
int i;
if (type == SMB_ACL_TYPE_ACCESS) {
acc_acl = acl_d;
def_acl = tmp_acl = sys_acl_get_fd_type(fd, SMB_ACL_TYPE_DEFAULT);
} else {
def_acl = acl_d;
acc_acl = tmp_acl = sys_acl_get_fd_type(fd, SMB_ACL_TYPE_ACCESS);
}
if (tmp_acl == NULL) {
return -1;
}
acl_count = acc_acl->count + def_acl->count;
acl_p = acl_buf = SMB_MALLOC_ARRAY(struct acl, acl_count);
if (acl_buf == NULL) {
sys_acl_free_acl(tmp_acl);
errno = ENOMEM;
return -1;
}
/* Concatenate access + default, then mark the default half. */
memcpy(&acl_buf[0], &acc_acl->acl[0],
acc_acl->count * sizeof acl_buf[0]);
memcpy(&acl_buf[acc_acl->count], &def_acl->acl[0],
def_acl->count * sizeof acl_buf[0]);
for (i = acc_acl->count; i < acl_count; i++) {
acl_buf[i].a_type |= ACL_DEFAULT;
}
sys_acl_free_acl(tmp_acl);
} else if (type != SMB_ACL_TYPE_ACCESS) {
errno = EINVAL;
return -1;
}
ret = facl(fd, SETACL, acl_count, acl_p);
SAFE_FREE(acl_buf);
return ret;
}
int sys_acl_set_fd(int fd, SMB_ACL_T acl_d)
{
return sys_acl_set_fd_type(fd, SMB_ACL_TYPE_ACCESS, acl_d);
}
int sys_acl_delete_def_fd(int fd)
{
SMB_ACL_T acl_d;
int ret;
/*
* Fetching the access ACL through the fd and rewriting it deletes the
* default ACL, without re-resolving the path.
*/
if ((acl_d = sys_acl_get_fd_type(fd, SMB_ACL_TYPE_ACCESS)) == NULL) {
return -1;
}
ret = facl(fd, SETACL, acl_d->count, acl_d->acl);
sys_acl_free_acl(acl_d);
return ret;
return facl(fd, SETACL, acl_d->count, &acl_d->acl[0]);
}
#endif
-11
View File
@@ -301,18 +301,7 @@ int sys_acl_valid(SMB_ACL_T theacl);
int sys_acl_set_file(const char *name, SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl);
int sys_acl_set_fd(int fd, SMB_ACL_T theacl);
int sys_acl_delete_def_file(const char *name);
#ifdef HAVE_SOLARIS_ACLS
SMB_ACL_T sys_acl_get_fd_type(int fd, SMB_ACL_TYPE_T type);
int sys_acl_set_fd_type(int fd, SMB_ACL_TYPE_T type, SMB_ACL_T theacl);
int sys_acl_delete_def_fd(int fd);
#endif
int sys_acl_free_acl(SMB_ACL_T the_acl);
int no_acl_syscall_error(int err);
#ifdef HAVE_LIBACL_AT
SMB_ACL_T sys_acl_get_file_at(int dirfd, const char *path_p, int at_flags, SMB_ACL_TYPE_T type);
int sys_acl_set_file_at(int dirfd, const char *path_p, int at_flags, SMB_ACL_TYPE_T type, SMB_ACL_T theacl);
int sys_acl_delete_def_file_at(int dirfd, const char *path_p, int at_flags);
#endif
#endif /* SUPPORT_ACLS */
+30 -146
View File
@@ -45,31 +45,16 @@ int sys_lsetxattr(const char *path, const char *name, const void *value, size_t
return lsetxattr(path, name, value, size, 0);
}
int sys_fsetxattr(int filedes, const char *name, const void *value, size_t size)
{
return fsetxattr(filedes, name, value, size, 0);
}
int sys_lremovexattr(const char *path, const char *name)
{
return lremovexattr(path, name);
}
int sys_fremovexattr(int filedes, const char *name)
{
return fremovexattr(filedes, name);
}
ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
return llistxattr(path, list, size);
}
ssize_t sys_flistxattr(int filedes, char *list, size_t size)
{
return flistxattr(filedes, list, size);
}
#elif HAVE_OSX_XATTRS
ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
@@ -104,31 +89,16 @@ int sys_lsetxattr(const char *path, const char *name, const void *value, size_t
return setxattr(path, name, value, size, 0, XATTR_NOFOLLOW);
}
int sys_fsetxattr(int filedes, const char *name, const void *value, size_t size)
{
return fsetxattr(filedes, name, value, size, 0, 0);
}
int sys_lremovexattr(const char *path, const char *name)
{
return removexattr(path, name, XATTR_NOFOLLOW);
}
int sys_fremovexattr(int filedes, const char *name)
{
return fremovexattr(filedes, name, 0);
}
ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
return listxattr(path, list, size, XATTR_NOFOLLOW);
}
ssize_t sys_flistxattr(int filedes, char *list, size_t size)
{
return flistxattr(filedes, list, size, 0);
}
#elif HAVE_FREEBSD_XATTRS
ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
@@ -146,42 +116,32 @@ int sys_lsetxattr(const char *path, const char *name, const void *value, size_t
return extattr_set_link(path, EXTATTR_NAMESPACE_USER, name, value, size);
}
int sys_fsetxattr(int filedes, const char *name, const void *value, size_t size)
{
return extattr_set_fd(filedes, EXTATTR_NAMESPACE_USER, name, value, size);
}
int sys_lremovexattr(const char *path, const char *name)
{
return extattr_delete_link(path, EXTATTR_NAMESPACE_USER, name);
}
int sys_fremovexattr(int filedes, const char *name)
{
return extattr_delete_fd(filedes, EXTATTR_NAMESPACE_USER, name);
}
/* Turn the FreeBSD extattr_list_xx() output (a single length byte before each
* name, no '\0' terminator) into the series of null-terminated strings that the
* rest of rsync expects. Since the size is unchanged, transform in place.
* Shared by the path and fd list variants. */
static ssize_t freebsd_list_finish(char *list, size_t size, ssize_t len)
ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
unsigned char keylen;
ssize_t off;
ssize_t off, len = extattr_list_link(path, EXTATTR_NAMESPACE_USER, list, size);
if (len <= 0 || size == 0)
return len;
if ((size_t)len >= size) {
/* FreeBSD extattr_list_xx() returns 'size' as 'len' in case there are
more data available, truncating the output, we solve this by signalling
ERANGE in case len == size so that the code in xattrs.c will retry with
a bigger buffer */
/* FreeBSD extattr_list_xx() returns 'size' as 'len' in case there are
more data available, truncating the output, we solve this by signalling
ERANGE in case len == size so that the code in xattrs.c will retry with
a bigger buffer */
errno = ERANGE;
return -1;
}
/* FreeBSD puts a single-byte length before each string, with no '\0'
* terminator. We need to change this into a series of null-terminted
* strings. Since the size is the same, we can simply transform the
* output in place. */
for (off = 0; off < len; off += keylen + 1) {
keylen = ((unsigned char*)list)[off];
if (off + keylen >= len) {
@@ -196,18 +156,6 @@ static ssize_t freebsd_list_finish(char *list, size_t size, ssize_t len)
return len;
}
ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
return freebsd_list_finish(list, size,
extattr_list_link(path, EXTATTR_NAMESPACE_USER, list, size));
}
ssize_t sys_flistxattr(int filedes, char *list, size_t size)
{
return freebsd_list_finish(list, size,
extattr_list_fd(filedes, EXTATTR_NAMESPACE_USER, list, size));
}
#elif HAVE_SOLARIS_XATTRS
static ssize_t read_xattr(int attrfd, void *buf, size_t buflen)
@@ -269,59 +217,29 @@ ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
return read_xattr(attrfd, value, size);
}
/* Write a datum to the already-opened attribute fd, closing it. Shared by the
* path- and fd-keyed setters below. */
static int write_xattr(int attrfd, const void *value, size_t size)
{
size_t bufpos;
int ret = 0, saved_errno = 0;
for (bufpos = 0; bufpos < size; ) {
ssize_t cnt = write(attrfd, (const char *)value + bufpos, size - bufpos);
if (cnt < 0) {
if (errno == EINTR)
continue;
ret = -1;
saved_errno = errno;
break;
}
if (cnt == 0) {
ret = -1;
saved_errno = EIO;
break;
}
bufpos += cnt;
}
/* Don't let close() clobber the write error; do report a close() failure. */
if (close(attrfd) < 0 && ret == 0)
return -1;
if (ret < 0 && saved_errno)
errno = saved_errno;
return ret;
}
int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
{
int attrfd;
size_t bufpos;
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
if ((attrfd = attropen(path, name, O_CREAT|O_TRUNC|O_WRONLY, mode)) < 0)
return -1;
return write_xattr(attrfd, value, size);
}
for (bufpos = 0; bufpos < size; ) {
ssize_t cnt = write(attrfd, (char*)value + bufpos, size);
if (cnt <= 0) {
if (cnt < 0 && errno == EINTR)
continue;
bufpos = -1;
break;
}
bufpos += cnt;
}
int sys_fsetxattr(int filedes, const char *name, const void *value, size_t size)
{
int attrfd;
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
close(attrfd);
if ((attrfd = openat(filedes, name, O_CREAT|O_TRUNC|O_WRONLY|O_XATTR, mode)) < 0)
return -1;
return write_xattr(attrfd, value, size);
return bufpos > 0 ? 0 : -1;
}
int sys_lremovexattr(const char *path, const char *name)
@@ -339,29 +257,18 @@ int sys_lremovexattr(const char *path, const char *name)
return ret;
}
int sys_fremovexattr(int filedes, const char *name)
ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
int attrdirfd;
int ret;
if ((attrdirfd = openat(filedes, ".", O_RDONLY|O_XATTR, 0)) < 0)
return -1;
ret = unlinkat(attrdirfd, name, 0);
close(attrdirfd);
return ret;
}
/* List the names in an already-opened attribute-dir fd, consuming it. Shared
* by the path- and fd-keyed listers below. */
static ssize_t list_xattr(int attrdirfd, char *list, size_t size)
{
DIR *dirp;
struct dirent *dp;
ssize_t ret = 0;
if ((attrdirfd = attropen(path, ".", O_RDONLY)) < 0) {
errno = ENOTSUP;
return -1;
}
if ((dirp = fdopendir(attrdirfd)) == NULL) {
close(attrdirfd);
return -1;
@@ -389,34 +296,11 @@ static ssize_t list_xattr(int attrdirfd, char *list, size_t size)
}
closedir(dirp);
close(attrdirfd);
return ret;
}
ssize_t sys_llistxattr(const char *path, char *list, size_t size)
{
int attrdirfd;
if ((attrdirfd = attropen(path, ".", O_RDONLY)) < 0) {
errno = ENOTSUP;
return -1;
}
return list_xattr(attrdirfd, list, size);
}
ssize_t sys_flistxattr(int filedes, char *list, size_t size)
{
int attrdirfd;
if ((attrdirfd = openat(filedes, ".", O_RDONLY|O_XATTR, 0)) < 0) {
errno = ENOTSUP;
return -1;
}
return list_xattr(attrdirfd, list, size);
}
#else
#error You need to create xattr compatibility functions.
-3
View File
@@ -16,11 +16,8 @@
ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size);
ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size);
int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size);
int sys_fsetxattr(int filedes, const char *name, const void *value, size_t size);
int sys_lremovexattr(const char *path, const char *name);
int sys_fremovexattr(int filedes, const char *name);
ssize_t sys_llistxattr(const char *path, char *list, size_t size);
ssize_t sys_flistxattr(int filedes, char *list, size_t size);
#else
+2 -15
View File
@@ -89,11 +89,6 @@ static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
p_ch = *++p;
/* FALLTHROUGH */
default:
/* iwildmatch() folds the text to lower case above; fold the pattern
* char too so matching is truly case-insensitive (not just text-side).
* Without this an upper-case "hosts deny" token fails OPEN. */
if (force_lower_case && ISUPPER(p_ch))
p_ch = tolower(p_ch);
if (t_ch != p_ch)
return FALSE;
continue;
@@ -155,8 +150,6 @@ static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
p_ch = *++p;
if (!p_ch)
return ABORT_ALL;
if (force_lower_case && ISUPPER(p_ch))
p_ch = tolower(p_ch);
if (t_ch == p_ch)
matched = TRUE;
} else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {
@@ -166,8 +159,6 @@ static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
if (!p_ch)
return ABORT_ALL;
}
if (force_lower_case && ISUPPER(p_ch))
p_ch = tolower(p_ch);
if (t_ch <= p_ch && t_ch >= prev_ch)
matched = TRUE;
p_ch = 0; /* This makes "prev_ch" get set to 0. */
@@ -225,12 +216,8 @@ static int dowild(const uchar *p, const uchar *text, const uchar*const *a)
} else /* malformed [:class:] string */
return ABORT_ALL;
p_ch = 0; /* This makes "prev_ch" get set to 0. */
} else {
if (force_lower_case && ISUPPER(p_ch))
p_ch = tolower(p_ch);
if (t_ch == p_ch)
matched = TRUE;
}
} else if (t_ch == p_ch)
matched = TRUE;
} while (prev_ch = p_ch, (p_ch = *++p) != ']');
if (matched == special || t_ch == '/')
return FALSE;
+5 -124
View File
@@ -164,81 +164,11 @@ static const struct enum_list enum_syslog_facility[] = {
/* Expand %VAR% references. Any unknown vars or unrecognized
* syntax leaves the raw chars unchanged. */
enum shell_quote_context {
SHELL_UNQUOTED,
SHELL_SINGLE_QUOTED,
SHELL_DOUBLE_QUOTED
};
/* Characters that can turn a substituted value into shell syntax rather than
* data, in any quoting context. Quoting alone cannot be relied on here:
* context-aware escaping is correct for exactly one level of shell parsing,
* and a hook such as `sh -c '... %RSYNC_USER_NAME% ...'` re-parses the word in
* a second shell that sees the value bare. Peer-supplied values carrying any
* of these are refused instead. */
static int shell_unsafe_value(const char *val)
{
const char *s;
for (s = val; *s; s++) {
/* '!' negates in command position (a hook `sh -c '%VAR% false'`
* becomes `! false` and reports success, inverting an access
* check); '~' is tilde-expanded; '{' and '}' brace-expand in
* bash and zsh. None of them execute anything on their own,
* which is why a set built from the obvious metacharacters
* missed them. */
if (strchr("'\"`$\\;&|<>()*?[]# !~{}", *s)
|| (unsigned char)*s < 0x20 || (unsigned char)*s == 0x7f)
return 1;
}
return 0;
}
static char *expand_vars_shell_escape(const char *val, int quote_context)
{
const char *s;
char *ret, *t;
/* A double-quoted value is deliberately BOTH backslash-escaped and
* wrapped in single quotes. The wrap is redundant for one level of
* shell parsing (and shows up as literal quotes in the value), but a
* hook such as `sh -c "... %RSYNC_USER_NAME% ..."` re-parses the word
* in a second shell, where the backslashes are already gone and only
* the quotes still protect it. */
size_t len = quote_context == SHELL_SINGLE_QUOTED ? 0 : 2;
for (s = val; *s; s++) {
if (quote_context == SHELL_DOUBLE_QUOTED
&& strchr("\\\"`$", *s))
len += 2;
else
len += *s == '\'' ? 4 : 1;
}
ret = new_array(char, len + 1);
t = ret;
if (quote_context != SHELL_SINGLE_QUOTED)
*t++ = '\'';
for (s = val; *s; s++) {
if (quote_context == SHELL_DOUBLE_QUOTED
&& strchr("\\\"`$", *s)) {
*t++ = '\\';
*t++ = *s;
} else if (*s == '\'') {
memcpy(t, "'\\''", 4);
t += 4;
} else
*t++ = *s;
}
if (quote_context != SHELL_SINGLE_QUOTED)
*t++ = '\'';
*t = '\0';
return ret;
}
static char *expand_vars(const char *str, int shell_escape)
static char *expand_vars(const char *str)
{
char *buf, *t;
const char *f;
int bufsize, quote_context = SHELL_UNQUOTED, escaped_char = 0;
int bufsize;
if (!str || !strchr(str, '%'))
return (char *)str; /* TODO change return value to const char* at some point. */
@@ -254,29 +184,7 @@ static char *expand_vars(const char *str, int shell_escape)
strlcpy(t, f+1, percent - f);
val = getenv(t);
if (val) {
char *escaped = NULL;
int len;
/* %RSYNC_*% values originate from the peer request/args.
* When the result is fed to a shell-executed hook, escape it
* for the template's current shell quote context so a value
* containing shell metacharacters can't inject. For ordinary string
* params (path, uid, gid, ...) leave them verbatim --
* quoting there would corrupt the value (e.g. a documented
* `path = /home/%RSYNC_USER_NAME%` would become /home/'x'). */
if (shell_escape && strncmp(t, "RSYNC_", 6) == 0) {
if (shell_unsafe_value(val)) {
/* Fail closed: the hook may be an access
* check, so skipping it is not an option. */
rprintf(FLOG,
"refusing to run shell hook: %%%s%% holds a shell metacharacter\n",
t);
exit_cleanup(RERR_UNSUPPORTED);
}
val = escaped = expand_vars_shell_escape(val, quote_context);
}
len = strlcpy(t, val, bufsize+1);
if (escaped)
free(escaped);
int len = strlcpy(t, val, bufsize+1);
if (len > bufsize)
break;
bufsize -= len;
@@ -286,28 +194,6 @@ static char *expand_vars(const char *str, int shell_escape)
}
}
}
if (shell_escape) {
if (quote_context == SHELL_SINGLE_QUOTED) {
/* Nothing is special inside '...', not even a backslash;
* only the closing quote ends it. */
if (*f == '\'')
quote_context = SHELL_UNQUOTED;
} else if (escaped_char)
escaped_char = 0;
else if (*f == '\\')
escaped_char = 1;
else if (quote_context == SHELL_DOUBLE_QUOTED) {
/* A single quote inside "..." is literal and must not be
* taken as opening a single-quoted run -- doing so would
* de-sync the tracker and escape a later value for the
* wrong context. */
if (*f == '"')
quote_context = SHELL_UNQUOTED;
} else if (*f == '\'')
quote_context = SHELL_SINGLE_QUOTED;
else if (*f == '"')
quote_context = SHELL_DOUBLE_QUOTED;
}
*t++ = *f++;
bufsize--;
}
@@ -327,10 +213,7 @@ static char *expand_vars(const char *str, int shell_escape)
/* Each "char* foo" has an associated "BOOL foo_EXP" that tracks if the string has been expanded yet or not. */
/* NOTE: use this function and all the FN_{GLOBAL,LOCAL} ones WITHOUT a trailing semicolon! */
#define RETURN_EXPANDED(val) {if (!val ## _EXP) {val = expand_vars(val, 0); val ## _EXP = True;} return val ? val : "";}
/* Variant for params whose expansion is fed to a shell-executed hook: quote
* %RSYNC_*% peer-controlled values to prevent shell injection. */
#define RETURN_EXPANDED_SHELL(val) {if (!val ## _EXP) {val = expand_vars(val, 1); val ## _EXP = True;} return val ? val : "";}
#define RETURN_EXPANDED(val) {if (!val ## _EXP) {val = expand_vars(val); val ## _EXP = True;} return val ? val : "";}
/* In this section all the functions that are used to access the
* parameters from the rest of the program are defined. */
@@ -346,8 +229,6 @@ static char *expand_vars(const char *str, int shell_escape)
#define FN_LOCAL_STRING(fn_name, val) \
char *fn_name(int i) {if (LP_SNUM_OK(i) && iSECTION(i).val) RETURN_EXPANDED(iSECTION(i).val) else RETURN_EXPANDED(Vars.l.val)}
#define FN_LOCAL_STRING_SHELL(fn_name, val) \
char *fn_name(int i) {if (LP_SNUM_OK(i) && iSECTION(i).val) RETURN_EXPANDED_SHELL(iSECTION(i).val) else RETURN_EXPANDED_SHELL(Vars.l.val)}
#define FN_LOCAL_BOOL(fn_name, val) \
BOOL fn_name(int i) {return LP_SNUM_OK(i)? iSECTION(i).val : Vars.l.val;}
#define FN_LOCAL_CHAR(fn_name, val) \
@@ -529,7 +410,7 @@ static BOOL do_parameter(char *parmname, char *parmvalue)
break;
default:
/* expand any %VAR% strings now */
parmvalue = expand_vars(parmvalue, 0);
parmvalue = expand_vars(parmvalue);
break;
}
+15 -61
View File
@@ -22,7 +22,6 @@
#include "rsync.h"
#include "itypes.h"
#include "inums.h"
#include "rounding.h" /* EXTRA_ROUNDING, so log_delete() aligns its file_struct */
extern int dry_run;
extern int am_daemon;
@@ -55,6 +54,7 @@ extern iconv_t ic_chck;
#ifdef ICONV_OPTION
extern iconv_t ic_recv;
#endif
extern char curr_dir[MAXPATHLEN];
extern char *full_module_path;
extern unsigned int module_dirlen;
extern char sender_file_sum[MAX_DIGEST_LEN];
@@ -119,20 +119,12 @@ static char const *rerr_name(int code)
return NULL;
}
static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, int escape_c1, char end_char);
static void logit(int priority, const char *buf)
{
if (logfile_was_closed)
logfile_reopen();
if (logfile_fp) {
/* Escape control chars in the message so an attacker-controlled
* filename can't inject terminal escapes into the log an admin later
* cat's (CWE-117); keep the trailing newline raw via end_char. */
int len = strlen(buf);
char trailing = len && (buf[len-1] == '\n' || buf[len-1] == '\r') ? buf[--len] : '\0';
fprintf(logfile_fp, "%s [%d] ", timestring(time(NULL)), (int)getpid());
filtered_fwrite(logfile_fp, buf, len, 0, 1, trailing);
fprintf(logfile_fp, "%s [%d] %s", timestring(time(NULL)), (int)getpid(), buf);
fflush(logfile_fp);
} else {
syslog(priority, "%s", buf);
@@ -161,15 +153,7 @@ static void syslog_init()
static void logfile_open(void)
{
mode_t old_umask = umask(022 | orig_umask);
/* --log-file/`log file =` are operator-supplied paths that may transit
* attacker-writable dirs; a planted symlink could redirect root's log
* into e.g. /root/.ssh/authorized_keys. Refuse symlinks not owned by
* uid 0 or our euid. */
int fd = vfs_open_owner_walk(logfile_name,
O_WRONLY | O_APPEND | O_CREAT, 0644, 0);
logfile_fp = fd >= 0 ? fdopen(fd, "a") : NULL;
if (!logfile_fp && fd >= 0)
close(fd);
logfile_fp = fopen(logfile_name, "a");
umask(old_umask);
if (!logfile_fp) {
int fopen_errno = errno;
@@ -238,7 +222,7 @@ void logfile_reopen(void)
}
}
static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, int escape_c1, char end_char)
static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isprint, char end_char)
{
char outbuf[1024], *ob = outbuf;
const char *end = in_buf + in_len;
@@ -250,8 +234,7 @@ static void filtered_fwrite(FILE *f, const char *in_buf, int in_len, int use_isp
}
if ((in_buf < end - 4 && *in_buf == '\\' && in_buf[1] == '#'
&& isDigit(in_buf + 2) && isDigit(in_buf + 3) && isDigit(in_buf + 4))
|| (*in_buf != '\t' && ((use_isprint && !isPrint(in_buf)) || *(uchar*)in_buf < ' '
|| (escape_c1 && *(uchar*)in_buf >= 0x80 && *(uchar*)in_buf <= 0x9f))))
|| (*in_buf != '\t' && ((use_isprint && !isPrint(in_buf)) || *(uchar*)in_buf < ' ')))
ob += snprintf(ob, 6, "\\#%03o", *(uchar*)in_buf++);
else
*ob++ = *in_buf++;
@@ -289,12 +272,8 @@ void rwrite(enum logcode code, const char *buf, int len, int is_utf8)
if (am_daemon > 0 && code != FCLIENT)
code = FLOG;
} else if (send_msgs_to_gen) {
/* Pass the message to our sibling in native charset. is_utf8
* may be set here if a malicious peer sends MSG_INFO/MSG_ERROR
* to a daemon receiver (read_a_msg passes !am_generator); the
* old assert(!is_utf8) made that a remotely-reachable abort.
* Forwarding the bytes raw is safe -- the generator's rwrite()
* gets is_utf8=0 and filtered_fwrite escapes non-printables. */
assert(!is_utf8);
/* Pass the message to our sibling in native charset. */
send_msg((enum msgcode)code, buf, len, 0);
return;
}
@@ -318,12 +297,7 @@ void rwrite(enum logcode code, const char *buf, int len, int is_utf8)
in_block = 1;
if (!log_initialised)
log_init(0);
/* buf holds exactly len bytes and is not necessarily NUL-terminated
* (e.g. a forwarded MSG_* payload from read_a_msg), so copy by length
* rather than strlcpy(), which would strlen() past the end of buf. */
int mlen = MIN((int)sizeof msg - 1, len);
memcpy(msg, buf, mlen);
msg[mlen] = '\0';
strlcpy(msg, buf, MIN((int)sizeof msg, len + 1));
logit(priority, msg);
in_block = 0;
@@ -398,7 +372,7 @@ void rwrite(enum logcode code, const char *buf, int len, int is_utf8)
ierrno = errno;
if (outbuf.len) {
char trailing = inbuf.len ? '\0' : trailing_CR_or_NL;
filtered_fwrite(f, convbuf, outbuf.len, 0, 0, trailing);
filtered_fwrite(f, convbuf, outbuf.len, 0, trailing);
if (trailing) {
trailing_CR_or_NL = '\0';
fflush(f);
@@ -421,7 +395,7 @@ void rwrite(enum logcode code, const char *buf, int len, int is_utf8)
} else
#endif
{
filtered_fwrite(f, buf, len, !allow_8bit_chars, 0, trailing_CR_or_NL);
filtered_fwrite(f, buf, len, !allow_8bit_chars, trailing_CR_or_NL);
if (trailing_CR_or_NL)
fflush(f);
}
@@ -525,17 +499,12 @@ void remember_initial_stats(void)
initial_data_written = total_data_written;
}
/* Size of log_formatted()'s per-escape "fmt" scratch buffer. log_format_has()
* must bound its width-digit scan to the same limit so the two parsers agree on
* where an escape letter falls (see the digit loop in each). */
#define LOG_FMT_SIZE 32
/* A generic logging routine for send/recv, with parameter substitiution. */
static void log_formatted(enum logcode code, const char *format, const char *op,
struct file_struct *file, const char *fname, int iflags,
const char *hlink)
{
char buf[MAXPATHLEN+1024], buf2[MAXPATHLEN], fmt[LOG_FMT_SIZE];
char buf[MAXPATHLEN+1024], buf2[MAXPATHLEN], fmt[32];
char *p, *s, *c;
const char *n;
size_t len, total;
@@ -647,7 +616,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
n = buf2;
} else if (am_daemon && *c != '/') {
pathjoin(buf2, sizeof buf2,
vfs.curr_dir + module_dirlen, c);
curr_dir + module_dirlen, c);
clean_fname(buf2, 0);
if (fmt[1]) {
strlcpy(c, buf2, MAXPATHLEN);
@@ -716,7 +685,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
case 'C':
n = NULL;
if (S_ISREG(file->mode)) {
if (always_checksum && !(iflags & ITEM_DELETED))
if (always_checksum)
n = sum_as_hex(file_sum_nni->num, F_SUM(file), 1);
else if (iflags & ITEM_TRANSFER)
n = sum_as_hex(xfer_sum_nni->num, sender_file_sum, 0);
@@ -781,9 +750,6 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
}
}
break;
case '%':
n = "%";
break;
}
/* "n" is the string to be inserted in place of this % code. */
@@ -827,33 +793,21 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
int log_format_has(const char *format, char esc)
{
const char *p;
int width;
if (!format)
return 0;
for (p = format; (p = strchr(p, '%')) != NULL; ) {
for (p++; *p == '\''; p++) {} /*SHARED ITERATOR*/
/* Mirror log_formatted()'s width-digit scan exactly (c starts at
* fmt+1, so width starts at 1): both must stop at the same digit
* or they disagree on where the escape letter is, which for %C
* can leave sender_keeps_checksum unset and over-read F_SUM. */
width = 1;
if (*p == '-') {
if (*p == '-')
p++;
width++;
}
while (isDigit(p) && width < LOG_FMT_SIZE - 8) {
while (isDigit(p))
p++;
width++;
}
while (*p == '\'') p++;
if (!*p)
break;
if (*p == esc)
return 1;
if (*p == '%') /* %% is a literal '%', not the start of an escape */
p++;
}
return 0;
}
+37 -117
View File
@@ -31,9 +31,6 @@
#ifdef __TANDEM
#include <floss.h(floss_execlp)>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
extern int dry_run;
extern int list_only;
@@ -51,7 +48,6 @@ extern int called_from_signal_handler;
extern int need_messages_from_generator;
extern int kluge_around_eof;
extern int got_xfer_error;
extern volatile sig_atomic_t got_sigusr2;
extern int old_style_args;
extern int msgs2stderr;
extern int module_id;
@@ -70,6 +66,7 @@ extern int protect_args;
extern int relative_paths;
extern int sanitize_paths;
extern int curr_dir_depth;
extern unsigned int curr_dir_len;
extern int module_id;
extern int rsync_port;
extern int whole_file;
@@ -105,6 +102,7 @@ extern char *password_file;
extern char *backup_dir;
extern char *copy_as;
extern char *tmpdir;
extern char curr_dir[MAXPATHLEN];
extern char backup_dir_buf[MAXPATHLEN];
extern char *basis_dir[MAX_BASIS_DIRS+1];
extern struct file_list *first_flist;
@@ -714,45 +712,41 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
dest_path = dot_dir_or_error();
if (daemon_filter_list.head) {
/* Collapse ".." for the NAME-based daemon filter check so a "../excluded"
* destination is matched by name, as stock rsync does on its sanitized
* arg. Done on a copy: the daemon exclude/filter is name-based (a symlink
* whose own name is not excluded is still followed -- see rsyncd.conf(5)
* "munge symlinks"), and the real dest_path is left for the resolver. */
char cleaned[MAXPATHLEN], *slash;
if (!sanitize_path(cleaned, dest_path, NULL, 0, SP_KEEP_DOT_DIRS))
strlcpy(cleaned, dest_path, sizeof cleaned);
slash = strrchr(cleaned, '/');
char *slash = strrchr(dest_path, '/');
if (slash && (slash[1] == '\0' || (slash[1] == '.' && slash[2] == '\0')))
*slash = '\0';
if ((*cleaned != '.' || cleaned[1] != '\0')
&& (check_filter(&daemon_filter_list, FLOG, cleaned, 0) < 0
|| check_filter(&daemon_filter_list, FLOG, cleaned, 1) < 0)) {
else
slash = NULL;
if ((*dest_path != '.' || dest_path[1] != '\0')
&& (check_filter(&daemon_filter_list, FLOG, dest_path, 0) < 0
|| check_filter(&daemon_filter_list, FLOG, dest_path, 1) < 0)) {
rprintf(FERROR, "ERROR: daemon has excluded destination \"%s\"\n",
dest_path);
exit_cleanup(RERR_FILESELECT);
}
if (slash)
*slash = '/';
}
/* See what currently exists at the destination. */
statret = vfs_stat(VFS_AT_FDCWD, dest_path, &st, VFS_ALLOW_SYMLINK);
statret = do_stat(dest_path, &st);
cp = strrchr(dest_path, '/');
trailing_slash = cp && !cp[1];
if (mkpath_dest_arg && statret < 0 && (cp || file_total > 1)) {
int save_errno = errno;
int ret = vfs_make_path(dest_path, file_total > 1 && !trailing_slash ? 0 : MKP_DROP_NAME, 0);
int ret = make_path(dest_path, file_total > 1 && !trailing_slash ? 0 : MKP_DROP_NAME);
if (ret < 0)
goto mkdir_error;
if (ret && (INFO_GTE(NAME, 1) || stdout_format_has_i)) {
if (cp && (file_total == 1 || trailing_slash))
if (file_total == 1 || trailing_slash)
*cp = '\0';
rprintf(FINFO, "created %d director%s for %s\n", ret, ret == 1 ? "y" : "ies", dest_path);
if (cp && (file_total == 1 || trailing_slash))
if (file_total == 1 || trailing_slash)
*cp = '/';
}
if (ret)
statret = vfs_stat(VFS_AT_FDCWD, dest_path, &st, VFS_ALLOW_SYMLINK);
statret = do_stat(dest_path, &st);
else
errno = save_errno;
}
@@ -799,7 +793,7 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
exit_cleanup(RERR_SYNTAX);
}
if (vfs_mkdir(VFS_AT_FDCWD, dest_path, ACCESSPERMS, VFS_ALLOW_SYMLINK) != 0) {
if (do_mkdir(dest_path, ACCESSPERMS) != 0) {
mkdir_error:
rsyserr(FERROR, errno, "mkdir %s failed",
full_fname(dest_path));
@@ -838,7 +832,7 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
dest_path = "/";
*cp = '\0';
if (dry_run && mkpath_dest_arg && vfs_stat(VFS_AT_FDCWD, dest_path, &st, VFS_ALLOW_SYMLINK) < 0) {
if (dry_run && mkpath_dest_arg && do_stat(dest_path, &st) < 0) {
/* --mkpath would have created this parent dir, but a dry run did
* not, so don't chdir into it; flag the destination as not yet
* present (as the dir-creation path above does) so the generator
@@ -860,42 +854,35 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
/* This function checks on our alternate-basis directories. If we're in
* dry-run mode and the destination dir does not yet exist, we'll try to
* tweak any dest-relative paths to make them work for a dry-run (the
* destination dir must be in vfs.curr_dir[] when this function is called).
* destination dir must be in curr_dir[] when this function is called).
* We also warn about any arg that is non-existent or not a directory. */
static void check_alt_basis_dirs(void)
{
STRUCT_STAT st;
char *slash = strrchr(vfs.curr_dir, '/');
char *slash = strrchr(curr_dir, '/');
int j;
for (j = 0; j < basis_dir_cnt; j++) {
char *bdir = basis_dir[j];
assert(bdir != NULL); /* option-supplied root; never NULL */
int bd_len = strlen(bdir);
if (bd_len > 1 && bdir[bd_len-1] == '/')
bdir[--bd_len] = '\0';
/* Make a relative --link-dest/--copy-dest/--compare-dest absolute
* (vs the destination vfs.curr_dir). These are operator-trusted roots, so
* an absolute path makes the do_*_at() wrappers use plain resolution
* rather than reject an operator '..' outside the dest tree (e.g.
* --copy-dest=../to). Skipped when sanitize_paths already confined
* them; the dry_run>1 case keeps its leading-"../"-strip. */
if (*bdir != '/' && (dry_run > 1 || !sanitize_paths)) {
int len = vfs.curr_dir_len + 1 + bd_len + 1;
if (dry_run > 1 && *bdir != '/') {
int len = curr_dir_len + 1 + bd_len + 1;
char *new = new_array(char, len);
if (dry_run > 1 && slash && strncmp(bdir, "../", 3) == 0) {
if (slash && strncmp(bdir, "../", 3) == 0) {
/* We want to remove only one leading "../" prefix for
* the directory we couldn't create in dry-run mode:
* this ensures that any other ".." references get
* evaluated the same as they would for a live copy. */
*slash = '\0';
pathjoin(new, len, vfs.curr_dir, bdir + 3);
pathjoin(new, len, curr_dir, bdir + 3);
*slash = '/';
} else
pathjoin(new, len, vfs.curr_dir, bdir);
pathjoin(new, len, curr_dir, bdir);
basis_dir[j] = bdir = new;
}
if (vfs_stat(VFS_AT_FDCWD, bdir, &st, VFS_ALLOW_SYMLINK) < 0)
if (do_stat(bdir, &st) < 0)
rprintf(FWARNING, "%s arg does not exist: %s\n", alt_dest_opt(0), bdir);
else if (!S_ISDIR(st.st_mode))
rprintf(FWARNING, "%s arg is not a dir: %s\n", alt_dest_opt(0), bdir);
@@ -1023,7 +1010,7 @@ static int do_recv(int f_in, int f_out, char *local_name)
int ret;
if (backup_dir_len > 1)
backup_dir_buf[backup_dir_len-1] = '\0';
ret = vfs_stat(VFS_AT_FDCWD, backup_dir_buf, &st, VFS_ALLOW_SYMLINK);
ret = do_stat(backup_dir_buf, &st);
if (ret != 0 || !S_ISDIR(st.st_mode)) {
if (ret == 0) {
rprintf(FERROR, "The backup-dir is not a directory: %s\n", backup_dir_buf);
@@ -1043,7 +1030,7 @@ static int do_recv(int f_in, int f_out, char *local_name)
if (tmpdir) {
STRUCT_STAT st;
int ret = vfs_stat(VFS_AT_FDCWD, tmpdir, &st, VFS_ALLOW_SYMLINK);
int ret = do_stat(tmpdir, &st);
if (ret < 0 || !S_ISDIR(st.st_mode)) {
if (ret == 0) {
rprintf(FERROR, "The temp-dir is not a directory: %s\n", tmpdir);
@@ -1110,13 +1097,11 @@ static int do_recv(int f_in, int f_out, char *local_name)
exit_cleanup(RERR_PROTOCOL);
}
/* Finally, we go to sleep until our parent tells us to wrap up
* with a USR2 signal. We sleep for a short time, as on some OSes
* a signal won't interrupt a sleep, then act on the flag the
* (async-signal-safe) handler set. */
while (!got_sigusr2)
/* Finally, we go to sleep until our parent kills us with a
* USR2 signal. We sleep for a short time, as on some OSes
* a signal won't interrupt a sleep! */
while (1)
msleep(20);
receive_sigusr2();
}
am_generator = 1;
@@ -1242,25 +1227,15 @@ static void do_server_recv(int f_in, int f_out, int argc, char *argv[])
char **dir_p;
filter_rule_list *elp = &daemon_filter_list;
/* Collapse ".." and strip the module-dir prefix to get the module-relative
* name, but keep a leading "/" for a "path = /" module (module_dirlen <= 1)
* so an absolute (module-rooted) filter rule still matches. */
char clean[MAXPATHLEN], *dir;
for (dir_p = basis_dir; *dir_p; dir_p++) {
if (!sanitize_path(clean, *dir_p, "/", 0, SP_DEFAULT))
strlcpy(clean, *dir_p, sizeof clean);
dir = clean + (*clean == '/' && module_dirlen > 1 ? module_dirlen : 0);
char *dir = *dir_p;
if (*dir == '/')
dir += module_dirlen;
if (check_filter(elp, FLOG, dir, 1) < 0)
goto options_rejected;
}
if (partial_dir && *partial_dir == '/') {
if (!sanitize_path(clean, partial_dir, "/", 0, SP_DEFAULT))
strlcpy(clean, partial_dir, sizeof clean);
dir = clean + (*clean == '/' && module_dirlen > 1 ? module_dirlen : 0);
if (check_filter(elp, FLOG, dir, 1) < 0)
goto options_rejected;
}
if (0) {
if (partial_dir && *partial_dir == '/'
&& check_filter(elp, FLOG, partial_dir + module_dirlen, 1) < 0) {
options_rejected:
rprintf(FERROR, "Your options have been rejected by the server.\n");
exit_cleanup(RERR_SYNTAX);
@@ -1294,17 +1269,6 @@ void start_server(int f_in, int f_out, int argc, char *argv[])
if (am_sender) {
keep_dirlinks = 0; /* Must be disabled on the sender. */
/* Mirror client_run()'s sender_keeps_checksum check: a daemon-
* as-sender with -c and a `log format` containing %C will read
* F_SUM(file) in log_formatted(), so make_file() must allocate
* SUM_EXTRA_CNT. Without this, F_SUM() reads past the pool slot
* and hex-encodes adjacent heap into the transfer log. */
if (always_checksum
&& (log_format_has(stdout_format, 'C')
|| log_format_has(logfile_format, 'C')))
sender_keeps_checksum = 1;
if (need_messages_from_generator)
io_start_multiplex_in(f_in);
else
@@ -1368,7 +1332,7 @@ int client_run(int f_in, int f_out, pid_t pid, int argc, char *argv[])
become_copy_as_user();
send_file_list(f_out, argc, argv);
flist = send_file_list(f_out, argc, argv);
if (DEBUG_GTE(FLIST, 3))
rprintf(FINFO,"file list sent\n");
@@ -1658,26 +1622,11 @@ static void sigusr1_handler(UNUSED(int val))
exit_cleanup(RERR_SIGNAL1);
}
/* SIGUSR2 tells the receiver child to wrap up. A signal handler must be
* async-signal-safe, so it only sets a flag here; receive_sigusr2() does the
* actual summary + shutdown (which use stdio/malloc/close) at a safe point in
* the receiver's post-transfer wait loops (read_final_goodbye via perform_io,
* and the trailing sleep). */
static void sigusr2_handler(UNUSED(int val))
{
got_sigusr2 = 1;
}
void receive_sigusr2(void)
{
if (!am_server)
output_summary();
close_all();
#ifdef GCOV_COVERAGE
/* The receiver child exits with _exit() here, bypassing the gcov atexit
* flush; without this it writes no .gcda. */
{ extern void __gcov_dump(void); __gcov_dump(); }
#endif
if (got_xfer_error)
_exit(RERR_PARTIAL);
_exit(0);
@@ -1780,31 +1729,6 @@ static void unset_env_var(const char *var)
}
/* The symlink-race-safe path resolver (vfs_resolve_open) holds one open
* dirfd per path component while it walks a path, plus an ancestor-dirfd cache
* -- far more descriptors than legacy rsync's single open(). On a host with a
* low default soft limit (e.g. OpenBSD's 128) a deep tree can hit EMFILE.
* Raise the soft RLIMIT_NOFILE toward the hard limit (unprivileged, per
* process; inherited by the sender/generator/receiver forks and daemon
* children), but cap it: some systems set an enormous hard limit (2^20+) that
* we don't want to adopt wholesale. */
static void raise_fd_limit(void)
{
#if defined HAVE_GETRLIMIT && defined HAVE_SETRLIMIT && defined RLIMIT_NOFILE
struct rlimit rl;
rlim_t want = 4096; /* covers a MAXPATHLEN-deep walk + cache + headroom */
if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
return;
if (want > rl.rlim_max)
want = rl.rlim_max; /* never exceed the (admin-set) hard limit */
if (rl.rlim_cur < want) { /* only ever raise, never lower an inherited limit */
rl.rlim_cur = want;
(void)setrlimit(RLIMIT_NOFILE, &rl); /* best-effort */
}
#endif
}
int main(int argc,char *argv[])
{
int ret;
@@ -1812,10 +1736,6 @@ int main(int argc,char *argv[])
raw_argc = argc;
raw_argv = argv;
vfs_init();
raise_fd_limit();
#ifdef HAVE_SIGACTION
# ifdef HAVE_SIGPROCMASK
sigset_t sigmask;
-41
View File
@@ -44,29 +44,6 @@ extern struct stats stats;
#define TRADITIONAL_TABLESIZE (1<<16)
/* The maximum number of same-weak-checksum candidates we will compare
* against at a single file offset before giving up and rolling forward a
* byte. A weak checksum that collides thousands of times (very common in
* disk/VM images, which contain large runs of identical blocks) would
* otherwise turn hash_search()'s inner loop into an O(file_size *
* chain_length) scan, pegging a CPU at 100% for hours with no apparent
* progress (issue #217).
*
* Concretely, a synthetic 40000-block basis whose blocks all share one weak
* checksum took ~18.4s to sync a 60KB source on a modern x86_64 box before
* this cap and ~0.7s after it -- and the unbounded cost grows with the
* square of the file size, which is what produced the multi-hour "hangs"
* reported against real multi-GB images.
*
* Capping the per-offset work keeps the search bounded; any block we skip
* over is simply sent as literal data, so the result is always correct --
* only the transfer size is (slightly) affected. This is purely a
* sender-side search limit: it changes no checksum, emitted byte, or
* protocol field, so a capped sender interoperates with any receiver. */
#ifndef MAX_CHAIN_LEN
#define MAX_CHAIN_LEN 1024
#endif
static uint32 tablesize;
static int32 *hash_table;
@@ -205,7 +182,6 @@ static void hash_search(int f,struct sum_struct *s,
int done_csum2 = 0;
uint32 hash_entry;
int32 i, *prev;
int32 chain_len = 0;
if (DEBUG_GTE(DELTASUM, 4)) {
rprintf(FINFO, "offset=%s sum=%04x%04x\n",
@@ -242,14 +218,6 @@ static void hash_search(int f,struct sum_struct *s,
if (sum != s->sums[i].sum1)
continue;
/* Bound the work spent on a single pathological hash
* bucket. If this weak checksum matches more than
* MAX_CHAIN_LEN records, stop scanning and treat this
* offset as a non-match (issue #217). The skipped data
* is sent literally, never corrupted. */
if (++chain_len > MAX_CHAIN_LEN)
break;
/* also make sure the two blocks are the same length */
l = (int32)MIN((OFF_T)s->blength, len-offset);
if (l != s->sums[i].len)
@@ -325,7 +293,6 @@ static void hash_search(int f,struct sum_struct *s,
&& (!updating_basis_file || s->sums[want_i].offset >= offset
|| s->sums[want_i].flags & SUMFLG_SAME_OFFSET)
&& sum == s->sums[want_i].sum1
&& l == s->sums[want_i].len
&& memcmp(sum2, sum2_at(s, want_i), s->s2length) == 0) {
/* we've found an adjacent match - the RLL coder
* will be happy */
@@ -403,14 +370,6 @@ void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len)
sum_init(xfer_sum_nni, checksum_seed);
if (append_mode > 0) {
if (s->flength > len) {
/* A hostile or confused peer can claim a verified-prefix
* length that exceeds what we have on disk -- including
* for an empty local file, where buf is NULL and the
* map_ptr() calls below would dereference it. Clamp to
* what we can actually read. */
s->flength = len;
}
if (append_mode == 2) {
OFF_T j = 0;
for (j = CHUNK_SIZE; j < s->flength; j += CHUNK_SIZE) {
+1 -1
View File
@@ -15,7 +15,7 @@ if [ ! -f "$flagfile" ]; then
if "$srcdir/md-convert" --test "$srcdir/rsync-ssl.1.md" >/dev/null 2>&1; then
touch $flagfile
else
outname=`basename "$inname" .md`
outname=`echo "$inname" | sed 's/\.md$//'`
if [ -f "$outname" ]; then
exit 0
elif [ -f "$srcdir/$outname" ]; then
+4 -14
View File
@@ -7,20 +7,10 @@ if [ ! -f git-version.h ]; then
fi
if test -d "$srcdir/.git" || test -f "$srcdir/.git"; then
# Identify a git build by the development version from version.h plus the
# exact commit (e.g. "3.5.0dev-g1234abcd"), rather than the nearest release
# tag that `git describe` would pick: that tag can sit far behind a rebased
# development branch and then misnames the line you are actually on (showing,
# say, 3.4.3 for a 3.5.0dev tree). This also works in a shallow/tag-less
# clone. A release tarball has no .git, so git-version.h stays empty and
# rsync prints the plain RSYNC_VERSION.
# cd into the subshell rather than "git -C" (avoids needing a newer git).
gitsha=`(cd "$srcdir" && git rev-parse --short=8 HEAD) 2>/dev/null`
# Tolerate any preprocessor spacing and a trailing comment; capture only the
# quoted value. Empty (define missing/unmatched) -> leave RSYNC_GITVER unset.
rsyncver=`sed -n 's/^[[:space:]]*#[[:space:]]*define[[:space:]][[:space:]]*RSYNC_VERSION[[:space:]][[:space:]]*"\([^"]*\)".*/\1/p' "$srcdir/version.h"`
if [ -n "$gitsha" ] && [ -n "$rsyncver" ]; then
gitver="$rsyncver-g$gitsha"
gitver=`git describe --abbrev=8 2>/dev/null`
# NOTE: I'm avoiding "|" in sed since I'm not sure if sed -r is portable and "\|" fails on some OSes.
verchk=`echo "$gitver-" | sed -n '/^v3\.[0-9][0-9]*\.[0-9][0-9]*\(pre[0-9]*\)*-/p'`
if [ -n "$verchk" ]; then
echo "#define RSYNC_GITVER \"$gitver\"" >git-version.h.new
if ! diff git-version.h.new git-version.h >/dev/null; then
echo "Updating git-version.h"
-1
View File
@@ -18,7 +18,6 @@ inheader {
sub(/^CHAR\(/, "char ")
sub(/^INTEGER\(/, "int ")
sub(/^STRING\(/, "char *")
sub(/^STRING_SHELL\(/, "char *")
protos = protos "\n" $0 (local ? "(int module_id);" : "(void);")
next
}
-87
View File
@@ -1,87 +0,0 @@
# Old rsync version archive
Static rsync binaries built from historical release tags. Two uses:
1. **Cross-version behaviour checks** — confirming whether a behaviour a user
reported on an old release is version-specific or option-driven.
2. **The version-mixing test suite**`runtests.py --rsync-bin2=...` runs the
current code against one of these as the daemon / remote-shell peer; CI
(`.github/workflows/ubuntu-version-mix.yml`) does this for every binary
here against the per-version manifests in `testsuite/expect/`.
Binaries are **statically linked** so they run regardless of the host's
shared libraries, and named `rsync_<version>`:
| Binary | Version | Protocol | Notes |
|----------------|---------|----------|-----------------------------------------|
| `rsync_2.6.0` | 2.6.0 | 27 | 2004; needs autoconf regen (see below) |
| `rsync_3.0.0` | 3.0.0 | 30 | 2008 |
| `rsync_3.1.0` | 3.1.0 | 31 | 2013 |
| `rsync_3.1.3` | 3.1.3 | 31 | Ubuntu 18.04 / Debian buster era (2018) |
| `rsync_3.2.0` | 3.2.0 | 31 | 2020 (zstd/lz4/xxhash negotiation added)|
| `rsync_3.2.7` | 3.2.7 | 31 | 2022 |
| `rsync_3.3.0` | 3.3.0 | 31 | 2024 |
| `rsync_3.4.0` | 3.4.0 | 32 | 2025 |
| `rsync_3.4.1` | 3.4.1 | 32 | 2025 |
These are every `x.y.0` release from 2.6.0 (2004) onward plus a few point
releases. 2.6.0 is the practical floor: older tags need progressively more
porting to build on a current toolchain.
All built `--disable-openssl` and with `_FORTIFY_SOURCE` disabled (see below);
xxhash/zstd/lz4 are compiled in where the version supports them.
## Adding a version
```bash
./build_static.sh 3.2.7 # uses git tag v3.2.7
./build_static.sh 3.0.9 v3.0.9 # explicit tag if naming differs
```
The script checks out the tag into a throwaway `git worktree`, applies the
minimal patches needed to compile old sources on a modern toolchain, links
statically, verifies the result is static and reports the requested version,
then installs `rsync_<version>` here and removes the worktree.
Override the source repo with `RSYNC_REPO=/path/to/rsync ./build_static.sh ...`
(defaults to `../rsync.4`).
## Why the patches?
Modern GCC (>= 14, C23 default) and glibc reject things old rsync relied on.
`build_static.sh` handles these, each guarded so it's a no-op when not needed:
1. **K&R `lseek64()` redeclaration** in `syscall.c` clashes with glibc's real
prototype — removed.
2. **`gettimeofday()`** — glibc only has the 2-arg form; configure misdetects
the 1-arg form, so `HAVE_GETTIMEOFDAY_TZ` is forced on in `config.h`.
3. **C23 `()` == `(void)`** breaks K&R prototypes called with arguments
(`qsort` comparator, `pool->bomb`, etc.) — built with `-std=gnu11`.
4. Assorted modern `-Werror` promotions (incompatible pointer types, implicit
declarations) downgraded to warnings; bundled zlib/popt used to keep the
static link self-contained.
5. **OpenSSL (3.2+)** is disabled with `--disable-openssl`: linking
`libcrypto.a` statically drags in jitterentropy (`jent_*`) and zlib's
`uncompress` (OpenSSL's COMP module), which don't resolve here. OpenSSL only
provided optional MD4/MD5, which rsync implements natively, so checksum
behaviour is unaffected.
6. **`_FORTIFY_SOURCE` disabled** (`-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0`):
modern Ubuntu defaults it to `=3`, whose stricter object-size checks turn
latent (historically benign) over-reads in OLD rsync into hard
`*** buffer overflow detected ***` aborts when the binary runs as a
server/daemon — which made e.g. 3.1.3 and 3.2.7 unusable as peers. Disabling
it makes the archival binaries behave as the released versions did.
7. **Pre-3.0 tags (e.g. 2.6.0)** ship `configure.in`, not a generated
`configure`. The script runs `autoheader`/`autoconf` to generate it, after
neutralizing the `AC_CHECK_FUNCS(fn,,AC_LIBOBJ(lib/...))` fallbacks for
`inet_ntop`/`inet_pton`/`getaddrinfo`/`getnameinfo` — modern autoconf emits
broken shell for those never-taken branches (the funcs exist in glibc). It
also generates `proto.h` (no make rule in that era) and stubs the vendored
`lib/addrinfo.h` the tag dropped (modern glibc supplies `struct addrinfo`).
All guarded so they no-op on 3.x.
Newer versions may need fewer or different tweaks; if a build fails, the
script prints the first compiler errors from its log.
-128
View File
@@ -1,128 +0,0 @@
#!/bin/bash
# Build a static rsync binary from a historical git tag, for cross-version
# behaviour testing. Produces ./rsync_<version> in this directory.
#
# Usage: ./build_static.sh <version> [git-tag]
# Example: ./build_static.sh 3.1.3 # uses tag v3.1.3
# ./build_static.sh 3.2.7 v3.2.7
#
# Old rsync releases don't compile cleanly on a modern toolchain (GCC >= 14
# defaults to C23, where an empty () prototype means (void); glibc dropped the
# 1-arg gettimeofday; lseek64 K&R redeclarations clash). This script applies
# the minimal, best-effort workarounds and links statically so the result is
# self-contained and reproducible regardless of the host's shared libraries.
#
# Each workaround is guarded so it's a no-op on versions that don't need it.
set -euo pipefail
VERSION="${1:?usage: build_static.sh <version> [git-tag]}"
TAG="${2:-v$VERSION}"
ARCHIVE_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO="${RSYNC_REPO:-/home/tridge/project/rsync/rsync.4}" # any rsync worktree
WORKTREE="$(mktemp -d /tmp/rsync-build-XXXXXX)"
OUT="$ARCHIVE_DIR/rsync_$VERSION"
# C standard restores K&R () semantics; permissive flags downgrade the pile of
# modern -Werror promotions (incompatible pointers, implicit decls) to warnings.
# _FORTIFY_SOURCE is forced OFF: modern Ubuntu defaults it to =3, whose stricter
# object-size checks turn latent (historically benign) over-reads in OLD rsync
# into hard "*** buffer overflow detected ***" aborts when the binary acts as a
# server/daemon. Disabling it makes these archival binaries behave the way the
# released versions did, which is the whole point of the archive.
CFLAGS_OLD="-I. -I./zlib -O2 -g -std=gnu11 -fcommon -DHAVE_CONFIG_H -Wno-error \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
-Wno-incompatible-pointer-types -Wno-implicit-function-declaration -Wno-int-conversion"
cleanup() {
cd "$REPO"
git worktree remove --force "$WORKTREE" 2>/dev/null || true
git worktree prune 2>/dev/null || true
}
trap cleanup EXIT
echo ">>> checking out $TAG into $WORKTREE"
# prefer an exact tag to avoid ambiguity with similarly-named branches
REF="$TAG"
if git -C "$REPO" rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
REF="refs/tags/$TAG"
fi
git -C "$REPO" worktree add --detach "$WORKTREE" "$REF"
cd "$WORKTREE"
# --- workaround 1: K&R lseek64 redeclaration clashes with glibc's prototype ---
if grep -q 'off64_t lseek64();' syscall.c 2>/dev/null; then
echo ">>> patching syscall.c lseek64 redeclaration"
perl -0pi -e 's/#ifdef HAVE_LSEEK64\n#if !SIZEOF_OFF64_T\n\tOFF_T lseek64\(\);\n#else\n\toff64_t lseek64\(\);\n#endif\n\treturn lseek64/#ifdef HAVE_LSEEK64\n\treturn lseek64/' syscall.c
fi
# --- workaround 0: pre-3.0 tags ship configure.in, not a generated configure.
# Generate it. Modern autoconf emits broken shell for their
# AC_CHECK_FUNCS(fn,,AC_LIBOBJ(lib/...)) fallbacks -- but those branches are
# dead on a modern host (glibc has inet_ntop/inet_pton/getaddrinfo/getnameinfo),
# so neutralize the AC_LIBOBJ replacements before regenerating.
OLD_TREE=0
if [ ! -f ./configure ] && { [ -f configure.in ] || [ -f configure.ac ]; }; then
OLD_TREE=1
acsrc=configure.ac; [ -f configure.in ] && acsrc=configure.in
echo ">>> generating configure for an old tag (autoheader/autoconf)"
sed -i 's#AC_LIBOBJ(lib/[a-zA-Z_]*)#:#g' "$acsrc"
autoheader 2>/dev/null || true
autoconf 2>/dev/null || { echo "autoconf failed"; exit 1; }
fi
CONF_ARGS=(--disable-md2man --with-included-zlib=yes --with-included-popt=yes)
# OpenSSL (3.2+) only adds optional MD4/MD5 that rsync already implements, but
# linking libcrypto.a statically drags in jitterentropy + zlib's uncompress,
# which aren't resolvable here. Drop it when the flag exists.
if ./configure --help 2>/dev/null | grep -q -- '--disable-openssl'; then
echo ">>> disabling openssl for self-contained static link"
CONF_ARGS+=(--disable-openssl)
fi
echo ">>> configure (bundled zlib + popt, static-friendly)"
./configure "${CONF_ARGS[@]}" \
>"$WORKTREE/conf.log" 2>&1 || { tail -20 "$WORKTREE/conf.log"; exit 1; }
# --- workaround 2: modern glibc only has the 2-arg gettimeofday ---------------
if grep -q '/\* #undef HAVE_GETTIMEOFDAY_TZ \*/' config.h; then
echo ">>> forcing HAVE_GETTIMEOFDAY_TZ (configure misdetects it)"
sed -i 's|/\* #undef HAVE_GETTIMEOFDAY_TZ \*/|#define HAVE_GETTIMEOFDAY_TZ 1|' config.h
fi
# --- workaround 4 (old trees only): generate proto.h if the tree has no make
# rule for it, and stub a vendored lib/addrinfo.h that the git tag dropped
# (modern glibc supplies struct addrinfo / sockaddr_storage, so empty is right).
if [ "$OLD_TREE" = 1 ]; then
if [ ! -f proto.h ] && [ -f mkproto.awk ]; then
echo ">>> generating proto.h"
cat ./*.c ./lib/compat.c 2>/dev/null | awk -f ./mkproto.awk > proto.h
fi
if grep -q 'include "lib/addrinfo.h"' rsync.h 2>/dev/null && [ ! -f lib/addrinfo.h ]; then
echo ">>> stubbing lib/addrinfo.h"
echo '/* emptied: modern glibc provides struct addrinfo */' > lib/addrinfo.h
fi
fi
echo ">>> building (static)"
make -j"$(nproc)" CFLAGS="$CFLAGS_OLD" LDFLAGS="-static" \
>"$WORKTREE/make.log" 2>&1 || { grep -E 'error:|\*\*\*' "$WORKTREE/make.log" | head; exit 1; }
# verify it's actually static before we keep it
if ldd ./rsync 2>&1 | grep -qv 'not a dynamic executable'; then
echo "ERROR: binary is not statically linked:" >&2
ldd ./rsync >&2
exit 1
fi
GOT="$(./rsync --version | head -1 | awk '{print $3}')"
if [ "$GOT" != "$VERSION" ]; then
echo "ERROR: built version '$GOT' != requested '$VERSION'" >&2
exit 1
fi
cp ./rsync "$OUT"
strip "$OUT"
echo ">>> installed $OUT"
"$OUT" --version | head -1
file "$OUT"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+26 -193
View File
@@ -59,9 +59,6 @@ int preserve_perms = 0;
int preserve_executability = 0;
int preserve_devices = 0;
int preserve_specials = 0;
int drop_devices = 0;
char *confine_root = NULL; /* --confine-root: see vfs/dirstack.c */
unsigned int confine_rootlen = 0;
int preserve_uid = 0;
int preserve_gid = 0;
int preserve_mtimes = 0;
@@ -90,7 +87,6 @@ int preallocate_files = 0;
int do_compression = 0;
int do_compression_level = CLVL_NOT_SPECIFIED;
int do_compression_threads = 0; /*n = 0 use rsync thread, n >= 1 spawn n threads for compression */
#define MAX_DAEMON_COMPRESSION_THREADS 8
int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */
int am_server = 0;
int am_sender = 0;
@@ -122,7 +118,7 @@ int am_daemon = 0;
* clientserver.c. NOT set for the daemon-level "daemon chroot = /X"
* chroot: that confines path resolution to /X, but module paths
* /X/modA, /X/modB, etc. are not chroot boundaries, so the per-module
* symlink-race defenses (vfs_resolve_open() / do_*_at() in
* symlink-race defenses (secure_relative_open() / do_*_at() in
* syscall.c, gated by `am_daemon && !am_chrooted`) must still fire
* even when the daemon is inside a daemon chroot. */
int am_chrooted = 0;
@@ -130,7 +126,6 @@ int connect_timeout = 0;
int keep_partial = 0;
int safe_symlinks = 0;
int copy_unsafe_links = 0;
int insecure_links = 0;
int munge_symlinks = 0;
int use_secure_symlinks = 0;
int size_only = 0;
@@ -330,7 +325,7 @@ static struct output_struct debug_words[COUNT_DEBUG+1] = {
};
static int verbose = 0;
static int vfs_stats = 0;
static int do_stats = 0;
static int do_progress = 0;
static int daemon_opt; /* sets am_daemon after option error-reporting */
static int F_option_cnt = 0;
@@ -456,10 +451,7 @@ static void parse_output_words(struct output_struct *words, short *levels, const
len--;
}
lev = isDigit(str+len) ? atoi(str+len) : 1;
/* atoi() of an overflowing positive digit string can return a
* negative int (LONG_MAX truncated on LP64); a negative lev
* here later indexes counts[lev] in make_output_option(). */
if (lev > MAX_OUT_LEVEL || lev < 0)
if (lev > MAX_OUT_LEVEL)
lev = MAX_OUT_LEVEL;
if (len == 4 && strncasecmp(str, "help", 4) == 0) {
output_item_help(words);
@@ -620,7 +612,7 @@ static struct poptOption long_options[] = {
{"quiet", 'q', POPT_ARG_NONE, 0, 'q', 0, 0 },
{"motd", 0, POPT_ARG_VAL, &output_motd, 1, 0, 0 },
{"no-motd", 0, POPT_ARG_VAL, &output_motd, 0, 0, 0 },
{"stats", 0, POPT_ARG_NONE, &vfs_stats, 0, 0, 0 },
{"stats", 0, POPT_ARG_NONE, &do_stats, 0, 0, 0 },
{"human-readable", 'h', POPT_ARG_NONE, 0, 'h', 0, 0},
{"no-human-readable",0, POPT_ARG_VAL, &human_readable, 0, 0, 0},
{"no-h", 0, POPT_ARG_VAL, &human_readable, 0, 0, 0},
@@ -684,17 +676,12 @@ static struct poptOption long_options[] = {
{"no-write-devices", 0, POPT_ARG_VAL, &write_devices, 0, 0, 0 },
{"specials", 0, POPT_ARG_VAL, &preserve_specials, 1, 0, 0 },
{"no-specials", 0, POPT_ARG_VAL, &preserve_specials, 0, 0, 0 },
{"drop-D", 0, POPT_ARG_VAL, &drop_devices, 1, 0, 0 },
{"no-drop-D", 0, POPT_ARG_VAL, &drop_devices, 0, 0, 0 },
{"confine-root", 0, POPT_ARG_STRING, &confine_root, 0, 0, 0 },
{"links", 'l', POPT_ARG_VAL, &preserve_links, 1, 0, 0 },
{"no-links", 0, POPT_ARG_VAL, &preserve_links, 0, 0, 0 },
{"no-l", 0, POPT_ARG_VAL, &preserve_links, 0, 0, 0 },
{"copy-links", 'L', POPT_ARG_NONE, &copy_links, 0, 0, 0 },
{"copy-unsafe-links",0, POPT_ARG_NONE, &copy_unsafe_links, 0, 0, 0 },
{"safe-links", 0, POPT_ARG_NONE, &safe_symlinks, 0, 0, 0 },
{"insecure-links", 0, POPT_ARG_VAL, &insecure_links, 1, 0, 0 },
{"no-insecure-links",0, POPT_ARG_VAL, &insecure_links, 0, 0, 0 },
{"munge-links", 0, POPT_ARG_VAL, &munge_symlinks, 1, 0, 0 },
{"no-munge-links", 0, POPT_ARG_VAL, &munge_symlinks, 0, 0, 0 },
{"copy-dirlinks", 'k', POPT_ARG_NONE, &copy_dirlinks, 0, 0, 0 },
@@ -917,54 +904,9 @@ void option_error(void)
}
/* Does this row store a compile-time constant, and if so which?
*
* popt's `val` is not comparable across argInfo kinds. For POPT_ARG_VAL it IS
* the value stored in `arg`; for the others a nonzero `val` is an action code
* handed to the parser's switch, and POPT_ARG_NONE with a destination stores 1
* regardless. Comparing the raw field therefore misses aliases spelled with
* different table shapes -- --del is POPT_ARG_NONE/&delete_during/0 and
* --delete-during is POPT_ARG_VAL/&delete_during/1, and both set it to 1. */
static int refuse_const_assign(const struct poptOption *op, int *valp)
{
if (!op->arg)
return 0;
if (op->argInfo == POPT_ARG_VAL) {
*valp = op->val;
return 1;
}
/* A nonzero val here means the row ALSO runs a parser action, so it is
* not merely an assignment and must not be folded in with one. */
if (op->argInfo == POPT_ARG_NONE && op->val == 0) {
*valp = 1;
return 1;
}
return 0;
}
/* Do two table rows name the same capability? An exact refuse rule names a
* capability, not one spelling of it. */
static int same_refuse_action(const struct poptOption *a, const struct poptOption *b)
{
int a_val, b_val;
/* Constant assignments: same destination, same resulting value. The
* value check keeps opposite switches such as --foo and --no-foo apart,
* since they differ only in what they store. */
if (refuse_const_assign(a, &a_val) && refuse_const_assign(b, &b_val))
return a->arg == b->arg && a_val == b_val;
/* Anything else has to match as a table entry: a row storing a runtime
* value (POPT_ARG_INT, POPT_ARG_STRING) needs the same destination and
* action code, and an action-only row the same nonzero code. */
if (a->argInfo != b->argInfo || a->val != b->val)
return 0;
return a->arg ? a->arg == b->arg : !b->arg && a->val != 0;
}
static void parse_one_refuse_match(int negated, const char *ref, const struct poptOption *list_end)
{
struct poptOption *op, *matched_op = NULL;
struct poptOption *op;
char shortName[2];
int is_wild = strpbrk(ref, "*?[") != NULL;
int found_match = 0;
@@ -985,21 +927,8 @@ static void parse_one_refuse_match(int negated, const char *ref, const struct po
else if (!is_wild)
op->descrip = negated ? "a=" : "r=";
found_match = 1;
if (!is_wild) {
matched_op = op;
if (!is_wild)
break;
}
}
}
if (matched_op) {
for (op = long_options; op != list_end; op++) {
if (op == matched_op || !same_refuse_action(op, matched_op))
continue;
if (op->descrip[1] == '*')
op->descrip = negated ? "a*" : "r*";
else
op->descrip = negated ? "a=" : "r=";
}
}
@@ -1079,11 +1008,6 @@ static void set_refuse_options(void)
parse_one_refuse_match(0, "iconv", list_end);
#endif
parse_one_refuse_match(0, "log-file*", list_end);
/* A client must never disable the daemon's symlink confinement:
* --insecure-links is a local-only flag, so the daemon hard-refuses it
* (dropping the connection). The daemon's own opt-out is the
* "insecure links" module parameter, not this flag. */
parse_one_refuse_match(0, "insecure-links", list_end);
}
#ifndef SUPPORT_ATIMES
@@ -1165,8 +1089,6 @@ static ssize_t parse_size_arg(const char *size_arg, char def_suf, const char *op
int reps, mult, len;
const char *arg, *err = "invalid", *min_max = NULL;
ssize_t limit = -1, size = 1;
ssize_t size_max = max_value >= 0 ? max_value : (ssize_t)(SIZE_MAX / 2);
double dsize;
for (arg = size_arg; isDigit(arg); arg++) {}
if (*arg == '.' || *arg == get_decimal_point()) /* backward compatibility: always allow '.' */
@@ -1201,38 +1123,11 @@ static ssize_t parse_size_arg(const char *size_arg, char def_suf, const char *op
mult = 1024, arg += 2;
else
goto failure;
while (reps--) {
if (size > size_max / mult) {
err = "too large";
min_max = "max";
limit = max_value;
goto failure;
}
while (reps--)
size *= mult;
}
errno = 0;
dsize = strtod(size_arg, NULL);
if (errno == ERANGE || dsize < 0 || dsize > (double)size_max / size
|| (max_value < 0 && dsize >= (double)size_max / size)) {
err = "too large";
min_max = "max";
limit = max_value;
goto failure;
}
size = (ssize_t)(dsize * size);
if ((*arg == '+' || *arg == '-') && arg[1] == '1' && arg != size_arg) {
if (*arg == '+') {
if (size == size_max) {
err = "too large";
min_max = "max";
limit = max_value;
goto failure;
}
size++;
} else
size--;
arg += 2;
}
size *= atof(size_arg);
if ((*arg == '+' || *arg == '-') && arg[1] == '1' && arg != size_arg)
size += atoi(arg), arg += 2;
if (*arg)
goto failure;
if (size < 0 || (max_value >= 0 && size > max_value)) {
@@ -1256,8 +1151,6 @@ failure:
min_max, do_big_num(limit, 3, NULL),
unlimited_0 && min_max[1] == 'i' ? " or 0 for unlimited" : "");
}
if (len < 0 || len > (int)sizeof err_buf - 2)
len = sizeof err_buf - 2;
err_buf[len] = '\n';
err_buf[len+1] = '\0';
return -1;
@@ -1596,6 +1489,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
*argc_p = 0;
} else if (poptDupArgv(argc, argv, argc_p, argv_p) != 0)
out_of_memory("parse_arguments");
argv = *argv_p;
poptFreeContext(pc);
am_starting_up = 0;
@@ -2066,10 +1960,6 @@ int parse_arguments(int *argc_p, const char ***argv_p)
ssize_t size = parse_size_arg(max_alloc_arg, 'B', "max-alloc", 1024*1024, -1, True);
if (size < 0)
goto cleanup;
if (size == 0) {
snprintf(err_buf, sizeof err_buf, "max-alloc must be greater than zero\n");
goto cleanup;
}
max_alloc = size;
}
if (!max_alloc)
@@ -2134,12 +2024,6 @@ int parse_arguments(int *argc_p, const char ***argv_p)
}
if (do_compression_threads < 0)
do_compression_threads = 0;
/* A daemon client controls the server-side sender arguments. Keep one
* unauthenticated connection from asking Zstandard to materialize its
* implementation maximum (currently hundreds) of worker threads. Local
* and remote-shell invocations retain the operator-requested value. */
if (am_daemon && do_compression_threads > MAX_DAEMON_COMPRESSION_THREADS)
do_compression_threads = MAX_DAEMON_COMPRESSION_THREADS;
}
#ifdef HAVE_SETVBUF
@@ -2177,7 +2061,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
set_output_verbosity(verbose, DEFAULT_PRIORITY);
if (vfs_stats) {
if (do_stats) {
parse_output_words(info_words, info_levels,
verbose > 1 ? "stats3" : "stats2", DEFAULT_PRIORITY);
}
@@ -2371,33 +2255,13 @@ int parse_arguments(int *argc_p, const char ***argv_p)
STRUCT_STAT st;
char prefix[SYMLINK_PREFIX_LEN]; /* NOT +1 ! */
strlcpy(prefix, SYMLINK_PREFIX, sizeof prefix); /* trim the trailing slash */
if (vfs_stat(VFS_AT_FDCWD, prefix, &st, VFS_ALLOW_SYMLINK) == 0 && S_ISDIR(st.st_mode)) {
if (do_stat(prefix, &st) == 0 && S_ISDIR(st.st_mode)) {
rprintf(FERROR, "Symlink munging is unsafe when a %s directory exists.\n",
prefix);
exit_cleanup(RERR_UNSUPPORTED);
}
}
if (confine_root) {
/* A daemon already has module_dir for this job, and honouring a
* peer-supplied root there could only loosen the module boundary. */
if (am_daemon)
confine_root = NULL;
else if (*confine_root != '/') {
snprintf(err_buf, sizeof err_buf,
"--confine-root must be an absolute path\n");
return 0;
} else if (insecure_links) {
/* The opt-out restores the legacy open, which short-circuits the
* walk that enforces the root -- so the pair would silently mean
* no confinement at all. Say so instead. */
snprintf(err_buf, sizeof err_buf,
"--insecure-links cannot be combined with --confine-root\n");
return 0;
} else
confine_root = normalize_path(confine_root, True, &confine_rootlen);
}
if (sanitize_paths) {
int i;
for (i = argc; i-- > 0; )
@@ -2409,26 +2273,21 @@ int parse_arguments(int *argc_p, const char ***argv_p)
}
if (daemon_filter_list.head && !am_sender) {
filter_rule_list *elp = &daemon_filter_list;
/* Strip the module-dir prefix to get the module-relative name, but keep a
* leading "/" for a "path = /" module (module_dirlen <= 1) so an absolute
* (module-rooted) filter rule still matches. */
if (tmpdir) {
char clean[MAXPATHLEN], *dir;
char *dir;
if (!*tmpdir)
goto options_rejected;
if (!sanitize_path(clean, tmpdir, "/", 0, SP_DEFAULT))
strlcpy(clean, tmpdir, sizeof clean);
dir = clean + (*clean == '/' && module_dirlen > 1 ? module_dirlen : 0);
dir = tmpdir + (*tmpdir == '/' ? module_dirlen : 0);
clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS);
if (check_filter(elp, FLOG, dir, 1) < 0)
goto options_rejected;
}
if (backup_dir) {
char clean[MAXPATHLEN], *dir;
char *dir;
if (!*backup_dir)
goto options_rejected;
if (!sanitize_path(clean, backup_dir, "/", 0, SP_DEFAULT))
strlcpy(clean, backup_dir, sizeof clean);
dir = clean + (*clean == '/' && module_dirlen > 1 ? module_dirlen : 0);
dir = backup_dir + (*backup_dir == '/' ? module_dirlen : 0);
clean_fname(dir, CFN_COLLAPSE_DOT_DOT_DIRS);
if (check_filter(elp, FLOG, dir, 1) < 0)
goto options_rejected;
}
@@ -2605,7 +2464,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (files_from) {
char *h, *p;
int q = 0;
int q;
if (argc > 2 || (!am_daemon && !am_server && argc == 1)) {
usage(FERROR);
exit_cleanup(RERR_SYNTAX);
@@ -2639,16 +2498,7 @@ int parse_arguments(int *argc_p, const char ***argv_p)
if (check_filter(&daemon_filter_list, FLOG, dir, 0) < 0)
goto options_rejected;
}
/* Operator-supplied path that may transit attacker-writable
* parents; refuse symlinks not owned by uid 0 or our euid,
* as for --exclude-from/--include-from/--filter in exclude.c.
* A daemon reads this list from a CLIENT-requested path
* (--files-from=:LIST) and it must stay inside the module:
* the is_operator walk also refuses a (trusted-owned) symlink
* that redirects the list outside the module root -- e.g. a
* root-owned backup symlink. No-op off a daemon (the module-root
* check only fires when am_daemon). */
filesfrom_fd = vfs_open_owner_walk(files_from, O_RDONLY|O_BINARY, 0, 1);
filesfrom_fd = open(files_from, O_RDONLY|O_BINARY);
if (filesfrom_fd < 0) {
snprintf(err_buf, sizeof err_buf,
"failed to open files-from file %s: %s\n",
@@ -2688,7 +2538,7 @@ static char SPLIT_ARG_WHEN_OLD[1];
**/
char *safe_arg(const char *opt, const char *arg)
{
#define SHELL_CHARS "!#$&;|<>(){}\"\'` \t\n\r\\"
#define SHELL_CHARS "!#$&;|<>(){}\"'` \t\\"
#define WILD_CHARS "*?[]" /* We don't allow remote brace expansion */
BOOL is_filename_arg = !opt;
char *escapes = is_filename_arg ? SHELL_CHARS : WILD_CHARS SHELL_CHARS;
@@ -2707,16 +2557,7 @@ char *safe_arg(const char *opt, const char *arg)
escape_leading_tilde = 1;
}
for (f = arg; *f; f++) {
if (*f == '\\') {
/* Mirror the writer below: in filename mode a backslash
* before a wildcard is not doubled, so don't reserve a slot
* for it. The "f[1] &&" also avoids the strchr(WILD_CHARS,
* '\0') footgun (which matches the terminator) on a trailing
* backslash -- otherwise the counter and writer disagree and
* an uninitialized heap byte leaks into the result. */
if (!is_filename_arg || !(f[1] && strchr(WILD_CHARS, f[1])))
extras++;
} else if (strchr(escapes, *f))
if (strchr(escapes, *f))
extras++;
}
}
@@ -2741,7 +2582,7 @@ char *safe_arg(const char *opt, const char *arg)
*t++ = '\\';
while (*f) {
if (*f == '\\') {
if (!is_filename_arg || !(f[1] && strchr(WILD_CHARS, f[1])))
if (!is_filename_arg || !strchr(WILD_CHARS, f[1]))
*t++ = '\\';
} else if (strchr(escapes, *f))
*t++ = '\\';
@@ -2781,10 +2622,7 @@ void server_options(char **args, int *argc_p)
if (protect_args)
argstr[x++] = 's';
/* `verbose` is unbounded (one increment per -v on our own command
* line), so an uncapped loop walks past argstr[64]. Anything beyond
* level ~5 is meaningless to the server anyway. */
for (i = 0; i < verbose && i < 9; i++)
for (i = 0; i < verbose; i++)
argstr[x++] = 'v';
if (quiet && msgs2stderr)
@@ -3015,7 +2853,7 @@ void server_options(char **args, int *argc_p)
args[ac++] = "--super";
if (size_only)
args[ac++] = "--size-only";
if (vfs_stats)
if (do_stats)
args[ac++] = "--stats";
} else {
if (skip_compress)
@@ -3061,11 +2899,6 @@ void server_options(char **args, int *argc_p)
if (copy_unsafe_links)
args[ac++] = "--copy-unsafe-links";
/* --insecure-links is NOT forwarded: it is a local-only opt-out. A daemon
* governs its own confinement via the "insecure links" module parameter and
* drops a connection that sends --insecure-links; a remote-shell peer that
* wants it must be given it on its own side (e.g. via --rsync-path). */
if (safe_symlinks)
args[ac++] = "--safe-links";
+1 -1
View File
@@ -1,4 +1,4 @@
TARGETS := all install install-ssl-daemon install-all install-strip uninstall uninstall-ssl-daemon uninstall-all conf gen reconfigure restatus \
TARGETS := all install install-ssl-daemon install-all install-strip conf gen reconfigure restatus \
proto man clean cleantests distclean test check check29 check30 installcheck splint \
doxygen doxygen-upload finddead rrsync
-2
View File
@@ -1,2 +0,0 @@
- /generated-files/
- /binaries/
+3 -3
View File
@@ -1,6 +1,6 @@
Summary: A fast, versatile, remote (and local) file-copying tool
Name: rsync
Version: 3.5.0
Version: 3.4.4
%define fullversion %{version}
Release: 1
%define srcdir src
@@ -79,5 +79,5 @@ rm -rf $RPM_BUILD_ROOT
%dir /etc/rsync-ssl/certs
%changelog
* Thu Aug 13 2026 Rsync Project <rsync.project@gmail.com>
Released 3.5.0.
* Mon Jun 08 2026 Rsync Project <rsync.project@gmail.com>
Released 3.4.4.
+2 -9
View File
@@ -206,14 +206,7 @@ def get_rsync_version():
die("Unable to find RSYNC_VERSION define in version.h")
def get_NEWS_version_info(skip_version=None):
"""Return (last_version, its protocol version, {version: protocol-change date}).
skip_version lets the caller exclude the version it is about to release.
Its NEWS entry may already carry a release date -- dated by hand, or by an
earlier run of --step-3-tweak -- and would otherwise be reported as the
PREVIOUS release, which is both wrong and fatal when it has no table row yet.
"""
def get_NEWS_version_info():
rel_re = re.compile(r'^\| \S{2} \w{3} \d{4}\s+\|\s+(?P<ver>\d+\.\d+\.\d+)\s+\|\s+(?P<pdate>\d{2} \w{3} \d{4})?\s+\|\s+(?P<pver>\d+)\s+\|')
last_version = last_protocol_version = None
pdate = { }
@@ -222,7 +215,7 @@ def get_NEWS_version_info(skip_version=None):
for line in fh:
if not last_version: # Find the first non-dev|pre version with a release date.
m = re.search(r'rsync (\d+\.\d+\.\d+) .*\d\d\d\d', line)
if m and m[1] != skip_version:
if m:
last_version = m[1]
m = rel_re.match(line)
if m:
+29 -40
View File
@@ -8,7 +8,7 @@
# the rsync git checkout):
#
# ../release/rsync-ftp/ mirror of samba.org:/home/ftp/pub/rsync
# ../release/rsync-html/ release-time snapshot of the html site
# ../release/rsync-html/ git checkout of rsync-web (the html site)
# ../release/work/ scratch space for tarball / diff staging
# ../release/release-state.json info shared between steps
#
@@ -35,11 +35,10 @@ HTML_DIR = os.path.join(RELEASE_DIR, 'rsync-html')
WORK_DIR = os.path.join(RELEASE_DIR, 'work')
STATE_FILE = os.path.join(RELEASE_DIR, 'release-state.json')
# The rsync-web/ subdirectory in the rsync source tree is the source-of-truth
# for the git-tracked html content. step-1-fetch snapshots it into HTML_DIR
# for the release flow, where it can be edited or augmented with server-side
# content before step-11-push-html sends it to samba.org.
HTML_SRC = os.path.realpath('rsync-web')
# Local rsync-web checkout (sibling of rsync-git) is the source-of-truth for
# the git-tracked html content. The maintainer pulls/commits/pushes there;
# step-1-fetch just snapshots it into HTML_DIR for the release flow.
HTML_SRC = os.path.realpath('../rsync-web')
FTP_REMOTE_PATH = '/home/ftp/pub/rsync'
HTML_REMOTE_PATH = '/home/httpd/html/rsync'
@@ -61,7 +60,7 @@ GEN_FILES = [
# ---------- Step registry ----------
STEPS = [
('step-1-fetch', 'mirror ../release/rsync-ftp from samba.org and snapshot ../release/rsync-html from rsync-web/'),
('step-1-fetch', 'mirror ../release/rsync-ftp from samba.org and snapshot ../release/rsync-html from ../rsync-web'),
('step-2-prepare', 'gather release info interactively and write release-state.json'),
('step-3-tweak', 'update version.h, rsync.h, NEWS.md, and packaging/*.spec'),
('step-4-build', 'run smart-make + make gen'),
@@ -104,8 +103,8 @@ def require_samba_host():
def require_top_of_checkout():
if not os.path.isfile('packaging/release.py'):
die("Run this script from the top of your rsync checkout.")
if not os.path.exists('.git'):
die("There is no .git in the current directory (run from the top of a git checkout or worktree).")
if not os.path.isdir('.git'):
die("There is no .git dir in the current directory.")
def replace_or_die(regex, repl, txt, die_msg):
@@ -137,29 +136,27 @@ def step_1_fetch(args):
section(f"Fetching ftp dir into {FTP_DIR}")
if not os.path.isdir(FTP_DIR):
os.makedirs(FTP_DIR)
# packaging/ftp.filt is the authoritative copy of the .filt filter file
# that controls which subtrees rsync excludes from the FTP mirror.
# Seed FTP_DIR/.filt from it so the bundled version is what step-1's
# rsync uses here, and so step-10-push-ftp propagates it back to the
# server. --exclude=/.filt below stops the server's copy from
# overwriting our bundled one on the way down.
# The .filt file lives in the ftp dir on the server; mirror down using the
# transmitted filter, falling back to no filter on the very first pull.
filt = os.path.join(FTP_DIR, '.filt')
bundled_filt = os.path.realpath('packaging/ftp.filt')
if not os.path.isfile(bundled_filt):
die(f"{bundled_filt} not found; cannot seed .filt for the FTP pull.")
shutil.copyfile(bundled_filt, filt)
cmd_chk(['rsync', '-aivOHP', f'-f:_{filt}', '--exclude=/.filt',
f'{host}:{FTP_REMOTE_PATH}/', f'{FTP_DIR}/'])
if os.path.exists(filt):
opts = ['-aivOHP', f'-f:_{filt}']
else:
opts = ['-aivOHP']
cmd_chk(['rsync', *opts, f'{host}:{FTP_REMOTE_PATH}/', f'{FTP_DIR}/'])
section(f"Snapshotting html dir from {HTML_SRC} into {HTML_DIR}")
if not os.path.isdir(HTML_SRC):
die(f"{HTML_SRC} not found. This should be the in-tree rsync-web/ "
f"subdirectory; something is wrong with your checkout.")
die(f"{HTML_SRC} not found. Clone the rsync-web repo there first.")
if not os.path.isdir(os.path.join(HTML_SRC, '.git')):
die(f"{HTML_SRC} exists but is not a git checkout.")
print(f"(Make sure {HTML_SRC} is up to date — this script does not 'git pull' for you.)")
os.makedirs(HTML_DIR, exist_ok=True)
cmd_chk(['rsync', '-aiv', f'{HTML_SRC}/', f'{HTML_DIR}/'])
cmd_chk(['rsync', '-aiv', '--exclude=/.git',
f'{HTML_SRC}/', f'{HTML_DIR}/'])
# Then mirror non-git html content from the server, skipping files that
# the html git already provides (driven by the 'filt' file in HTML_DIR).
# Then mirror non-git html content from the server (mirroring samba-rsync's
# behavior: skip files that the html git already provides).
filt = os.path.join(HTML_DIR, 'filt')
if os.path.exists(filt):
tmp_filt = os.path.join(HTML_DIR, 'tmp-filt')
@@ -189,10 +186,7 @@ def step_2_prepare(args):
tz_num = tz_now[0:1].replace('+', '') + str(float(tz_now[1:3]) + float(tz_now[3:]) / 60)
curversion = get_rsync_version()
# Skip the version we are releasing: its NEWS entry may already be dated,
# in which case it would otherwise be taken for the previous release.
lastversion, last_protocol_version, pdate = get_NEWS_version_info(
skip_version=re.sub(r'(pre\d+|dev)$', '', curversion))
lastversion, last_protocol_version, pdate = get_NEWS_version_info()
protocol_version, subprotocol_version = get_protocol_versions()
# Default next version: bump preN, or move dev -> pre1.
@@ -340,11 +334,8 @@ def step_3_tweak(args):
f"Unable to find SUBPROTOCOL_VERSION in {fn}")
elif fn == 'NEWS.md':
efv = re.escape(finalversion)
# Accept either "(UNRELEASED)" or an already-filled date, so a
# release entry that was dated by hand (or by an earlier run of
# this step) does not have to be reverted before releasing.
x_re = re.compile(
r'^# NEWS for rsync %s \((?:UNRELEASED|\d+ \w{3} \d{4})\)\s+## Changes in this version:\n' % efv
r'^# NEWS for rsync %s \(UNRELEASED\)\s+## Changes in this version:\n' % efv
+ r'(\n### PROTOCOL NUMBER:\s+- The protocol number was changed to \d+\.\n)?')
rel_day = 'UNRELEASED' if pre else today
repl = (f'# NEWS for rsync {finalversion} ({rel_day})\n\n'
@@ -352,8 +343,7 @@ def step_3_tweak(args):
if proto_changed:
repl += f'\n### PROTOCOL NUMBER:\n\n - The protocol number was changed to {protocol_version}.\n'
good_top = re.sub(r'\(.*?\)', '(UNRELEASED)', repl, 1)
msg = (f"The top of {fn} is not in the right format. It should be:\n" + good_top
+ "(an already-filled release date in place of UNRELEASED is also accepted)")
msg = (f"The top of {fn} is not in the right format. It should be:\n" + good_top)
txt = replace_or_die(x_re, repl, txt, msg)
x_re = re.compile(
r'^(\| )(\S{2} \S{3} \d{4})(\s+\|\s+%s\s+\| ).{11}(\s+\| )\S{2}(\s+\|+)$' % efv,
@@ -641,10 +631,9 @@ If you have a 'samba' remote configured (git.samba.org:/data/git/rsync.git):
git push samba {master_branch}
git push samba {v_ver}
Then upload the tarball + .asc to the GitHub release for {v_ver},
and announce on rsync-announce@, rsync@, and Discord.
NOTE! Also update the PPAs if needed
Then upload the tarball + .asc to the GitHub release for {v_ver}, run
packaging/send-news (when convenient), and announce on rsync-announce@,
rsync@, and Discord.
""")
+124
View File
@@ -0,0 +1,124 @@
#!/bin/bash
# This script makes it easy to update the ftp & html directories on the samba.org server.
# It expects the 2 *_DEST directories to contain updated files that need to be sent to
# the remote server. If these directories don't exist yet, they will be copied from the
# remote server (while also making the html dir a git checkout).
FTP_SRC="$HOME/samba-rsync-ftp"
HTML_SRC="$HOME/samba-rsync-html"
FTP_DEST="/home/ftp/pub/rsync"
HTML_DEST="/home/httpd/html/rsync"
HTML_GIT='git.samba.org:/data/git/rsync-web.git'
export RSYNC_PARTIAL_DIR=''
case "$RSYNC_SAMBA_HOST" in
*.samba.org) ;;
*)
echo "You must set RSYNC_SAMBA_HOST in your environment to the samba hostname to use." >&2
exit 1
;;
esac
MODE=''
REVERSE=''
while (( $# )); do
case "$1" in
-R|--reverse) REVERSE=yes ;;
f|ftp) MODE=ftp ;;
h|html) MODE=html ;;
-h|--help)
echo "Usage: [-R] [f|ftp|h|html]"
echo "-R --reverse Copy the files from the server to the local host."
echo " The default is to update the remote files."
echo "-h --help Output this help message."
echo " "
echo "The script will prompt if ftp or html is not specified on the command line."
echo "Only one category can be copied at a time. When pulling html files, a git"
echo "checkout will be either created or updated prior to the rsync copy."
exit
;;
*)
echo "Invalid option: $1" >&2
exit 1
;;
esac
shift
done
while [ ! "$MODE" ]; do
if [ "$REVERSE" = yes ]; then
DIRECTION=FROM
else
DIRECTION=TO
fi
echo -n "Copy which files $DIRECTION the server? ftp or html? "
read ans
case "$ans" in
f*) MODE=ftp ;;
h*) MODE=html ;;
'') exit 1 ;;
*) echo "You must answer f or h to copy the ftp or html data." ;;
esac
done
if [ "$MODE" = ftp ]; then
SRC_DIR="$FTP_SRC"
DEST_DIR="$FTP_DEST"
FILT=".filt"
else
SRC_DIR="$HTML_SRC"
DEST_DIR="$HTML_DEST"
FILT="filt"
fi
function do_rsync {
rsync --dry-run "${@}" | grep -v 'is uptodate$'
echo ''
echo -n "Run without --dry-run? [n] "
read ans
case "$ans" in
y*) rsync "${@}" | grep -v 'is uptodate$' ;;
esac
}
if [ -d "$SRC_DIR" ]; then
REVERSE_RSYNC=do_rsync
else
echo "The directory $SRC_DIR does not exist yet."
echo -n "Do you want to create it? [n] "
read ans
case "$ans" in
y*) ;;
*) exit 1 ;;
esac
REVERSE=yes
REVERSE_RSYNC=rsync
fi
if [ "$REVERSE" = yes ]; then
OPTS='-aivOHP'
TMP_FILT="$SRC_DIR/tmp-filt"
echo "Copying files from $RSYNC_SAMBA_HOST to $SRC_DIR ..."
if [ "$MODE" = html ]; then
if [ $REVERSE_RSYNC = rsync ]; then
git clone "$HTML_GIT" "$SRC_DIR" || exit 1
else
cd "$SRC_DIR" || exit 1
git pull || exit 1
fi
sed -n -e 's/[-P]/H/p' "$SRC_DIR/$FILT" >"$TMP_FILT"
OPTS="${OPTS}f._$TMP_FILT"
else
OPTS="${OPTS}f:_$FILT"
fi
$REVERSE_RSYNC "$OPTS" "$RSYNC_SAMBA_HOST:$DEST_DIR/" "$SRC_DIR/"
rm -f "$TMP_FILT"
exit
fi
cd "$SRC_DIR" || exit 1
echo "Copying files from $SRC_DIR to $RSYNC_SAMBA_HOST ..."
do_rsync -aivOHP --chown=:rsync --del -f._$FILT . "$RSYNC_SAMBA_HOST:$DEST_DIR/"
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash -e
# This script expects the ~/src/rsync directory to contain the rsync
# source that has been updated. It also expects the auto-build-save
# directory to have been created prior to the running of configure so
# that each branch has its own build directory underneath. This supports
# the maintainer workflow for the rsync-patches files maintenace.
FTP_SRC="$HOME/samba-rsync-ftp"
FTP_DEST="/home/ftp/pub/rsync"
MD_FILES="README.md INSTALL.md NEWS.md"
case "$RSYNC_SAMBA_HOST" in
*.samba.org) ;;
*)
echo "You must set RSYNC_SAMBA_HOST in your environment to the samba hostname to use." >&2
exit 1
;;
esac
if [ ! -d "$FTP_SRC" ]; then
packaging/samba-rsync ftp # Ask to initialize the local ftp dir
fi
cd ~/src/rsync
make man
./md-convert --dest="$FTP_SRC" $MD_FILES
rsync -aiic $MD_FILES auto-build-save/master/*.?.html "$FTP_SRC"
cd "$FTP_SRC"
rsync -aiic README.* INSTALL.* NEWS.* *.?.html "$RSYNC_SAMBA_HOST:$FTP_DEST/"
+2 -9
View File
@@ -416,7 +416,7 @@ static int include_config(char *include, int manage_globals)
char *match = manage_globals ? "*.conf" : "*.inc";
int ret;
if (vfs_stat(VFS_AT_FDCWD, include, &sb, VFS_ALLOW_SYMLINK) < 0) {
if (do_stat(include, &sb) < 0) {
rsyserr(FLOG, errno, "unable to stat config file \"%s\"", include);
return 0;
}
@@ -580,14 +580,7 @@ static FILE *OpenConfFile( char *FileName )
return( NULL );
}
/* rsyncd.conf path (--config or default): a planted symlink could redirect
* the daemon's config read. Refuse symlinks not owned by uid 0 or euid. */
{
int cfg_fd = vfs_open_owner_walk( FileName, O_RDONLY, 0 , 0);
OpenedFile = cfg_fd >= 0 ? fdopen( cfg_fd, "r" ) : NULL;
if( !OpenedFile && cfg_fd >= 0 )
close( cfg_fd );
}
OpenedFile = fopen( FileName, "r" );
if( NULL == OpenedFile )
{
rsyserr(FLOG, errno, "unable to open config file \"%s\"",
+52 -303
View File
@@ -25,7 +25,6 @@
extern int dry_run;
extern int do_xfers;
extern int am_root;
extern int am_daemon;
extern int am_server;
extern int inc_recurse;
extern int log_before_transfer;
@@ -86,7 +85,7 @@ static int updating_basis_or_equiv;
/* Open a basis/output path that may legitimately be an operator-trusted
* ABSOLUTE path -- e.g. an absolute --partial-dir ("a directory reserved for
* partial-dir work") or --backup-dir. vfs_resolve_open() deliberately
* partial-dir work") or --backup-dir. secure_relative_open() deliberately
* rejects an absolute relpath, so feeding it the whole absolute partialptr
* (with a NULL basedir) returns EINVAL: the basis fd is then -1, no basis is
* mapped, and receive_data() omits every matched block from the whole-file
@@ -98,66 +97,27 @@ static int updating_basis_or_equiv;
* (trusted) and leaf and confine just the leaf -- exactly how secure_relative_
* open already trusts an absolute basedir while O_NOFOLLOW-confining the leaf.
* Anything else is a straight pass-through that preserves the strict contract. */
static int secure_basis_open(const char *basedir, const char *relpath, int flags, mode_t mode, int is_operator)
static int secure_basis_open(const char *basedir, const char *relpath, int flags, mode_t mode)
{
extern int am_daemon, am_chrooted;
extern unsigned int module_dirlen;
/* "insecure links = yes": restore the 3.2.7 plain open so an operator/peer
* alt-dest basis follows symlinks like legacy rsync, the same opt-out the
* other daemon symlink sites honour. */
if (vfs_symlink_optout_allowed()) {
/* The confined resolver is only needed for the sanitizing daemon
* (am_daemon && !am_chrooted, i.e. use_secure_symlinks). Local /
* remote-shell mode has no module boundary, and "use chroot = yes" makes
* the kernel root the boundary, so there an alt-dest basis like
* --link-dest=../01 must resolve against the cwd as a bare open did before
* the hardening (confining it would reject the legitimate sibling "..",
* #915). */
if (!am_daemon || am_chrooted) {
if (basedir) {
char fullpath[MAXPATHLEN];
if (pathjoin(fullpath, sizeof fullpath, basedir, relpath) >= sizeof fullpath) {
errno = ENAMETOOLONG;
return -1;
}
return vfs_open(fullpath, flags, mode);
return do_open(fullpath, flags, mode);
}
return vfs_open(relpath, flags, mode);
}
/* A peer-supplied --partial-dir basis/staging path (is_operator, set by the
* recv_files caller) may be absolute (module_dir-prefixed on a non-chroot
* daemon) and traverse a symlink the vfs_resolve_open path can't confine:
* resolve it with the ownership walk, which follows a uid0/euid-owned symlink
* but refuses a foreign one AND (via abspath_outside_confinement) refuses a
* target the module's exclude hides -- closing the partial-dir exclude bypass. */
if (is_operator) {
char fullpath[MAXPATHLEN];
const char *p = relpath;
if (basedir) {
if (pathjoin(fullpath, sizeof fullpath, basedir, relpath) >= sizeof fullpath) {
errno = ENAMETOOLONG;
return -1;
}
p = fullpath;
}
return vfs_open_owner_walk(p, flags, mode, is_operator);
}
/* The confined resolver is needed for the sanitizing daemon
* (am_daemon && !am_chrooted) and for a /./ inner-module chroot
* (am_chrooted && module_dirlen) -- in the latter the kernel chroot confines
* only the outer path, so a peer-chosen alt-dest basis index (fnamecmp_type)
* could otherwise reach an outside-inner-module file through a symlinked
* parent. Local / remote-shell mode has no module boundary, and a plain
* "use chroot = yes" makes the kernel root the boundary, so there an alt-dest
* basis like --link-dest=../01 must resolve against the cwd as a bare open did
* before the hardening (confining it would reject the legitimate sibling
* "..", #915). The re-anchoring in vfs_resolve_open() covers the
* in-module ".." climb for the inner-module case too. */
if (!am_daemon || (am_chrooted && !module_dirlen)) {
if (basedir) {
char fullpath[MAXPATHLEN];
if (pathjoin(fullpath, sizeof fullpath, basedir, relpath) >= sizeof fullpath) {
errno = ENAMETOOLONG;
return -1;
}
return vfs_open(fullpath, flags, mode);
}
return vfs_open(relpath, flags, mode);
return do_open(relpath, flags, mode);
}
if (!basedir && relpath && *relpath == '/') {
@@ -177,107 +137,9 @@ static int secure_basis_open(const char *basedir, const char *relpath, int flags
dirbuf[dlen] = '\0';
dir = dirbuf;
}
return vfs_resolve_open(dir, leaf, flags, mode);
return secure_relative_open(dir, leaf, flags, mode);
}
return vfs_resolve_open(basedir, relpath, flags, mode);
}
/* Keep the ownership policy for every attempt to open a one-inplace partial
* output. In particular, Linux's protected_regular compatibility retries
* must not downgrade an operator-path open to the ordinary path resolver. */
static int secure_recv_open(const char *path, int flags, mode_t mode, int owner_walk)
{
return secure_basis_open(NULL, path, flags, mode,
owner_walk ? VFS_OPERATOR_PATH : 0);
}
/* Open a read-only regular file for an in-place update without leaving its
* mode relaxed while protocol data is received. Once a writable descriptor is
* open, later writes no longer depend on the pathname's permission bits, so
* restore the exact prior mode before returning to the transfer loop -- an
* abort (peer EOF, checksum failure, a signal) must not strand the file at
* 0600. Requires a regular file: O_NOFOLLOW refuses a symlink at the leaf but
* not a directory swapped in after the type probe. */
static int open_readonly_inplace(const char *fname, int one_inplace)
{
STRUCT_STAT cst;
mode_t prior_mode;
int cfd = -1, fd = -1;
int open_errno, restore_errno;
if (use_secure_symlinks || one_inplace) {
#ifdef O_NOFOLLOW
cfd = secure_recv_open(fname, O_RDONLY|O_NOFOLLOW, 0, one_inplace);
if (cfd < 0)
goto failed;
if (vfs_fstat(cfd, &cst) < 0 || !S_ISREG(cst.st_mode)) {
errno = EACCES; /* refused: not the read-only regular file we recover */
goto failed;
}
prior_mode = cst.st_mode & CHMOD_BITS;
if (prior_mode & S_IWUSR) {
/* Already owner-writable, so adding S_IWUSR cannot be what an
* EACCES is about (an ACL, or the parent dir). Don't touch the
* mode for nothing: each chmod risks losing a special bit. */
errno = EACCES;
goto failed;
}
if (fchmod(cfd, prior_mode | S_IWUSR) < 0)
goto failed;
fd = secure_recv_open(fname, O_WRONLY, 0600, one_inplace);
open_errno = errno;
if (fchmod(cfd, prior_mode) < 0) {
restore_errno = errno;
if (fd >= 0)
close(fd);
fd = -1;
open_errno = restore_errno;
}
close(cfd);
errno = open_errno;
return fd;
#else
/* Without O_NOFOLLOW the resolver's oldest fallback would follow a
* raced symlink, so fail closed rather than chmod through it. */
errno = EACCES;
return -1;
#endif
}
/* Local and chrooted transfers retain the existing pathname semantics.
* Note the S_ISREG test here is a type check on a stable path, NOT race
* protection: vfs_stat() follows a leaf symlink and each call below
* re-resolves the name. The fd-based branch above is the one that
* pins an inode; a chroot is what confines this one. */
if (vfs_stat(VFS_AT_FDCWD, fname, &cst, VFS_ALLOW_SYMLINK) < 0) {
errno = EACCES;
return -1;
}
if (!S_ISREG(cst.st_mode) || (cst.st_mode & CHMOD_BITS & S_IWUSR)) {
errno = EACCES;
return -1;
}
prior_mode = cst.st_mode & CHMOD_BITS;
if (vfs_chmod(VFS_AT_FDCWD, fname, prior_mode | S_IWUSR, 0) < 0)
return -1;
fd = vfs_open(fname, O_WRONLY, 0600);
open_errno = errno;
if (vfs_chmod(VFS_AT_FDCWD, fname, prior_mode, 0) < 0) {
restore_errno = errno;
if (fd >= 0)
close(fd);
fd = -1;
open_errno = restore_errno;
}
errno = open_errno;
return fd;
failed:
open_errno = errno;
if (cfd >= 0)
close(cfd);
errno = open_errno;
return -1;
return secure_relative_open(basedir, relpath, flags, mode);
}
/* get_tmpname() - create a tmp filename for a given filename
@@ -412,34 +274,22 @@ int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file)
* access to ensure that there is no race condition. They will be
* correctly updated after the right owner and group info is set.
* (Thanks to snabb@epipe.fi for pointing this out.) */
/* For any non-chrooted receiver (vfs_relpath_active()), create the
* temp file securely so a parent-symlink race can't redirect it. When
* the temp lives in the entry's own dir (the common case, no --temp-dir)
* use the cached held dir fd; otherwise fall back to vfs_secure_mkstemp. An
* operator-supplied --temp-dir (tmpdir) gets the ownership-walk resolver
* (it may legitimately point outside the tree); the deep-entry-dir fallback,
* when the held-dirfd cache declines, gets the strict transfer-path one. */
if (vfs_relpath_active()) {
int dfd = vfs_cached_dirfd(fnametmp, file);
if (dfd >= 0) {
char *slash = strrchr(fnametmp, '/');
fd = vfs_mkstemp_atfd(dfd, slash ? slash + 1 : fnametmp,
(file->mode|added_perms) & INITACCESSPERMS);
} else
fd = vfs_secure_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS,
tmpdir != NULL);
} else
fd = vfs_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
/* When use_secure_symlinks is on (non-chroot daemon with munge_symlinks),
* use secure_mkstemp to prevent symlink race attacks on parent directories. */
if (use_secure_symlinks)
fd = secure_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
else
fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
#if 0
/* In most cases parent directories will already exist because their
* information should have been previously transferred, but that may
* not be the case with -R */
if (fd == -1 && relative_paths && errno == ENOENT
&& vfs_make_path(fnametmp, MKP_SKIP_SLASH | MKP_DROP_NAME, 0) == 0) {
&& make_path(fnametmp, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) {
/* Get back to name with XXXXXX in it. */
get_tmpname(fnametmp, fname, False);
fd = vfs_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
fd = do_mkstemp(fnametmp, (file->mode|added_perms) & INITACCESSPERMS);
}
#endif
@@ -470,14 +320,14 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
if (preallocate_files && fd != -1 && total_size > 0 && (!inplace_sizing || total_size > size_r)) {
/* Try to preallocate enough space for file's eventual length. Can
* reduce fragmentation on filesystems like ext4, xfs, and NTFS. */
if ((preallocated_len = vfs_fallocate(fd, 0, total_size)) < 0)
rsyserr(FWARNING, errno, "vfs_fallocate %s", full_fname(fname));
if ((preallocated_len = do_fallocate(fd, 0, total_size)) < 0)
rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(fname));
} else
#endif
if (inplace_sizing) {
#ifdef HAVE_FTRUNCATE
/* The most compatible way to create a sparse file is to start with no length. */
if (sparse_files > 0 && whole_file && fd >= 0 && vfs_ftruncate(fd, 0) == 0)
if (sparse_files > 0 && whole_file && fd >= 0 && do_ftruncate(fd, 0) == 0)
preallocated_len = 0;
else
#endif
@@ -520,7 +370,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
}
}
offset = sum.flength;
if (fd != -1 && (j = vfs_lseek(fd, offset, SEEK_SET)) != offset) {
if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) {
rsyserr(FERROR_XFER, errno, "lseek of %s returned %s, not %s",
full_fname(fname), big_num(j), big_num(offset));
exit_cleanup(RERR_FILEIO);
@@ -644,7 +494,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
* preallocate_files: total_size could have been an overestimate.
* Cut off any extra preallocated zeros from dest file. */
if ((inplace_sizing || preallocated_len > offset) && fd != -1 && !IS_DEVICE(file->mode)) {
if (vfs_ftruncate(fd, offset) < 0)
if (do_ftruncate(fd, offset) < 0)
rsyserr(FERROR_XFER, errno, "ftruncate failed on %s", full_fname(fname));
}
#endif
@@ -692,15 +542,8 @@ static void handle_delayed_updates(char *local_name)
partialptr, fname);
}
/* We don't use robust_rename() here because the
* partial-dir must be on the same drive. Resolve the
* --partial-dir source through the exclude-aware ownership
* walk so a symlinked partial-dir can't move a file out of
* an excluded subtree. */
int rret;
/* partialptr is the operator-supplied --partial-dir source (owner
* walk); fname is the transfer destination (secure receiver resolve). */
rret = vfs_rename_at(partialptr, fname, VFS_OPERATOR_PATH, 0);
if (rret < 0) {
* partial-dir must be on the same drive. */
if (do_rename_at(partialptr, fname) < 0) {
rsyserr(FERROR_XFER, errno,
"rename failed for %s (from %s)",
full_fname(fname), partialptr);
@@ -805,8 +648,7 @@ int recv_files(int f_in, int f_out, char *local_name)
#ifdef SUPPORT_ACLS
const char *parent_dirname = "";
#endif
int ndx, recv_ok, one_inplace, write_to_device;
mode_t write_devices_saved_mode = 0;
int ndx, recv_ok, one_inplace;
if (DEBUG_GTE(RECV, 1))
rprintf(FINFO, "recv_files(%d) starting\n", cur_flist->used);
@@ -857,22 +699,10 @@ int recv_files(int f_in, int f_out, char *local_name)
if (ndx - cur_flist->ndx_start >= 0)
file = cur_flist->files[ndx - cur_flist->ndx_start];
else if (cur_flist->parent_ndx < 0
|| cur_flist->parent_ndx >= dir_flist->used)
else if (cur_flist->parent_ndx < 0)
exit_cleanup(RERR_PROTOCOL);
else
file = dir_flist->files[cur_flist->parent_ndx];
if (!F_IS_ACTIVE(file)) {
/* A peer that sends duplicate file-list entries gets
* one of them clear_file()'d by flist_sort_and_clean();
* referencing that slot here yields fname == NULL and
* a crash in the first deref (daemon filter check,
* set_file_attrs full_fname, ). */
rprintf(FERROR,
"rsync: refusing transfer of cleared file index %d\n",
ndx);
exit_cleanup(RERR_PROTOCOL);
}
fname = local_name ? local_name : f_name(file, fbuf);
if (DEBUG_GTE(RECV, 1))
@@ -1050,60 +880,15 @@ int recv_files(int f_in, int f_out, char *local_name)
fnamecmp = fname;
}
/* Open the delta basis. When it lives in the entry's own dir (no
* alternate basedir), read it via the held dir fd with O_NOFOLLOW: a
* regular-file basis is never legitimately a symlink, and refusing a
* planted one avoids both an escape and a basis-content info-leak.
* Otherwise use secure_basis_open, which also tolerates an operator-
* trusted absolute fnamecmp (e.g. an absolute --partial-dir basis). */
{
int bdfd;
if (fnamecmp_type == FNAMECMP_PARTIAL_DIR
&& fnamecmp && *fnamecmp != '/') {
/* The relative partial path contains peer-derived directory
* components. It is not an operator-trusted path as a whole. */
fd1 = vfs_resolve_open(NULL, fnamecmp, O_RDONLY, 0);
} else if (!basedir && (bdfd = vfs_cached_dirfd(fnamecmp, file)) >= 0) {
const char *slash;
assert(fnamecmp != NULL); /* set on every path above */
slash = strrchr(fnamecmp, '/');
fd1 = vfs_open_atfd(bdfd, slash ? slash + 1 : fnamecmp, O_RDONLY, 0);
} else {
/* An operator-supplied basis -- a --partial-dir, or an
* alt-dest basedir (--copy-dest/--compare-dest/--link-dest) --
* is a peer/operator path: resolve it with the exclude-aware
* ownership walk so a flipped foreign-owned parent symlink can't
* read (and feed back as delta) an out-of-tree / excluded file.
* The walk still allows the legitimate "../sibling" basis (#915)
* and the operator's own uid0/euid symlinks. A daemon keeps its
* stronger confinement branch in secure_basis_open(), so only
* route the alt-dest basedir read through the walk off-daemon. */
fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0,
((basedir && !am_daemon) || fnamecmp_type == FNAMECMP_PARTIAL_DIR) ? VFS_OPERATOR_PATH : 0);
}
}
if (fnamecmp_type == FNAMECMP_PARTIAL_DIR && fd1 == -1) {
/* The sender may claim a partial basis even when the generator's
* confined lookup rejected it. Drop that path as the delta basis
* and in-place output target. A daemon that negotiated in-place
* partial updates rejects the unsafe peer option; otherwise (a pre-30
* peer, where no in-place partial redirect is possible, or a pull
* client) fall back to a safe no-basis update. */
if (am_daemon && inplace_partial) {
rprintf(FERROR,
"rsync: refusing unconfined partial basis for %s\n", fname);
exit_cleanup(RERR_PROTOCOL);
}
fnamecmp = fname;
fnamecmp_type = FNAMECMP_FNAME;
}
/* open the file (secure_basis_open tolerates an operator-trusted
* absolute fnamecmp, e.g. an absolute --partial-dir basis) */
fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0);
if (fd1 == -1 && protocol_version < 29) {
if (fnamecmp != fname) {
fnamecmp = fname;
fnamecmp_type = FNAMECMP_FNAME;
fd1 = vfs_open_nofollow(fnamecmp, O_RDONLY);
fd1 = do_open_nofollow(fnamecmp, O_RDONLY);
}
if (fd1 == -1 && basis_dir[0]) {
@@ -1111,8 +896,7 @@ int recv_files(int f_in, int f_out, char *local_name)
basedir = basis_dir[0];
fnamecmp = fname;
fnamecmp_type = FNAMECMP_BASIS_DIR_LOW;
fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0,
!am_daemon ? VFS_OPERATOR_PATH : 0);
fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0);
}
}
@@ -1123,17 +907,14 @@ int recv_files(int f_in, int f_out, char *local_name)
fnamecmp = fnamecmpbuf;
}
/* A peer's basis selector cannot enable direct output through a path
* that the confined basis open did not validate. */
one_inplace = inplace_partial && fnamecmp_type == FNAMECMP_PARTIAL_DIR
&& fd1 != -1;
one_inplace = inplace_partial && fnamecmp_type == FNAMECMP_PARTIAL_DIR;
updating_basis_or_equiv = one_inplace
|| (inplace && (fnamecmp == fname || fnamecmp_type == FNAMECMP_BACKUP));
if (fd1 == -1) {
st.st_mode = 0;
st.st_size = 0;
} else if (vfs_fstat(fd1,&st) != 0) {
} else if (do_fstat(fd1,&st) != 0) {
rsyserr(FERROR_XFER, errno, "fstat %s failed",
full_fname(fnamecmp));
discard_receive_data(f_in, file);
@@ -1158,10 +939,11 @@ int recv_files(int f_in, int f_out, char *local_name)
continue;
}
write_to_device = write_devices && IS_DEVICE(st.st_mode);
if (write_to_device) {
if (write_devices && IS_DEVICE(st.st_mode)) {
if (fd1 != -1 && st.st_size == 0)
st.st_size = get_device_size(fd1, fname);
/* Mark the file entry as a device so that we don't try to truncate it later on. */
file->mode = S_IFBLK | (file->mode & ACCESSPERMS);
} else if (fd1 != -1 && !(S_ISREG(st.st_mode))) {
close(fd1);
fd1 = -1;
@@ -1185,34 +967,23 @@ int recv_files(int f_in, int f_out, char *local_name)
/* We now check to see if we are writing the file "inplace" */
if (inplace || one_inplace) {
fnametmp = one_inplace ? partialptr : fname;
/* For any non-chrooted receiver (vfs_relpath_active()),
/* When use_secure_symlinks is on (non-chroot daemon),
* use secure open to prevent symlink race attacks where an
* attacker could switch a directory to a symlink between
* path validation and file open. */
/* one_inplace stages into the operator/peer --partial-dir path:
* resolve it with the ownership walk (exclude-aware) so it can't be
* redirected through a symlink into an excluded subtree. */
if (vfs_relpath_active())
fd2 = secure_recv_open(fnametmp, O_WRONLY|O_CREAT, 0600,
one_inplace);
if (use_secure_symlinks)
fd2 = secure_basis_open(NULL, fnametmp, O_WRONLY|O_CREAT, 0600);
else
fd2 = vfs_open(fnametmp, O_WRONLY|O_CREAT, 0600);
fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600);
#ifdef linux
if (fd2 == -1 && errno == EACCES) {
/* Maybe the error was due to protected_regular setting? */
if (use_secure_symlinks || one_inplace)
fd2 = secure_recv_open(fnametmp, O_WRONLY, 0600,
one_inplace);
if (use_secure_symlinks)
fd2 = secure_relative_open(NULL, fname, O_WRONLY, 0600);
else
fd2 = vfs_open(fnametmp, O_WRONLY, 0600);
fd2 = do_open(fname, O_WRONLY, 0600);
}
#endif
if (fd2 == -1 && errno == EACCES) {
/* Temporarily add owner-write access only long enough to open
* a writable descriptor; the helper restores the old mode
* before any network data is consumed, including on failure. */
fd2 = open_readonly_inplace(fnametmp, one_inplace);
}
if (fd2 == -1) {
rsyserr(FERROR_XFER, errno, "open %s failed",
full_fname(fnametmp));
@@ -1240,27 +1011,9 @@ int recv_files(int f_in, int f_out, char *local_name)
else if (!am_server && INFO_GTE(NAME, 1) && INFO_EQ(PROGRESS, 1))
rprintf(FINFO, "%s\n", fname);
/* --write-devices writes a regular source file's content into an
* existing destination device. Flip file->mode to S_IFBLK just for
* receive_data()'s ftruncate gate (!IS_DEVICE), then restore it right
* after -- the file_struct was built as a regular file with no
* DEV_EXTRA_CNT, so leaving it S_IFBLK would make set_stat_xattr's
* F_RDEV_P() read past the allocation. Kept tight here (after the
* pre-transfer log and the fd2 bail-out) so no continue skips the
* restore and dest_mode() above ran on the real mode. */
if (write_to_device) {
write_devices_saved_mode = file->mode;
file->mode = S_IFBLK | (file->mode & ACCESSPERMS);
}
/* recv file data */
recv_ok = receive_data(f_in, fnamecmp, fd1, st.st_size, fname, fd2, file, inplace || one_inplace);
if (write_to_device) {
file->mode = write_devices_saved_mode;
write_devices_saved_mode = 0;
}
log_item(log_code, file, iflags, NULL);
if (want_progress_now)
instant_progress(fname);
@@ -1279,12 +1032,8 @@ int recv_files(int f_in, int f_out, char *local_name)
if (!finish_transfer(fname, fnametmp, fnamecmp, partialptr, file, recv_ok, 1))
recv_ok = -1;
else if (fnamecmp == partialptr) {
if (!one_inplace) {
/* Unlink the consumed --partial-dir basis through the
* exclude-aware ownership walk (a symlinked partial-dir
* must not delete a file in an excluded subtree). */
vfs_unlink(VFS_AT_FDCWD, partialptr, VFS_OPERATOR_PATH);
}
if (!one_inplace)
do_unlink_at(partialptr);
handle_partial_dir(partialptr, PDIR_DELETE);
}
} else if (keep_partial && partialptr && (!one_inplace || delay_updates)) {
@@ -1293,7 +1042,7 @@ int recv_files(int f_in, int f_out, char *local_name)
"Unable to create partial-dir for %s -- discarding %s.\n",
local_name ? local_name : f_name(file, NULL),
recv_ok ? "completed file" : "partial file");
vfs_unlink(VFS_AT_FDCWD, fnametmp, 0);
do_unlink_at(fnametmp);
recv_ok = -1;
} else if (!finish_transfer(partialptr, fnametmp, fnamecmp, NULL,
file, recv_ok, !partial_dir))
@@ -1304,7 +1053,7 @@ int recv_files(int f_in, int f_out, char *local_name)
} else
partialptr = NULL;
} else if (!one_inplace)
vfs_unlink(VFS_AT_FDCWD, fnametmp, 0);
do_unlink_at(fnametmp);
cleanup_disable();
+4 -40
View File
@@ -26,7 +26,7 @@ function rsync_ssl_run {
;;
esac
exec rsync --rsh="'$0' --HELPER" "${@}"
exec rsync --rsh="$0 --HELPER" "${@}"
}
function rsync_ssl_helper {
@@ -90,9 +90,6 @@ function rsync_ssl_helper {
# openssl:
caopt="-verify_return_error -verify 4"
# gnutls:
# gnutls-cli has no reliable "use the system trust store AND make
# verification fatal" switch across versions, so the gnutls path
# refuses below unless RSYNC_SSL_ALLOW_INSECURE_GNUTLS is set.
gnutls_opts=""
# stunnel:
# Since there is no way of using the default CA certificate collection,
@@ -138,59 +135,26 @@ function rsync_ssl_helper {
echo "Usage: rsync-ssl --HELPER HOSTNAME rsync --server --daemon ." 1>&2
exit 1
fi
validate_ssl_hostname "$hostname"
if [[ $RSYNC_SSL_TYPE == stunnel && -z ${RSYNC_SSL_CA_CERT+x} && "$RSYNC_SSL_ALLOW_INSECURE_STUNNEL" != 1 ]]; then
echo "stunnel requires RSYNC_SSL_CA_CERT for server certificate validation (or set RSYNC_SSL_ALLOW_INSECURE_STUNNEL=1 to opt out)" 1>&2
exit 1
fi
# Bind the server certificate to the requested host (openssl -verify_hostname,
# stunnel checkHost) so a cert that is CA-valid for a *different* name can't be
# used to MITM. Set RSYNC_SSL_SKIP_HOSTNAME_CHECK=1 to keep CA chain
# verification but skip the identity check (e.g. bare-IP or internal-CA setups).
# checkHost only applies while the chain is being verified.
openssl_host_opt="-verify_hostname $hostname"
stunnel_host_opt=""
[[ "$verify" == "verifyChain = yes" ]] && stunnel_host_opt="checkHost = $hostname"
if [[ "$RSYNC_SSL_SKIP_HOSTNAME_CHECK" == 1 ]]; then
openssl_host_opt=""
stunnel_host_opt=""
fi
if [[ $RSYNC_SSL_TYPE == gnutls && -z ${RSYNC_SSL_CA_CERT+x} && "$RSYNC_SSL_ALLOW_INSECURE_GNUTLS" != 1 ]]; then
echo "gnutls-cli requires RSYNC_SSL_CA_CERT for server certificate validation (or set RSYNC_SSL_ALLOW_INSECURE_GNUTLS=1 to opt out)" 1>&2
exit 1
fi
if [[ $RSYNC_SSL_TYPE == openssl ]]; then
exec "$RSYNC_SSL_OPENSSL" s_client $caopt $certopt $keyopt -quiet -verify_quiet -servername $hostname $openssl_host_opt -connect $hostname:$port
exec $RSYNC_SSL_OPENSSL s_client $caopt $certopt $keyopt -quiet -verify_quiet -servername $hostname -verify_hostname $hostname -connect $hostname:$port
elif [[ $RSYNC_SSL_TYPE == gnutls ]]; then
exec "$RSYNC_SSL_GNUTLS" --logfile=/dev/null $gnutls_cert_opt $gnutls_key_opt $gnutls_opts $hostname:$port
exec $RSYNC_SSL_GNUTLS --logfile=/dev/null $gnutls_cert_opt $gnutls_key_opt $gnutls_opts $hostname:$port
else
# devzero@web.de came up with this no-tmpfile calling syntax:
exec "$RSYNC_SSL_STUNNEL" -fd 10 11<&0 <<EOF 10<&0 0<&11 11<&-
exec $RSYNC_SSL_STUNNEL -fd 10 11<&0 <<EOF 10<&0 0<&11 11<&-
foreground = yes
debug = crit
connect = $hostname:$port
client = yes
TIMEOUTclose = 0
$verify
$stunnel_host_opt
$certopt
$cafile
EOF
fi
}
function validate_ssl_hostname {
local host="$1"
if [[ -z "$host" || "$host" == -* || "$host" =~ [^A-Za-z0-9._:-] ]]; then
echo "invalid rsync-ssl hostname: $host" 1>&2
exit 1
fi
}
function path_search {
IFS_SAVE="$IFS"
IFS=:
+1 -22
View File
@@ -66,28 +66,7 @@ The ssl helper scripts are affected by the following environment variables:
0. `RSYNC_SSL_CA_CERT`
If specified, the value is a filename that contains a certificate authority
certificate that is used to validate the connection. When set, the server
certificate is verified against this CA **and** its name is checked against
the host you are connecting to (the chain and the identity), for all of the
openssl, gnutls, and stunnel backends. Set it to an empty string to disable
certificate validation entirely (an encrypt-only connection).
0. `RSYNC_SSL_ALLOW_INSECURE_STUNNEL`
Set to `1` to allow the stunnel backend to run without a CA certificate (and
thus with no server-certificate validation at all). Without this, stunnel
mode refuses to start unless `RSYNC_SSL_CA_CERT` is set, since an unvalidated
TLS connection can be silently man-in-the-middled.
0. `RSYNC_SSL_SKIP_HOSTNAME_CHECK`
Set to `1` to verify the certificate **chain** but skip the **hostname**
(identity) check, for the openssl and stunnel backends. Use this only when
the server certificate legitimately cannot match the host you connect to --
for example connecting by bare IP address, or to a server whose internal-CA
certificate carries a different name. The CA chain is still validated, so
this is much narrower than disabling validation with an empty
`RSYNC_SSL_CA_CERT`.
certificate that is used to validate the connection.
0. `RSYNC_SSL_OPENSSL`
-11
View File
@@ -1,11 +0,0 @@
/.xvpics
/doxygen/
/tech_report/IMG_PARAMS.dir
/tech_report/IMG_PARAMS.pag
/upload
/netware
/pre-change
/index.html-*
/rsync-and-debian/rsync-and-debian.html
/rsync-and-debian/rsync-and-debian.ps
/badge.svg
-674
View File
@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. &lt;<a href="https://fsf.org/">https://fsf.org/</a>&gt;
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
&lt;one line to give the program's name and a brief idea of what it does.&gt;
Copyright (C) &lt;year&gt; &lt;name of author&gt;
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see &lt;<a href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>&gt;.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
&lt;program&gt; Copyright (C) &lt;year&gt; &lt;name of author&gt;
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
&lt;<a href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>&gt;.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
&lt;<a href="https://www.gnu.org/philosophy/why-not-lgpl.html">https://www.gnu.org/philosophy/why-not-lgpl.html</a>&gt;.
-340
View File
@@ -1,340 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
<hr>
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
<hr>
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
<hr>
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
<hr>
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
<hr>
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
&lt;one line to give the program's name and a brief idea of what it does.&gt;
Copyright (C) &lt;year&gt; &lt;name of author&gt;
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
&lt;signature of Ty Coon&gt;, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
-284
View File
@@ -1,284 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<TITLE>rsync FAQ</TITLE>
</HEAD>
<!--#include virtual="header.html" -->
<H2 align="center">Frequently Asked Questions</H2>
<table><tr valign=top><td><ul>
<li><a href="#1">the transfer fails to finish</a><br>
<li><a href="#2">rsync recopies the same files</a><br>
<li><a href="#3">is your shell clean</a><br>
<li><a href="#4">memory usage</a><br>
<li><a href="#5">out of memory</a><br>
<li><a href="#6">rsync through a firewall</a><br>
<li><a href="#7">rsync and cron</a><br>
</ul></td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td><ul>
<li><a href="#8">rsync: Command not found</a><br>
<li><a href="#9">spaces in filenames</a><br>
<li><a href="#10">ignore "vanished files" warning</a><br>
<li><a href="#11">read-only file system</a><br>
<li><a href="#12">multiplexing overflow 101:7104843</a><br>
<li><a href="#13">inflate (token) returned -5</a><br>
</ul></td></tr></table>
<hr>
<h3><a name=1>the transfer fails to finish</a></h3>
<p>If you get an error like one of these:
<pre>rsync: error writing 4 unbuffered bytes - exiting: Broken pipe
rsync error: error in rsync protocol data stream (code 12) at io.c(463)
</pre>
<p>or
<pre>rsync: connection unexpectedly closed (24 bytes read so far)
rsync error: error in rsync protocol data stream (code 12) at io.c(342)
</pre>
<p>please read the <a href="issues.html">issues and debugging page</a>
for details on how you can try to figure out what is going wrong.
<hr>
<h3><a name=2>rsync recopies the same files</a></h3>
<p>Some people occasionally report that rsync copies too many files when
they expect it to copy only a few. In most cases the explanation is
that you forgot to include the --times (-t) option in the original copy,
so rsync is forced to (efficiently) transfer every file that differs in
its modified time to discover what data (if any) has changed.
<p>Another common cause involves sending files to an Microsoft filesystem:
if the file's modified time is an odd value but the receiving filesystem
can only store even values, then rsync will re-transfer too many files.
You can avoid this by specifying the --modify-window=1 option.
<p>Yet another periodic case can happen when daylight-savings time
changes if your OS+filesystem saves file times in local time instead of
UTC. For a full explanation of this and some suggestions on how to
avoid them problem, see <a href="daylight-savings.html">this document</a>.
<p>Something else that can trip up rsync is a filesystem changeing the
filename behind the scenes. This can happen when a filesystem changes
an all-uppercase name into lowercase, or when it decomposes UTF-8 behind
your back.
<blockquote>
<p>An example of the latter can occur with HFS+ on Mac OS X: if you
copy a directory with a file that has a UTF-8 character sequence in it,
say a 2-byte umlaut-u (\0303\0274), the file will get that character
stored by the filesystem using 3 bytes (\0165\0314\0210), and rsync will
not know that these differing filenames are the same file (it will, in
fact, remove a prior copy of the file if --delete is enabled, and then
recreate it).
<p>You can avoid a charset problem by passing an appropriate --iconv
option to rsync that tells it what character-set the source files are,
and what character-set the destination files get stored in. For
instance, the above Mac OS X problem would be dealt with by using
--iconv=UTF-8,UTF8-MAC (UTF8-MAC is a pseudo-charset recognized by Mac
OS X iconv in which all characters are decomposed).
</blockquote>
<p>If you think that rsync is copying too many files, look at the
itemized output (-i) to see why rsync is doing the update (e.g. the 't'
flag indicates that the time differs, or all pluses indicates that rsync
thinks the file doesn't exist). You can also look at the stats produced
with -v and see if rsync is really sending all the data. See also the
--checksum (-c) option for one way to avoid the extra copying of files
that don't have synchronized modified times (but keep in mind that the
-c option eats lots of disk I/O, and can be rather slow).
<hr>
<h3><a name=3>is your shell clean</a></h3>
<p>The "is your shell clean" message and the "protocol mismatch" message
are usually caused by having some sort of program in your .cshrc, .profile,
.bashrc or equivalent file that writes a message every time you connect
using a remote-shell program (such as ssh or rsh). Data written in this
way corrupts the rsync data stream. rsync detects this at startup and
produces those error messages. However, if you are using rsync-daemon
syntax (host::path or rsync://) without using a remote-shell program (no
--rsh or -e option), there is not remote-shell program involved, and the
problem is probably caused by an error on the daemon side (so check the
daemon logs).
<p>A good way to test if your remote-shell connection is clean is to try
something like this (use ssh or rsh, as appropriate):
<blockquote><pre>ssh remotesystem /bin/true &gt; test.dat</pre></blockquote>
<p>That should create a file called test.dat with nothing in it. If
test.dat is not of zero length then your shell is not clean. Look at the
contents of test.dat to see what was sent. Look at all the startup files on
remotesystem to try and find the problem.
<hr>
<h3><a name=4>memory usage</a></h3>
<p>Rsync versions before 3.0.0 always build the entire list of files to be
transferred at the beginning and hold it in memory for the entire run. Rsync
needs about 100 bytes to store all the relevant information for one file,
so (for example) a run with 800,000 files would consume about 80M of
memory. -H and --delete increase the memory usage further.
<p>Version 3.0.0 slightly reduced the memory used per file by not storing fields
not needed for a particular file. It also introduced an incremental recursion
mode that builds the file list in chunks and holds each chunk in memory only as
long as it is needed. This mode dramatically reduces memory usage, but it
only works provided that both sides are 3.0.0 or newer and certain options that
rsync currently can't handle in this mode are not being used.
<hr>
<h3><a name=5>out of memory</a></h3>
<p>The usual reason for "out of memory" when running rsync is that you are
transferring a _very_ large number of files. The size of the files doesn't
matter, only the total number of files. If memory is a problem, first try to
use the incremental recursion mode: upgrade both sides to rsync 3.0.0 or
newer and avoid options that disable incremental recursion (e.g., use
<tt>--delete-delay</tt> instead of <tt>--delete-after</tt>). If this is not
possible, you can break the rsync run into smaller chunks operating on
individual subdirectories using <tt>--relative</tt> and/or exclude rules.
<hr>
<h3><a name=6>rsync through a firewall</a></h3>
<p>If you have a setup where there is no way to directly connect two
systems for an rsync transfer, there are several ways to get a firewall
system to act as an intermediary in the transfer. You'll find full details
on the <a href="firewall.html">firewall methods</a> page.
<hr>
<h3><a name=7>rsync and cron</a></h3>
<p>On some systems (notably SunOS4) cron supplies what looks like a socket
to rsync, so rsync thinks that stdin is a socket. This means that if you
start rsync with the --daemon switch from a cron job you end up rsync
thinking it has been started from inetd. The fix is simple&mdash;just
redirect stdin from /dev/null in your cron job.
<hr>
<h3><a name=8>rsync: Command not found</a></h3>
<p>This error is produced when the remote shell is unable to locate the rsync
binary in your path. There are 3 possible solutions:
<ol>
<li>install rsync in a "standard" location that is in your remote path.
<li>modify your .cshrc, .bashrc etc on the remote system to include the path
that rsync is in
<li>use the --rsync-path option to explicitly specify the path on the
remote system where rsync is installed
</ol>
<p>You may echo find the command:
<blockquote><pre>ssh host 'echo $PATH'</pre></blockquote>
<p>for determining what your remote path is.
<hr>
<h3><a name=9>spaces in filenames</a></h3>
<p>Can rsync copy files with spaces in them?
<p>Short answer: Yes, rsync can handle filenames with spaces.
<p>Long answer:
<p>Rsync handles spaces just like any other unix command line application.
Within the code spaces are treated just like any other character so a
filename with a space is no different from a filename with any other
character in it.
<p>The problem of spaces is in the argv processing done to interpret the
command line. As with any other unix application you have to escape spaces
in some way on the command line or they will be used to separate arguments.
<p>It is slightly trickier in rsync (and other remote-copy programs like
scp) because rsync sends a command line to the remote system to launch the
peer copy of rsync (this assumes that we're not talking about daemon mode,
which is not affected by this problem because no remote shell is involved
in the reception of the filenames). The command line is interpreted by the
remote shell and thus the spaces need to arrive on the remote system
escaped so that the shell doesn't split such filenames into multiple
arguments.
<p>For example:
<blockquote><pre>rsync -av host:'a long filename' /tmp/</pre></blockquote>
<p>This is usually a request for rsync to copy 3 files from the remote
system, "a", "long", and "filename" (the only exception to this is for a
system running a shell that does not word-split arguments in its commands,
and that is exceedingly rare). If you wanted to request a single file with
spaces, you need to get some kind of space-quoting characters to the remote
shell that is running the remote rsync command. The following commands
should all work:
<blockquote><pre>rsync -av host:'"a long filename"' /tmp/
rsync -av host:'a\ long\ filename' /tmp/
rsync -av host:a\\\ long\\\ filename /tmp/</pre></blockquote>
<p>You might also like to use a '?' in place of a space as long as there
are no other matching filenames than the one with spaces (since '?' matches
any character):
<blockquote><pre>rsync -av host:a?long?filename /tmp/</pre></blockquote>
<p>As long as you know that the remote filenames on the command line
are interpreted by the remote shell then it all works fine.
<hr>
<h3><a name=10>ignore "vanished files" warning</a></h3>
<p>Some folks would like to ignore the "vanished files" warning, which
manifests as an exit-code 24. The easiest way to do this is to create
a shell script wrapper. For instance, name this something like
"rsync-no24":
<blockquote><pre>#!/bin/sh
rsync "$@"
e=$?
if test $e = 24; then
exit 0
fi
exit $e</pre></blockquote>
<hr>
<h3><a name=11>read-only file system</a></h3>
<p>If you get "Read-only file system" as an error when sending to a rsync
daemon then you probably forgot to set "read only = no" for that module.
<hr>
<h3><a name=12>multiplexing overflow 101:7104843</a></h3>
<p>This mysterious error, or the similar "invalid message 101:7104843", can
happen if one of the rsync processes is killed for some reason and a message
beginning with the four characters "Kill" gets inserted into the protocol
stream as a result. To solve the problem, you'll need to figure out why rsync
is being killed.
<hr>
<h3><a name=13>inflate (token) returned -5</a></h3>
This error means that rsync failed to handle an expected error from the
compression code for a file that happened to be transferred with a block size
of 32816 bytes. You can avoid this issue for the affected file by transferring
it with a manually-set block size (e.g. --block-size=33000), or by upgrading
the receiving side to rsync 3.0.7.
<hr>
<!--#include virtual="footer.html" -->
-16
View File
@@ -1,16 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<TITLE>rsync's license</TITLE>
</HEAD>
<!--#include virtual="header.html" -->
Beginning with 3.0.0, rsync is available under the <b>GNU General Public
License version 3</b>. <i>(Older releases were available under the
<a href="GPL2.html">GPL version 2</a>.)</i>
<pre><small>
<!--#include virtual="COPYING.html" -->
</small></pre>
<!--#include virtual="footer.html" -->
Loaded 100 of 659 files, more files were not shown because too many files have changed in this diff. Show more