mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-05-24 23:05:52 -04:00
Compare commits
47 Commits
pr-remove-
...
v3.4.1-sec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc84711567 | ||
|
|
765d7446cc | ||
|
|
f872627579 | ||
|
|
25100258e9 | ||
|
|
ec46dac93a | ||
|
|
861062369f | ||
|
|
c633941941 | ||
|
|
fc9a5d6452 | ||
|
|
3d5a5a6568 | ||
|
|
ddd7b59a4f | ||
|
|
ac735929dc | ||
|
|
5564c88150 | ||
|
|
275258bd76 | ||
|
|
9ad27c9d7b | ||
|
|
0cac014f89 | ||
|
|
90495eecd0 | ||
|
|
72a6634479 | ||
|
|
a2c1b98c2a | ||
|
|
d870b43a32 | ||
|
|
e57fd03284 | ||
|
|
f629772cc6 | ||
|
|
6226386332 | ||
|
|
b32ba3ddb3 | ||
|
|
45ce54b546 | ||
|
|
3e5e159459 | ||
|
|
892b48a60b | ||
|
|
c009fcc8e6 | ||
|
|
dff93c92d1 | ||
|
|
c35df318ad | ||
|
|
09187a49a7 | ||
|
|
6994fdf50e | ||
|
|
350469f7cf | ||
|
|
79ffc5e3c5 | ||
|
|
4585f8a6f2 | ||
|
|
487a548f70 | ||
|
|
82fe213f7f | ||
|
|
0df583089d | ||
|
|
21e0496559 | ||
|
|
ca987c47fb | ||
|
|
c966f3864d | ||
|
|
bbecd5bc1a | ||
|
|
4ce604114a | ||
|
|
86c59ba944 | ||
|
|
3305a7a063 | ||
|
|
494879b819 | ||
|
|
8d6da040e5 | ||
|
|
68e9add76a |
76
.github/workflows/almalinux-8-build.yml
vendored
Normal file
76
.github/workflows/almalinux-8-build.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: Test rsync on AlmaLinux 8
|
||||
|
||||
# Older-LTS coverage on the Fedora/RHEL family to help with backporting
|
||||
# security fixes. AlmaLinux 8 is the RHEL 8 rebuild and is the oldest
|
||||
# active LTS in this family (RHEL 8 full support runs to 2029).
|
||||
# GitHub Actions has no native runner for this family, so the job runs
|
||||
# inside an almalinux:8 container hosted on ubuntu-latest.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/almalinux-8-build.yml'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/almalinux-8-build.yml'
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: almalinux:8
|
||||
name: Test rsync on AlmaLinux 8
|
||||
steps:
|
||||
- name: install git
|
||||
# actions/checkout needs git in the container before the checkout step.
|
||||
run: dnf -y install git
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: prep
|
||||
# PowerTools is needed for libzstd-devel etc; xxhash and lz4 dev
|
||||
# headers live in EPEL on RHEL 8. The default python3 on RHEL 8
|
||||
# is 3.6, which is too old for runtests.py (uses capture_output=
|
||||
# / text= introduced in 3.7), so install python39 and point
|
||||
# /usr/bin/python3 at it.
|
||||
run: |
|
||||
dnf -y install epel-release
|
||||
dnf config-manager --set-enabled powertools
|
||||
dnf -y install gcc gcc-c++ make autoconf automake m4 \
|
||||
python39 python39-pip diffutils \
|
||||
openssl openssl-devel \
|
||||
attr libattr-devel acl libacl-devel \
|
||||
zstd libzstd-devel \
|
||||
lz4 lz4-devel \
|
||||
xxhash xxhash-devel
|
||||
alternatives --set python3 /usr/bin/python3.9
|
||||
pip3 install commonmark
|
||||
- name: configure
|
||||
run: ./configure --with-rrsync
|
||||
- name: make
|
||||
run: make
|
||||
- name: info
|
||||
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.
|
||||
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:
|
||||
name: almalinux-8-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
3
.github/workflows/cygwin-build.yml
vendored
3
.github/workflows/cygwin-build.yml
vendored
@@ -7,7 +7,6 @@ on:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/cygwin-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/cygwin-build.yml'
|
||||
@@ -39,7 +38,7 @@ jobs:
|
||||
- name: info
|
||||
run: bash -c '/usr/local/bin/rsync --version'
|
||||
- name: check
|
||||
run: bash -c 'RSYNC_EXPECT_SKIPPED=acls-default,acls,chown,devices,dir-sgid,protected-regular make check'
|
||||
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,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
|
||||
|
||||
2
.github/workflows/freebsd-build.yml
vendored
2
.github/workflows/freebsd-build.yml
vendored
@@ -7,7 +7,6 @@ on:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/freebsd-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/freebsd-build.yml'
|
||||
@@ -34,6 +33,7 @@ jobs:
|
||||
./configure --with-rrsync -disable-zstd --disable-md2man --disable-xxhash --disable-lz4
|
||||
make
|
||||
./rsync --version
|
||||
make check
|
||||
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
|
||||
- name: save artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
16
.github/workflows/macos-build.yml
vendored
16
.github/workflows/macos-build.yml
vendored
@@ -7,7 +7,6 @@ on:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/macos-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/macos-build.yml'
|
||||
@@ -25,10 +24,15 @@ jobs:
|
||||
- name: prep
|
||||
run: |
|
||||
brew install automake openssl xxhash zstd lz4
|
||||
sudo pip3 install commonmark
|
||||
echo "/usr/local/bin" >>$GITHUB_PATH
|
||||
pip3 install --user --break-system-packages commonmark
|
||||
echo "$(brew --prefix)/bin" >>$GITHUB_PATH
|
||||
- name: configure
|
||||
run: CPPFLAGS=-I/usr/local/opt/openssl/include/ LDFLAGS=-L/usr/local/opt/openssl/lib/ ./configure --with-rrsync
|
||||
run: |
|
||||
BREW_PREFIX=$(brew --prefix)
|
||||
OPENSSL_PREFIX=$(brew --prefix openssl)
|
||||
CPPFLAGS="-I${BREW_PREFIX}/include -I${OPENSSL_PREFIX}/include" \
|
||||
LDFLAGS="-L${BREW_PREFIX}/lib -L${OPENSSL_PREFIX}/lib" \
|
||||
./configure --with-rrsync
|
||||
- name: make
|
||||
run: make
|
||||
- name: install
|
||||
@@ -36,11 +40,11 @@ jobs:
|
||||
- name: info
|
||||
run: rsync --version
|
||||
- name: check
|
||||
run: sudo RSYNC_EXPECT_SKIPPED=acls-default,chmod-temp-dir,chown-fake,devices-fake,dir-sgid,protected-regular,xattrs-hlink,xattrs make check
|
||||
run: sudo RSYNC_EXPECT_SKIPPED=acls-default,chmod-temp-dir,chown-fake,daemon-chroot-acl,devices-fake,dir-sgid,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@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-bin
|
||||
path: |
|
||||
|
||||
51
.github/workflows/netbsd-build.yml
vendored
Normal file
51
.github/workflows/netbsd-build.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Test rsync on NetBSD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/netbsd-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/netbsd-build.yml'
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test rsync on NetBSD
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Test in NetBSD VM
|
||||
id: test
|
||||
uses: vmactions/netbsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
PATH=/usr/sbin:$PATH pkg_add autoconf automake python312
|
||||
ln -sf /usr/pkg/bin/python3.12 /usr/pkg/bin/python3
|
||||
run: |
|
||||
uname -a
|
||||
./configure --with-rrsync --disable-zstd --disable-md2man --disable-xxhash --disable-lz4
|
||||
make
|
||||
./rsync --version
|
||||
make check
|
||||
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
|
||||
- name: save artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: netbsd-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
52
.github/workflows/openbsd-build.yml
vendored
Normal file
52
.github/workflows/openbsd-build.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Test rsync on OpenBSD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/openbsd-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/openbsd-build.yml'
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test rsync on OpenBSD
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Test in OpenBSD VM
|
||||
id: test
|
||||
uses: vmactions/openbsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg_add -I bash autoconf%2.71 automake%1.16
|
||||
run: |
|
||||
uname -a
|
||||
export AUTOCONF_VERSION=2.71
|
||||
export AUTOMAKE_VERSION=1.16
|
||||
./configure --with-rrsync --disable-zstd --disable-md2man --disable-xxhash --disable-lz4
|
||||
make
|
||||
./rsync --version
|
||||
make check
|
||||
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
|
||||
- name: save artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: openbsd-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
2
.github/workflows/solaris-build.yml
vendored
2
.github/workflows/solaris-build.yml
vendored
@@ -7,7 +7,6 @@ on:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/solaris-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/solaris-build.yml'
|
||||
@@ -34,6 +33,7 @@ jobs:
|
||||
./configure --with-rrsync -disable-zstd --disable-md2man --disable-xxhash --disable-lz4
|
||||
make
|
||||
./rsync --version
|
||||
make check
|
||||
./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
|
||||
- name: save artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
59
.github/workflows/ubuntu-22.04-build.yml
vendored
Normal file
59
.github/workflows/ubuntu-22.04-build.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: Test rsync on Ubuntu 22.04
|
||||
|
||||
# Older-LTS coverage to help with backporting security fixes. ubuntu-22.04
|
||||
# is currently the oldest GitHub Actions runner image (20.04 was retired
|
||||
# in April 2025).
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/ubuntu-22.04-build.yml'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/ubuntu-22.04-build.yml'
|
||||
schedule:
|
||||
- cron: '42 8 * * *'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Test rsync on Ubuntu 22.04
|
||||
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
|
||||
run: make
|
||||
- name: install
|
||||
run: sudo make install
|
||||
- name: info
|
||||
run: rsync --version
|
||||
- name: check
|
||||
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check
|
||||
- name: check30
|
||||
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check30
|
||||
- name: check29
|
||||
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:
|
||||
name: ubuntu-22.04-bin
|
||||
path: |
|
||||
rsync
|
||||
rsync-ssl
|
||||
rsync.1
|
||||
rsync-ssl.1
|
||||
rsyncd.conf.5
|
||||
rrsync.1
|
||||
rrsync
|
||||
3
.github/workflows/ubuntu-build.yml
vendored
3
.github/workflows/ubuntu-build.yml
vendored
@@ -7,7 +7,6 @@ on:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/ubuntu-build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/workflows/*.yml'
|
||||
- '!.github/workflows/ubuntu-build.yml'
|
||||
@@ -16,7 +15,7 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
name: Test rsync on Ubuntu
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
21
Makefile.in
21
Makefile.in
@@ -57,12 +57,13 @@ TLS_OBJ = tls.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/perms
|
||||
|
||||
# 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) wildtest$(EXEEXT)
|
||||
testrun$(EXEEXT) trimslash$(EXEEXT) t_unsafe$(EXEEXT) t_chmod_secure$(EXEEXT) \
|
||||
t_secure_relpath$(EXEEXT) wildtest$(EXEEXT) simdtest$(EXEEXT)
|
||||
|
||||
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 trimslash.o wildtest.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:
|
||||
@@ -178,6 +179,14 @@ T_UNSAFE_OBJ = t_unsafe.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/sn
|
||||
t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS)
|
||||
|
||||
T_CHMOD_SECURE_OBJ = t_chmod_secure.o syscall.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_SECURE_RELPATH_OBJ = t_secure_relpath.o syscall.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)
|
||||
|
||||
.PHONY: conf
|
||||
conf: configure.sh config.h.in
|
||||
|
||||
@@ -326,6 +335,14 @@ 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@
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@ $(LIBS)
|
||||
|
||||
simdtest$(EXEEXT): simd-checksum-x86_64.cpp $(HEADERS)
|
||||
@if test x"@ROLL_SIMD@" != x; then \
|
||||
$(CXX) -I. $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -DTEST_SIMD_CHECKSUM1 \
|
||||
-o $@ $(srcdir)/simd-checksum-x86_64.cpp @ROLL_ASM@ $(LIBS); \
|
||||
else \
|
||||
touch $@; \
|
||||
fi
|
||||
|
||||
testsuite/chown-fake.test:
|
||||
ln -s chown.test $(srcdir)/testsuite/chown-fake.test
|
||||
|
||||
|
||||
22
NEWS.md
22
NEWS.md
@@ -1,3 +1,24 @@
|
||||
# NEWS for rsync 3.4.1 (16 Jan 2025)
|
||||
|
||||
Release 3.4.1 is a fix for regressions introduced in 3.4.0
|
||||
|
||||
## Changes in this version:
|
||||
|
||||
### BUG FIXES:
|
||||
|
||||
- fixed handling of -H flag with conflict in internal flag values
|
||||
|
||||
- fixed a user after free in logging of failed rename
|
||||
|
||||
- fixed build on systems without openat()
|
||||
|
||||
- removed dependency on alloca() in bundled popt
|
||||
|
||||
### DEVELOPER RELATED:
|
||||
|
||||
- fix to permissions handling in the developer release script
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
# NEWS for rsync 3.4.0 (15 Jan 2025)
|
||||
|
||||
Release 3.4.0 is a security release that fixes a number of important vulnerabilities.
|
||||
@@ -4816,6 +4837,7 @@ to develop and test fixes.
|
||||
|
||||
| RELEASE DATE | VER. | DATE OF COMMIT\* | PROTOCOL |
|
||||
|--------------|--------|------------------|-------------|
|
||||
| 16 Jan 2025 | 3.4.1 | | 32 |
|
||||
| 15 Jan 2025 | 3.4.0 | | 32 |
|
||||
| 06 Apr 2024 | 3.3.0 | | 31 |
|
||||
| 20 Oct 2022 | 3.2.7 | | 31 |
|
||||
|
||||
2
access.c
2
access.c
@@ -99,7 +99,7 @@ static void make_mask(char *mask, int plen, int addrlen)
|
||||
return;
|
||||
}
|
||||
|
||||
static int match_address(const char *addr, const char *tok)
|
||||
static int match_address(const char *addr, char *tok)
|
||||
{
|
||||
char *p;
|
||||
struct addrinfo hints, *resa, *rest;
|
||||
|
||||
4
acls.c
4
acls.c
@@ -697,7 +697,7 @@ static uint32 recv_acl_access(int f, uchar *name_follows_ptr)
|
||||
static uchar recv_ida_entries(int f, ida_entries *ent)
|
||||
{
|
||||
uchar computed_mask_bits = 0;
|
||||
int i, count = read_varint(f);
|
||||
int i, count = read_varint_bounded(f, 0, MAX_WIRE_ACL_COUNT, "ACL count");
|
||||
|
||||
ent->idas = count ? new_array(id_access, count) : NULL;
|
||||
ent->count = count;
|
||||
@@ -713,7 +713,7 @@ static uchar recv_ida_entries(int f, ida_entries *ent)
|
||||
else
|
||||
id = recv_group_name(f, id, NULL);
|
||||
} else if (access & NAME_IS_USER) {
|
||||
if (inc_recurse && am_root && !numeric_ids)
|
||||
if (inc_recurse && !numeric_ids)
|
||||
id = match_uid(id);
|
||||
} else {
|
||||
if (inc_recurse && (!am_root || !numeric_ids))
|
||||
|
||||
14
backup.c
14
backup.c
@@ -39,7 +39,7 @@ static int validate_backup_dir(void)
|
||||
{
|
||||
STRUCT_STAT st;
|
||||
|
||||
if (do_lstat(backup_dir_buf, &st) < 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);
|
||||
@@ -98,7 +98,7 @@ static BOOL copy_valid_path(const char *fname)
|
||||
for ( ; b; name = b + 1, b = strchr(name, '/')) {
|
||||
*b = '\0';
|
||||
|
||||
while (do_mkdir(backup_dir_buf, ACCESSPERMS) < 0) {
|
||||
while (do_mkdir_at(backup_dir_buf, ACCESSPERMS) < 0) {
|
||||
if (errno == EEXIST) {
|
||||
val = validate_backup_dir();
|
||||
if (val > 0)
|
||||
@@ -197,7 +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
|
||||
if (do_link(from, to) == 0) {
|
||||
if (do_link_at(from, to) == 0) {
|
||||
if (DEBUG_GTE(BACKUP, 1))
|
||||
rprintf(FINFO, "make_backup: HLINK %s successful.\n", from);
|
||||
return 2;
|
||||
@@ -207,7 +207,7 @@ static inline int link_or_rename(const char *from, const char *to,
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (do_rename(from, to) == 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! */
|
||||
@@ -246,7 +246,7 @@ int make_backup(const char *fname, BOOL prefer_rename)
|
||||
goto success;
|
||||
if (errno == EEXIST || errno == EISDIR) {
|
||||
STRUCT_STAT bakst;
|
||||
if (do_lstat(buf, &bakst) == 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;
|
||||
@@ -277,7 +277,7 @@ int make_backup(const char *fname, BOOL prefer_rename)
|
||||
/* 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 (do_mknod(buf, file->mode, sx.st.st_rdev) < 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);
|
||||
@@ -294,7 +294,7 @@ int make_backup(const char *fname, BOOL prefer_rename)
|
||||
}
|
||||
ret = 2;
|
||||
} else {
|
||||
if (do_symlink(sl, buf) < 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);
|
||||
|
||||
@@ -176,7 +176,7 @@ void parse_checksum_choice(int final_call)
|
||||
if (valid_checksums.negotiated_nni)
|
||||
xfer_sum_nni = file_sum_nni = valid_checksums.negotiated_nni;
|
||||
else {
|
||||
char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL;
|
||||
const char *cp = checksum_choice ? strchr(checksum_choice, ',') : NULL;
|
||||
if (cp) {
|
||||
xfer_sum_nni = parse_csum_name(checksum_choice, cp - checksum_choice);
|
||||
file_sum_nni = parse_csum_name(cp+1, -1);
|
||||
@@ -366,9 +366,8 @@ void get_checksum2(char *buf, int32 len, char *sum)
|
||||
|
||||
mdfour_begin(&m);
|
||||
|
||||
if (len > len1) {
|
||||
if (buf1)
|
||||
free(buf1);
|
||||
if (len > len1 || !buf1) {
|
||||
free(buf1);
|
||||
buf1 = new_array(char, len+4);
|
||||
len1 = len;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ NORETURN void _exit_cleanup(int code, const char *file, int line)
|
||||
switch_step++;
|
||||
|
||||
if (cleanup_fname)
|
||||
do_unlink(cleanup_fname);
|
||||
do_unlink_at(cleanup_fname);
|
||||
if (exit_code)
|
||||
kill_all(SIGUSR1);
|
||||
if (cleanup_pid && cleanup_pid == getpid()) {
|
||||
|
||||
@@ -167,7 +167,7 @@ int read_proxy_protocol_header(int fd)
|
||||
char sig[PROXY_V2_SIG_SIZE];
|
||||
char ver_cmd;
|
||||
char fam;
|
||||
char len[2];
|
||||
unsigned char len[2];
|
||||
union {
|
||||
struct {
|
||||
char src_addr[4];
|
||||
|
||||
@@ -30,6 +30,7 @@ extern int list_only;
|
||||
extern int am_sender;
|
||||
extern int am_server;
|
||||
extern int am_daemon;
|
||||
extern int am_chrooted;
|
||||
extern int am_root;
|
||||
extern int msgs2stderr;
|
||||
extern int rsync_port;
|
||||
@@ -38,6 +39,7 @@ extern int ignore_errors;
|
||||
extern int preserve_xattrs;
|
||||
extern int kluge_around_eof;
|
||||
extern int munge_symlinks;
|
||||
extern int use_secure_symlinks;
|
||||
extern int open_noatime;
|
||||
extern int sanitize_paths;
|
||||
extern int numeric_ids;
|
||||
@@ -976,11 +978,14 @@ 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();
|
||||
if (chroot(module_chdir)) {
|
||||
rsyserr(FLOG, errno, "chroot(\"%s\") failed", module_chdir);
|
||||
io_printf(f_out, "@ERROR: chroot failed\n");
|
||||
return -1;
|
||||
}
|
||||
am_chrooted = 1;
|
||||
module_chdir = module_dir;
|
||||
}
|
||||
|
||||
@@ -1003,6 +1008,15 @@ 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.
|
||||
* 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;
|
||||
if (setgid(gid_array[0])) {
|
||||
@@ -1298,13 +1312,49 @@ int start_daemon(int f_in, int f_out)
|
||||
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
|
||||
* ensures hostname-based ACLs work even when DNS is unavailable
|
||||
* after chroot.
|
||||
*
|
||||
* "reverse lookup" can be set globally OR per-module, so we also
|
||||
* scan each module: a deployment with "reverse lookup = no" in the
|
||||
* global section but "reverse lookup = yes" in a specific module
|
||||
* still triggers a post-chroot lookup at access-check time
|
||||
* (rsync_module() in this file), which would also fail in the
|
||||
* chroot and turn hostname-based deny rules into silent bypasses. */
|
||||
{
|
||||
int need_reverse = lp_reverse_lookup(-1);
|
||||
int j, num_modules = lp_num_modules();
|
||||
for (j = 0; !need_reverse && j < num_modules; j++) {
|
||||
if (lp_reverse_lookup(j))
|
||||
need_reverse = 1;
|
||||
}
|
||||
if (need_reverse)
|
||||
(void)client_name(client_addr(f_in));
|
||||
}
|
||||
|
||||
p = lp_daemon_chroot();
|
||||
if (*p) {
|
||||
log_init(0); /* Make use we've initialized syslog before chrooting. */
|
||||
tzset();
|
||||
if (chroot(p) < 0) {
|
||||
rsyserr(FLOG, errno, "daemon chroot(\"%s\") failed", p);
|
||||
return -1;
|
||||
}
|
||||
/* Deliberately do NOT set am_chrooted here. am_chrooted
|
||||
* gates the per-module symlink-race defenses
|
||||
* (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,
|
||||
* not to any individual module path -- modules sharing the
|
||||
* daemon chroot are still distinguishable filesystem
|
||||
* 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 secure_relative_open()
|
||||
* still fires for "use chroot = no" modules. */
|
||||
if (chdir("/") < 0) {
|
||||
rsyserr(FLOG, errno, "daemon chdir(\"/\") failed");
|
||||
return -1;
|
||||
|
||||
4
compat.c
4
compat.c
@@ -131,7 +131,7 @@ static const char *client_info;
|
||||
* of that protocol for it to be advertised as available. */
|
||||
static void check_sub_protocol(void)
|
||||
{
|
||||
char *dot;
|
||||
const char *dot;
|
||||
int their_protocol, their_sub;
|
||||
int our_sub = get_subprotocol_version();
|
||||
|
||||
@@ -414,7 +414,7 @@ static const char *getenv_nstr(int ntype)
|
||||
env_str = ntype == NSTR_COMPRESS ? "zlib" : protocol_version >= 30 ? "md5" : "md4";
|
||||
|
||||
if (am_server && env_str) {
|
||||
char *cp = strchr(env_str, '&');
|
||||
const char *cp = strchr(env_str, '&');
|
||||
if (cp)
|
||||
env_str = cp + 1;
|
||||
}
|
||||
|
||||
@@ -1392,7 +1392,7 @@ else
|
||||
AC_DEFINE(HAVE_LINUX_XATTRS, 1, [True if you have Linux xattrs (or equivalent)])
|
||||
AC_DEFINE(SUPPORT_XATTRS, 1)
|
||||
AC_DEFINE(NO_SYMLINK_USER_XATTRS, 1, [True if symlinks do not support user xattrs])
|
||||
AC_CHECK_LIB(attr,getxattr)
|
||||
AC_SEARCH_LIBS(getxattr,attr)
|
||||
;;
|
||||
darwin*)
|
||||
AC_MSG_RESULT(Using OS X xattrs)
|
||||
|
||||
6
delete.c
6
delete.c
@@ -98,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)
|
||||
do_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)
|
||||
@@ -139,7 +139,7 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
|
||||
}
|
||||
|
||||
if (flags & DEL_NO_UID_WRITE)
|
||||
do_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
|
||||
@@ -160,7 +160,7 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
|
||||
|
||||
if (S_ISDIR(mode)) {
|
||||
what = "rmdir";
|
||||
ok = do_rmdir(fbuf) == 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";
|
||||
|
||||
@@ -904,7 +904,7 @@ static int rule_matches(const char *fname, filter_rule *ex, int name_flags)
|
||||
{
|
||||
int slash_handling, str_cnt = 0, anchored_match = 0;
|
||||
int ret_match = ex->rflags & FILTRULE_NEGATE ? 0 : 1;
|
||||
char *p, *pattern = ex->pattern;
|
||||
const char *p, *pattern = ex->pattern;
|
||||
const char *strings[16]; /* more than enough */
|
||||
const char *name = fname + (*fname == '/');
|
||||
|
||||
|
||||
17
flist.c
17
flist.c
@@ -840,9 +840,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
|
||||
}
|
||||
if (xflags & XMIT_MOD_NSEC)
|
||||
#ifndef CAN_SET_NSEC
|
||||
(void)read_varint(f);
|
||||
(void)read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
|
||||
#else
|
||||
modtime_nsec = read_varint(f);
|
||||
modtime_nsec = read_varint_bounded(f, 0, MAX_WIRE_NSEC, "modtime_nsec");
|
||||
else
|
||||
modtime_nsec = 0;
|
||||
#endif
|
||||
@@ -861,8 +861,19 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
if (!(xflags & XMIT_SAME_MODE))
|
||||
if (!(xflags & XMIT_SAME_MODE)) {
|
||||
mode = from_wire_mode(read_int(f));
|
||||
/* Reject modes whose type bits are not one of the standard
|
||||
* file types; otherwise garbage mode values propagate through
|
||||
* the file-type checks below unpredictably. */
|
||||
if (!S_ISREG(mode) && !S_ISDIR(mode) && !S_ISLNK(mode)
|
||||
&& !S_ISCHR(mode) && !S_ISBLK(mode)
|
||||
&& !S_ISFIFO(mode) && !S_ISSOCK(mode)) {
|
||||
rprintf(FERROR, "invalid file mode 0%o for %s [%s]\n",
|
||||
(unsigned)mode, lastname, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
}
|
||||
if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) {
|
||||
atime = read_varlong(f, 4);
|
||||
#if SIZEOF_TIME_T < SIZEOF_INT64
|
||||
|
||||
46
generator.c
46
generator.c
@@ -229,11 +229,13 @@ static int read_delay_line(char *buf, int *flags_p)
|
||||
*flags_p = 0;
|
||||
|
||||
if (sscanf(bp, "%x ", &mode) != 1) {
|
||||
invalid_data:
|
||||
rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
|
||||
return -1;
|
||||
goto invalid_data;
|
||||
}
|
||||
past_space = strchr(bp, ' ') + 1;
|
||||
past_space = strchr(bp, ' ');
|
||||
if (!past_space) {
|
||||
goto invalid_data;
|
||||
}
|
||||
past_space++;
|
||||
len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */
|
||||
read_pos = j + 1;
|
||||
|
||||
@@ -247,6 +249,10 @@ static int read_delay_line(char *buf, int *flags_p)
|
||||
memcpy(buf, past_space, len);
|
||||
|
||||
return mode;
|
||||
|
||||
invalid_data:
|
||||
rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void do_delayed_deletions(char *delbuf)
|
||||
@@ -984,7 +990,7 @@ 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 (do_unlink(fname) < 0 && errno != ENOENT)
|
||||
if (do_unlink_at(fname) < 0 && errno != ENOENT)
|
||||
goto got_nothing_for_ya;
|
||||
}
|
||||
#ifdef SUPPORT_HARD_LINKS
|
||||
@@ -1112,7 +1118,7 @@ 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)) {
|
||||
if (do_link(cmpbuf, fname) < 0) {
|
||||
if (do_link_at(cmpbuf, fname) < 0) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"failed to hard-link %s with %s",
|
||||
cmpbuf, fname);
|
||||
@@ -1315,7 +1321,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
}
|
||||
}
|
||||
if (relative_paths && !implied_dirs && file->mode != 0
|
||||
&& do_stat(dn, &sx.st) < 0) {
|
||||
&& do_stat_at(dn, &sx.st) < 0) {
|
||||
if (dry_run)
|
||||
goto parent_is_dry_missing;
|
||||
if (make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0) {
|
||||
@@ -1427,7 +1433,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 (do_mkdir(fname, (file->mode|added_perms) & 0700) == 0)
|
||||
if (do_mkdir_at(fname, (file->mode|added_perms) & 0700) == 0)
|
||||
file->flags |= FLAG_DIR_CREATED;
|
||||
goto cleanup;
|
||||
}
|
||||
@@ -1469,10 +1475,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 && do_mkdir(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
|
||||
if (real_ret != 0 && do_mkdir_at(fname,file->mode|added_perms) < 0 && errno != EEXIST) {
|
||||
if (!relative_paths || errno != ENOENT
|
||||
|| make_path(fname, MKP_DROP_NAME | MKP_SKIP_SLASH) < 0
|
||||
|| (do_mkdir(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
|
||||
|| (do_mkdir_at(fname, file->mode|added_perms) < 0 && errno != EEXIST)) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"recv_generator: mkdir %s failed",
|
||||
full_fname(fname));
|
||||
@@ -1499,7 +1505,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 (do_chmod(fname, mode) < 0) {
|
||||
if (do_chmod_at(fname, mode) < 0) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"failed to modify permissions on %s",
|
||||
full_fname(fname));
|
||||
@@ -1808,7 +1814,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) {
|
||||
do_unlink(partialptr);
|
||||
do_unlink_at(partialptr);
|
||||
handle_partial_dir(partialptr, PDIR_DELETE);
|
||||
}
|
||||
set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT | maybe_ATTRS_ACCURATE_TIME);
|
||||
@@ -1896,7 +1902,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
|
||||
back_file = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
if ((f_copy = do_open(backupptr, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 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;
|
||||
@@ -2016,7 +2022,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
|
||||
|
||||
if (slnk) {
|
||||
#ifdef SUPPORT_LINKS
|
||||
if (do_symlink(slnk, create_name) < 0) {
|
||||
if (do_symlink_at(slnk, create_name) < 0) {
|
||||
rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed",
|
||||
full_fname(create_name), slnk);
|
||||
return 0;
|
||||
@@ -2032,7 +2038,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
|
||||
return 0;
|
||||
#endif
|
||||
} else {
|
||||
if (do_mknod(create_name, file->mode, rdev) < 0) {
|
||||
if (do_mknod_at(create_name, file->mode, rdev) < 0) {
|
||||
rsyserr(FERROR_XFER, errno, "mknod %s failed",
|
||||
full_fname(create_name));
|
||||
return 0;
|
||||
@@ -2040,14 +2046,14 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
|
||||
}
|
||||
|
||||
if (!skip_atomic) {
|
||||
if (do_rename(tmpname, fname) < 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);
|
||||
do_unlink(tmpname);
|
||||
do_unlink_at(tmpname);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2111,7 +2117,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
|
||||
continue;
|
||||
fname = f_name(file, NULL);
|
||||
if (fix_dir_perms)
|
||||
do_chmod(fname, file->mode);
|
||||
do_chmod_at(fname, file->mode);
|
||||
if (need_retouch_dir_times) {
|
||||
STRUCT_STAT st;
|
||||
if (link_stat(fname, &st, 0) == 0 && mtime_differs(&st, file)) {
|
||||
@@ -2146,6 +2152,8 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
|
||||
if (send_failed)
|
||||
ndx = get_hlink_num();
|
||||
flist = flist_for_ndx(ndx, "check_for_finished_files.1");
|
||||
if (ndx < flist->ndx_start)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
file = flist->files[ndx - flist->ndx_start];
|
||||
assert(file->flags & FLAG_HLINKED);
|
||||
if (send_failed)
|
||||
@@ -2174,6 +2182,8 @@ void check_for_finished_files(int itemizing, enum logcode code, int check_redo)
|
||||
|
||||
flist = cur_flist;
|
||||
cur_flist = flist_for_ndx(ndx, "check_for_finished_files.2");
|
||||
if (ndx < cur_flist->ndx_start)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
|
||||
file = cur_flist->files[ndx - cur_flist->ndx_start];
|
||||
if (solo_file)
|
||||
|
||||
2
hlink.c
2
hlink.c
@@ -454,7 +454,7 @@ 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)
|
||||
{
|
||||
if (do_link(oldname, fname) < 0) {
|
||||
if (do_link_at(oldname, fname) < 0) {
|
||||
enum logcode code;
|
||||
if (terse) {
|
||||
if (!INFO_GTE(NAME, 1))
|
||||
|
||||
61
io.c
61
io.c
@@ -1090,6 +1090,9 @@ static void got_flist_entry_status(enum festatus status, int ndx)
|
||||
{
|
||||
struct file_list *flist = flist_for_ndx(ndx, "got_flist_entry_status");
|
||||
|
||||
if (ndx < flist->ndx_start)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
|
||||
if (remove_source_files) {
|
||||
active_filecnt--;
|
||||
active_bytecnt -= F_LENGTH(flist->files[ndx - flist->ndx_start]);
|
||||
@@ -1158,8 +1161,8 @@ void set_io_timeout(int secs)
|
||||
|
||||
static void check_for_d_option_error(const char *msg)
|
||||
{
|
||||
static char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
|
||||
char *colon;
|
||||
static const char rsync263_opts[] = "BCDHIKLPRSTWabceghlnopqrtuvxz";
|
||||
const char *colon;
|
||||
int saw_d = 0;
|
||||
|
||||
if (*msg != 'r'
|
||||
@@ -1865,6 +1868,45 @@ int64 read_varlong(int f, uchar min_bytes)
|
||||
return u.x;
|
||||
}
|
||||
|
||||
/* Read an int32 and verify lo <= v <= hi. On out-of-range, abort with a
|
||||
* protocol error naming "what". The bound is co-located with the read so it
|
||||
* cannot be forgotten by a downstream user. */
|
||||
int32 read_int_bounded(int f, int32 lo, int32 hi, const char *what)
|
||||
{
|
||||
int32 v = read_int(f);
|
||||
if (v < lo || v > hi) {
|
||||
rprintf(FERROR, "wire value %s out of range: %ld not in [%ld,%ld] [%s]\n",
|
||||
what, (long)v, (long)lo, (long)hi, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* As read_int_bounded but for varint-encoded values. */
|
||||
int32 read_varint_bounded(int f, int32 lo, int32 hi, const char *what)
|
||||
{
|
||||
int32 v = read_varint(f);
|
||||
if (v < lo || v > hi) {
|
||||
rprintf(FERROR, "wire value %s out of range: %ld not in [%ld,%ld] [%s]\n",
|
||||
what, (long)v, (long)lo, (long)hi, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Read a varint that will be used as a size_t. Rejects negative values
|
||||
* (which would wrap to ~SIZE_MAX) and values exceeding the supplied max. */
|
||||
size_t read_varint_size(int f, size_t max, const char *what)
|
||||
{
|
||||
int32 v = read_varint(f);
|
||||
if (v < 0 || (size_t)v > max) {
|
||||
rprintf(FERROR, "wire size %s out of range: %ld > %lu [%s]\n",
|
||||
what, (long)v, (unsigned long)max, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
return (size_t)v;
|
||||
}
|
||||
|
||||
int64 read_longint(int f)
|
||||
{
|
||||
#if SIZEOF_INT64 >= 8
|
||||
@@ -1971,6 +2013,21 @@ void read_sum_head(int f, struct sum_struct *sum)
|
||||
(long)sum->count, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
/* Guard against integer overflow in downstream allocations sized by
|
||||
* count*element_size. my_alloc uses divide-not-multiply so it is
|
||||
* already wraparound-safe, but checking here gives a clearer error
|
||||
* and also covers the (size_t)count * xfer_sum_len arithmetic that
|
||||
* is performed *before* reaching my_alloc. */
|
||||
if (xfer_sum_len > 0 && (size_t)sum->count > SIZE_MAX / (size_t)xfer_sum_len) {
|
||||
rprintf(FERROR, "Invalid checksum count %ld (too large) [%s]\n",
|
||||
(long)sum->count, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
if ((size_t)sum->count > SIZE_MAX / sizeof(struct sum_buf)) {
|
||||
rprintf(FERROR, "Invalid checksum count %ld (sum_buf overflow) [%s]\n",
|
||||
(long)sum->count, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
sum->blength = read_int(f);
|
||||
if (sum->blength < 0 || sum->blength > max_blength) {
|
||||
rprintf(FERROR, "Invalid block length %ld [%s]\n",
|
||||
|
||||
@@ -178,7 +178,7 @@ static char *expand_vars(const char *str)
|
||||
|
||||
for (t = buf, f = str; bufsize && *f; ) {
|
||||
if (*f == '%' && isUpper(f+1)) {
|
||||
char *percent = strchr(f+1, '%');
|
||||
const char *percent = strchr(f+1, '%');
|
||||
if (percent && percent - f < bufsize) {
|
||||
char *val;
|
||||
strlcpy(t, f+1, percent - f);
|
||||
|
||||
12
log.c
12
log.c
@@ -456,11 +456,17 @@ void rsyserr(enum logcode code, int errcode, const char *format, ...)
|
||||
char buf[BIGPATHBUFLEN];
|
||||
size_t len;
|
||||
|
||||
/* snprintf returns the would-have-been length on truncation, so
|
||||
* each cumulative call must be guarded; if not, sizeof buf - len
|
||||
* can underflow when promoted to size_t and the next call writes
|
||||
* past the buffer. */
|
||||
len = snprintf(buf, sizeof buf, RSYNC_NAME ": [%s] ", who_am_i());
|
||||
|
||||
va_start(ap, format);
|
||||
len += vsnprintf(buf + len, sizeof buf - len, format, ap);
|
||||
va_end(ap);
|
||||
if (len < sizeof buf) {
|
||||
va_start(ap, format);
|
||||
len += vsnprintf(buf + len, sizeof buf - len, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
if (len < sizeof buf) {
|
||||
len += snprintf(buf + len, sizeof buf - len,
|
||||
|
||||
23
main.c
23
main.c
@@ -239,11 +239,11 @@ void write_del_stats(int f)
|
||||
|
||||
void read_del_stats(int f)
|
||||
{
|
||||
stats.deleted_files = read_varint(f);
|
||||
stats.deleted_files += stats.deleted_dirs = read_varint(f);
|
||||
stats.deleted_files += stats.deleted_symlinks = read_varint(f);
|
||||
stats.deleted_files += stats.deleted_devices = read_varint(f);
|
||||
stats.deleted_files += stats.deleted_specials = read_varint(f);
|
||||
stats.deleted_files = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_files");
|
||||
stats.deleted_files += stats.deleted_dirs = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_dirs");
|
||||
stats.deleted_files += stats.deleted_symlinks = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_symlinks");
|
||||
stats.deleted_files += stats.deleted_devices = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_devices");
|
||||
stats.deleted_files += stats.deleted_specials = read_varint_bounded(f, 0, MAX_WIRE_DEL_STAT, "deleted_specials");
|
||||
}
|
||||
|
||||
static void become_copy_as_user()
|
||||
@@ -394,9 +394,18 @@ static void output_itemized_counts(const char *prefix, int *counts)
|
||||
counts[0] -= counts[1] + counts[2] + counts[3] + counts[4];
|
||||
for (j = 0; j < 5; j++) {
|
||||
if (counts[j]) {
|
||||
/* snprintf can return more than its size arg
|
||||
* on truncation; keep len <= sizeof buf - 2 so
|
||||
* the closing ')' and trailing NUL always
|
||||
* have room and the next iteration's
|
||||
* sizeof buf - len - 2 cannot underflow. */
|
||||
if (len >= (int)sizeof buf - 2)
|
||||
break;
|
||||
len += snprintf(buf+len, sizeof buf - len - 2,
|
||||
"%s%s: %s",
|
||||
pre, labels[j], comma_num(counts[j]));
|
||||
if (len > (int)sizeof buf - 2)
|
||||
len = (int)sizeof buf - 2;
|
||||
pre = ", ";
|
||||
}
|
||||
}
|
||||
@@ -1559,6 +1568,10 @@ static int start_client(int argc, char *argv[])
|
||||
shell_user = shell_machine;
|
||||
shell_machine = p+1;
|
||||
}
|
||||
if (*shell_machine == '-') {
|
||||
rprintf(FERROR, "Invalid remote host: hostnames may not start with '-'.\n");
|
||||
exit_cleanup(RERR_SYNTAX);
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG_GTE(CMD, 2)) {
|
||||
|
||||
15
options.c
15
options.c
@@ -113,11 +113,20 @@ int mkpath_dest_arg = 0;
|
||||
int allow_inc_recurse = 1;
|
||||
int xfer_dirs = -1;
|
||||
int am_daemon = 0;
|
||||
/* Set after a successful per-module chroot ("use chroot = yes") in
|
||||
* 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 (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;
|
||||
int connect_timeout = 0;
|
||||
int keep_partial = 0;
|
||||
int safe_symlinks = 0;
|
||||
int copy_unsafe_links = 0;
|
||||
int munge_symlinks = 0;
|
||||
int use_secure_symlinks = 0;
|
||||
int size_only = 0;
|
||||
int daemon_bwlimit = 0;
|
||||
int bwlimit = 0;
|
||||
@@ -1156,7 +1165,7 @@ static time_t parse_time(const char *arg)
|
||||
{
|
||||
const char *cp;
|
||||
time_t val, now = time(NULL);
|
||||
struct tm t, *today = localtime(&now);
|
||||
struct tm t, tmp, *today = localtime_r(&now, &tmp);
|
||||
int in_date, old_mday, n;
|
||||
|
||||
memset(&t, 0, sizeof t);
|
||||
@@ -1369,6 +1378,10 @@ int parse_arguments(int *argc_p, const char ***argv_p)
|
||||
/* TODO: Call poptReadDefaultConfig; handle errors. */
|
||||
|
||||
pc = poptGetContext(RSYNC_NAME, argc, argv, long_options, 0);
|
||||
if (pc == NULL) {
|
||||
strlcpy(err_buf, "poptGetContext returned NULL\n", sizeof err_buf);
|
||||
return 0;
|
||||
}
|
||||
if (!am_server) {
|
||||
poptReadDefaultConfig(pc, 0);
|
||||
popt_unalias(pc, "--daemon");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Summary: A fast, versatile, remote (and local) file-copying tool
|
||||
Name: rsync
|
||||
Version: 3.4.0
|
||||
Version: 3.4.1
|
||||
%define fullversion %{version}
|
||||
Release: 1
|
||||
%define srcdir src
|
||||
@@ -79,5 +79,5 @@ rm -rf $RPM_BUILD_ROOT
|
||||
%dir /etc/rsync-ssl/certs
|
||||
|
||||
%changelog
|
||||
* Wed Jan 15 2025 Rsync Project <rsync.project@gmail.com>
|
||||
Released 3.4.0.
|
||||
* Thu Jan 16 2025 Rsync Project <rsync.project@gmail.com>
|
||||
Released 3.4.1.
|
||||
|
||||
@@ -25,12 +25,15 @@ const char * findProgramPath(const char * argv0)
|
||||
if (path == NULL) return NULL;
|
||||
|
||||
bufsize = strlen(path) + 1;
|
||||
start = pathbuf = alloca(bufsize);
|
||||
start = pathbuf = malloc(bufsize);
|
||||
if (pathbuf == NULL) return NULL; /* XXX can't happen */
|
||||
strlcpy(pathbuf, path, bufsize);
|
||||
bufsize += sizeof "/" - 1 + strlen(argv0);
|
||||
buf = malloc(bufsize);
|
||||
if (buf == NULL) return NULL; /* XXX can't happen */
|
||||
if (buf == NULL) {
|
||||
free(pathbuf);
|
||||
return NULL; /* XXX can't happen */
|
||||
}
|
||||
|
||||
chptr = NULL;
|
||||
/*@-branchstate@*/
|
||||
@@ -39,8 +42,10 @@ const char * findProgramPath(const char * argv0)
|
||||
*chptr = '\0';
|
||||
snprintf(buf, bufsize, "%s/%s", start, argv0);
|
||||
|
||||
if (!access(buf, X_OK))
|
||||
if (!access(buf, X_OK)) {
|
||||
free(pathbuf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
if (chptr)
|
||||
start = chptr + 1;
|
||||
@@ -49,6 +54,7 @@ const char * findProgramPath(const char * argv0)
|
||||
} while (start && *start);
|
||||
/*@=branchstate@*/
|
||||
|
||||
free(pathbuf);
|
||||
free(buf);
|
||||
|
||||
return NULL;
|
||||
|
||||
53
receiver.c
53
receiver.c
@@ -70,6 +70,7 @@ extern int fuzzy_basis;
|
||||
|
||||
extern struct name_num_item *xfer_sum_nni;
|
||||
extern int xfer_sum_len;
|
||||
extern int use_secure_symlinks;
|
||||
|
||||
static struct bitbag *delayed_bits = NULL;
|
||||
static int phase = 0, redoing = 0;
|
||||
@@ -214,7 +215,12 @@ 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.) */
|
||||
fd = do_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
|
||||
@@ -312,7 +318,12 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
|
||||
}
|
||||
}
|
||||
|
||||
while ((i = recv_token(f_in, &data)) != 0) {
|
||||
while (1) {
|
||||
data = NULL;
|
||||
i = recv_token(f_in, &data);
|
||||
if (i == 0)
|
||||
break;
|
||||
|
||||
if (INFO_GTE(PROGRESS, 1))
|
||||
show_progress(offset, total_size);
|
||||
|
||||
@@ -320,6 +331,10 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
|
||||
maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH | MSK_ACTIVE_RECEIVER);
|
||||
|
||||
if (i > 0) {
|
||||
if (!data) {
|
||||
rprintf(FERROR, "Invalid literal token with no data [%s]\n", who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
if (DEBUG_GTE(DELTASUM, 3)) {
|
||||
rprintf(FINFO,"data recv %d at %s\n",
|
||||
i, big_num(offset));
|
||||
@@ -337,6 +352,11 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
|
||||
}
|
||||
|
||||
i = -(i+1);
|
||||
if (i < 0 || i >= sum.count) {
|
||||
rprintf(FERROR, "Invalid block index %d (count=%ld) [%s]\n",
|
||||
i, (long)sum.count, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
offset2 = i * (OFF_T)sum.blength;
|
||||
len = sum.blength;
|
||||
if (i == (int)sum.count-1 && sum.remainder != 0)
|
||||
@@ -436,7 +456,7 @@ static void handle_delayed_updates(char *local_name)
|
||||
}
|
||||
/* We don't use robust_rename() here because the
|
||||
* partial-dir must be on the same drive. */
|
||||
if (do_rename(partialptr, fname) < 0) {
|
||||
if (do_rename_at(partialptr, fname) < 0) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"rename failed for %s (from %s)",
|
||||
full_fname(fname), partialptr);
|
||||
@@ -452,7 +472,10 @@ static void handle_delayed_updates(char *local_name)
|
||||
static void no_batched_update(int ndx, BOOL is_redo)
|
||||
{
|
||||
struct file_list *flist = flist_for_ndx(ndx, "no_batched_update");
|
||||
struct file_struct *file = flist->files[ndx - flist->ndx_start];
|
||||
struct file_struct *file;
|
||||
if (ndx < flist->ndx_start)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
file = flist->files[ndx - flist->ndx_start];
|
||||
|
||||
rprintf(FERROR_XFER, "(No batched update for%s \"%s\")\n",
|
||||
is_redo ? " resend of" : "", f_name(file, NULL));
|
||||
@@ -589,6 +612,8 @@ 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)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
else
|
||||
file = dir_flist->files[cur_flist->parent_ndx];
|
||||
fname = local_name ? local_name : f_name(file, fbuf);
|
||||
@@ -854,11 +879,21 @@ 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;
|
||||
fd2 = do_open(fnametmp, O_WRONLY|O_CREAT, 0600);
|
||||
/* 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. */
|
||||
if (use_secure_symlinks)
|
||||
fd2 = secure_relative_open(NULL, fnametmp, O_WRONLY|O_CREAT, 0600);
|
||||
else
|
||||
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? */
|
||||
fd2 = do_open(fname, O_WRONLY, 0600);
|
||||
if (use_secure_symlinks)
|
||||
fd2 = secure_relative_open(NULL, fname, O_WRONLY, 0600);
|
||||
else
|
||||
fd2 = do_open(fname, O_WRONLY, 0600);
|
||||
}
|
||||
#endif
|
||||
if (fd2 == -1) {
|
||||
@@ -910,7 +945,7 @@ int recv_files(int f_in, int f_out, char *local_name)
|
||||
recv_ok = -1;
|
||||
else if (fnamecmp == partialptr) {
|
||||
if (!one_inplace)
|
||||
do_unlink(partialptr);
|
||||
do_unlink_at(partialptr);
|
||||
handle_partial_dir(partialptr, PDIR_DELETE);
|
||||
}
|
||||
} else if (keep_partial && partialptr && (!one_inplace || delay_updates)) {
|
||||
@@ -919,7 +954,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");
|
||||
do_unlink(fnametmp);
|
||||
do_unlink_at(fnametmp);
|
||||
recv_ok = -1;
|
||||
} else if (!finish_transfer(partialptr, fnametmp, fnamecmp, NULL,
|
||||
file, recv_ok, !partial_dir))
|
||||
@@ -930,7 +965,7 @@ int recv_files(int f_in, int f_out, char *local_name)
|
||||
} else
|
||||
partialptr = NULL;
|
||||
} else if (!one_inplace)
|
||||
do_unlink(fnametmp);
|
||||
do_unlink_at(fnametmp);
|
||||
|
||||
cleanup_disable();
|
||||
|
||||
|
||||
8
rsync.c
8
rsync.c
@@ -547,7 +547,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
|
||||
if (am_root >= 0) {
|
||||
uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid;
|
||||
gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid;
|
||||
if (do_lchown(fname, uid, gid) != 0) {
|
||||
if (do_lchown_at(fname, uid, gid) != 0) {
|
||||
/* We shouldn't have attempted to change uid
|
||||
* or gid unless have the privilege. */
|
||||
rsyserr(FERROR_XFER, errno, "%s %s failed",
|
||||
@@ -657,7 +657,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
|
||||
|
||||
#ifdef HAVE_CHMOD
|
||||
if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
|
||||
int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
|
||||
int ret = am_root < 0 ? 0 : do_chmod_at(fname, new_mode);
|
||||
if (ret < 0) {
|
||||
rsyserr(FERROR_XFER, errno,
|
||||
"failed to set permissions on %s",
|
||||
@@ -758,7 +758,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
|
||||
full_fname(fnametmp), fname);
|
||||
if (!partialptr || (ret == -2 && temp_copy_name)
|
||||
|| robust_rename(fnametmp, partialptr, NULL, file->mode) < 0)
|
||||
do_unlink(fnametmp);
|
||||
do_unlink_at(fnametmp);
|
||||
return 0;
|
||||
}
|
||||
if (ret == 0) {
|
||||
@@ -774,7 +774,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
|
||||
ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);
|
||||
|
||||
if (temp_copy_name) {
|
||||
if (do_rename(fnametmp, fname) < 0) {
|
||||
if (do_rename_at(fnametmp, fname) < 0) {
|
||||
rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\"",
|
||||
full_fname(fnametmp), fname);
|
||||
return 0;
|
||||
|
||||
23
rsync.h
23
rsync.h
@@ -163,6 +163,29 @@
|
||||
/* For compatibility with older rsyncs */
|
||||
#define OLD_MAX_BLOCK_SIZE ((int32)1 << 29)
|
||||
|
||||
/* Policy ceilings on attacker-controlled wire values. Picked well above any
|
||||
* legitimate filesystem / protocol traffic but well below sizes that could
|
||||
* cause integer overflow or DoS-grade allocations. See input_checking.txt.
|
||||
*
|
||||
* Note on MAX_WIRE_XATTR_DATALEN: xattr datum size is bounded only by the
|
||||
* wire-format maximum (signed int32 varint, ~2GB). macOS resource forks
|
||||
* are transferred as the com.apple.ResourceFork xattr and can legitimately
|
||||
* be many GB; --max-alloc (default 1GB, configurable) is the real
|
||||
* allocation cap. read_varint_size() still rejects negative values so a
|
||||
* hostile peer cannot wrap to ~SIZE_MAX. */
|
||||
#define MAX_WIRE_XATTR_COUNT 65536
|
||||
#define MAX_WIRE_XATTR_NAMELEN 4096
|
||||
#define MAX_WIRE_XATTR_DATALEN ((int32)0x7fffffff)
|
||||
#define MAX_WIRE_ACL_COUNT 65536
|
||||
#define MAX_WIRE_NSEC 999999999
|
||||
/* MAX_WIRE_DEL_STAT is the per-category cap for read_del_stats() in main.c,
|
||||
* which accumulates 5 wire-supplied counts into the int32 stats.deleted_files
|
||||
* accumulator. Capped at 2^28 so 5 * 2^28 = 1.34 GB stays under INT32_MAX
|
||||
* (2.15 GB) with margin -- a higher cap (e.g. 2^30) would let a hostile peer
|
||||
* supplying 3+ max-sized counts overflow the accumulator, which is signed-int
|
||||
* UB. 2^28 is still well above any plausible real transfer's deletion count. */
|
||||
#define MAX_WIRE_DEL_STAT ((int32)1 << 28)
|
||||
|
||||
#define ROUND_UP_1024(siz) ((siz) & (1024-1) ? ((siz) | (1024-1)) + 1 : (siz))
|
||||
|
||||
#define IOERR_GENERAL (1<<0) /* For backward compatibility, this must == 1 */
|
||||
|
||||
26
sender.c
26
sender.c
@@ -48,6 +48,8 @@ extern int make_backups;
|
||||
extern int inplace;
|
||||
extern int inplace_partial;
|
||||
extern int batch_fd;
|
||||
extern int use_secure_symlinks;
|
||||
extern char *module_dir;
|
||||
extern int write_batch;
|
||||
extern int file_old_total;
|
||||
extern BOOL want_progress_now;
|
||||
@@ -138,6 +140,8 @@ void successful_send(int ndx)
|
||||
return;
|
||||
|
||||
flist = flist_for_ndx(ndx, "successful_send");
|
||||
if (ndx < flist->ndx_start)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
file = flist->files[ndx - flist->ndx_start];
|
||||
if (!change_pathname(file, NULL, 0))
|
||||
return;
|
||||
@@ -262,6 +266,8 @@ void send_files(int f_in, int f_out)
|
||||
|
||||
if (ndx - cur_flist->ndx_start >= 0)
|
||||
file = cur_flist->files[ndx - cur_flist->ndx_start];
|
||||
else if (cur_flist->parent_ndx < 0)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
else
|
||||
file = dir_flist->files[cur_flist->parent_ndx];
|
||||
if (F_PATHNAME(file)) {
|
||||
@@ -350,7 +356,25 @@ void send_files(int f_in, int f_out)
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
|
||||
fd = do_open_checklinks(fname);
|
||||
if (use_secure_symlinks) {
|
||||
/* Open from module root to prevent TOCTOU race where
|
||||
* change_pathname's chdir follows a directory symlink.
|
||||
* Reconstruct the full path relative to module_dir
|
||||
* from F_PATHNAME (path) and f_name (fname). */
|
||||
char secure_path[MAXPATHLEN];
|
||||
int slen = snprintf(secure_path, sizeof secure_path, "%s%s%s", path, slash, fname);
|
||||
if (slen >= (int)sizeof secure_path) {
|
||||
io_error |= IOERR_GENERAL;
|
||||
rprintf(FERROR_XFER, "path too long: %s%s%s\n", path, slash, fname);
|
||||
free_sums(s);
|
||||
if (protocol_version >= 30)
|
||||
send_msg_int(MSG_NO_SEND, ndx);
|
||||
continue;
|
||||
}
|
||||
fd = secure_relative_open(module_dir, secure_path, O_RDONLY, 0);
|
||||
} else {
|
||||
fd = do_open_checklinks(fname);
|
||||
}
|
||||
if (fd == -1) {
|
||||
if (errno == ENOENT) {
|
||||
enum logcode c = am_daemon && protocol_version < 28 ? FERROR : FWARNING;
|
||||
|
||||
@@ -347,8 +347,7 @@ __attribute__ ((target("avx2"))) MVSTATIC int32 get_checksum1_avx2_64(schar* buf
|
||||
__m128i tmp = _mm_load_si128((__m128i*) mul_t1_buf);
|
||||
__m256i mul_t1 = _mm256_cvtepu8_epi16(tmp);
|
||||
__m256i mul_const = _mm256_broadcastd_epi32(_mm_cvtsi32_si128(4 | (3 << 8) | (2 << 16) | (1 << 24)));
|
||||
__m256i mul_one;
|
||||
mul_one = _mm256_abs_epi8(_mm256_cmpeq_epi16(mul_one,mul_one)); // set all vector elements to 1
|
||||
__m256i mul_one = _mm256_set1_epi8(1);
|
||||
|
||||
for (; i < (len-64); i+=64) {
|
||||
// Load ... 4*[int8*16]
|
||||
@@ -548,6 +547,118 @@ int main() {
|
||||
#pragma clang optimize on
|
||||
#endif /* BENCHMARK_SIMD_CHECKSUM1 */
|
||||
|
||||
#ifdef TEST_SIMD_CHECKSUM1
|
||||
|
||||
static uint32 checksum_via_default(char *buf, int32 len)
|
||||
{
|
||||
uint32 s1 = 0, s2 = 0;
|
||||
get_checksum1_default_1((schar*)buf, len, 0, &s1, &s2);
|
||||
return (s1 & 0xffff) + (s2 << 16);
|
||||
}
|
||||
|
||||
static uint32 checksum_via_sse2(char *buf, int32 len)
|
||||
{
|
||||
int32 i;
|
||||
uint32 s1 = 0, s2 = 0;
|
||||
i = get_checksum1_sse2_32((schar*)buf, len, 0, &s1, &s2);
|
||||
get_checksum1_default_1((schar*)buf, len, i, &s1, &s2);
|
||||
return (s1 & 0xffff) + (s2 << 16);
|
||||
}
|
||||
|
||||
static uint32 checksum_via_ssse3(char *buf, int32 len)
|
||||
{
|
||||
int32 i;
|
||||
uint32 s1 = 0, s2 = 0;
|
||||
i = get_checksum1_ssse3_32((schar*)buf, len, 0, &s1, &s2);
|
||||
get_checksum1_default_1((schar*)buf, len, i, &s1, &s2);
|
||||
return (s1 & 0xffff) + (s2 << 16);
|
||||
}
|
||||
|
||||
static uint32 checksum_via_avx2(char *buf, int32 len)
|
||||
{
|
||||
int32 i;
|
||||
uint32 s1 = 0, s2 = 0;
|
||||
#ifdef USE_ROLL_ASM
|
||||
i = get_checksum1_avx2_asm((schar*)buf, len, 0, &s1, &s2);
|
||||
#else
|
||||
i = get_checksum1_avx2_64((schar*)buf, len, 0, &s1, &s2);
|
||||
#endif
|
||||
get_checksum1_default_1((schar*)buf, len, i, &s1, &s2);
|
||||
return (s1 & 0xffff) + (s2 << 16);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
static const int sizes[] = {1, 4, 31, 32, 33, 63, 64, 65, 128, 129, 256, 700, 1024, 4096, 65536};
|
||||
int num_sizes = sizeof(sizes) / sizeof(sizes[0]);
|
||||
int max_size = sizes[num_sizes - 1];
|
||||
int failures = 0;
|
||||
|
||||
/* Allocate with extra bytes for unaligned test */
|
||||
unsigned char *raw = (unsigned char *)malloc(max_size + 64 + 1);
|
||||
if (!raw) {
|
||||
fprintf(stderr, "malloc failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Fill with deterministic data */
|
||||
for (int i = 0; i < max_size + 64 + 1; i++)
|
||||
raw[i] = (i + (i % 3) + (i % 11)) % 256;
|
||||
|
||||
/* Test with aligned buffer (64-byte aligned) */
|
||||
unsigned char *aligned = raw + (64 - ((uintptr_t)raw % 64));
|
||||
|
||||
/* Test with unaligned buffer (+1 byte offset) */
|
||||
unsigned char *unaligned = aligned + 1;
|
||||
|
||||
struct { const char *name; unsigned char *buf; } buffers[] = {
|
||||
{"aligned", aligned},
|
||||
{"unaligned", unaligned},
|
||||
};
|
||||
|
||||
for (int b = 0; b < 2; b++) {
|
||||
char *buf = (char *)buffers[b].buf;
|
||||
const char *bname = buffers[b].name;
|
||||
|
||||
for (int s = 0; s < num_sizes; s++) {
|
||||
int32 len = sizes[s];
|
||||
uint32 ref = checksum_via_default(buf, len);
|
||||
uint32 cs_sse2 = checksum_via_sse2(buf, len);
|
||||
uint32 cs_ssse3 = checksum_via_ssse3(buf, len);
|
||||
uint32 cs_avx2 = checksum_via_avx2(buf, len);
|
||||
uint32 cs_auto = get_checksum1(buf, len);
|
||||
|
||||
if (cs_sse2 != ref) {
|
||||
printf("FAIL %-9s size=%5d: SSE2=%08x ref=%08x\n", bname, len, cs_sse2, ref);
|
||||
failures++;
|
||||
}
|
||||
if (cs_ssse3 != ref) {
|
||||
printf("FAIL %-9s size=%5d: SSSE3=%08x ref=%08x\n", bname, len, cs_ssse3, ref);
|
||||
failures++;
|
||||
}
|
||||
if (cs_avx2 != ref) {
|
||||
printf("FAIL %-9s size=%5d: AVX2=%08x ref=%08x\n", bname, len, cs_avx2, ref);
|
||||
failures++;
|
||||
}
|
||||
if (cs_auto != ref) {
|
||||
printf("FAIL %-9s size=%5d: auto=%08x ref=%08x\n", bname, len, cs_auto, ref);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(raw);
|
||||
|
||||
if (failures) {
|
||||
printf("%d checksum mismatches!\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("All SIMD checksum tests passed.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* TEST_SIMD_CHECKSUM1 */
|
||||
|
||||
#endif /* } USE_ROLL_SIMD */
|
||||
#endif /* } __cplusplus */
|
||||
#endif /* } __x86_64__ */
|
||||
|
||||
30
socket.c
30
socket.c
@@ -47,21 +47,23 @@ static struct sigaction sigact;
|
||||
|
||||
static int sock_exec(const char *prog);
|
||||
|
||||
#define PROXY_BUF_SIZE 1024
|
||||
|
||||
/* Establish a proxy connection on an open socket to a web proxy by using the
|
||||
* CONNECT method. If proxy_user and proxy_pass are not NULL, they are used to
|
||||
* authenticate to the proxy using the "Basic" proxy-authorization protocol. */
|
||||
static int establish_proxy_connection(int fd, char *host, int port, char *proxy_user, char *proxy_pass)
|
||||
{
|
||||
char *cp, buffer[1024];
|
||||
char *authhdr, authbuf[1024];
|
||||
char *cp, buffer[PROXY_BUF_SIZE + 1];
|
||||
char *authhdr, authbuf[PROXY_BUF_SIZE + 1];
|
||||
int len;
|
||||
|
||||
if (proxy_user && proxy_pass) {
|
||||
stringjoin(buffer, sizeof buffer,
|
||||
stringjoin(buffer, PROXY_BUF_SIZE,
|
||||
proxy_user, ":", proxy_pass, NULL);
|
||||
len = strlen(buffer);
|
||||
|
||||
if ((len*8 + 5) / 6 >= (int)sizeof authbuf - 3) {
|
||||
if ((len*8 + 5) / 6 >= PROXY_BUF_SIZE - 3) {
|
||||
rprintf(FERROR,
|
||||
"authentication information is too long\n");
|
||||
return -1;
|
||||
@@ -74,14 +76,14 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
|
||||
authhdr = "";
|
||||
}
|
||||
|
||||
len = snprintf(buffer, sizeof buffer, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", host, port, authhdr, authbuf);
|
||||
assert(len > 0 && len < (int)sizeof buffer);
|
||||
len = snprintf(buffer, PROXY_BUF_SIZE, "CONNECT %s:%d HTTP/1.0%s%s\r\n\r\n", host, port, authhdr, authbuf);
|
||||
assert(len > 0 && len < PROXY_BUF_SIZE);
|
||||
if (write(fd, buffer, len) != len) {
|
||||
rsyserr(FERROR, errno, "failed to write to proxy");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
|
||||
for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE - 1]; cp++) {
|
||||
if (read(fd, cp, 1) != 1) {
|
||||
rsyserr(FERROR, errno, "failed to read from proxy");
|
||||
return -1;
|
||||
@@ -90,11 +92,13 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
|
||||
break;
|
||||
}
|
||||
|
||||
if (*cp != '\n')
|
||||
cp++;
|
||||
*cp-- = '\0';
|
||||
if (*cp == '\r')
|
||||
*cp = '\0';
|
||||
if (cp == &buffer[PROXY_BUF_SIZE - 1]) {
|
||||
rprintf(FERROR, "proxy response line too long\n");
|
||||
return -1;
|
||||
}
|
||||
*cp = '\0';
|
||||
if (cp > buffer && cp[-1] == '\r')
|
||||
cp[-1] = '\0';
|
||||
if (strncmp(buffer, "HTTP/", 5) != 0) {
|
||||
rprintf(FERROR, "bad response from proxy -- %s\n",
|
||||
buffer);
|
||||
@@ -110,7 +114,7 @@ static int establish_proxy_connection(int fd, char *host, int port, char *proxy_
|
||||
}
|
||||
/* throw away the rest of the HTTP header */
|
||||
while (1) {
|
||||
for (cp = buffer; cp < &buffer[sizeof buffer - 1]; cp++) {
|
||||
for (cp = buffer; cp < &buffer[PROXY_BUF_SIZE]; cp++) {
|
||||
if (read(fd, cp, 1) != 1) {
|
||||
rsyserr(FERROR, errno,
|
||||
"failed to read from proxy");
|
||||
|
||||
117
t_chmod_secure.c
Normal file
117
t_chmod_secure.c
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Test harness for do_chmod_at(). Confirms the symlink-TOCTOU
|
||||
* primitive used by CVE-2026-29518 (and its incomplete-fix follow-up
|
||||
* for chmod) is closed by do_chmod_at(): a parent directory component
|
||||
* being a symlink that escapes the receiver's confinement must be
|
||||
* rejected, while a parent symlink that resolves *within* the tree
|
||||
* must still work (so legitimate dir-symlinks are not regressed).
|
||||
*
|
||||
* Not linked into rsync itself.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include "rsync.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
int dry_run = 0;
|
||||
int am_root = 0;
|
||||
int am_sender = 0;
|
||||
int read_only = 0;
|
||||
int list_only = 0;
|
||||
int copy_links = 0;
|
||||
int copy_unsafe_links = 0;
|
||||
extern int am_daemon, am_chrooted;
|
||||
|
||||
short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
|
||||
|
||||
static int errs = 0;
|
||||
|
||||
static void check(const char *label, int actual_rc, int expect_ok,
|
||||
const char *path, mode_t expected_mode)
|
||||
{
|
||||
struct stat st;
|
||||
int got_ok = (actual_rc == 0);
|
||||
if (got_ok != expect_ok) {
|
||||
fprintf(stderr, "FAIL [%s]: rc=%d errno=%d (%s), expected %s\n",
|
||||
label, actual_rc, errno, strerror(errno),
|
||||
expect_ok ? "success" : "rejection");
|
||||
errs++;
|
||||
return;
|
||||
}
|
||||
if (path && stat(path, &st) < 0) {
|
||||
fprintf(stderr, "FAIL [%s]: stat(%s) failed: %s\n",
|
||||
label, path, strerror(errno));
|
||||
errs++;
|
||||
return;
|
||||
}
|
||||
if (path && (st.st_mode & 07777) != expected_mode) {
|
||||
fprintf(stderr,
|
||||
"FAIL [%s]: %s mode is 0%o, expected 0%o\n",
|
||||
label, path, st.st_mode & 07777, expected_mode);
|
||||
errs++;
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, "OK [%s]\n", label);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "usage: %s <module-dir>\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
if (chdir(argv[1]) < 0) {
|
||||
perror("chdir");
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Simulate the daemon-without-chroot deployment that do_chmod_at()
|
||||
* defends. With am_daemon=0 or am_chrooted=1 the wrapper falls
|
||||
* through to plain do_chmod() and the symlink-race test would be
|
||||
* meaningless. */
|
||||
am_daemon = 1;
|
||||
am_chrooted = 0;
|
||||
|
||||
/* Test layout (all inside the directory we just chdir'd to):
|
||||
*
|
||||
* ./realdir/sentinel -- regular target file
|
||||
* ./inside_link -> realdir -- legitimate dir-symlink within the tree
|
||||
* ./escape_link -> ../trap -- attacker swap, target outside tree
|
||||
* ../trap/sentinel -- the file the attacker wants to alter
|
||||
*
|
||||
* The shell wrapper that calls this helper has set both sentinel
|
||||
* files to mode 0600 so we have a clean baseline to compare.
|
||||
*/
|
||||
|
||||
/* Scenario A: legitimate parent dir-symlink, chmod must succeed. */
|
||||
int rc = do_chmod_at("inside_link/sentinel", 0640);
|
||||
check("A: legit dir-symlink within tree",
|
||||
rc, 1, "realdir/sentinel", 0640);
|
||||
|
||||
/* Scenario B: parent symlink escapes the tree -- chmod must be
|
||||
* rejected and the outside file's mode must be unchanged. */
|
||||
rc = do_chmod_at("escape_link/sentinel", 0666);
|
||||
check("B: parent symlink escapes tree (the attack)",
|
||||
rc, 0, "../trap/sentinel", 0600);
|
||||
|
||||
/* Scenario C: plain relative path with no symlink components,
|
||||
* regression check that the safe wrapper doesn't break the
|
||||
* normal case. */
|
||||
rc = do_chmod_at("realdir/sentinel", 0644);
|
||||
check("C: plain relative path (regression check)",
|
||||
rc, 1, "realdir/sentinel", 0644);
|
||||
|
||||
/* Scenario D: top-level file, no parent directory component.
|
||||
* Falls back to do_chmod(); should succeed. */
|
||||
rc = do_chmod_at("topfile", 0640);
|
||||
check("D: top-level file, no parent component",
|
||||
rc, 1, "topfile", 0640);
|
||||
|
||||
if (errs)
|
||||
fprintf(stderr, "%d failure(s)\n", errs);
|
||||
return errs ? 1 : 0;
|
||||
}
|
||||
151
t_secure_relpath.c
Normal file
151
t_secure_relpath.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Test harness for secure_relative_open()'s front-door input
|
||||
* validation. Codex audit Finding 5 noted that the existing check
|
||||
*
|
||||
* if (strncmp(relpath, "../", 3) == 0 || strstr(relpath, "/../"))
|
||||
*
|
||||
* catches "../foo" and "foo/../bar" but misses bare ".." (an actual
|
||||
* one-level escape on platforms that fall back to the per-component
|
||||
* walk), as well as "a/..", "foo/..", and any other form that
|
||||
* decomposes to a ".." component when split on "/". The kernel-
|
||||
* enforced RESOLVE_BENEATH (Linux 5.6+) and O_RESOLVE_BENEATH
|
||||
* (FreeBSD 13+, macOS 15+) reject these in-kernel; the per-
|
||||
* component fallback used on NetBSD, OpenBSD, Solaris, Cygwin and
|
||||
* pre-5.6 Linux does not, so the validation must happen at the
|
||||
* front door.
|
||||
*
|
||||
* This helper invokes secure_relative_open() with each suspect
|
||||
* input and checks both the failure (rc < 0) and the errno
|
||||
* (EINVAL means "rejected at the front door"). Pre-fix, the kernel
|
||||
* may reject with a different errno (EXDEV from RESOLVE_BENEATH);
|
||||
* post-fix, the front-door check catches every variant up front
|
||||
* with a consistent EINVAL across platforms.
|
||||
*
|
||||
* Not linked into rsync itself.
|
||||
*/
|
||||
|
||||
#include "rsync.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
int dry_run = 0;
|
||||
int am_root = 0;
|
||||
int am_sender = 0;
|
||||
int read_only = 0;
|
||||
int list_only = 0;
|
||||
int copy_links = 0;
|
||||
int copy_unsafe_links = 0;
|
||||
extern int am_daemon, am_chrooted;
|
||||
|
||||
short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
|
||||
|
||||
static int errs = 0;
|
||||
|
||||
static void check_relpath(const char *relpath)
|
||||
{
|
||||
int fd;
|
||||
int saved_errno;
|
||||
|
||||
errno = 0;
|
||||
fd = secure_relative_open(NULL, relpath, O_RDONLY | O_DIRECTORY, 0);
|
||||
saved_errno = errno;
|
||||
|
||||
if (fd >= 0) {
|
||||
fprintf(stderr,
|
||||
"FAIL [relpath=%-12s]: returned valid fd %d (escape) -- expected -1 EINVAL\n",
|
||||
relpath, fd);
|
||||
close(fd);
|
||||
errs++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (saved_errno != EINVAL) {
|
||||
fprintf(stderr,
|
||||
"FAIL [relpath=%-12s]: rejected but errno=%d (%s), expected EINVAL\n",
|
||||
relpath, saved_errno, strerror(saved_errno));
|
||||
errs++;
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr, "OK [relpath=%-12s]: rejected with EINVAL\n", relpath);
|
||||
}
|
||||
|
||||
static void check_basedir(const char *basedir)
|
||||
{
|
||||
int fd;
|
||||
int saved_errno;
|
||||
|
||||
errno = 0;
|
||||
fd = secure_relative_open(basedir, "ok", O_RDONLY | O_DIRECTORY, 0);
|
||||
saved_errno = errno;
|
||||
|
||||
if (fd >= 0) {
|
||||
fprintf(stderr,
|
||||
"FAIL [basedir=%-12s]: returned valid fd %d -- expected -1 EINVAL\n",
|
||||
basedir, fd);
|
||||
close(fd);
|
||||
errs++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (saved_errno != EINVAL) {
|
||||
fprintf(stderr,
|
||||
"FAIL [basedir=%-12s]: rejected but errno=%d (%s), expected EINVAL\n",
|
||||
basedir, saved_errno, strerror(saved_errno));
|
||||
errs++;
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr, "OK [basedir=%-12s]: rejected with EINVAL\n", basedir);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "usage: %s <test-dir>\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
if (chdir(argv[1]) < 0) {
|
||||
perror("chdir");
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* secure_relative_open's daemon-only confinement protections only
|
||||
* fire when am_daemon && !am_chrooted (the threat model is the
|
||||
* daemon-no-chroot deployment), but the front-door input
|
||||
* validation runs unconditionally. We set am_daemon anyway so the
|
||||
* helper exercises the same code shape the receiver does. */
|
||||
am_daemon = 1;
|
||||
am_chrooted = 0;
|
||||
|
||||
mkdir("subdir", 0755);
|
||||
|
||||
/* Each of these relpaths must be rejected with EINVAL at the
|
||||
* secure_relative_open() front door. ".." is the actual one-level
|
||||
* escape; the others ("subdir/..", "subdir/../subdir") resolve
|
||||
* back to the start dir on systems that allow them, but we still
|
||||
* reject them as defence-in-depth: a path containing a ".." token
|
||||
* is suspicious and the caller should normalise before passing
|
||||
* it in. The "../foo" / "foo/../bar" / "/foo" / "/" cases are
|
||||
* regression checks for the existing checks. */
|
||||
check_relpath("..");
|
||||
check_relpath("../foo");
|
||||
check_relpath("subdir/..");
|
||||
check_relpath("subdir/../subdir");
|
||||
check_relpath("foo/../bar");
|
||||
check_relpath("/foo");
|
||||
check_relpath("/");
|
||||
|
||||
/* Same checks against basedir (which the codex Finding 2 fix
|
||||
* routes through the same RESOLVE_BENEATH-equivalent). Absolute
|
||||
* basedirs are operator-trusted and intentionally not validated
|
||||
* here. */
|
||||
check_basedir("..");
|
||||
check_basedir("../subdir");
|
||||
check_basedir("subdir/..");
|
||||
check_basedir("foo/../bar");
|
||||
|
||||
if (errs)
|
||||
fprintf(stderr, "\n%d failure(s)\n", errs);
|
||||
return errs ? 1 : 0;
|
||||
}
|
||||
2
t_stub.c
2
t_stub.c
@@ -23,6 +23,8 @@
|
||||
|
||||
int do_fsync = 0;
|
||||
int inplace = 0;
|
||||
int am_daemon = 0;
|
||||
int am_chrooted = 0;
|
||||
int modify_window = 0;
|
||||
int preallocate_files = 0;
|
||||
int protect_args = 0;
|
||||
|
||||
113
testsuite/alt-dest-symlink-race.test
Executable file
113
testsuite/alt-dest-symlink-race.test
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Regression test for the basedir-confinement gap in
|
||||
# secure_relative_open(). The function opens basedir with a plain
|
||||
# openat(AT_FDCWD, basedir, O_RDONLY | O_DIRECTORY), without
|
||||
# RESOLVE_BENEATH or a per-component O_NOFOLLOW walk, so a parent
|
||||
# symlink ON basedir is followed unrestrictedly. RESOLVE_BENEATH is
|
||||
# then applied only to relpath, anchored at the wrong directory.
|
||||
#
|
||||
# The receiver's basis-file lookup at receiver.c passes
|
||||
# basis_dir[fnamecmp_type] (from --copy-dest / --link-dest /
|
||||
# --compare-dest -- all sender-controllable in daemon mode) as
|
||||
# basedir. A daemon-module attacker with write access can plant a
|
||||
# symlink at module/cd -> /outside, then run --link-dest=cd to
|
||||
# make the daemon's basis-file lookup resolve into /outside,
|
||||
# leaking the contents of daemon-readable files via the rsync
|
||||
# delta-rolling read-disclosure primitive.
|
||||
#
|
||||
# We detect the escape by leveraging --link-dest: when basis
|
||||
# matches source exactly (content + mtime + mode), --link-dest
|
||||
# hard-links the destination to the basis file. With the bug, the
|
||||
# destination ends up as a hard link to the outside-the-module
|
||||
# file (same inode). With the fix, no basis is found and the
|
||||
# destination is a fresh copy (different inode).
|
||||
#
|
||||
# The vulnerable code path is the same on every platform
|
||||
# (including the per-component fallback on systems without
|
||||
# RESOLVE_BENEATH), so this test is not platform-gated.
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
mod="$scratchdir/module"
|
||||
outside="$scratchdir/outside"
|
||||
src="$scratchdir/src"
|
||||
conf="$scratchdir/test-rsyncd.conf"
|
||||
|
||||
rm -rf "$mod" "$outside" "$src"
|
||||
mkdir -p "$mod" "$outside" "$src"
|
||||
|
||||
# Portable inode-number helper (GNU coreutils stat -c, BSD stat -f).
|
||||
file_inode() {
|
||||
stat -c %i "$1" 2>/dev/null || stat -f %i "$1"
|
||||
}
|
||||
|
||||
# Outside-the-module file an attacker would like the daemon to
|
||||
# treat as a basis.
|
||||
echo "OUTSIDE_SECRET_DATA" > "$outside/target.txt"
|
||||
chmod 0644 "$outside/target.txt"
|
||||
|
||||
# The symlink trap planted in the module by the local attacker.
|
||||
ln -s "$outside" "$mod/cd"
|
||||
|
||||
# Source file matches outside/target.txt exactly (content + mtime
|
||||
# + mode) so --link-dest will hard-link the destination to the
|
||||
# basis file iff the daemon's basedir lookup reaches outside/.
|
||||
echo "OUTSIDE_SECRET_DATA" > "$src/target.txt"
|
||||
touch -r "$outside/target.txt" "$src/target.txt"
|
||||
chmod 0644 "$src/target.txt"
|
||||
|
||||
# When running as root the daemon would drop to "nobody" by
|
||||
# default, which can't write into the test scratch dir. Force the
|
||||
# daemon to keep our uid/gid in that case so the basis-link
|
||||
# transfer can actually create the destination file. (Non-root
|
||||
# can't specify uid/gid in rsyncd.conf -- comment them out then.)
|
||||
my_uid=`get_testuid`
|
||||
root_uid=`get_rootuid`
|
||||
root_gid=`get_rootgid`
|
||||
uid_setting="uid = $root_uid"
|
||||
gid_setting="gid = $root_gid"
|
||||
if test x"$my_uid" != x"$root_uid"; then
|
||||
uid_setting="#$uid_setting"
|
||||
gid_setting="#$gid_setting"
|
||||
fi
|
||||
|
||||
cat > "$conf" <<EOF
|
||||
use chroot = no
|
||||
$uid_setting
|
||||
$gid_setting
|
||||
log file = $scratchdir/rsyncd.log
|
||||
[upload]
|
||||
path = $mod
|
||||
use chroot = no
|
||||
read only = no
|
||||
EOF
|
||||
|
||||
# Recursive --link-dest push directly into the module root. We
|
||||
# avoid pushing into a destination subdir because the receiver
|
||||
# would chdir into it before resolving --link-dest, making the
|
||||
# relative basedir "cd" resolve in the wrong CWD and masking the
|
||||
# bug. The realistic attack pushes into the module root (or the
|
||||
# attacker uses a basedir path that resolves correctly from
|
||||
# whichever subdir the receiver chdirs into).
|
||||
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
|
||||
$RSYNC -rtp --link-dest=cd "$src/" rsync://localhost/upload/ \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
if [ ! -f "$mod/target.txt" ]; then
|
||||
test_fail "destination file was not created -- daemon transfer failed before the test could observe the basedir behaviour"
|
||||
fi
|
||||
|
||||
outside_inode=$(file_inode "$outside/target.txt")
|
||||
dst_inode=$(file_inode "$mod/target.txt")
|
||||
|
||||
if [ "$outside_inode" = "$dst_inode" ]; then
|
||||
test_fail "basedir-escape: --link-dest hard-linked module/target.txt to outside/target.txt (inode $outside_inode); daemon's basis-file lookup followed the parent symlink on the basedir"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
206
testsuite/bare-do-open-symlink-race.test
Executable file
206
testsuite/bare-do-open-symlink-race.test
Executable file
@@ -0,0 +1,206 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Regression test for codex audit Findings 3b and 3c:
|
||||
#
|
||||
# 3b: generator.c:1905 -- the in-place backup creation opens
|
||||
# backupptr via bare do_open(O_WRONLY|O_CREAT|O_TRUNC|O_EXCL).
|
||||
# With --backup-dir set to an attacker-planted parent symlink,
|
||||
# the backup file is written outside the module under the
|
||||
# daemon's authority.
|
||||
#
|
||||
# 3c-symlink: syscall.c:207 -- do_symlink_at falls through to bare
|
||||
# do_symlink for am_root < 0 (fake-super), which then opens
|
||||
# the destination path with bare open() (final-component
|
||||
# fake-super file). A parent symlink on the destination path
|
||||
# redirects the file creation outside the module.
|
||||
#
|
||||
# 3c-mknod: syscall.c:506 -- do_mknod_at falls through to bare
|
||||
# do_mknod for am_root < 0, same path-based open(). For
|
||||
# FIFOs/sockets/devices the bare path is also used.
|
||||
#
|
||||
# Each scenario plants a "secret" file outside the module at a
|
||||
# location the symlink trap points to. The check is that the
|
||||
# outside file's content and mode are unchanged after the attack
|
||||
# attempt.
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
# All three scenarios depend on receiver-side daemon code paths
|
||||
# that are only secured on platforms with a working
|
||||
# secure_relative_open. The chdir/chmod tests already skip the
|
||||
# same set; mirror that.
|
||||
case "$(uname -s)" in
|
||||
SunOS|OpenBSD|NetBSD|CYGWIN*)
|
||||
test_skipped "secure_relative_open relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
|
||||
;;
|
||||
esac
|
||||
|
||||
mod="$scratchdir/module"
|
||||
outside="$scratchdir/outside"
|
||||
src="$scratchdir/src"
|
||||
conf="$scratchdir/test-rsyncd.conf"
|
||||
|
||||
# Portable inode-and-mode helpers.
|
||||
file_mode() {
|
||||
stat -c %a "$1" 2>/dev/null || stat -f %Lp "$1"
|
||||
}
|
||||
|
||||
setup() {
|
||||
rm -rf "$mod" "$outside" "$src"
|
||||
mkdir -p "$mod" "$outside" "$src"
|
||||
|
||||
echo "OUTSIDE_PROTECTED_DATA" > "$outside/target.txt"
|
||||
chmod 0644 "$outside/target.txt"
|
||||
outside_pristine="$scratchdir/outside-pristine.txt"
|
||||
cp -p "$outside/target.txt" "$outside_pristine"
|
||||
|
||||
ln -s "$outside" "$mod/cd"
|
||||
}
|
||||
|
||||
verify_outside_unchanged() {
|
||||
label="$1"
|
||||
mode=$(file_mode "$outside/target.txt")
|
||||
case "$mode" in
|
||||
644|0644) ;;
|
||||
*) test_fail "$label: outside/target.txt mode changed from 644 to $mode" ;;
|
||||
esac
|
||||
if ! cmp -s "$outside/target.txt" "$outside_pristine"; then
|
||||
test_fail "$label: outside/target.txt content changed -- daemon followed the cd symlink"
|
||||
fi
|
||||
}
|
||||
|
||||
verify_outside_unchanged_or_absent() {
|
||||
label="$1"
|
||||
target="$2" # specific file under outside/ to check absence of
|
||||
if [ -e "$outside/$target" ]; then
|
||||
test_fail "$label: outside/$target was created -- daemon followed the cd symlink"
|
||||
fi
|
||||
}
|
||||
|
||||
# When running as root the daemon would drop to "nobody" by default
|
||||
# and fail to write into the test scratch dir. Force it to keep our
|
||||
# uid/gid in that case so the receiver actually runs the code paths
|
||||
# we want to test.
|
||||
my_uid=`get_testuid`
|
||||
root_uid=`get_rootuid`
|
||||
root_gid=`get_rootgid`
|
||||
uid_setting="uid = $root_uid"
|
||||
gid_setting="gid = $root_gid"
|
||||
if test x"$my_uid" != x"$root_uid"; then
|
||||
uid_setting="#$uid_setting"
|
||||
gid_setting="#$gid_setting"
|
||||
fi
|
||||
|
||||
|
||||
############################################################
|
||||
# Scenario 3b: --inplace --backup --backup-dir=cd
|
||||
#
|
||||
# Pre-create module/target.txt so the receiver enters the in-place
|
||||
# update path; a backup of the existing content must be made
|
||||
# before the update. With --backup-dir=cd, backupptr resolves to
|
||||
# "cd/target.txt"; with the bug, robust_unlink and the bare
|
||||
# do_open at generator.c:1905 both follow the cd symlink, the
|
||||
# unlink deletes outside/target.txt and the create writes the
|
||||
# pre-existing module/target.txt content there.
|
||||
############################################################
|
||||
|
||||
setup
|
||||
echo "EXISTING_MODULE_DATA" > "$mod/target.txt"
|
||||
chmod 0666 "$mod/target.txt"
|
||||
echo "NEW_DATA_FROM_SENDER" > "$src/target.txt"
|
||||
chmod 0644 "$src/target.txt"
|
||||
|
||||
cat > "$conf" <<EOF
|
||||
use chroot = no
|
||||
$uid_setting
|
||||
$gid_setting
|
||||
log file = $scratchdir/rsyncd.log
|
||||
[upload]
|
||||
path = $mod
|
||||
use chroot = no
|
||||
read only = no
|
||||
EOF
|
||||
|
||||
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
|
||||
$RSYNC --inplace --backup --backup-dir=cd "$src/target.txt" \
|
||||
rsync://localhost/upload/target.txt >/dev/null 2>&1 || true
|
||||
|
||||
verify_outside_unchanged "3b inplace+backup-dir=cd"
|
||||
|
||||
|
||||
############################################################
|
||||
# Scenario 3c-symlink: fake-super symlink push to a path with a
|
||||
# symlinked parent
|
||||
#
|
||||
# With "fake super = yes" set on the module, the receiver
|
||||
# represents symlinks as fake-super files (regular files with the
|
||||
# link target written to them). The path-based open() in
|
||||
# do_symlink's fake-super branch follows parent symlinks. We push
|
||||
# a single symlink to the destination path "cd/sym" so the
|
||||
# receiver's create-file call lands at "cd/sym" relative to the
|
||||
# module root, where cd is the symlink trap.
|
||||
############################################################
|
||||
|
||||
setup
|
||||
|
||||
mkdir -p "$src/cd"
|
||||
ln -s /etc/passwd "$src/cd/sym"
|
||||
|
||||
cat > "$conf" <<EOF
|
||||
use chroot = no
|
||||
$uid_setting
|
||||
$gid_setting
|
||||
log file = $scratchdir/rsyncd.log
|
||||
[upload_fake]
|
||||
path = $mod
|
||||
use chroot = no
|
||||
read only = no
|
||||
fake super = yes
|
||||
EOF
|
||||
|
||||
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
|
||||
$RSYNC -rl "$src/" rsync://localhost/upload_fake/ >/dev/null 2>&1 || true
|
||||
|
||||
verify_outside_unchanged_or_absent "3c-symlink fake-super symlink push" "sym"
|
||||
|
||||
|
||||
############################################################
|
||||
# Scenario 3c-mknod: fake-super FIFO push to a path with a
|
||||
# symlinked parent
|
||||
#
|
||||
# Similar to 3c-symlink but for special files. mkfifo works
|
||||
# without root; we push a FIFO and verify the receiver doesn't
|
||||
# create a fake-super file at outside/fifo.
|
||||
############################################################
|
||||
|
||||
setup
|
||||
|
||||
mkdir -p "$src/cd"
|
||||
mkfifo "$src/cd/fifo" 2>/dev/null
|
||||
if [ ! -p "$src/cd/fifo" ]; then
|
||||
test_skipped "mkfifo unavailable; cannot exercise 3c-mknod"
|
||||
fi
|
||||
|
||||
cat > "$conf" <<EOF
|
||||
use chroot = no
|
||||
$uid_setting
|
||||
$gid_setting
|
||||
log file = $scratchdir/rsyncd.log
|
||||
[upload_fake]
|
||||
path = $mod
|
||||
use chroot = no
|
||||
read only = no
|
||||
fake super = yes
|
||||
EOF
|
||||
|
||||
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
|
||||
$RSYNC -rD "$src/" rsync://localhost/upload_fake/ >/dev/null 2>&1 || true
|
||||
|
||||
verify_outside_unchanged_or_absent "3c-mknod fake-super FIFO push" "fifo"
|
||||
|
||||
exit 0
|
||||
135
testsuite/chdir-symlink-race.test
Executable file
135
testsuite/chdir-symlink-race.test
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Regression test for the symlink-TOCTOU class of bug at the receiver's
|
||||
# chdir(). After the CVE-2026-29518 fix to secure_relative_open(), an
|
||||
# attack remained where the receiver's chdir() into a destination
|
||||
# subdirectory followed an attacker-planted symlink, escaping the
|
||||
# module. Every subsequent path-relative syscall (open, chmod, lchown,
|
||||
# utimes, etc.) inherited the escape -- secure_relative_open's
|
||||
# RESOLVE_BENEATH anchor itself was outside the module by then, so it
|
||||
# stopped protecting against anything.
|
||||
#
|
||||
# This test runs an actual rsync daemon (via RSYNC_CONNECT_PROG to
|
||||
# avoid the network) configured with "use chroot = no", plants a
|
||||
# symlink at module/subdir -> ../outside, and runs four flavours of
|
||||
# rsync transfer that previously all reached files in ../outside:
|
||||
#
|
||||
# 1. single-file dest = subdir/target.txt (the original poc_chmod)
|
||||
# 2. -r src/subdir/ to upload/subdir/ (the chdir-escape case)
|
||||
# 3. -r src/subdir/ to upload/subdir/ (no --size-only: forces basis read+write)
|
||||
# 4. -r src/ to upload/ (was already protected by the
|
||||
# original CVE-2026-29518 fix;
|
||||
# regression-checked here)
|
||||
#
|
||||
# All four must leave the outside-the-module sentinel file's mode AND
|
||||
# content unchanged.
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
case "$(uname -s)" in
|
||||
SunOS|OpenBSD|NetBSD|CYGWIN*)
|
||||
test_skipped "secure chdir relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
|
||||
;;
|
||||
esac
|
||||
|
||||
mod="$scratchdir/module"
|
||||
outside="$scratchdir/outside"
|
||||
src="$scratchdir/src"
|
||||
conf="$scratchdir/test-rsyncd.conf"
|
||||
|
||||
rm -rf "$mod" "$outside" "$src"
|
||||
mkdir -p "$mod" "$outside" "$src" "$src/subdir"
|
||||
|
||||
# Portable octal-mode helper -- macOS and FreeBSD's stat use -f, GNU
|
||||
# coreutils stat uses -c.
|
||||
file_mode() {
|
||||
stat -c %a "$1" 2>/dev/null || stat -f %Lp "$1"
|
||||
}
|
||||
|
||||
# The "secret" file outside the module the attacker is trying to alter.
|
||||
# Save a pristine copy alongside it so we can compare with cmp(1) rather
|
||||
# than depending on sha1sum/shasum/sha1, which differ across platforms.
|
||||
echo "OUTSIDE_SECRET_DATA" > "$outside/target.txt"
|
||||
chmod 0600 "$outside/target.txt"
|
||||
outside_pristine="$scratchdir/outside-pristine.txt"
|
||||
cp -p "$outside/target.txt" "$outside_pristine"
|
||||
|
||||
# Symlink trap planted in the module by the local attacker.
|
||||
ln -s "$outside" "$mod/subdir"
|
||||
|
||||
# Source files the sender will push: same size as the outside target,
|
||||
# different content, mode 0666 (the perms the attacker tries to push).
|
||||
SIZE=$(stat -c %s "$outside/target.txt" 2>/dev/null \
|
||||
|| stat -f %z "$outside/target.txt")
|
||||
head -c "$SIZE" /dev/urandom > "$src/target.txt"
|
||||
head -c "$SIZE" /dev/urandom > "$src/subdir/target.txt"
|
||||
chmod 0666 "$src/target.txt" "$src/subdir/target.txt"
|
||||
|
||||
cat > "$conf" <<EOF
|
||||
use chroot = no
|
||||
log file = $scratchdir/rsyncd.log
|
||||
[upload]
|
||||
path = $mod
|
||||
use chroot = no
|
||||
read only = no
|
||||
EOF
|
||||
|
||||
reset_outside() {
|
||||
chmod 0600 "$outside/target.txt"
|
||||
echo "OUTSIDE_SECRET_DATA" > "$outside/target.txt"
|
||||
}
|
||||
|
||||
verify_unchanged() {
|
||||
label="$1"
|
||||
mode=$(file_mode "$outside/target.txt")
|
||||
case "$mode" in
|
||||
600|0600) ;;
|
||||
*) test_fail "$label: outside file mode changed from 600 to $mode (chmod escape)" ;;
|
||||
esac
|
||||
if ! cmp -s "$outside/target.txt" "$outside_pristine"; then
|
||||
test_fail "$label: outside file content changed (write escape)"
|
||||
fi
|
||||
}
|
||||
|
||||
run_attack() {
|
||||
label="$1"; shift
|
||||
reset_outside
|
||||
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
|
||||
$RSYNC "$@" >/dev/null 2>&1 || true
|
||||
verify_unchanged "$label"
|
||||
}
|
||||
|
||||
# 1. The original poc_chmod scenario: single file, dest path with
|
||||
# the symlinked subdir as a path component. With --size-only the
|
||||
# receiver normally skips the basis open and goes straight to chmod
|
||||
# -- only the chdir-escape blocks the chmod from reaching outside.
|
||||
run_attack "single-file --size-only" \
|
||||
-tp --size-only \
|
||||
"$src/target.txt" rsync://localhost/upload/subdir/target.txt
|
||||
|
||||
# 2. -r push into the symlinked subdir: receiver chdir's into "subdir",
|
||||
# follows the symlink, ends up in outside.
|
||||
run_attack "-r --size-only into subdir/" \
|
||||
-rtp --size-only \
|
||||
"$src/subdir/" rsync://localhost/upload/subdir/
|
||||
|
||||
# 3. Same but no --size-only -- forces the basis-file open and a real
|
||||
# rename, so this exercises the read-disclosure and write-escape
|
||||
# paths together.
|
||||
run_attack "-r without --size-only into subdir/" \
|
||||
-rtp \
|
||||
"$src/subdir/" rsync://localhost/upload/subdir/
|
||||
|
||||
# 4. -r src/ to upload/ -- this case was already covered by the
|
||||
# original CVE-2026-29518 fix because the receiver stays at module
|
||||
# root and operates on slashed paths. Regression check.
|
||||
run_attack "-r --size-only into upload/ root" \
|
||||
-rtp --size-only \
|
||||
"$src/" rsync://localhost/upload/
|
||||
|
||||
exit 0
|
||||
68
testsuite/chmod-symlink-race.test
Executable file
68
testsuite/chmod-symlink-race.test
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Regression test for the symlink-TOCTOU class of bug applied to
|
||||
# chmod() on the receiver side. The CVE-2026-29518 fix used
|
||||
# secure_relative_open() for the basis-file open, but every other
|
||||
# path-based syscall the receiver runs on sender-controllable paths
|
||||
# is vulnerable to the same primitive: a local attacker swaps a
|
||||
# symlink into one of the parent directory components between the
|
||||
# receiver's check and its act, and the syscall escapes the module.
|
||||
#
|
||||
# This test exercises the new do_chmod_at() wrapper via the
|
||||
# t_chmod_secure helper. The helper sets up two scenarios:
|
||||
# - a parent dir-symlink that resolves WITHIN the module tree
|
||||
# (legitimate -K-style use, must continue to work)
|
||||
# - a parent dir-symlink that escapes the module tree (the
|
||||
# attack, must be rejected)
|
||||
# plus two regression scenarios (plain relative path, top-level
|
||||
# file) that just confirm the safe wrapper doesn't break the
|
||||
# normal case.
|
||||
#
|
||||
# The kernel-enforced "stay below dirfd" path resolution is
|
||||
# only available on Linux 5.6+, FreeBSD 13+, and macOS 15+.
|
||||
# Skip on platforms that fall back to per-component O_NOFOLLOW
|
||||
# (Solaris, OpenBSD, NetBSD, Cygwin); the per-component fallback
|
||||
# would also reject the attack but the legitimate dir-symlink
|
||||
# scenario would fail there.
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
case "$(uname -s)" in
|
||||
SunOS|OpenBSD|NetBSD|CYGWIN*)
|
||||
test_skipped "do_chmod_at relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
|
||||
;;
|
||||
esac
|
||||
|
||||
mod="$scratchdir/module"
|
||||
trap_outside="$scratchdir/trap"
|
||||
rm -rf "$mod" "$trap_outside"
|
||||
mkdir -p "$mod/realdir" "$trap_outside"
|
||||
|
||||
# Set up the four file-system objects the helper expects:
|
||||
echo bystander > "$mod/realdir/sentinel"
|
||||
chmod 0600 "$mod/realdir/sentinel"
|
||||
echo target > "$trap_outside/sentinel"
|
||||
chmod 0600 "$trap_outside/sentinel"
|
||||
ln -s realdir "$mod/inside_link"
|
||||
ln -s ../trap "$mod/escape_link"
|
||||
echo top > "$mod/topfile"
|
||||
chmod 0600 "$mod/topfile"
|
||||
|
||||
"$TOOLDIR/t_chmod_secure" "$mod" || \
|
||||
test_fail "t_chmod_secure reported failures (see stderr above)"
|
||||
|
||||
# Sanity-check from the shell side too: the outside file's mode must
|
||||
# still be 0600 -- the helper checked this, but a second look from
|
||||
# the shell guards against a helper-internal stat() bug.
|
||||
mode=$(stat -c '%a' "$trap_outside/sentinel" 2>/dev/null \
|
||||
|| stat -f '%Lp' "$trap_outside/sentinel" 2>/dev/null)
|
||||
if [ "$mode" != "600" ]; then
|
||||
test_fail "outside sentinel mode changed from 600 to $mode -- chmod escaped the module"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
68
testsuite/clean-fname-underflow.test
Normal file
68
testsuite/clean-fname-underflow.test
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/bin/sh
|
||||
# clean-fname-underflow.test
|
||||
# Ensure clean_fname() does not read-before-buffer when collapsing "..".
|
||||
# This exercises the --server path where a crafted merge filename hits clean_fname().
|
||||
#
|
||||
# Usage:
|
||||
# ./configure && make
|
||||
# make check TESTS='clean-fname-underflow.test'
|
||||
|
||||
set -eu
|
||||
|
||||
# Try to find the just-built rsync binary if RSYNC_BIN isn't set.
|
||||
if [ -z "${RSYNC_BIN:-}" ]; then
|
||||
if [ -x "./rsync" ]; then
|
||||
RSYNC_BIN=./rsync
|
||||
elif [ -x "../rsync" ]; then
|
||||
RSYNC_BIN=../rsync
|
||||
else
|
||||
RSYNC_BIN=rsync
|
||||
fi
|
||||
fi
|
||||
|
||||
workdir="${TMPDIR:-/tmp}/rsync-clean-fname.$$"
|
||||
mkdir -p "$workdir"
|
||||
# Solaris's rm refuses to delete a directory in the path of the cwd,
|
||||
# so cd out before the trap runs.
|
||||
trap 'cd /; rm -rf "$workdir"' EXIT INT TERM
|
||||
cd "$workdir"
|
||||
|
||||
# Minimal rsyncd.conf using chroot so the crafted path reaches the server parser.
|
||||
cat > rsyncd.conf <<'EOF'
|
||||
pid file = rsyncd.pid
|
||||
use chroot = true
|
||||
[mod]
|
||||
path = ./mod
|
||||
read only = false
|
||||
EOF
|
||||
mkdir -p mod
|
||||
|
||||
# Start daemon on a random high port.
|
||||
PORT=$(awk 'BEGIN{srand(); printf "%d", 20000+int(rand()*20000)}')
|
||||
"$RSYNC_BIN" --daemon --no-detach --config=rsyncd.conf --port="$PORT" >/dev/null 2>&1 &
|
||||
DAEMON_PID=$!
|
||||
# Give the daemon a moment to come up.
|
||||
sleep 0.3
|
||||
|
||||
# Invoke the server-side path. We don't need a real transfer; we just want to
|
||||
# ensure clean_fname() doesn't crash when given "a/../test" via --filter=merge.
|
||||
EXIT_OK=0
|
||||
if "$RSYNC_BIN" --server --sender -vlr --filter='merge a/../test' . mod/ >/dev/null 2>&1; then
|
||||
EXIT_OK=1
|
||||
else
|
||||
status=$?
|
||||
# Non-zero exit is expected for bogus input; ensure it wasn't a signal/crash.
|
||||
if [ $status -lt 128 ]; then
|
||||
EXIT_OK=1
|
||||
fi
|
||||
fi
|
||||
|
||||
kill "$DAEMON_PID" >/dev/null 2>&1 || true
|
||||
|
||||
if [ "$EXIT_OK" -ne 1 ]; then
|
||||
echo "clean-fname-underflow.test: rsync exited due to a signal or unexpected status"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "OK: clean_fname() handled 'a/../test' without crashing"
|
||||
exit 0
|
||||
98
testsuite/copy-dest-source-symlink.test
Executable file
98
testsuite/copy-dest-source-symlink.test
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Regression test for codex audit Finding 3a: copy_file()'s source
|
||||
# open in copy_altdest_file() is via do_open_nofollow(), which only
|
||||
# refuses a final-component symlink. Parent components are still
|
||||
# resolved with normal symlink-following. A daemon module attacker
|
||||
# who plants a parent symlink at module/cd -> /outside, then runs
|
||||
# --copy-dest=cd against a source file matching the size+mtime of
|
||||
# /outside/target.txt, drives the receiver to:
|
||||
#
|
||||
# 1. Find a match-level >= 2 basis at "cd/target.txt"
|
||||
# 2. Call copy_altdest_file -> copy_file(src="cd/target.txt", ...)
|
||||
# 3. do_open_nofollow follows the "cd" parent symlink and reads
|
||||
# the contents of /outside/target.txt under the daemon's
|
||||
# authority
|
||||
# 4. Copy that content into the module destination
|
||||
#
|
||||
# Result: outside/target.txt content lands at module/target.txt,
|
||||
# accessible to the attacker on a subsequent pull.
|
||||
#
|
||||
# We detect by content: src/target.txt and outside/target.txt have
|
||||
# identical metadata (size + mtime + mode) but different content.
|
||||
# After the transfer, module/target.txt should match src (no
|
||||
# basedir escape) -- if it matches outside, the bug copied across
|
||||
# the symlink boundary.
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
mod="$scratchdir/module"
|
||||
outside="$scratchdir/outside"
|
||||
src="$scratchdir/src"
|
||||
conf="$scratchdir/test-rsyncd.conf"
|
||||
|
||||
rm -rf "$mod" "$outside" "$src"
|
||||
mkdir -p "$mod" "$outside" "$src"
|
||||
|
||||
# Outside-the-module file the daemon should not read on the
|
||||
# attacker's behalf.
|
||||
echo "OUTSIDE_LEAKED_DATA!" > "$outside/target.txt"
|
||||
chmod 0644 "$outside/target.txt"
|
||||
|
||||
# The symlink trap.
|
||||
ln -s "$outside" "$mod/cd"
|
||||
|
||||
# Source: same size, same mtime, same mode as outside -- so the
|
||||
# generator's link_stat + quick_check_ok finds a match-level >= 2
|
||||
# basis and calls copy_altdest_file.
|
||||
echo "ATTACKER_KNOWN_DATA!" > "$src/target.txt"
|
||||
touch -r "$outside/target.txt" "$src/target.txt"
|
||||
chmod 0644 "$src/target.txt"
|
||||
|
||||
# When running as root the daemon would drop to "nobody" by
|
||||
# default and fail to mkstemp in the scratch dir; force it to
|
||||
# keep our uid/gid in that case.
|
||||
my_uid=`get_testuid`
|
||||
root_uid=`get_rootuid`
|
||||
root_gid=`get_rootgid`
|
||||
uid_setting="uid = $root_uid"
|
||||
gid_setting="gid = $root_gid"
|
||||
if test x"$my_uid" != x"$root_uid"; then
|
||||
uid_setting="#$uid_setting"
|
||||
gid_setting="#$gid_setting"
|
||||
fi
|
||||
|
||||
cat > "$conf" <<EOF
|
||||
use chroot = no
|
||||
$uid_setting
|
||||
$gid_setting
|
||||
log file = $scratchdir/rsyncd.log
|
||||
[upload]
|
||||
path = $mod
|
||||
use chroot = no
|
||||
read only = no
|
||||
EOF
|
||||
|
||||
# --copy-dest push to module root.
|
||||
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
|
||||
$RSYNC -rtp --copy-dest=cd "$src/" rsync://localhost/upload/ \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
if [ ! -f "$mod/target.txt" ]; then
|
||||
test_fail "destination file was not created -- daemon transfer failed before the test could observe the basedir behaviour"
|
||||
fi
|
||||
|
||||
if cmp -s "$mod/target.txt" "$outside/target.txt"; then
|
||||
test_fail "basedir-escape via copy_file source: module/target.txt now contains the contents of outside/target.txt -- daemon read /outside via the cd symlink and copied it into the module"
|
||||
fi
|
||||
|
||||
if ! cmp -s "$mod/target.txt" "$src/target.txt"; then
|
||||
test_fail "destination doesn't match source content (and isn't outside content either): unexpected state"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
111
testsuite/daemon-chroot-acl.test
Normal file
111
testsuite/daemon-chroot-acl.test
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Regression test for GHSA-rjfm-3w2m-jf4f: a hostname-based "hosts deny"
|
||||
# rule must still match when the daemon performs a 'daemon chroot' and
|
||||
# the chroot does not contain the NSS files glibc needs for reverse DNS.
|
||||
#
|
||||
# Pre-fix, reverse DNS happened *after* the daemon chroot. With an empty
|
||||
# chroot the NSS lookup failed, client_name() returned "UNKNOWN", and a
|
||||
# deny rule referring to the connecting hostname silently failed to
|
||||
# match.
|
||||
#
|
||||
# Two scenarios are exercised so we can distinguish the case the fix
|
||||
# definitely covers from the per-module path that may still be
|
||||
# vulnerable:
|
||||
# A. global "reverse lookup = yes" (covered by b6abdb4c)
|
||||
# B. only module "reverse lookup = yes" (gap to verify)
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
case `uname -s` in
|
||||
Linux*) ;;
|
||||
*) test_skipped "test is Linux-specific (uses chroot+unshare)" ;;
|
||||
esac
|
||||
|
||||
# We need CAP_SYS_CHROOT. Re-exec under a user namespace if not root.
|
||||
if ! chroot / /bin/true 2>/dev/null; then
|
||||
if [ -z "$RSYNC_UNSHARED" ] && unshare --user --map-root-user true 2>/dev/null; then
|
||||
echo "Re-running under unshare --user --map-root-user..."
|
||||
RSYNC_UNSHARED=1 exec unshare --user --map-root-user "$SHELL_PATH" $RUNSHFLAGS "$0"
|
||||
fi
|
||||
test_skipped "need CAP_SYS_CHROOT (root or unshare --user --map-root-user)"
|
||||
fi
|
||||
|
||||
# We need 127.0.0.1 to reverse-resolve to a real hostname while NSS is
|
||||
# still working (i.e. before the daemon's chroot). The daemon will
|
||||
# look that name up itself as part of its hostname-based ACL check;
|
||||
# we then deny that name and assert the connection is rejected.
|
||||
client_hostname=`getent hosts 127.0.0.1 2>/dev/null | awk 'NR==1 {print $2}'`
|
||||
if [ -z "$client_hostname" ] || [ "$client_hostname" = "127.0.0.1" ]; then
|
||||
test_skipped "no reverse DNS for 127.0.0.1"
|
||||
fi
|
||||
|
||||
chrootdir="$scratchdir/chroot"
|
||||
rm -rf "$chrootdir"
|
||||
mkdir -p "$chrootdir/modroot"
|
||||
echo "from chroot" > "$chrootdir/modroot/file1"
|
||||
|
||||
conf="$scratchdir/test-rsyncd.conf"
|
||||
logfile="$scratchdir/rsyncd.log"
|
||||
|
||||
write_conf() {
|
||||
cat >"$conf" <<EOF
|
||||
use chroot = no
|
||||
log file = $logfile
|
||||
daemon chroot = $chrootdir
|
||||
reverse lookup = $1
|
||||
hosts deny = $client_hostname
|
||||
max verbosity = 4
|
||||
|
||||
[chrootmod]
|
||||
path = /modroot
|
||||
read only = yes
|
||||
reverse lookup = $2
|
||||
EOF
|
||||
}
|
||||
|
||||
# Run a transfer and return 0 if the daemon refused with @ERROR access
|
||||
# denied (the expected outcome when the deny rule matches).
|
||||
run_check() {
|
||||
label="$1"
|
||||
|
||||
rm -f "$logfile"
|
||||
rm -rf "$todir"
|
||||
mkdir -p "$todir"
|
||||
|
||||
out="$scratchdir/run.out"
|
||||
|
||||
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
|
||||
$RSYNC -av localhost::chrootmod/ "$todir/" >"$out" 2>&1
|
||||
rc=$?
|
||||
|
||||
echo "----- $label (rsync exit $rc):"
|
||||
cat "$out"
|
||||
echo "----- daemon log:"
|
||||
[ -f "$logfile" ] && cat "$logfile"
|
||||
echo "-----"
|
||||
|
||||
grep -q '@ERROR.*access denied' "$out"
|
||||
}
|
||||
|
||||
# Scenario A: global reverse lookup. Covered by b6abdb4c.
|
||||
write_conf yes yes
|
||||
if ! run_check "Scenario A (global reverse lookup = yes)"; then
|
||||
test_fail "Scenario A: hostname deny rule was bypassed"
|
||||
fi
|
||||
|
||||
# Scenario B: only the per-module reverse-lookup setting is enabled.
|
||||
# The b6abdb4c fix only pre-warms client_name()'s cache when the
|
||||
# global setting is on, so the post-chroot lookup in this path may
|
||||
# still produce "UNKNOWN" and bypass the deny rule.
|
||||
write_conf no yes
|
||||
if ! run_check "Scenario B (per-module reverse lookup only)"; then
|
||||
test_fail "Scenario B: hostname deny rule was bypassed (per-module reverse lookup with daemon chroot still has the bypass)"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
51
testsuite/daemon-refuse-compress.test
Normal file
51
testsuite/daemon-refuse-compress.test
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Test that a daemon module configured with "refuse options = compress"
|
||||
# rejects clients that ask for compression and still serves the same
|
||||
# transfer when the client does not.
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
build_rsyncd_conf
|
||||
|
||||
# Append a module that refuses --compress (-z).
|
||||
cat >>"$conf" <<EOF
|
||||
|
||||
[no-compress]
|
||||
path = $fromdir
|
||||
read only = yes
|
||||
refuse options = compress
|
||||
EOF
|
||||
|
||||
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon"
|
||||
export RSYNC_CONNECT_PROG
|
||||
|
||||
hands_setup
|
||||
|
||||
# Build a reference tree mirroring the daemon's global exclude rule.
|
||||
$RSYNC -av --exclude=foobar.baz "$fromdir/" "$chkdir/"
|
||||
|
||||
# A compressed transfer must be refused.
|
||||
errlog="$scratchdir/refuse.err"
|
||||
if $RSYNC -avz localhost::no-compress/ "$todir/" >/dev/null 2>"$errlog"; then
|
||||
cat "$errlog" >&2
|
||||
test_fail "compressed transfer was not refused"
|
||||
fi
|
||||
|
||||
grep -- '--compress' "$errlog" >/dev/null || {
|
||||
cat "$errlog" >&2
|
||||
test_fail "expected refuse error mentioning --compress"
|
||||
}
|
||||
|
||||
# The same transfer without -z must succeed.
|
||||
rm -rf "$todir"
|
||||
mkdir "$todir"
|
||||
checkit "$RSYNC -av localhost::no-compress/ '$todir/'" "$chkdir" "$todir"
|
||||
|
||||
# The script would have aborted on error, so getting here means we've won.
|
||||
exit 0
|
||||
@@ -81,7 +81,8 @@ diff $diffopt "$name1" "$todir" || test_fail "solo copy of name1 failed"
|
||||
# enabled (this has broken in 3.4.0 so far, so we need this test).
|
||||
rm -rf "$fromdir" "$todir"
|
||||
makepath "$fromdir/sym" "$todir"
|
||||
checkit "$RSYNC -aH '$fromdir/sym' '$todir'" "$fromdir" "$todir"
|
||||
$RSYNC -aH "$fromdir/sym" "$todir"
|
||||
diff $diffopt "$fromdir" "$todir" || test_fail "solo copy of sym failed"
|
||||
|
||||
# The script would have aborted on error, so getting here means we've won.
|
||||
exit 0
|
||||
|
||||
128
testsuite/proxy-response-line-too-long.test
Executable file
128
testsuite/proxy-response-line-too-long.test
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Regression test for the off-by-one stack OOB write in
|
||||
# establish_proxy_connection() in socket.c when a malicious or
|
||||
# man-in-the-middle HTTP proxy returns a first response line of
|
||||
# 1023+ bytes without a '\n' terminator.
|
||||
#
|
||||
# Pre-fix: the read loop walked buffer[0..sizeof-2] one byte at a
|
||||
# time, then post-loop logic did "if (*cp != '\n') cp++; *cp-- =
|
||||
# '\0';". If no newline arrived before the loop filled the buffer,
|
||||
# cp was left at &buffer[sizeof-1] (never written by the loop),
|
||||
# *cp held stale stack bytes, and cp++ pushed cp one past the array.
|
||||
# The null-termination then wrote one byte out of bounds on the
|
||||
# stack. AddressSanitizer reports stack-buffer-overflow at the
|
||||
# null-termination site.
|
||||
#
|
||||
# Post-fix: the bound-exhaustion case is detected by position and
|
||||
# rejected with an "proxy response line too long" message, so no
|
||||
# OOB write occurs and rsync exits with a non-signal status.
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
command -v python3 >/dev/null 2>&1 || test_skipped "python3 not available"
|
||||
|
||||
workdir="$scratchdir/workdir"
|
||||
mkdir -p "$workdir"
|
||||
cd "$workdir"
|
||||
|
||||
port_file="$workdir/port"
|
||||
proxy_log="$workdir/proxy.log"
|
||||
|
||||
# A minimal TCP listener: binds to an ephemeral port on 127.0.0.1,
|
||||
# writes the chosen port to $port_file *before* accept() so the test
|
||||
# can synchronise without a sleep, accepts one connection, reads
|
||||
# until end-of-headers or 64 KiB, sends exactly 1023 bytes of 'X'
|
||||
# with no '\n', then closes.
|
||||
python3 - "$port_file" >"$proxy_log" 2>&1 <<'PYEOF' &
|
||||
import socket, sys, os
|
||||
port_file = sys.argv[1]
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(("127.0.0.1", 0))
|
||||
port = s.getsockname()[1]
|
||||
tmp = port_file + ".tmp"
|
||||
with open(tmp, "w") as fp:
|
||||
fp.write("%d\n" % port)
|
||||
os.rename(tmp, port_file) # atomic visibility to the shell side
|
||||
s.listen(1)
|
||||
conn, _ = s.accept()
|
||||
conn.settimeout(5)
|
||||
try:
|
||||
data = b""
|
||||
while b"\r\n\r\n" not in data and len(data) < 65536:
|
||||
chunk = conn.recv(8192)
|
||||
if not chunk:
|
||||
break
|
||||
data += chunk
|
||||
except socket.timeout:
|
||||
pass
|
||||
conn.sendall(b"X" * 1023) # exactly the buffer-1 trigger size
|
||||
try:
|
||||
conn.shutdown(socket.SHUT_RDWR)
|
||||
except OSError:
|
||||
pass
|
||||
conn.close()
|
||||
s.close()
|
||||
PYEOF
|
||||
proxy_pid=$!
|
||||
|
||||
# Wait up to ~10s for the listener to publish its port.
|
||||
i=0
|
||||
while [ ! -s "$port_file" ] && [ $i -lt 10 ]; do
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
if [ ! -s "$port_file" ]; then
|
||||
kill "$proxy_pid" 2>/dev/null
|
||||
cat "$proxy_log" >&2 2>/dev/null
|
||||
test_fail "proxy listener never published a port"
|
||||
fi
|
||||
|
||||
port=`cat "$port_file"`
|
||||
case "$port" in
|
||||
*[!0-9]*|"") kill "$proxy_pid" 2>/dev/null; test_fail "bogus port from listener: '$port'" ;;
|
||||
esac
|
||||
|
||||
# Run rsync through the malicious proxy. Any rsync:// URL works:
|
||||
# the proxy intercepts the CONNECT and never forwards anywhere.
|
||||
rsync_err="$workdir/rsync.err"
|
||||
|
||||
# rsync MUST exit non-zero here (the proxy is misbehaving).
|
||||
# Use `|| status=$?` so we capture the real exit code under `sh -e`;
|
||||
# `if ! cmd; then status=$?` would only ever see 0 because the `!`
|
||||
# is the last command before `$?`.
|
||||
status=0
|
||||
RSYNC_PROXY="127.0.0.1:$port" \
|
||||
$RSYNC rsync://example.invalid:873/whatever/ "$workdir/out/" \
|
||||
>/dev/null 2>"$rsync_err" || status=$?
|
||||
|
||||
# Reap the listener.
|
||||
wait "$proxy_pid" 2>/dev/null || true
|
||||
|
||||
# 1. rsync must not have crashed (SIGSEGV/SIGABRT report >= 128).
|
||||
if [ "$status" -ge 128 ]; then
|
||||
cat "$rsync_err" >&2
|
||||
test_fail "rsync killed by signal (status=$status) -- possible stack OOB regression"
|
||||
fi
|
||||
|
||||
# 2. rsync must have actually exited non-zero (i.e. saw the bad proxy).
|
||||
if [ "$status" -eq 0 ]; then
|
||||
cat "$rsync_err" >&2
|
||||
test_fail "rsync returned success despite malformed proxy response"
|
||||
fi
|
||||
|
||||
# 3. The new error message must appear.
|
||||
if ! grep -q "proxy response line too long" "$rsync_err"; then
|
||||
cat "$rsync_err" >&2
|
||||
test_fail "expected 'proxy response line too long' in rsync stderr"
|
||||
fi
|
||||
|
||||
echo "OK: over-long proxy response line rejected cleanly without crashing"
|
||||
exit 0
|
||||
34
testsuite/secure-relpath-validation.test
Executable file
34
testsuite/secure-relpath-validation.test
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Regression test for codex audit Finding 5: secure_relative_open()'s
|
||||
# front-door input check rejects "../foo" and "foo/../bar" but
|
||||
# misses bare "..", "subdir/..", and other variants whose "/"-split
|
||||
# components contain a literal "..". The kernel-enforced
|
||||
# RESOLVE_BENEATH (Linux 5.6+) and O_RESOLVE_BENEATH
|
||||
# (FreeBSD 13+, macOS 15+) reject these in-kernel; the per-component
|
||||
# walk fallback used on NetBSD, OpenBSD, Solaris, Cygwin and pre-5.6
|
||||
# Linux does not -- so the validation must happen at the front door.
|
||||
#
|
||||
# This test invokes the t_secure_relpath helper, which calls
|
||||
# secure_relative_open() with each suspect input and verifies the
|
||||
# return value is -1 with errno == EINVAL. EINVAL is the marker
|
||||
# that the front-door rejected the input, not the kernel; pre-fix
|
||||
# the kernel returns -1 with EXDEV (or, on the per-component
|
||||
# fallback, may return a valid fd at all -- "escape").
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
testdir="$scratchdir/relpath-test"
|
||||
rm -rf "$testdir"
|
||||
mkdir -p "$testdir"
|
||||
|
||||
if ! "$TOOLDIR/t_secure_relpath" "$testdir"; then
|
||||
test_fail "t_secure_relpath rejected one or more inputs incorrectly (see stderr above for the specific case)"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
90
testsuite/sender-flist-symlink-leak.test
Executable file
90
testsuite/sender-flist-symlink-leak.test
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (C) 2026 by Andrew Tridgell
|
||||
|
||||
# This program is distributable under the terms of the GNU GPL (see
|
||||
# COPYING).
|
||||
|
||||
# Regression test for codex re-check finding: the sender-side file-
|
||||
# list generator can still follow an attacker-planted symlink out of
|
||||
# the module via change_pathname() -> change_dir(...,CD_SKIP_CHDIR)
|
||||
# followed by change_dir(...,CD_NORMAL). The CD_SKIP_CHDIR sets
|
||||
# skipped_chdir=1, and the next CD_NORMAL call's secure-branch in
|
||||
# util1.c is gated on `!skipped_chdir`, so the secure path is
|
||||
# bypassed and a raw chdir(curr_dir) follows attacker-controlled
|
||||
# symlinks during flist generation.
|
||||
#
|
||||
# Reach: rsync daemon module with `use chroot = no`. A local
|
||||
# attacker plants module/cd -> /outside. A client (innocent or
|
||||
# malicious) pulls rsync://<daemon>/<module>/cd/. The daemon, as
|
||||
# sender, enumerates files in /outside and ships their metadata
|
||||
# (names, sizes, modes, mtimes) to the client. The actual content
|
||||
# transfer fails later at the secure_relative_open step with EXDEV,
|
||||
# but by then the metadata has already leaked.
|
||||
#
|
||||
# We detect by running a dry-run pull of the symlinked subdir and
|
||||
# checking whether the client's --list-only output mentions any
|
||||
# file from /outside. With the bug, /outside/secret.txt appears in
|
||||
# the list with its size; with the fix, the daemon's chdir into
|
||||
# the symlinked subdir is rejected and no /outside file is listed.
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
case "$(uname -s)" in
|
||||
SunOS|OpenBSD|NetBSD|CYGWIN*)
|
||||
test_skipped "secure change_dir relies on RESOLVE_BENEATH-equivalent kernel support not available on $(uname -s)"
|
||||
;;
|
||||
esac
|
||||
|
||||
mod="$scratchdir/module"
|
||||
outside="$scratchdir/outside"
|
||||
listfile="$scratchdir/listed.txt"
|
||||
conf="$scratchdir/test-rsyncd.conf"
|
||||
|
||||
rm -rf "$mod" "$outside"
|
||||
mkdir -p "$mod" "$outside"
|
||||
|
||||
# Outside-the-module file the daemon should NOT enumerate to clients.
|
||||
# A distinctive name + non-trivial size makes the leak easy to spot.
|
||||
echo "OUTSIDE_PROTECTED_FILE_USED_AS_LEAK_DETECTOR" > "$outside/leak_marker.txt"
|
||||
chmod 0644 "$outside/leak_marker.txt"
|
||||
|
||||
# The symlink trap planted by the local attacker.
|
||||
ln -s "$outside" "$mod/cd"
|
||||
|
||||
my_uid=`get_testuid`
|
||||
root_uid=`get_rootuid`
|
||||
root_gid=`get_rootgid`
|
||||
uid_setting="uid = $root_uid"
|
||||
gid_setting="gid = $root_gid"
|
||||
if test x"$my_uid" != x"$root_uid"; then
|
||||
uid_setting="#$uid_setting"
|
||||
gid_setting="#$gid_setting"
|
||||
fi
|
||||
|
||||
cat > "$conf" <<EOF
|
||||
use chroot = no
|
||||
$uid_setting
|
||||
$gid_setting
|
||||
log file = $scratchdir/rsyncd.log
|
||||
[upload]
|
||||
path = $mod
|
||||
use chroot = no
|
||||
read only = no
|
||||
EOF
|
||||
|
||||
# Pull recursively into the symlinked subdir with dry-run + verbose,
|
||||
# capturing the daemon's flist (file list) on stdout. If the daemon
|
||||
# enumerates /outside, leak_marker.txt will appear in the listing.
|
||||
RSYNC_CONNECT_PROG="$RSYNC --config=$conf --daemon" \
|
||||
$RSYNC -nrv rsync://localhost/upload/cd/ "$scratchdir/dst/" \
|
||||
> "$listfile" 2>&1 || true
|
||||
|
||||
if grep -q "leak_marker\.txt" "$listfile"; then
|
||||
echo "----- leaked listing follows" >&2
|
||||
sed 's/^/ /' "$listfile" >&2
|
||||
echo "----- leaked listing ends" >&2
|
||||
test_fail "sender flist leak: outside/leak_marker.txt was enumerated to the client (daemon's chdir followed the cd symlink during flist generation)"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
11
testsuite/simd-checksum.test
Executable file
11
testsuite/simd-checksum.test
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test SIMD checksum implementations against the C reference
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
if ! test -x "$TOOLDIR/simdtest"; then
|
||||
test_skipped "simdtest not built (SIMD not available)"
|
||||
fi
|
||||
|
||||
"$TOOLDIR/simdtest"
|
||||
259
testsuite/symlink-dirlink-basis.test
Executable file
259
testsuite/symlink-dirlink-basis.test
Executable file
@@ -0,0 +1,259 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test that updating a file through a directory symlink works when using
|
||||
# -K (--copy-dirlinks). This is a regression test for:
|
||||
# https://github.com/RsyncProject/rsync/issues/715
|
||||
#
|
||||
# The CVE fix in commit c35e283 introduced secure_relative_open() which
|
||||
# uses O_NOFOLLOW on all path components, breaking legitimate directory
|
||||
# symlinks on the receiver side. The fix splits the path into basedir
|
||||
# (dirname, symlinks followed) and basename (O_NOFOLLOW) so that
|
||||
# directory symlinks are traversed while the final file component is
|
||||
# still protected.
|
||||
#
|
||||
# The regression only manifests when delta matching is triggered (i.e.,
|
||||
# the sender finds matching blocks in the old file). Small files with
|
||||
# completely different content are transferred in full and don't trigger
|
||||
# the bug. We use a large file with a small modification to ensure
|
||||
# delta transfer is used.
|
||||
#
|
||||
# In addition to the original regression, this test covers edge cases
|
||||
# in the fix itself:
|
||||
# - --backup with directory symlinks (finish_transfer pointer identity)
|
||||
# - --partial-dir with protocol < 29 (fnamecmp != partialptr guard)
|
||||
# - --inplace with directory symlinks (updating_basis_or_equiv check)
|
||||
# - Files without a dirname (top-level files, no split needed)
|
||||
|
||||
. "$suitedir/rsync.fns"
|
||||
|
||||
# secure_relative_open() uses kernel-enforced "stay below dirfd" via
|
||||
# openat2(RESOLVE_BENEATH) on Linux 5.6+ and openat(O_RESOLVE_BENEATH)
|
||||
# on FreeBSD 13+. Other platforms fall back to a per-component
|
||||
# O_NOFOLLOW walk that rejects every symlink including legitimate
|
||||
# directory symlinks -- the very case this test exercises. Skip on
|
||||
# those rather than report a known failure.
|
||||
case "$(uname -s)" in
|
||||
SunOS|OpenBSD|NetBSD|CYGWIN*)
|
||||
test_skipped "secure_relative_open lacks RESOLVE_BENEATH equivalent on $(uname -s); issue #715 still affects this platform"
|
||||
;;
|
||||
esac
|
||||
|
||||
RSYNC_RSH="$scratchdir/src/support/lsh.sh"
|
||||
export RSYNC_RSH
|
||||
|
||||
# $HOME is set to $scratchdir by rsync.fns
|
||||
# localhost: destination will cd to $HOME (i.e., $scratchdir)
|
||||
|
||||
# Helper: create a large file suitable for delta transfers.
|
||||
# ~32KB is large enough for rsync's block matching to find matches.
|
||||
make_testfile() {
|
||||
dd if=/dev/urandom of="$1" bs=1024 count=32 2>/dev/null \
|
||||
|| test_fail "failed to create test file $1"
|
||||
}
|
||||
|
||||
# Set up source tree
|
||||
srcbase="$tmpdir/src"
|
||||
|
||||
######################################################################
|
||||
# Test 1: Basic directory symlink update (the original issue #715)
|
||||
######################################################################
|
||||
|
||||
mkdir -p "$HOME/real-dir"
|
||||
ln -s real-dir "$HOME/dir"
|
||||
|
||||
mkdir -p "$srcbase/dir"
|
||||
make_testfile "$srcbase/dir/file"
|
||||
|
||||
# First transfer (initial): should create the file through the symlink
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 1: initial transfer failed"
|
||||
|
||||
if [ ! -f "$HOME/real-dir/file" ]; then
|
||||
test_fail "test 1: initial transfer did not create file through symlink"
|
||||
fi
|
||||
|
||||
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|
||||
|| test_fail "test 1: initial transfer content mismatch"
|
||||
|
||||
# Small modification to trigger delta transfer
|
||||
echo "appended update" >> "$srcbase/dir/file"
|
||||
sleep 1
|
||||
touch "$srcbase/dir/file"
|
||||
|
||||
# Second transfer (update): was failing with "failed verification"
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 1: update through directory symlink failed"
|
||||
|
||||
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|
||||
|| test_fail "test 1: update transfer content mismatch"
|
||||
|
||||
######################################################################
|
||||
# Test 2: Compression (-z) as in the original reproducer
|
||||
######################################################################
|
||||
|
||||
echo "another line" >> "$srcbase/dir/file"
|
||||
sleep 1
|
||||
touch "$srcbase/dir/file"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptzv --rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 2: compressed update through directory symlink failed"
|
||||
|
||||
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|
||||
|| test_fail "test 2: compressed update content mismatch"
|
||||
|
||||
######################################################################
|
||||
# Test 3: Nested directory symlinks (nested/sub/data.txt where
|
||||
# "nested" is a symlink to "nested_real")
|
||||
######################################################################
|
||||
|
||||
mkdir -p "$HOME/nested_real/sub"
|
||||
ln -s nested_real "$HOME/nested"
|
||||
|
||||
mkdir -p "$srcbase/nested/sub"
|
||||
make_testfile "$srcbase/nested/sub/data.txt"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" nested/sub/data.txt localhost:) \
|
||||
|| test_fail "test 3: initial nested transfer failed"
|
||||
|
||||
echo "appended nested" >> "$srcbase/nested/sub/data.txt"
|
||||
sleep 1
|
||||
touch "$srcbase/nested/sub/data.txt"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" nested/sub/data.txt localhost:) \
|
||||
|| test_fail "test 3: update through nested directory symlink failed"
|
||||
|
||||
diff "$srcbase/nested/sub/data.txt" "$HOME/nested_real/sub/data.txt" >/dev/null \
|
||||
|| test_fail "test 3: nested update content mismatch"
|
||||
|
||||
######################################################################
|
||||
# Test 4: --backup with directory symlinks
|
||||
#
|
||||
# Exercises the finish_transfer() "fnamecmp == fname" pointer
|
||||
# comparison that determines whether to update fnamecmp to the
|
||||
# backup name. If broken, --backup would reference a renamed file
|
||||
# for xattr handling.
|
||||
######################################################################
|
||||
|
||||
# Reset destination
|
||||
rm -f "$HOME/real-dir/file" "$HOME/real-dir/file~"
|
||||
|
||||
make_testfile "$srcbase/dir/file"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 4: initial transfer for backup test failed"
|
||||
|
||||
echo "backup update" >> "$srcbase/dir/file"
|
||||
sleep 1
|
||||
touch "$srcbase/dir/file"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --backup --rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 4: update with --backup through directory symlink failed"
|
||||
|
||||
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|
||||
|| test_fail "test 4: backup update content mismatch"
|
||||
|
||||
if [ ! -f "$HOME/real-dir/file~" ]; then
|
||||
test_fail "test 4: backup file was not created"
|
||||
fi
|
||||
|
||||
######################################################################
|
||||
# Test 5: --inplace with directory symlinks
|
||||
#
|
||||
# Exercises the updating_basis_or_equiv check which uses
|
||||
# "fnamecmp == fname". With --inplace, rsync writes directly to
|
||||
# the destination file instead of a temp file.
|
||||
######################################################################
|
||||
|
||||
rm -f "$HOME/real-dir/file" "$HOME/real-dir/file~"
|
||||
|
||||
make_testfile "$srcbase/dir/file"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --inplace --rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 5: initial inplace transfer failed"
|
||||
|
||||
echo "inplace update" >> "$srcbase/dir/file"
|
||||
sleep 1
|
||||
touch "$srcbase/dir/file"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --inplace --rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 5: inplace update through directory symlink failed"
|
||||
|
||||
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|
||||
|| test_fail "test 5: inplace update content mismatch"
|
||||
|
||||
######################################################################
|
||||
# Test 6: Top-level file (no dirname, no split needed)
|
||||
#
|
||||
# Ensures the dirname/basename split is not attempted for files
|
||||
# at the top level (file->dirname is NULL).
|
||||
######################################################################
|
||||
|
||||
make_testfile "$srcbase/topfile"
|
||||
mkdir -p "$HOME"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -Rlptv --rsync-path="$RSYNC" topfile localhost:) \
|
||||
|| test_fail "test 6: initial top-level transfer failed"
|
||||
|
||||
echo "toplevel update" >> "$srcbase/topfile"
|
||||
sleep 1
|
||||
touch "$srcbase/topfile"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -Rlptv --rsync-path="$RSYNC" topfile localhost:) \
|
||||
|| test_fail "test 6: top-level update failed"
|
||||
|
||||
diff "$srcbase/topfile" "$HOME/topfile" >/dev/null \
|
||||
|| test_fail "test 6: top-level update content mismatch"
|
||||
|
||||
######################################################################
|
||||
# Test 7: --partial-dir with protocol < 29
|
||||
#
|
||||
# For protocol < 29, fnamecmp_type stays FNAMECMP_FNAME even when
|
||||
# fnamecmp is set to partialptr. The dirname/basename split must
|
||||
# NOT trigger in this case (guarded by "fnamecmp == fname").
|
||||
######################################################################
|
||||
|
||||
rm -f "$HOME/real-dir/file"
|
||||
make_testfile "$srcbase/dir/file"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 --partial-dir=.rsync-partial \
|
||||
--rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 7: initial proto28 partial-dir transfer failed"
|
||||
|
||||
echo "partial-dir update" >> "$srcbase/dir/file"
|
||||
sleep 1
|
||||
touch "$srcbase/dir/file"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 --partial-dir=.rsync-partial \
|
||||
--rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 7: proto28 partial-dir update through dirlink failed"
|
||||
|
||||
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|
||||
|| test_fail "test 7: proto28 partial-dir update content mismatch"
|
||||
|
||||
######################################################################
|
||||
# Test 8: Protocol < 29 basic directory symlink update
|
||||
#
|
||||
# Exercises the protocol < 29 code path and its fallback logic
|
||||
# (clearing basedir on retry).
|
||||
######################################################################
|
||||
|
||||
rm -f "$HOME/real-dir/file"
|
||||
make_testfile "$srcbase/dir/file"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 \
|
||||
--rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 8: initial proto28 transfer failed"
|
||||
|
||||
echo "proto28 update" >> "$srcbase/dir/file"
|
||||
sleep 1
|
||||
touch "$srcbase/dir/file"
|
||||
|
||||
(cd "$srcbase" && $RSYNC -KRlptv --protocol=28 \
|
||||
--rsync-path="$RSYNC" dir/file localhost:) \
|
||||
|| test_fail "test 8: proto28 update through directory symlink failed"
|
||||
|
||||
diff "$srcbase/dir/file" "$HOME/real-dir/file" >/dev/null \
|
||||
|| test_fail "test 8: proto28 update content mismatch"
|
||||
|
||||
# The script would have aborted on error, so getting here means we've won.
|
||||
exit 0
|
||||
@@ -38,7 +38,10 @@ EOF
|
||||
xls() {
|
||||
for fn in "${@}"; do
|
||||
runat "$fn" "$SHELL_PATH" <<EOF
|
||||
for x in *; do echo "\$x=\`cat \$x\`"; done
|
||||
for x in *; do
|
||||
case "\$x" in SUNWattr_*) continue;; esac
|
||||
echo "\$x=\`cat \$x\`"
|
||||
done
|
||||
EOF
|
||||
done
|
||||
}
|
||||
|
||||
2
tls.c
2
tls.c
@@ -127,7 +127,7 @@ static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
|
||||
{
|
||||
if (t) {
|
||||
int len;
|
||||
struct tm *mt = gmtime(&t);
|
||||
struct tm tmp, *mt = gmtime_r(&t, &tmp);
|
||||
|
||||
len = snprintf(dest, destsize,
|
||||
" %04d-%02d-%02d %02d:%02d:%02d",
|
||||
|
||||
102
token.c
102
token.c
@@ -291,6 +291,14 @@ static int32 simple_recv_token(int f, char **data)
|
||||
int32 i = read_int(f);
|
||||
if (i <= 0)
|
||||
return i;
|
||||
/* simple_send_token caps each literal chunk at CHUNK_SIZE;
|
||||
* reject anything larger so a hostile peer cannot drive the
|
||||
* read_buf below past our static CHUNK_SIZE buffer. */
|
||||
if (i > CHUNK_SIZE) {
|
||||
rprintf(FERROR, "invalid uncompressed token length %ld [%s]\n",
|
||||
(long)i, who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
residue = i;
|
||||
}
|
||||
|
||||
@@ -493,9 +501,52 @@ static char *cbuf;
|
||||
static char *dbuf;
|
||||
|
||||
/* for decoding runs of tokens */
|
||||
#define MAX_TOKEN_INDEX ((int32)0x7ffffffe)
|
||||
|
||||
static int32 rx_token;
|
||||
static int32 rx_run;
|
||||
|
||||
static NORETURN void invalid_compressed_token(void)
|
||||
{
|
||||
rprintf(FERROR, "invalid token number in compressed stream\n");
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
|
||||
static int32 recv_compressed_token_num(int f, int32 flag)
|
||||
{
|
||||
if (flag & TOKEN_REL) {
|
||||
int32 incr = flag & 0x3f;
|
||||
if (rx_token > MAX_TOKEN_INDEX - incr)
|
||||
invalid_compressed_token();
|
||||
rx_token += incr;
|
||||
flag >>= 6;
|
||||
} else {
|
||||
rx_token = read_int(f);
|
||||
if (rx_token < 0 || rx_token > MAX_TOKEN_INDEX)
|
||||
invalid_compressed_token();
|
||||
}
|
||||
|
||||
if (flag & 1) {
|
||||
rx_run = read_byte(f);
|
||||
rx_run += read_byte(f) << 8;
|
||||
if (rx_run <= 0 || rx_token > MAX_TOKEN_INDEX - rx_run)
|
||||
invalid_compressed_token();
|
||||
recv_state = r_running;
|
||||
}
|
||||
|
||||
return -1 - rx_token;
|
||||
}
|
||||
|
||||
static int32 recv_compressed_token_run(void)
|
||||
{
|
||||
if (rx_run <= 0 || rx_token >= MAX_TOKEN_INDEX)
|
||||
invalid_compressed_token();
|
||||
++rx_token;
|
||||
if (--rx_run == 0)
|
||||
recv_state = r_idle;
|
||||
return -1 - rx_token;
|
||||
}
|
||||
|
||||
/* Receive a deflated token and inflate it */
|
||||
static int32 recv_deflated_token(int f, char **data)
|
||||
{
|
||||
@@ -586,17 +637,7 @@ static int32 recv_deflated_token(int f, char **data)
|
||||
}
|
||||
|
||||
/* here we have a token of some kind */
|
||||
if (flag & TOKEN_REL) {
|
||||
rx_token += flag & 0x3f;
|
||||
flag >>= 6;
|
||||
} else
|
||||
rx_token = read_int(f);
|
||||
if (flag & 1) {
|
||||
rx_run = read_byte(f);
|
||||
rx_run += read_byte(f) << 8;
|
||||
recv_state = r_running;
|
||||
}
|
||||
return -1 - rx_token;
|
||||
return recv_compressed_token_num(f, flag);
|
||||
|
||||
case r_inflating:
|
||||
rx_strm.next_out = (Bytef *)dbuf;
|
||||
@@ -616,10 +657,7 @@ static int32 recv_deflated_token(int f, char **data)
|
||||
break;
|
||||
|
||||
case r_running:
|
||||
++rx_token;
|
||||
if (--rx_run == 0)
|
||||
recv_state = r_idle;
|
||||
return -1 - rx_token;
|
||||
return recv_compressed_token_run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -828,17 +866,7 @@ static int32 recv_zstd_token(int f, char **data)
|
||||
return 0;
|
||||
}
|
||||
/* here we have a token of some kind */
|
||||
if (flag & TOKEN_REL) {
|
||||
rx_token += flag & 0x3f;
|
||||
flag >>= 6;
|
||||
} else
|
||||
rx_token = read_int(f);
|
||||
if (flag & 1) {
|
||||
rx_run = read_byte(f);
|
||||
rx_run += read_byte(f) << 8;
|
||||
recv_state = r_running;
|
||||
}
|
||||
return -1 - rx_token;
|
||||
return recv_compressed_token_num(f, flag);
|
||||
|
||||
case r_inflated: /* zstd doesn't get into this state */
|
||||
break;
|
||||
@@ -869,10 +897,7 @@ static int32 recv_zstd_token(int f, char **data)
|
||||
break;
|
||||
|
||||
case r_running:
|
||||
++rx_token;
|
||||
if (--rx_run == 0)
|
||||
recv_state = r_idle;
|
||||
return -1 - rx_token;
|
||||
return recv_compressed_token_run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -992,17 +1017,7 @@ static int32 recv_compressed_token(int f, char **data)
|
||||
}
|
||||
|
||||
/* here we have a token of some kind */
|
||||
if (flag & TOKEN_REL) {
|
||||
rx_token += flag & 0x3f;
|
||||
flag >>= 6;
|
||||
} else
|
||||
rx_token = read_int(f);
|
||||
if (flag & 1) {
|
||||
rx_run = read_byte(f);
|
||||
rx_run += read_byte(f) << 8;
|
||||
recv_state = r_running;
|
||||
}
|
||||
return -1 - rx_token;
|
||||
return recv_compressed_token_num(f, flag);
|
||||
|
||||
case r_inflating:
|
||||
avail_out = LZ4_decompress_safe(next_in, dbuf, avail_in, size);
|
||||
@@ -1018,10 +1033,7 @@ static int32 recv_compressed_token(int f, char **data)
|
||||
break;
|
||||
|
||||
case r_running:
|
||||
++rx_token;
|
||||
if (--rx_run == 0)
|
||||
recv_state = r_idle;
|
||||
return -1 - rx_token;
|
||||
return recv_compressed_token_run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
120
util1.c
120
util1.c
@@ -141,7 +141,7 @@ int set_times(const char *fname, STRUCT_STAT *stp)
|
||||
|
||||
#ifdef HAVE_UTIMENSAT
|
||||
#include "case_N.h"
|
||||
if (do_utimensat(fname, stp) == 0)
|
||||
if (do_utimensat_at(fname, stp) == 0)
|
||||
break;
|
||||
if (errno != ENOSYS)
|
||||
return -1;
|
||||
@@ -336,7 +336,13 @@ static int unlink_and_reopen(const char *dest, mode_t mode)
|
||||
mode |= S_IWUSR;
|
||||
#endif
|
||||
mode &= INITACCESSPERMS;
|
||||
if ((ofd = do_open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
|
||||
/* Use do_open_at so the create/truncate goes through a secure
|
||||
* parent dirfd in the daemon-no-chroot deployment. Otherwise
|
||||
* an attacker could swap a parent component with a symlink in
|
||||
* the window between robust_unlink (which uses do_unlink_at,
|
||||
* already secure) and the create here, and redirect the new
|
||||
* file outside the module. */
|
||||
if ((ofd = do_open_at(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, mode)) < 0) {
|
||||
int save_errno = errno;
|
||||
rsyserr(FERROR_XFER, save_errno, "open %s", full_fname(dest));
|
||||
errno = save_errno;
|
||||
@@ -360,12 +366,23 @@ static int unlink_and_reopen(const char *dest, mode_t mode)
|
||||
* --copy-dest options. */
|
||||
int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode)
|
||||
{
|
||||
extern int am_daemon, am_chrooted;
|
||||
int ifd, ofd;
|
||||
char buf[1024 * 8];
|
||||
int len; /* Number of bytes read into `buf'. */
|
||||
OFF_T prealloc_len = 0, offset = 0;
|
||||
|
||||
if ((ifd = do_open_nofollow(source, O_RDONLY)) < 0) {
|
||||
/* On a daemon without chroot, route the source open through
|
||||
* secure_relative_open so a parent-symlink on the source path
|
||||
* (e.g. --copy-dest=cd where cd is a symlink to an outside
|
||||
* directory) cannot redirect the read to a file the daemon can
|
||||
* see but the attacker should not. Plain do_open_nofollow only
|
||||
* refuses a final-component symlink; parents are still followed. */
|
||||
if (am_daemon && !am_chrooted && source && *source && source[0] != '/')
|
||||
ifd = secure_relative_open(NULL, source, O_RDONLY | O_NOFOLLOW, 0);
|
||||
else
|
||||
ifd = do_open_nofollow(source, O_RDONLY);
|
||||
if (ifd < 0) {
|
||||
int save_errno = errno;
|
||||
rsyserr(FERROR_XFER, errno, "open %s", full_fname(source));
|
||||
errno = save_errno;
|
||||
@@ -479,13 +496,13 @@ int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode)
|
||||
int robust_unlink(const char *fname)
|
||||
{
|
||||
#ifndef ETXTBSY
|
||||
return do_unlink(fname);
|
||||
return do_unlink_at(fname);
|
||||
#else
|
||||
static int counter = 1;
|
||||
int rc, pos, start;
|
||||
char path[MAXPATHLEN];
|
||||
|
||||
rc = do_unlink(fname);
|
||||
rc = do_unlink_at(fname);
|
||||
if (rc == 0 || errno != ETXTBSY)
|
||||
return rc;
|
||||
|
||||
@@ -515,7 +532,7 @@ int robust_unlink(const char *fname)
|
||||
}
|
||||
|
||||
/* maybe we should return rename()'s exit status? Nah. */
|
||||
if (do_rename(fname, path) != 0) {
|
||||
if (do_rename_at(fname, path) != 0) {
|
||||
errno = ETXTBSY;
|
||||
return -1;
|
||||
}
|
||||
@@ -538,7 +555,7 @@ int robust_rename(const char *from, const char *to, const char *partialptr,
|
||||
return 0;
|
||||
|
||||
while (tries--) {
|
||||
if (do_rename(from, to) == 0)
|
||||
if (do_rename_at(from, to) == 0)
|
||||
return 0;
|
||||
|
||||
switch (errno) {
|
||||
@@ -559,7 +576,7 @@ int robust_rename(const char *from, const char *to, const char *partialptr,
|
||||
}
|
||||
if (copy_file(from, to, -1, mode) != 0)
|
||||
return -2;
|
||||
do_unlink(from);
|
||||
do_unlink_at(from);
|
||||
return 1;
|
||||
default:
|
||||
return -1;
|
||||
@@ -942,7 +959,7 @@ int count_dir_elements(const char *p)
|
||||
* resulting name would be empty, returns ".". */
|
||||
int clean_fname(char *name, int flags)
|
||||
{
|
||||
char *limit = name - 1, *t = name, *f = name;
|
||||
char *limit = name, *t = name, *f = name;
|
||||
int anchored;
|
||||
|
||||
if (!name)
|
||||
@@ -987,9 +1004,13 @@ int clean_fname(char *name, int flags)
|
||||
f += 2;
|
||||
continue;
|
||||
}
|
||||
while (s > limit && *--s != '/') {}
|
||||
if (s != t - 1 && (s < name || *s == '/')) {
|
||||
t = s + 1;
|
||||
/* backing up for ".." — avoid reading before 'name' */
|
||||
while (s > limit && s[-1] != '/')
|
||||
s--;
|
||||
|
||||
/* If found prior '/', or we reached the start, adjust t. */
|
||||
if (s != t - 1 && (s <= name || *s == '/')) {
|
||||
t = (s == name) ? name : s + 1;
|
||||
f += 2;
|
||||
continue;
|
||||
}
|
||||
@@ -1112,6 +1133,7 @@ char *sanitize_path(char *dest, const char *p, const char *rootdir, int depth, i
|
||||
* Also cleans the path using the clean_fname() function. */
|
||||
int change_dir(const char *dir, int set_path_only)
|
||||
{
|
||||
extern int am_daemon, am_chrooted;
|
||||
static int initialised, skipped_chdir;
|
||||
unsigned int len;
|
||||
|
||||
@@ -1150,10 +1172,57 @@ int change_dir(const char *dir, int set_path_only)
|
||||
curr_dir[curr_dir_len++] = '/';
|
||||
memcpy(curr_dir + curr_dir_len, dir, len + 1);
|
||||
|
||||
if (!set_path_only && chdir(curr_dir)) {
|
||||
curr_dir_len = save_dir_len;
|
||||
curr_dir[curr_dir_len] = '\0';
|
||||
return 0;
|
||||
if (!set_path_only) {
|
||||
int chdir_failed;
|
||||
/* In the daemon-without-chroot deployment we must not
|
||||
* follow a symlink in any component of the chdir
|
||||
* target -- otherwise CWD escapes the module and
|
||||
* every subsequent path-relative syscall (open,
|
||||
* chmod, lchown, ...) inherits the escape, which
|
||||
* defeats secure_relative_open's RESOLVE_BENEATH
|
||||
* anchor and re-opens the CVE-2026-29518 class of
|
||||
* symlink TOCTOU attacks. Use the secure resolver
|
||||
* to get a confined dirfd, then fchdir() to it.
|
||||
*
|
||||
* If skipped_chdir is set, a previous CD_SKIP_CHDIR
|
||||
* call buffered an absolute prefix in curr_dir
|
||||
* (e.g. change_pathname's CD_SKIP_CHDIR to orig_dir)
|
||||
* without syncing the kernel's CWD. Resolve `dir`
|
||||
* relative to that prefix as basedir so the secure
|
||||
* branch still anchors at the operator-trusted
|
||||
* directory rather than wherever the kernel CWD
|
||||
* happens to be. */
|
||||
if (am_daemon && !am_chrooted) {
|
||||
const char *basedir = NULL;
|
||||
char prefix[MAXPATHLEN];
|
||||
int dfd;
|
||||
if (skipped_chdir) {
|
||||
if (save_dir_len >= sizeof prefix) {
|
||||
errno = ENAMETOOLONG;
|
||||
chdir_failed = 1;
|
||||
goto chdir_cleanup;
|
||||
}
|
||||
memcpy(prefix, curr_dir, save_dir_len);
|
||||
prefix[save_dir_len] = '\0';
|
||||
basedir = prefix;
|
||||
}
|
||||
dfd = secure_relative_open(basedir, dir,
|
||||
O_RDONLY | O_DIRECTORY, 0);
|
||||
if (dfd < 0) {
|
||||
chdir_failed = 1;
|
||||
} else {
|
||||
chdir_failed = fchdir(dfd) != 0;
|
||||
close(dfd);
|
||||
}
|
||||
} else {
|
||||
chdir_failed = chdir(curr_dir) != 0;
|
||||
}
|
||||
chdir_cleanup:
|
||||
if (chdir_failed) {
|
||||
curr_dir_len = save_dir_len;
|
||||
curr_dir[curr_dir_len] = '\0';
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
skipped_chdir = set_path_only;
|
||||
}
|
||||
@@ -1281,20 +1350,20 @@ int handle_partial_dir(const char *fname, int create)
|
||||
dir = partial_fname;
|
||||
if (create) {
|
||||
STRUCT_STAT st;
|
||||
int statret = do_lstat(dir, &st);
|
||||
int statret = do_lstat_at(dir, &st);
|
||||
if (statret == 0 && !S_ISDIR(st.st_mode)) {
|
||||
if (do_unlink(dir) < 0) {
|
||||
if (do_unlink_at(dir) < 0) {
|
||||
*fn = '/';
|
||||
return 0;
|
||||
}
|
||||
statret = -1;
|
||||
}
|
||||
if (statret < 0 && do_mkdir(dir, 0700) < 0) {
|
||||
if (statret < 0 && do_mkdir_at(dir, 0700) < 0) {
|
||||
*fn = '/';
|
||||
return 0;
|
||||
}
|
||||
} else
|
||||
do_rmdir(dir);
|
||||
do_rmdir_at(dir);
|
||||
*fn = '/';
|
||||
|
||||
return 1;
|
||||
@@ -1389,8 +1458,13 @@ char *timestring(time_t t)
|
||||
static int ndx = 0;
|
||||
static char buffers[4][20]; /* We support 4 simultaneous timestring results. */
|
||||
char *TimeBuf = buffers[ndx = (ndx + 1) % 4];
|
||||
struct tm *tm = localtime(&t);
|
||||
int len = snprintf(TimeBuf, sizeof buffers[0], "%4d/%02d/%02d %02d:%02d:%02d",
|
||||
struct tm tmp, *tm = localtime_r(&t, &tmp);
|
||||
int len;
|
||||
if (!tm) {
|
||||
strlcpy(TimeBuf, "(time out of range)", sizeof buffers[0]);
|
||||
return TimeBuf;
|
||||
}
|
||||
len = snprintf(TimeBuf, sizeof buffers[0], "%4d/%02d/%02d %02d:%02d:%02d",
|
||||
(int)tm->tm_year + 1900, (int)tm->tm_mon + 1, (int)tm->tm_mday,
|
||||
(int)tm->tm_hour, (int)tm->tm_min, (int)tm->tm_sec);
|
||||
assert(len > 0); /* Silence gcc warning */
|
||||
@@ -1714,6 +1788,8 @@ void *expand_item_list(item_list *lp, size_t item_size, const char *desc, int in
|
||||
new_ptr == lp->items ? " not" : "");
|
||||
}
|
||||
|
||||
memset((char *)new_ptr + lp->malloced * item_size, 0,
|
||||
(expand_size - lp->malloced) * item_size);
|
||||
lp->items = new_ptr;
|
||||
lp->malloced = expand_size;
|
||||
}
|
||||
|
||||
4
util2.c
4
util2.c
@@ -79,9 +79,7 @@ void *my_alloc(void *ptr, size_t num, size_t size, const char *file, int line)
|
||||
who_am_i(), do_big_num(max_alloc, 0, NULL), src_file(file), line);
|
||||
exit_cleanup(RERR_MALLOC);
|
||||
}
|
||||
if (!ptr)
|
||||
ptr = malloc(num * size);
|
||||
else if (ptr == do_calloc)
|
||||
if (!ptr || ptr == do_calloc)
|
||||
ptr = calloc(num, size);
|
||||
else
|
||||
ptr = realloc(ptr, num * size);
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#define RSYNC_VERSION "3.4.0"
|
||||
#define RSYNC_VERSION "3.4.1"
|
||||
#define MAINTAINER_TZ_OFFSET -7.0
|
||||
|
||||
@@ -32,7 +32,9 @@ int fnmatch_errors = 0;
|
||||
|
||||
int wildmatch_errors = 0;
|
||||
|
||||
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L
|
||||
typedef char bool;
|
||||
#endif
|
||||
|
||||
int output_iterations = 0;
|
||||
int explode_mod = 0;
|
||||
|
||||
35
xattrs.c
35
xattrs.c
@@ -697,6 +697,13 @@ int recv_xattr_request(struct file_struct *file, int f_in)
|
||||
rxa = lst->items;
|
||||
num = 0;
|
||||
while ((rel_pos = read_varint(f_in)) != 0) {
|
||||
/* Detect signed overflow before the accumulating add. A hostile
|
||||
* peer could otherwise wrap 'num' to land on an arbitrary value. */
|
||||
if ((rel_pos > 0 && num > INT_MAX - rel_pos)
|
||||
|| (rel_pos < 0 && num < INT_MIN - rel_pos)) {
|
||||
rprintf(FERROR, "xattr rel_pos accumulation overflow [%s]\n", who_am_i());
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
num += rel_pos;
|
||||
if (am_sender) {
|
||||
/* The sender-related num values are only in order on the sender.
|
||||
@@ -742,7 +749,7 @@ int recv_xattr_request(struct file_struct *file, int f_in)
|
||||
}
|
||||
|
||||
old_datum = rxa->datum;
|
||||
rxa->datum_len = read_varint(f_in);
|
||||
rxa->datum_len = read_varint_size(f_in, MAX_WIRE_XATTR_DATALEN, "xattr datum_len");
|
||||
|
||||
if (SIZE_MAX - rxa->name_len < rxa->datum_len)
|
||||
overflow_exit("recv_xattr_request");
|
||||
@@ -783,7 +790,8 @@ void receive_xattr(int f, struct file_struct *file)
|
||||
return;
|
||||
}
|
||||
|
||||
if ((count = read_varint(f)) != 0) {
|
||||
count = read_varint_bounded(f, 0, MAX_WIRE_XATTR_COUNT, "xattr count");
|
||||
if (count != 0) {
|
||||
(void)EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, count);
|
||||
temp_xattr.count = 0;
|
||||
}
|
||||
@@ -791,8 +799,8 @@ void receive_xattr(int f, struct file_struct *file)
|
||||
for (num = 1; num <= count; num++) {
|
||||
char *ptr, *name;
|
||||
rsync_xa *rxa;
|
||||
size_t name_len = read_varint(f);
|
||||
size_t datum_len = read_varint(f);
|
||||
size_t name_len = read_varint_size(f, MAX_WIRE_XATTR_NAMELEN, "xattr name_len");
|
||||
size_t datum_len = read_varint_size(f, MAX_WIRE_XATTR_DATALEN, "xattr datum_len");
|
||||
size_t dget_len = datum_len > MAX_FULL_DATUM ? 1 + (size_t)xattr_sum_len : datum_len;
|
||||
size_t extra_len = MIGHT_NEED_RPRE ? RPRE_LEN : 0;
|
||||
if (SIZE_MAX - dget_len < extra_len || SIZE_MAX - dget_len - extra_len < name_len)
|
||||
@@ -860,8 +868,8 @@ void receive_xattr(int f, struct file_struct *file)
|
||||
rxa->num = num;
|
||||
}
|
||||
|
||||
if (need_sort && count > 1)
|
||||
qsort(temp_xattr.items, count, sizeof (rsync_xa), rsync_xal_compare_names);
|
||||
if (need_sort && temp_xattr.count > 1)
|
||||
qsort(temp_xattr.items, temp_xattr.count, sizeof (rsync_xa), rsync_xal_compare_names);
|
||||
|
||||
ndx = rsync_xal_store(&temp_xattr); /* adds item to rsync_xal_l */
|
||||
|
||||
@@ -1086,7 +1094,7 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
|
||||
&& !S_ISLNK(sxp->st.st_mode)
|
||||
#endif
|
||||
&& access(fname, W_OK) < 0
|
||||
&& do_chmod(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR) == 0)
|
||||
&& do_chmod_at(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR) == 0)
|
||||
added_write_perm = 1;
|
||||
|
||||
ndx = F_XATTR(file);
|
||||
@@ -1094,7 +1102,7 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
|
||||
lst = &glst->xa_items;
|
||||
int return_value = rsync_xal_set(fname, lst, fnamecmp, sxp);
|
||||
if (added_write_perm) /* remove the temporary write permission */
|
||||
do_chmod(fname, sxp->st.st_mode);
|
||||
do_chmod_at(fname, sxp->st.st_mode);
|
||||
return return_value;
|
||||
}
|
||||
|
||||
@@ -1211,7 +1219,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
|
||||
mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
|
||||
| (S_ISDIR(fst.st_mode) ? 0700 : 0600);
|
||||
if (fst.st_mode != mode)
|
||||
do_chmod(fname, mode);
|
||||
do_chmod_at(fname, mode);
|
||||
if (!IS_DEVICE(fst.st_mode))
|
||||
fst.st_rdev = 0; /* just in case */
|
||||
|
||||
@@ -1249,7 +1257,12 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
|
||||
|
||||
int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
|
||||
{
|
||||
int ret = do_stat(fname, fst);
|
||||
/* Use the *_at variants so that on a daemon-no-chroot deployment
|
||||
* the metadata read goes through a secure parent dirfd instead
|
||||
* of bare path resolution. The *_at wrappers fall through to
|
||||
* plain do_stat outside the daemon-no-chroot context, so this
|
||||
* change is transparent for non-daemon use. */
|
||||
int ret = do_stat_at(fname, fst);
|
||||
if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
|
||||
xst->st_mode = 0;
|
||||
return ret;
|
||||
@@ -1257,7 +1270,7 @@ int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
|
||||
|
||||
int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
|
||||
{
|
||||
int ret = do_lstat(fname, fst);
|
||||
int ret = do_lstat_at(fname, fst);
|
||||
if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
|
||||
xst->st_mode = 0;
|
||||
return ret;
|
||||
|
||||
@@ -62,10 +62,7 @@ local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2));
|
||||
#endif
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT adler32(adler, buf, len)
|
||||
uLong adler;
|
||||
const Bytef *buf;
|
||||
uInt len;
|
||||
uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len)
|
||||
{
|
||||
unsigned long sum2;
|
||||
unsigned n;
|
||||
@@ -133,10 +130,7 @@ uLong ZEXPORT adler32(adler, buf, len)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
local uLong adler32_combine_(adler1, adler2, len2)
|
||||
uLong adler1;
|
||||
uLong adler2;
|
||||
z_off64_t len2;
|
||||
local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2)
|
||||
{
|
||||
unsigned long sum1;
|
||||
unsigned long sum2;
|
||||
@@ -162,18 +156,12 @@ local uLong adler32_combine_(adler1, adler2, len2)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT adler32_combine(adler1, adler2, len2)
|
||||
uLong adler1;
|
||||
uLong adler2;
|
||||
z_off_t len2;
|
||||
uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, z_off_t len2)
|
||||
{
|
||||
return adler32_combine_(adler1, adler2, len2);
|
||||
}
|
||||
|
||||
uLong ZEXPORT adler32_combine64(adler1, adler2, len2)
|
||||
uLong adler1;
|
||||
uLong adler2;
|
||||
z_off64_t len2;
|
||||
uLong ZEXPORT adler32_combine64(uLong adler1, uLong adler2, z_off64_t len2)
|
||||
{
|
||||
return adler32_combine_(adler1, adler2, len2);
|
||||
}
|
||||
|
||||
@@ -19,12 +19,7 @@
|
||||
memory, Z_BUF_ERROR if there was not enough room in the output buffer,
|
||||
Z_STREAM_ERROR if the level parameter is invalid.
|
||||
*/
|
||||
int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
|
||||
Bytef *dest;
|
||||
uLongf *destLen;
|
||||
const Bytef *source;
|
||||
uLong sourceLen;
|
||||
int level;
|
||||
int ZEXPORT compress2 (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen, int level)
|
||||
{
|
||||
z_stream stream;
|
||||
int err;
|
||||
@@ -59,11 +54,7 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level)
|
||||
|
||||
/* ===========================================================================
|
||||
*/
|
||||
int ZEXPORT compress (dest, destLen, source, sourceLen)
|
||||
Bytef *dest;
|
||||
uLongf *destLen;
|
||||
const Bytef *source;
|
||||
uLong sourceLen;
|
||||
int ZEXPORT compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
|
||||
{
|
||||
return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION);
|
||||
}
|
||||
@@ -72,8 +63,7 @@ int ZEXPORT compress (dest, destLen, source, sourceLen)
|
||||
If the default memLevel or windowBits for deflateInit() is changed, then
|
||||
this function needs to be updated.
|
||||
*/
|
||||
uLong ZEXPORT compressBound (sourceLen)
|
||||
uLong sourceLen;
|
||||
uLong ZEXPORT compressBound (uLong sourceLen)
|
||||
{
|
||||
return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
|
||||
(sourceLen >> 25) + 13;
|
||||
|
||||
46
zlib/crc32.c
46
zlib/crc32.c
@@ -87,7 +87,7 @@ local void make_crc_table OF((void));
|
||||
allow for word-at-a-time CRC calculation for both big-endian and little-
|
||||
endian machines, where a word is four bytes.
|
||||
*/
|
||||
local void make_crc_table()
|
||||
local void make_crc_table(void)
|
||||
{
|
||||
z_crc_t c;
|
||||
int n, k;
|
||||
@@ -164,9 +164,7 @@ local void make_crc_table()
|
||||
}
|
||||
|
||||
#ifdef MAKECRCH
|
||||
local void write_table(out, table)
|
||||
FILE *out;
|
||||
const z_crc_t FAR *table;
|
||||
local void write_table(FILE *out, const z_crc_t FAR *table)
|
||||
{
|
||||
int n;
|
||||
|
||||
@@ -187,7 +185,7 @@ local void write_table(out, table)
|
||||
/* =========================================================================
|
||||
* This function can be used by asm versions of crc32()
|
||||
*/
|
||||
const z_crc_t FAR * ZEXPORT get_crc_table()
|
||||
const z_crc_t FAR * ZEXPORT get_crc_table(void)
|
||||
{
|
||||
#ifdef DYNAMIC_CRC_TABLE
|
||||
if (crc_table_empty)
|
||||
@@ -201,10 +199,7 @@ const z_crc_t FAR * ZEXPORT get_crc_table()
|
||||
#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1
|
||||
|
||||
/* ========================================================================= */
|
||||
unsigned long ZEXPORT crc32(crc, buf, len)
|
||||
unsigned long crc;
|
||||
const unsigned char FAR *buf;
|
||||
uInt len;
|
||||
unsigned long ZEXPORT crc32(unsigned long crc, const unsigned char FAR *buf, uInt len)
|
||||
{
|
||||
if (buf == Z_NULL) return 0UL;
|
||||
|
||||
@@ -244,10 +239,7 @@ unsigned long ZEXPORT crc32(crc, buf, len)
|
||||
#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4
|
||||
|
||||
/* ========================================================================= */
|
||||
local unsigned long crc32_little(crc, buf, len)
|
||||
unsigned long crc;
|
||||
const unsigned char FAR *buf;
|
||||
unsigned len;
|
||||
local unsigned long crc32_little(unsigned long crc, const unsigned char FAR *buf, unsigned len)
|
||||
{
|
||||
register z_crc_t c;
|
||||
register const z_crc_t FAR *buf4;
|
||||
@@ -284,10 +276,7 @@ local unsigned long crc32_little(crc, buf, len)
|
||||
#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4
|
||||
|
||||
/* ========================================================================= */
|
||||
local unsigned long crc32_big(crc, buf, len)
|
||||
unsigned long crc;
|
||||
const unsigned char FAR *buf;
|
||||
unsigned len;
|
||||
local unsigned long crc32_big(unsigned long crc, const unsigned char FAR *buf, unsigned len)
|
||||
{
|
||||
register z_crc_t c;
|
||||
register const z_crc_t FAR *buf4;
|
||||
@@ -322,9 +311,7 @@ local unsigned long crc32_big(crc, buf, len)
|
||||
#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */
|
||||
|
||||
/* ========================================================================= */
|
||||
local unsigned long gf2_matrix_times(mat, vec)
|
||||
unsigned long *mat;
|
||||
unsigned long vec;
|
||||
local unsigned long gf2_matrix_times(unsigned long *mat, unsigned long vec)
|
||||
{
|
||||
unsigned long sum;
|
||||
|
||||
@@ -339,9 +326,7 @@ local unsigned long gf2_matrix_times(mat, vec)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
local void gf2_matrix_square(square, mat)
|
||||
unsigned long *square;
|
||||
unsigned long *mat;
|
||||
local void gf2_matrix_square(unsigned long *square, unsigned long *mat)
|
||||
{
|
||||
int n;
|
||||
|
||||
@@ -350,10 +335,7 @@ local void gf2_matrix_square(square, mat)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
local uLong crc32_combine_(crc1, crc2, len2)
|
||||
uLong crc1;
|
||||
uLong crc2;
|
||||
z_off64_t len2;
|
||||
local uLong crc32_combine_(uLong crc1, uLong crc2, z_off64_t len2)
|
||||
{
|
||||
int n;
|
||||
unsigned long row;
|
||||
@@ -406,18 +388,12 @@ local uLong crc32_combine_(crc1, crc2, len2)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
uLong ZEXPORT crc32_combine(crc1, crc2, len2)
|
||||
uLong crc1;
|
||||
uLong crc2;
|
||||
z_off_t len2;
|
||||
uLong ZEXPORT crc32_combine(uLong crc1, uLong crc2, z_off_t len2)
|
||||
{
|
||||
return crc32_combine_(crc1, crc2, len2);
|
||||
}
|
||||
|
||||
uLong ZEXPORT crc32_combine64(crc1, crc2, len2)
|
||||
uLong crc1;
|
||||
uLong crc2;
|
||||
z_off64_t len2;
|
||||
uLong ZEXPORT crc32_combine64(uLong crc1, uLong crc2, z_off64_t len2)
|
||||
{
|
||||
return crc32_combine_(crc1, crc2, len2);
|
||||
}
|
||||
|
||||
124
zlib/deflate.c
124
zlib/deflate.c
@@ -200,11 +200,8 @@ struct static_tree_desc_s {int dummy;}; /* for buggy compilers */
|
||||
zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflateInit_(strm, level, version, stream_size)
|
||||
z_streamp strm;
|
||||
int level;
|
||||
const char *version;
|
||||
int stream_size;
|
||||
int ZEXPORT deflateInit_(z_streamp strm, int level, const char *version,
|
||||
int stream_size)
|
||||
{
|
||||
return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL,
|
||||
Z_DEFAULT_STRATEGY, version, stream_size);
|
||||
@@ -212,16 +209,8 @@ int ZEXPORT deflateInit_(strm, level, version, stream_size)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
|
||||
version, stream_size)
|
||||
z_streamp strm;
|
||||
int level;
|
||||
int method;
|
||||
int windowBits;
|
||||
int memLevel;
|
||||
int strategy;
|
||||
const char *version;
|
||||
int stream_size;
|
||||
int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, int windowBits,
|
||||
int memLevel, int strategy, const char *version, int stream_size)
|
||||
{
|
||||
deflate_state *s;
|
||||
int wrap = 1;
|
||||
@@ -359,10 +348,8 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
|
||||
z_streamp strm;
|
||||
const Bytef *dictionary;
|
||||
uInt dictLength;
|
||||
int ZEXPORT deflateSetDictionary (z_streamp strm, const Bytef *dictionary,
|
||||
uInt dictLength)
|
||||
{
|
||||
deflate_state *s;
|
||||
uInt str, n;
|
||||
@@ -428,8 +415,7 @@ int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflateResetKeep (strm)
|
||||
z_streamp strm;
|
||||
int ZEXPORT deflateResetKeep (z_streamp strm)
|
||||
{
|
||||
deflate_state *s;
|
||||
|
||||
@@ -463,8 +449,7 @@ int ZEXPORT deflateResetKeep (strm)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflateReset (strm)
|
||||
z_streamp strm;
|
||||
int ZEXPORT deflateReset (z_streamp strm)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@@ -475,9 +460,7 @@ int ZEXPORT deflateReset (strm)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflateSetHeader (strm, head)
|
||||
z_streamp strm;
|
||||
gz_headerp head;
|
||||
int ZEXPORT deflateSetHeader (z_streamp strm, gz_headerp head)
|
||||
{
|
||||
if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
|
||||
if (strm->state->wrap != 2) return Z_STREAM_ERROR;
|
||||
@@ -486,10 +469,7 @@ int ZEXPORT deflateSetHeader (strm, head)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflatePending (strm, pending, bits)
|
||||
unsigned *pending;
|
||||
int *bits;
|
||||
z_streamp strm;
|
||||
int ZEXPORT deflatePending (z_streamp strm, unsigned *pending, int *bits)
|
||||
{
|
||||
if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
|
||||
if (pending != Z_NULL)
|
||||
@@ -500,10 +480,7 @@ int ZEXPORT deflatePending (strm, pending, bits)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflatePrime (strm, bits, value)
|
||||
z_streamp strm;
|
||||
int bits;
|
||||
int value;
|
||||
int ZEXPORT deflatePrime (z_streamp strm, int bits, int value)
|
||||
{
|
||||
deflate_state *s;
|
||||
int put;
|
||||
@@ -526,10 +503,7 @@ int ZEXPORT deflatePrime (strm, bits, value)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflateParams(strm, level, strategy)
|
||||
z_streamp strm;
|
||||
int level;
|
||||
int strategy;
|
||||
int ZEXPORT deflateParams(z_streamp strm, int level, int strategy)
|
||||
{
|
||||
deflate_state *s;
|
||||
compress_func func;
|
||||
@@ -567,12 +541,8 @@ int ZEXPORT deflateParams(strm, level, strategy)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
|
||||
z_streamp strm;
|
||||
int good_length;
|
||||
int max_lazy;
|
||||
int nice_length;
|
||||
int max_chain;
|
||||
int ZEXPORT deflateTune(z_streamp strm, int good_length, int max_lazy,
|
||||
int nice_length, int max_chain)
|
||||
{
|
||||
deflate_state *s;
|
||||
|
||||
@@ -602,9 +572,7 @@ int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
|
||||
* upper bound of about 14% expansion does not seem onerous for output buffer
|
||||
* allocation.
|
||||
*/
|
||||
uLong ZEXPORT deflateBound(strm, sourceLen)
|
||||
z_streamp strm;
|
||||
uLong sourceLen;
|
||||
uLong ZEXPORT deflateBound(z_streamp strm, uLong sourceLen)
|
||||
{
|
||||
deflate_state *s;
|
||||
uLong complen, wraplen;
|
||||
@@ -664,9 +632,7 @@ uLong ZEXPORT deflateBound(strm, sourceLen)
|
||||
* IN assertion: the stream state is correct and there is enough room in
|
||||
* pending_buf.
|
||||
*/
|
||||
local void putShortMSB (s, b)
|
||||
deflate_state *s;
|
||||
uInt b;
|
||||
local void putShortMSB (deflate_state *s, uInt b)
|
||||
{
|
||||
put_byte(s, (Byte)(b >> 8));
|
||||
put_byte(s, (Byte)(b & 0xff));
|
||||
@@ -678,8 +644,7 @@ local void putShortMSB (s, b)
|
||||
* to avoid allocating a large strm->next_out buffer and copying into it.
|
||||
* (See also read_buf()).
|
||||
*/
|
||||
local void flush_pending(strm)
|
||||
z_streamp strm;
|
||||
local void flush_pending(z_streamp strm)
|
||||
{
|
||||
unsigned len;
|
||||
deflate_state *s = strm->state;
|
||||
@@ -701,9 +666,7 @@ local void flush_pending(strm)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflate (strm, flush)
|
||||
z_streamp strm;
|
||||
int flush;
|
||||
int ZEXPORT deflate (z_streamp strm, int flush)
|
||||
{
|
||||
int old_flush; /* value of flush param for previous deflate call */
|
||||
deflate_state *s;
|
||||
@@ -1015,8 +978,7 @@ int ZEXPORT deflate (strm, flush)
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
int ZEXPORT deflateEnd (strm)
|
||||
z_streamp strm;
|
||||
int ZEXPORT deflateEnd (z_streamp strm)
|
||||
{
|
||||
int status;
|
||||
|
||||
@@ -1050,9 +1012,7 @@ int ZEXPORT deflateEnd (strm)
|
||||
* To simplify the source, this is not supported for 16-bit MSDOS (which
|
||||
* doesn't have enough memory anyway to duplicate compression states).
|
||||
*/
|
||||
int ZEXPORT deflateCopy (dest, source)
|
||||
z_streamp dest;
|
||||
z_streamp source;
|
||||
int ZEXPORT deflateCopy (z_streamp dest, z_streamp source)
|
||||
{
|
||||
#ifdef MAXSEG_64K
|
||||
return Z_STREAM_ERROR;
|
||||
@@ -1109,10 +1069,7 @@ int ZEXPORT deflateCopy (dest, source)
|
||||
* allocating a large strm->next_in buffer and copying from it.
|
||||
* (See also flush_pending()).
|
||||
*/
|
||||
local int read_buf(strm, buf, size)
|
||||
z_streamp strm;
|
||||
Bytef *buf;
|
||||
unsigned size;
|
||||
local int read_buf(z_streamp strm, Bytef *buf, unsigned size)
|
||||
{
|
||||
unsigned len = strm->avail_in;
|
||||
|
||||
@@ -1139,8 +1096,7 @@ local int read_buf(strm, buf, size)
|
||||
/* ===========================================================================
|
||||
* Initialize the "longest match" routines for a new zlib stream
|
||||
*/
|
||||
local void lm_init (s)
|
||||
deflate_state *s;
|
||||
local void lm_init (deflate_state *s)
|
||||
{
|
||||
s->window_size = (ulg)2L*s->w_size;
|
||||
|
||||
@@ -1181,9 +1137,7 @@ local void lm_init (s)
|
||||
/* For 80x86 and 680x0, an optimized version will be provided in match.asm or
|
||||
* match.S. The code will be functionally equivalent.
|
||||
*/
|
||||
local uInt longest_match(s, cur_match)
|
||||
deflate_state *s;
|
||||
IPos cur_match; /* current match */
|
||||
local uInt longest_match(deflate_state *s, IPos cur_match)
|
||||
{
|
||||
unsigned chain_length = s->max_chain_length;/* max hash chain length */
|
||||
register Bytef *scan = s->window + s->strstart; /* current string */
|
||||
@@ -1330,9 +1284,7 @@ local uInt longest_match(s, cur_match)
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Optimized version for FASTEST only
|
||||
*/
|
||||
local uInt longest_match(s, cur_match)
|
||||
deflate_state *s;
|
||||
IPos cur_match; /* current match */
|
||||
local uInt longest_match(deflate_state *s, IPos cur_match)
|
||||
{
|
||||
register Bytef *scan = s->window + s->strstart; /* current string */
|
||||
register Bytef *match; /* matched string */
|
||||
@@ -1389,10 +1341,7 @@ local uInt longest_match(s, cur_match)
|
||||
/* ===========================================================================
|
||||
* Check that the match at match_start is indeed a match.
|
||||
*/
|
||||
local void check_match(s, start, match, length)
|
||||
deflate_state *s;
|
||||
IPos start, match;
|
||||
int length;
|
||||
local void check_match(deflate_state *s, IPos start, IPos match, int length)
|
||||
{
|
||||
/* check that the match is indeed a match */
|
||||
if (zmemcmp(s->window + match,
|
||||
@@ -1423,8 +1372,7 @@ local void check_match(s, start, match, length)
|
||||
* performed for at least two bytes (required for the zip translate_eol
|
||||
* option -- not supported here).
|
||||
*/
|
||||
local void fill_window(s)
|
||||
deflate_state *s;
|
||||
local void fill_window(deflate_state *s)
|
||||
{
|
||||
register unsigned n, m;
|
||||
register Posf *p;
|
||||
@@ -1597,9 +1545,7 @@ local void fill_window(s)
|
||||
* NOTE: this function should be optimized to avoid extra copying from
|
||||
* window to pending_buf.
|
||||
*/
|
||||
local block_state deflate_stored(s, flush)
|
||||
deflate_state *s;
|
||||
int flush;
|
||||
local block_state deflate_stored(deflate_state *s, int flush)
|
||||
{
|
||||
/* Stored blocks are limited to 0xffff bytes, pending_buf is limited
|
||||
* to pending_buf_size, and each stored block has a 5 byte header:
|
||||
@@ -1670,9 +1616,7 @@ local block_state deflate_stored(s, flush)
|
||||
* new strings in the dictionary only for unmatched strings or for short
|
||||
* matches. It is used only for the fast compression options.
|
||||
*/
|
||||
local block_state deflate_fast(s, flush)
|
||||
deflate_state *s;
|
||||
int flush;
|
||||
local block_state deflate_fast(deflate_state *s, int flush)
|
||||
{
|
||||
IPos hash_head; /* head of the hash chain */
|
||||
int bflush; /* set if current block must be flushed */
|
||||
@@ -1782,9 +1726,7 @@ local block_state deflate_fast(s, flush)
|
||||
* evaluation for matches: a match is finally adopted only if there is
|
||||
* no better match at the next window position.
|
||||
*/
|
||||
local block_state deflate_slow(s, flush)
|
||||
deflate_state *s;
|
||||
int flush;
|
||||
local block_state deflate_slow(deflate_state *s, int flush)
|
||||
{
|
||||
IPos hash_head; /* head of hash chain */
|
||||
int bflush; /* set if current block must be flushed */
|
||||
@@ -1923,9 +1865,7 @@ local block_state deflate_slow(s, flush)
|
||||
* one. Do not maintain a hash table. (It will be regenerated if this run of
|
||||
* deflate switches away from Z_RLE.)
|
||||
*/
|
||||
local block_state deflate_rle(s, flush)
|
||||
deflate_state *s;
|
||||
int flush;
|
||||
local block_state deflate_rle(deflate_state *s, int flush)
|
||||
{
|
||||
int bflush; /* set if current block must be flushed */
|
||||
uInt prev; /* byte at distance one to match */
|
||||
@@ -1996,9 +1936,7 @@ local block_state deflate_rle(s, flush)
|
||||
* For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table.
|
||||
* (It will be regenerated if this run of deflate switches away from Huffman.)
|
||||
*/
|
||||
local block_state deflate_huff(s, flush)
|
||||
deflate_state *s;
|
||||
int flush;
|
||||
local block_state deflate_huff(deflate_state *s, int flush)
|
||||
{
|
||||
int bflush; /* set if current block must be flushed */
|
||||
|
||||
|
||||
@@ -45,9 +45,7 @@
|
||||
requires strm->avail_out >= 258 for each loop to avoid checking for
|
||||
output space.
|
||||
*/
|
||||
void ZLIB_INTERNAL inflate_fast(strm, start)
|
||||
z_streamp strm;
|
||||
unsigned start; /* inflate()'s starting value for strm->avail_out */
|
||||
void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
z_const unsigned char FAR *in; /* local strm->next_in */
|
||||
|
||||
@@ -101,8 +101,7 @@ local int updatewindow OF((z_streamp strm, const unsigned char FAR *end,
|
||||
local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf,
|
||||
unsigned len));
|
||||
|
||||
int ZEXPORT inflateResetKeep(strm)
|
||||
z_streamp strm;
|
||||
int ZEXPORT inflateResetKeep(z_streamp strm)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
|
||||
@@ -126,8 +125,7 @@ z_streamp strm;
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
int ZEXPORT inflateReset(strm)
|
||||
z_streamp strm;
|
||||
int ZEXPORT inflateReset(z_streamp strm)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
|
||||
@@ -139,9 +137,7 @@ z_streamp strm;
|
||||
return inflateResetKeep(strm);
|
||||
}
|
||||
|
||||
int ZEXPORT inflateReset2(strm, windowBits)
|
||||
z_streamp strm;
|
||||
int windowBits;
|
||||
int ZEXPORT inflateReset2(z_streamp strm, int windowBits)
|
||||
{
|
||||
int wrap;
|
||||
struct inflate_state FAR *state;
|
||||
@@ -177,11 +173,7 @@ int windowBits;
|
||||
return inflateReset(strm);
|
||||
}
|
||||
|
||||
int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size)
|
||||
z_streamp strm;
|
||||
int windowBits;
|
||||
const char *version;
|
||||
int stream_size;
|
||||
int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, const char *version, int stream_size)
|
||||
{
|
||||
int ret;
|
||||
struct inflate_state FAR *state;
|
||||
@@ -219,18 +211,12 @@ int stream_size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ZEXPORT inflateInit_(strm, version, stream_size)
|
||||
z_streamp strm;
|
||||
const char *version;
|
||||
int stream_size;
|
||||
int ZEXPORT inflateInit_(z_streamp strm, const char *version, int stream_size)
|
||||
{
|
||||
return inflateInit2_(strm, DEF_WBITS, version, stream_size);
|
||||
}
|
||||
|
||||
int ZEXPORT inflatePrime(strm, bits, value)
|
||||
z_streamp strm;
|
||||
int bits;
|
||||
int value;
|
||||
int ZEXPORT inflatePrime(z_streamp strm, int bits, int value)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
|
||||
@@ -258,8 +244,7 @@ int value;
|
||||
used for threaded applications, since the rewriting of the tables and virgin
|
||||
may not be thread-safe.
|
||||
*/
|
||||
local void fixedtables(state)
|
||||
struct inflate_state FAR *state;
|
||||
local void fixedtables(struct inflate_state FAR *state)
|
||||
{
|
||||
#ifdef BUILDFIXED
|
||||
static int virgin = 1;
|
||||
@@ -376,10 +361,7 @@ void makefixed()
|
||||
output will fall in the output data, making match copies simpler and faster.
|
||||
The advantage may be dependent on the size of the processor's data caches.
|
||||
*/
|
||||
local int updatewindow(strm, end, copy)
|
||||
z_streamp strm;
|
||||
const Bytef *end;
|
||||
unsigned copy;
|
||||
local int updatewindow(z_streamp strm, const Bytef *end, unsigned copy)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
unsigned dist;
|
||||
@@ -602,9 +584,7 @@ unsigned copy;
|
||||
will return Z_BUF_ERROR if it has not reached the end of the stream.
|
||||
*/
|
||||
|
||||
int ZEXPORT inflate(strm, flush)
|
||||
z_streamp strm;
|
||||
int flush;
|
||||
int ZEXPORT inflate(z_streamp strm, int flush)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
z_const unsigned char FAR *next; /* next input */
|
||||
@@ -1274,8 +1254,7 @@ int flush;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ZEXPORT inflateEnd(strm)
|
||||
z_streamp strm;
|
||||
int ZEXPORT inflateEnd(z_streamp strm)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
|
||||
@@ -1288,10 +1267,7 @@ z_streamp strm;
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength)
|
||||
z_streamp strm;
|
||||
Bytef *dictionary;
|
||||
uInt *dictLength;
|
||||
int ZEXPORT inflateGetDictionary(z_streamp strm, Bytef *dictionary, uInt *dictLength)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
|
||||
@@ -1311,10 +1287,7 @@ uInt *dictLength;
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength)
|
||||
z_streamp strm;
|
||||
const Bytef *dictionary;
|
||||
uInt dictLength;
|
||||
int ZEXPORT inflateSetDictionary(z_streamp strm, const Bytef *dictionary, uInt dictLength)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
unsigned long dictid;
|
||||
@@ -1346,9 +1319,7 @@ uInt dictLength;
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
int ZEXPORT inflateGetHeader(strm, head)
|
||||
z_streamp strm;
|
||||
gz_headerp head;
|
||||
int ZEXPORT inflateGetHeader(z_streamp strm, gz_headerp head)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
|
||||
@@ -1374,10 +1345,7 @@ gz_headerp head;
|
||||
called again with more data and the *have state. *have is initialized to
|
||||
zero for the first call.
|
||||
*/
|
||||
local unsigned syncsearch(have, buf, len)
|
||||
unsigned FAR *have;
|
||||
const unsigned char FAR *buf;
|
||||
unsigned len;
|
||||
local unsigned syncsearch(unsigned FAR *have, const unsigned char FAR *buf, unsigned len)
|
||||
{
|
||||
unsigned got;
|
||||
unsigned next;
|
||||
@@ -1397,8 +1365,7 @@ unsigned len;
|
||||
return next;
|
||||
}
|
||||
|
||||
int ZEXPORT inflateSync(strm)
|
||||
z_streamp strm;
|
||||
int ZEXPORT inflateSync(z_streamp strm)
|
||||
{
|
||||
unsigned len; /* number of bytes to look at or looked at */
|
||||
unsigned long in, out; /* temporary to save total_in and total_out */
|
||||
@@ -1448,8 +1415,7 @@ z_streamp strm;
|
||||
block. When decompressing, PPP checks that at the end of input packet,
|
||||
inflate is waiting for these length bytes.
|
||||
*/
|
||||
int ZEXPORT inflateSyncPoint(strm)
|
||||
z_streamp strm;
|
||||
int ZEXPORT inflateSyncPoint(z_streamp strm)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
|
||||
@@ -1458,9 +1424,7 @@ z_streamp strm;
|
||||
return state->mode == STORED && state->bits == 0;
|
||||
}
|
||||
|
||||
int ZEXPORT inflateCopy(dest, source)
|
||||
z_streamp dest;
|
||||
z_streamp source;
|
||||
int ZEXPORT inflateCopy(z_streamp dest, z_streamp source)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
struct inflate_state FAR *copy;
|
||||
@@ -1505,9 +1469,7 @@ z_streamp source;
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
int ZEXPORT inflateUndermine(strm, subvert)
|
||||
z_streamp strm;
|
||||
int subvert;
|
||||
int ZEXPORT inflateUndermine(z_streamp strm, int subvert)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
|
||||
@@ -1522,8 +1484,7 @@ int subvert;
|
||||
#endif
|
||||
}
|
||||
|
||||
long ZEXPORT inflateMark(strm)
|
||||
z_streamp strm;
|
||||
long ZEXPORT inflateMark(z_streamp strm)
|
||||
{
|
||||
struct inflate_state FAR *state;
|
||||
|
||||
|
||||
@@ -29,13 +29,9 @@ const char inflate_copyright[] =
|
||||
table index bits. It will differ if the request is greater than the
|
||||
longest code or if it is less than the shortest code.
|
||||
*/
|
||||
int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work)
|
||||
codetype type;
|
||||
unsigned short FAR *lens;
|
||||
unsigned codes;
|
||||
code FAR * FAR *table;
|
||||
unsigned FAR *bits;
|
||||
unsigned short FAR *work;
|
||||
int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens,
|
||||
unsigned codes, code FAR * FAR *table, unsigned FAR *bits,
|
||||
unsigned short FAR *work)
|
||||
{
|
||||
unsigned len; /* a code's length in bits */
|
||||
unsigned sym; /* index of code symbols */
|
||||
|
||||
97
zlib/trees.c
97
zlib/trees.c
@@ -185,10 +185,7 @@ local void gen_trees_header OF((void));
|
||||
#ifdef DEBUG
|
||||
local void send_bits OF((deflate_state *s, int value, int length));
|
||||
|
||||
local void send_bits(s, value, length)
|
||||
deflate_state *s;
|
||||
int value; /* value to send */
|
||||
int length; /* number of bits */
|
||||
local void send_bits(deflate_state *s, int value, int length)
|
||||
{
|
||||
Tracevv((stderr," l %2d v %4x ", length, value));
|
||||
Assert(length > 0 && length <= 15, "invalid length");
|
||||
@@ -231,7 +228,7 @@ local void send_bits(s, value, length)
|
||||
/* ===========================================================================
|
||||
* Initialize the various 'constant' tables.
|
||||
*/
|
||||
local void tr_static_init()
|
||||
local void tr_static_init(void)
|
||||
{
|
||||
#if defined(GEN_TREES_H) || !defined(STDC)
|
||||
static int static_init_done = 0;
|
||||
@@ -325,7 +322,7 @@ local void tr_static_init()
|
||||
((i) == (last)? "\n};\n\n" : \
|
||||
((i) % (width) == (width)-1 ? ",\n" : ", "))
|
||||
|
||||
void gen_trees_header()
|
||||
void gen_trees_header(void)
|
||||
{
|
||||
FILE *header = fopen("trees.h", "w");
|
||||
int i;
|
||||
@@ -378,8 +375,7 @@ void gen_trees_header()
|
||||
/* ===========================================================================
|
||||
* Initialize the tree data structures for a new zlib stream.
|
||||
*/
|
||||
void ZLIB_INTERNAL _tr_init(s)
|
||||
deflate_state *s;
|
||||
void ZLIB_INTERNAL _tr_init(deflate_state *s)
|
||||
{
|
||||
tr_static_init();
|
||||
|
||||
@@ -406,8 +402,7 @@ void ZLIB_INTERNAL _tr_init(s)
|
||||
/* ===========================================================================
|
||||
* Initialize a new block.
|
||||
*/
|
||||
local void init_block(s)
|
||||
deflate_state *s;
|
||||
local void init_block(deflate_state *s)
|
||||
{
|
||||
int n; /* iterates over tree elements */
|
||||
|
||||
@@ -450,10 +445,7 @@ local void init_block(s)
|
||||
* when the heap property is re-established (each father smaller than its
|
||||
* two sons).
|
||||
*/
|
||||
local void pqdownheap(s, tree, k)
|
||||
deflate_state *s;
|
||||
ct_data *tree; /* the tree to restore */
|
||||
int k; /* node to move down */
|
||||
local void pqdownheap(deflate_state *s, ct_data *tree, int k)
|
||||
{
|
||||
int v = s->heap[k];
|
||||
int j = k << 1; /* left son of k */
|
||||
@@ -485,9 +477,7 @@ local void pqdownheap(s, tree, k)
|
||||
* The length opt_len is updated; static_len is also updated if stree is
|
||||
* not null.
|
||||
*/
|
||||
local void gen_bitlen(s, desc)
|
||||
deflate_state *s;
|
||||
tree_desc *desc; /* the tree descriptor */
|
||||
local void gen_bitlen(deflate_state *s, tree_desc *desc)
|
||||
{
|
||||
ct_data *tree = desc->dyn_tree;
|
||||
int max_code = desc->max_code;
|
||||
@@ -572,10 +562,7 @@ local void gen_bitlen(s, desc)
|
||||
* OUT assertion: the field code is set for all tree elements of non
|
||||
* zero code length.
|
||||
*/
|
||||
local void gen_codes (tree, max_code, bl_count)
|
||||
ct_data *tree; /* the tree to decorate */
|
||||
int max_code; /* largest code with non zero frequency */
|
||||
ushf *bl_count; /* number of codes at each bit length */
|
||||
local void gen_codes(ct_data *tree, int max_code, ushf *bl_count)
|
||||
{
|
||||
ush next_code[MAX_BITS+1]; /* next code value for each bit length */
|
||||
ush code = 0; /* running code value */
|
||||
@@ -614,9 +601,7 @@ local void gen_codes (tree, max_code, bl_count)
|
||||
* and corresponding code. The length opt_len is updated; static_len is
|
||||
* also updated if stree is not null. The field max_code is set.
|
||||
*/
|
||||
local void build_tree(s, desc)
|
||||
deflate_state *s;
|
||||
tree_desc *desc; /* the tree descriptor */
|
||||
local void build_tree(deflate_state *s, tree_desc *desc)
|
||||
{
|
||||
ct_data *tree = desc->dyn_tree;
|
||||
const ct_data *stree = desc->stat_desc->static_tree;
|
||||
@@ -702,10 +687,7 @@ local void build_tree(s, desc)
|
||||
* Scan a literal or distance tree to determine the frequencies of the codes
|
||||
* in the bit length tree.
|
||||
*/
|
||||
local void scan_tree (s, tree, max_code)
|
||||
deflate_state *s;
|
||||
ct_data *tree; /* the tree to be scanned */
|
||||
int max_code; /* and its largest code of non zero frequency */
|
||||
local void scan_tree(deflate_state *s, ct_data *tree, int max_code)
|
||||
{
|
||||
int n; /* iterates over all tree elements */
|
||||
int prevlen = -1; /* last emitted length */
|
||||
@@ -747,10 +729,7 @@ local void scan_tree (s, tree, max_code)
|
||||
* Send a literal or distance tree in compressed form, using the codes in
|
||||
* bl_tree.
|
||||
*/
|
||||
local void send_tree (s, tree, max_code)
|
||||
deflate_state *s;
|
||||
ct_data *tree; /* the tree to be scanned */
|
||||
int max_code; /* and its largest code of non zero frequency */
|
||||
local void send_tree(deflate_state *s, ct_data *tree, int max_code)
|
||||
{
|
||||
int n; /* iterates over all tree elements */
|
||||
int prevlen = -1; /* last emitted length */
|
||||
@@ -798,8 +777,7 @@ local void send_tree (s, tree, max_code)
|
||||
* Construct the Huffman tree for the bit lengths and return the index in
|
||||
* bl_order of the last bit length code to send.
|
||||
*/
|
||||
local int build_bl_tree(s)
|
||||
deflate_state *s;
|
||||
local int build_bl_tree(deflate_state *s)
|
||||
{
|
||||
int max_blindex; /* index of last bit length code of non zero freq */
|
||||
|
||||
@@ -833,9 +811,7 @@ local int build_bl_tree(s)
|
||||
* lengths of the bit length codes, the literal tree and the distance tree.
|
||||
* IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
|
||||
*/
|
||||
local void send_all_trees(s, lcodes, dcodes, blcodes)
|
||||
deflate_state *s;
|
||||
int lcodes, dcodes, blcodes; /* number of codes for each tree */
|
||||
local void send_all_trees(deflate_state *s, int lcodes, int dcodes, int blcodes)
|
||||
{
|
||||
int rank; /* index in bl_order */
|
||||
|
||||
@@ -862,11 +838,7 @@ local void send_all_trees(s, lcodes, dcodes, blcodes)
|
||||
/* ===========================================================================
|
||||
* Send a stored block
|
||||
*/
|
||||
void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
|
||||
deflate_state *s;
|
||||
charf *buf; /* input block */
|
||||
ulg stored_len; /* length of input block */
|
||||
int last; /* one if this is the last block for a file */
|
||||
void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, ulg stored_len, int last)
|
||||
{
|
||||
send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */
|
||||
#ifdef DEBUG
|
||||
@@ -879,8 +851,7 @@ void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
|
||||
/* ===========================================================================
|
||||
* Flush the bits in the bit buffer to pending output (leaves at most 7 bits)
|
||||
*/
|
||||
void ZLIB_INTERNAL _tr_flush_bits(s)
|
||||
deflate_state *s;
|
||||
void ZLIB_INTERNAL _tr_flush_bits(deflate_state *s)
|
||||
{
|
||||
bi_flush(s);
|
||||
}
|
||||
@@ -889,8 +860,7 @@ void ZLIB_INTERNAL _tr_flush_bits(s)
|
||||
* Send one empty static block to give enough lookahead for inflate.
|
||||
* This takes 10 bits, of which 7 may remain in the bit buffer.
|
||||
*/
|
||||
void ZLIB_INTERNAL _tr_align(s)
|
||||
deflate_state *s;
|
||||
void ZLIB_INTERNAL _tr_align(deflate_state *s)
|
||||
{
|
||||
send_bits(s, STATIC_TREES<<1, 3);
|
||||
send_code(s, END_BLOCK, static_ltree);
|
||||
@@ -904,11 +874,7 @@ void ZLIB_INTERNAL _tr_align(s)
|
||||
* Determine the best encoding for the current block: dynamic trees, static
|
||||
* trees or store, and output the encoded block to the zip file.
|
||||
*/
|
||||
void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
|
||||
deflate_state *s;
|
||||
charf *buf; /* input block, or NULL if too old */
|
||||
ulg stored_len; /* length of input block */
|
||||
int last; /* one if this is the last block for a file */
|
||||
void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, ulg stored_len, int last)
|
||||
{
|
||||
ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */
|
||||
int max_blindex = 0; /* index of last bit length code of non zero freq */
|
||||
@@ -1007,10 +973,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
|
||||
* Save the match info and tally the frequency counts. Return true if
|
||||
* the current block must be flushed.
|
||||
*/
|
||||
int ZLIB_INTERNAL _tr_tally (s, dist, lc)
|
||||
deflate_state *s;
|
||||
unsigned dist; /* distance of matched string */
|
||||
unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */
|
||||
int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc)
|
||||
{
|
||||
s->sym_buf[s->sym_next++] = dist;
|
||||
s->sym_buf[s->sym_next++] = dist >> 8;
|
||||
@@ -1035,10 +998,7 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc)
|
||||
/* ===========================================================================
|
||||
* Send the block data compressed using the given Huffman trees
|
||||
*/
|
||||
local void compress_block(s, ltree, dtree)
|
||||
deflate_state *s;
|
||||
const ct_data *ltree; /* literal tree */
|
||||
const ct_data *dtree; /* distance tree */
|
||||
local void compress_block(deflate_state *s, const ct_data *ltree, const ct_data *dtree)
|
||||
{
|
||||
unsigned dist; /* distance of matched string */
|
||||
int lc; /* match length or unmatched char (if dist == 0) */
|
||||
@@ -1095,8 +1055,7 @@ local void compress_block(s, ltree, dtree)
|
||||
* (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
|
||||
* IN assertion: the fields Freq of dyn_ltree are set.
|
||||
*/
|
||||
local int detect_data_type(s)
|
||||
deflate_state *s;
|
||||
local int detect_data_type(deflate_state *s)
|
||||
{
|
||||
/* black_mask is the bit mask of black-listed bytes
|
||||
* set bits 0..6, 14..25, and 28..31
|
||||
@@ -1129,9 +1088,7 @@ local int detect_data_type(s)
|
||||
* method would use a table)
|
||||
* IN assertion: 1 <= len <= 15
|
||||
*/
|
||||
local unsigned bi_reverse(code, len)
|
||||
unsigned code; /* the value to invert */
|
||||
int len; /* its bit length */
|
||||
local unsigned bi_reverse(unsigned code, int len)
|
||||
{
|
||||
register unsigned res = 0;
|
||||
do {
|
||||
@@ -1144,8 +1101,7 @@ local unsigned bi_reverse(code, len)
|
||||
/* ===========================================================================
|
||||
* Flush the bit buffer, keeping at most 7 bits in it.
|
||||
*/
|
||||
local void bi_flush(s)
|
||||
deflate_state *s;
|
||||
local void bi_flush(deflate_state *s)
|
||||
{
|
||||
if (s->bi_valid == 16) {
|
||||
put_short(s, s->bi_buf);
|
||||
@@ -1161,8 +1117,7 @@ local void bi_flush(s)
|
||||
/* ===========================================================================
|
||||
* Flush the bit buffer and align the output on a byte boundary
|
||||
*/
|
||||
local void bi_windup(s)
|
||||
deflate_state *s;
|
||||
local void bi_windup(deflate_state *s)
|
||||
{
|
||||
if (s->bi_valid > 8) {
|
||||
put_short(s, s->bi_buf);
|
||||
@@ -1180,11 +1135,7 @@ local void bi_windup(s)
|
||||
* Copy a stored block, storing first the length and its
|
||||
* one's complement if requested.
|
||||
*/
|
||||
local void copy_block(s, buf, len, header)
|
||||
deflate_state *s;
|
||||
charf *buf; /* the input data */
|
||||
unsigned len; /* its length */
|
||||
int header; /* true if block header must be written */
|
||||
local void copy_block(deflate_state *s, charf *buf, unsigned len, int header)
|
||||
{
|
||||
bi_windup(s); /* align on byte boundary */
|
||||
|
||||
|
||||
33
zlib/zutil.c
33
zlib/zutil.c
@@ -27,12 +27,12 @@ z_const char * const z_errmsg[10] = {
|
||||
""};
|
||||
|
||||
|
||||
const char * ZEXPORT zlibVersion()
|
||||
const char * ZEXPORT zlibVersion(void)
|
||||
{
|
||||
return ZLIB_VERSION;
|
||||
}
|
||||
|
||||
uLong ZEXPORT zlibCompileFlags()
|
||||
uLong ZEXPORT zlibCompileFlags(void)
|
||||
{
|
||||
uLong flags;
|
||||
|
||||
@@ -122,8 +122,7 @@ uLong ZEXPORT zlibCompileFlags()
|
||||
# endif
|
||||
int ZLIB_INTERNAL z_verbose = verbose;
|
||||
|
||||
void ZLIB_INTERNAL z_error (m)
|
||||
char *m;
|
||||
void ZLIB_INTERNAL z_error (char *m)
|
||||
{
|
||||
fprintf(stderr, "%s\n", m);
|
||||
exit(1);
|
||||
@@ -133,8 +132,7 @@ void ZLIB_INTERNAL z_error (m)
|
||||
/* exported to allow conversion of error code to string for compress() and
|
||||
* uncompress()
|
||||
*/
|
||||
const char * ZEXPORT zError(err)
|
||||
int err;
|
||||
const char * ZEXPORT zError(int err)
|
||||
{
|
||||
return ERR_MSG(err);
|
||||
}
|
||||
@@ -149,10 +147,7 @@ const char * ZEXPORT zError(err)
|
||||
|
||||
#ifndef HAVE_MEMCPY
|
||||
|
||||
void ZLIB_INTERNAL zmemcpy(dest, source, len)
|
||||
Bytef* dest;
|
||||
const Bytef* source;
|
||||
uInt len;
|
||||
void ZLIB_INTERNAL zmemcpy(Bytef* dest, const Bytef* source, uInt len)
|
||||
{
|
||||
if (len == 0) return;
|
||||
do {
|
||||
@@ -160,10 +155,7 @@ void ZLIB_INTERNAL zmemcpy(dest, source, len)
|
||||
} while (--len != 0);
|
||||
}
|
||||
|
||||
int ZLIB_INTERNAL zmemcmp(s1, s2, len)
|
||||
const Bytef* s1;
|
||||
const Bytef* s2;
|
||||
uInt len;
|
||||
int ZLIB_INTERNAL zmemcmp(const Bytef* s1, const Bytef* s2, uInt len)
|
||||
{
|
||||
uInt j;
|
||||
|
||||
@@ -173,9 +165,7 @@ int ZLIB_INTERNAL zmemcmp(s1, s2, len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ZLIB_INTERNAL zmemzero(dest, len)
|
||||
Bytef* dest;
|
||||
uInt len;
|
||||
void ZLIB_INTERNAL zmemzero(Bytef* dest, uInt len)
|
||||
{
|
||||
if (len == 0) return;
|
||||
do {
|
||||
@@ -301,19 +291,14 @@ extern voidp calloc OF((uInt items, uInt size));
|
||||
extern void free OF((voidpf ptr));
|
||||
#endif
|
||||
|
||||
voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
|
||||
voidpf opaque;
|
||||
unsigned items;
|
||||
unsigned size;
|
||||
voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
|
||||
{
|
||||
if (opaque) items += size - size; /* make compiler happy */
|
||||
return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) :
|
||||
(voidpf)calloc(items, size);
|
||||
}
|
||||
|
||||
void ZLIB_INTERNAL zcfree (opaque, ptr)
|
||||
voidpf opaque;
|
||||
voidpf ptr;
|
||||
void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
|
||||
{
|
||||
free(ptr);
|
||||
if (opaque) return; /* make compiler happy */
|
||||
|
||||
Reference in New Issue
Block a user