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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/debian-10-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Test rsync on Debian 10 Buster

# This workflow also ensures compatibility with Python 3.7

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

on:
push:
branches: [ master ]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/debian-10-build.yml'
pull_request:
types: [opened, synchronize, reopened]
paths-ignore:
- '.github/workflows/*.yml'
- '!.github/workflows/debian-10-build.yml'
schedule:
- cron: '42 8 * * *'

jobs:
test:
runs-on: ubuntu-latest
container:
image: debian:buster
name: Test rsync on Debian 10 Buster
steps:
- name: point apt sources to archive.debian.org
run: sed -i 's/deb\.debian\.org/archive.debian.org/' /etc/apt/sources.list
- name: populate apt cache
run: apt-get update
- name: install git
# actions/checkout needs git in the container before the checkout step.
run: apt-get -y install git
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: prep
run: |
apt-get -y install \
build-essential autoconf automake autotools-dev m4 \
acl libacl1-dev attr libattr1-dev \
libidn2-dev liblz4-dev libpopt-dev libssl-dev zlib1g-dev \
python3:native python3-cmarkgfm:native
- name: configure
# Don't use bundled zlib
# Disable md5-asm optimization: we link against openssl so rsync ends up
# using that library's implementation of the hash
# Disable xxhash and zstd: the system libs are too old for the build
run: ./configure --with-rrsync --with-included-zlib=no --disable-md5-asm --disable-xxhash --disable-zstd
- name: make
run: make
- name: install
run: make install
- name: info
run: rsync --version
- name: check
run: RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt make check
- name: save artifact
uses: actions/upload-artifact@v4
with:
retention-days: 45
name: debian-10-bin
path: |
rsync
rsync-ssl
rsync.1
rsync-ssl.1
rsyncd.conf.5
rrsync.1
rrsync
9 changes: 9 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
on macOS. Native configuration now proves lookup, creation,
permission-revocation and no-follow behaviour before selecting a traversal
descriptor. Unverified targets fail closed instead of using raceable paths.
- Rejected config and filter files after read or stream setup errors instead
of accepting partial contents. MOTD and `--early-input` reads now stop on
I/O errors instead of looping; interrupted reads retain their data.
- Fixed an out-of-bounds read while reporting an incomplete daemon config
section header.
- Preserved sender file-open errors when closing a held directory descriptor
during `--copy-links` transfers.
- Changed to `/` immediately after a module chroot, before selecting an inner
module directory. A failed working-directory reset aborts the connection.

# NEWS for rsync 3.5.2 (4 Nov 2026)

Expand Down
47 changes: 43 additions & 4 deletions clientserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@ int start_socket_client(char *host, int remote_argc, char *remote_argv[],
return ret ? ret : client_run(fd, fd, -1, argc, argv);
}

static size_t fread_retry(void *buffer, size_t length, FILE *file)
{
size_t total = fread(buffer, 1, length, file);
while (total < length && ferror(file) && errno == EINTR) {
clearerr(file);
/* Preserve partial input across EINTR. Clang 21 does not model
* the recoverable position after an interrupted stream read. */
#ifdef __clang_analyzer__
__attribute__((suppress))
#endif
total += fread((char *)buffer + total, 1, length - total, file);
}
return total;
}

static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int am_client)
{
int remote_sub = -1;
Expand All @@ -191,9 +206,20 @@ static int exchange_protocols(int f_in, int f_out, char *buf, size_t bufsiz, int
* symlinks not owned by uid 0 or our euid. */
int motd_fd = open_no_attacker_symlinks(motd, O_RDONLY, 0);
FILE *f = motd_fd >= 0 ? fdopen(motd_fd, "r") : NULL;
if (!f && motd_fd >= 0) close(motd_fd);
if (!f && motd_fd >= 0) {
int fdopen_errno = errno;
close(motd_fd);
rsyserr(FLOG, fdopen_errno, "failed to open motd file %s", motd);
return -1;
}
while (f && !feof(f)) {
int len = fread(buf, 1, bufsiz - 1, f);
int len = fread_retry(buf, bufsiz - 1, f);
if (ferror(f)) {
int read_errno = errno;
fclose(f);
rsyserr(FLOG, read_errno, "failed to read motd file %s", motd);
return -1;
}
if (len > 0)
write_buf(f_out, buf, len);
}
Expand Down Expand Up @@ -306,7 +332,11 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char
* our euid anywhere in the path. */
int ei_fd = open_no_attacker_symlinks(early_input_file, O_RDONLY, 0);
FILE *f = ei_fd >= 0 ? fdopen(ei_fd, "rb") : NULL;
if (!f && ei_fd >= 0) close(ei_fd);
if (!f && ei_fd >= 0) {
int fdopen_errno = errno;
close(ei_fd);
errno = fdopen_errno;
}
if (!f || do_fstat(fileno(f), &st) < 0) {
rsyserr(FERROR, errno, "failed to open %s", early_input_file);
if (f)
Expand All @@ -331,7 +361,14 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char
free(modname);
return -1;
}
len = fread(line, 1, early_input_len, f);
len = fread_retry(line, early_input_len, f);
if (ferror(f)) {
int read_errno = errno;
fclose(f);
free(modname);
rsyserr(FERROR, read_errno, "failed to read early-input file %s", early_input_file);
return -1;
}
if (len > 0) {
write_buf(f_out, line, len);
early_input_len -= len;
Expand Down Expand Up @@ -1056,6 +1093,8 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
io_printf(f_out, "@ERROR: chroot failed\n");
return -1;
}
if (chdir("/") < 0)
return path_failure(f_out, "/", True);
am_chrooted = 1;
module_chdir = module_dir;
}
Expand Down
38 changes: 29 additions & 9 deletions exclude.c
Original file line number Diff line number Diff line change
Expand Up @@ -1598,6 +1598,21 @@ void parse_filter_str(filter_rule_list *listp, const char *rulestr,
}
}

static int get_filter_char(FILE *file)
{
int c = getc(file);
while (c == EOF && ferror(file) && errno == EINTR) {
clearerr(file);
/* EINTR leaves the unread byte in place, including on pipes.
* Clang 21's stream model does not recognise this recovery. */
#ifdef __clang_analyzer__
__attribute__((suppress))
#endif
c = getc(file);
}
return c;
}

void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_rule *template, int xflags)
{
FILE *fp;
Expand Down Expand Up @@ -1685,8 +1700,11 @@ void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_

if (fd < 0)
fp = NULL;
else if (!(fp = fdopen(fd, "rb")))
else if (!(fp = fdopen(fd, "rb"))) {
int fdopen_errno = errno;
close(fd);
errno = fdopen_errno;
}
} else
fp = stdin;

Expand Down Expand Up @@ -1762,21 +1780,15 @@ void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_
if (pending != EOF) { /* a CR lookahead we could not push back */
ch = pending;
pending = EOF;
} else if ((ch = getc(fp)) == EOF) {
if (ferror(fp) && errno == EINTR) {
clearerr(fp);
continue;
}
} else if ((ch = get_filter_char(fp)) == EOF) {
break;
}
if (word_split && isspace(ch))
break;
if (eol_nulls? !ch : (ch == '\n' || ch == '\r')) {
if (ch == '\r') { /* CRLF is one line, not two */
int nxt;
while ((nxt = getc(fp)) == EOF
&& ferror(fp) && errno == EINTR)
clearerr(fp);
nxt = get_filter_char(fp);
if (nxt == EOF) {
if (!ferror(fp))
ch = EOF; /* real end of file */
Expand All @@ -1796,6 +1808,14 @@ void parse_filter_file(filter_rule_list *listp, const char *fname, const filter_
else
overflow = 1;
}
if (ferror(fp)) {
if (named_by_file)
rprintf(FERROR, "failed to read filter file %s\n", rule_text(template, src_name));
else
rsyserr(FERROR, errno, "failed to read filter file %s", src_name);
fclose(fp);
exit_cleanup(RERR_FILEIO);
}
if (overflow) {
rprintf(FERROR, "discarding over-long filter: %s\n",
rule_text_len(NULL, line, 0));
Expand Down
Loading
Loading