Compare commits

..

No commits in common. "c8" and "c9" have entirely different histories.
c8 ... c9

35 changed files with 616 additions and 6086 deletions

4
.gitignore vendored
View File

@ -1,2 +1,2 @@
SOURCES/rsync-3.1.3.tar.gz SOURCES/rsync-3.2.3.tar.gz
SOURCES/rsync-patches-3.1.3.tar.gz SOURCES/rsync-patches-3.2.3.tar.gz

View File

@ -1,2 +1,2 @@
82e7829c0b3cefbd33c233005341e2073c425629 SOURCES/rsync-3.1.3.tar.gz 00823f43901e7da39f3f0daf20ec9efae47e959e SOURCES/rsync-3.2.3.tar.gz
74c16510a18ef43d797f9ceba6150f0862568cc0 SOURCES/rsync-patches-3.1.3.tar.gz 5c4c2866931b0f4dbb0d07567b2963c9dc66ec87 SOURCES/rsync-patches-3.2.3.tar.gz

View File

@ -1,22 +0,0 @@
diff --git a/log.c b/log.c
index 34a013b..1aca728 100644
--- a/log.c
+++ b/log.c
@@ -377,10 +377,13 @@ output_msg:
filtered_fwrite(f, convbuf, outbuf.len, 0);
outbuf.len = 0;
}
- if (!ierrno || ierrno == E2BIG)
- continue;
- fprintf(f, "\\#%03o", CVAL(inbuf.buf, inbuf.pos++));
- inbuf.len--;
+ /* Log one byte of illegal/incomplete sequence and continue with
+ * the next character. Check that the buffer is non-empty for the
+ * sake of robustness. */
+ if ((ierrno == EILSEQ || ierrno == EINVAL) && inbuf.len) {
+ fprintf(f, "\\#%03o", CVAL(inbuf.buf, inbuf.pos++));
+ inbuf.len--;
+ }
}
} else
#endif

View File

@ -1,33 +0,0 @@
diff --git a/sender.c b/sender.c
index 03e4aadd..9b432ed9 100644
--- a/sender.c
+++ b/sender.c
@@ -32,6 +32,7 @@ extern int logfile_format_has_i;
extern int want_xattr_optim;
extern int csum_length;
extern int append_mode;
+extern int copy_links;
extern int io_error;
extern int flist_eof;
extern int allowed_lull;
@@ -138,17 +139,16 @@ void successful_send(int ndx)
return;
f_name(file, fname);
- if (do_lstat(fname, &st) < 0) {
+ if ((copy_links ? do_stat(fname, &st) : do_lstat(fname, &st)) < 0) {
failed_op = "re-lstat";
goto failed;
}
- if (S_ISREG(file->mode) /* Symlinks & devices don't need this check: */
- && (st.st_size != F_LENGTH(file) || st.st_mtime != file->modtime
+ if (st.st_size != F_LENGTH(file) || st.st_mtime != file->modtime
#ifdef ST_MTIME_NSEC
|| (NSEC_BUMP(file) && (uint32)st.ST_MTIME_NSEC != F_MOD_NSEC(file))
#endif
- )) {
+ ) {
rprintf(FERROR_XFER, "ERROR: Skipping sender remove for changed file: %s\n", fname);
return;
}

View File

@ -1,33 +0,0 @@
diff --git a/io.c b/io.c
index 999c34e5..ceff3784 100644
--- a/io.c
+++ b/io.c
@@ -954,8 +954,17 @@ int send_msg(enum msgcode code, const char *buf, size_t len, int convert)
} else
#endif
needed = len + 4 + 3;
- if (iobuf.msg.len + needed > iobuf.msg.size)
- perform_io(needed, PIO_NEED_MSGROOM);
+ if (iobuf.msg.len + needed > iobuf.msg.size) {
+ if (!am_receiver)
+ perform_io(needed, PIO_NEED_MSGROOM);
+ else { /* We allow the receiver to increase their iobuf.msg size to avoid a deadlock. */
+ size_t old_size = iobuf.msg.size;
+ restore_iobuf_size(&iobuf.msg);
+ realloc_xbuf(&iobuf.msg, iobuf.msg.size * 2);
+ if (iobuf.msg.pos + iobuf.msg.len > old_size)
+ memcpy(iobuf.msg.buf + old_size, iobuf.msg.buf, iobuf.msg.pos + iobuf.msg.len - old_size);
+ }
+ }
pos = iobuf.msg.pos + iobuf.msg.len; /* Must be set after any flushing. */
if (pos >= iobuf.msg.size)
@@ -1176,7 +1185,7 @@ int read_line(int fd, char *buf, size_t bufsiz, int flags)
#ifdef ICONV_OPTION
if (flags & RL_CONVERT && iconv_buf.size < bufsiz)
- realloc_xbuf(&iconv_buf, bufsiz + 1024);
+ realloc_xbuf(&iconv_buf, ROUND_UP_1024(bufsiz) + 1024);
#endif
start:

View File

@ -1,44 +0,0 @@
commit bd17c2a4e237ca1f38544db65053ecfea6054009
Author: Tomas Korbar <tkorbar@redhat.com>
Date: Thu Sep 24 13:17:45 2020 +0200
Skip files for transfer that has been truncated during negotiation
diff --git a/rsync.1 b/rsync.1
index 6cabd44..855dd47 100644
--- a/rsync.1
+++ b/rsync.1
@@ -1004,8 +1004,10 @@ This causes rsync to update a file by appending data onto
the end of the file, which presumes that the data that already exists on
the receiving side is identical with the start of the file on the sending
side. If a file needs to be transferred and its size on the receiver is
-the same or longer than the size on the sender, the file is skipped. This
-does not interfere with the updating of a file\(cq\&s non\-content attributes
+the same or longer than the size on the sender, the file is skipped. It
+also skips any files whose size on the sending side gets shorter during
+the send negotiations (rsync warns about a "diminished" file when this
+happens). This does not interfere with the updating of a file\(cq\&s non\-content attributes
(e.g. permissions, ownership, etc.) when the file does not need to be
transferred, nor does it affect the updating of any non\-regular files.
Implies \fB\-\-inplace\fP.
diff --git a/sender.c b/sender.c
index 1cc28a1..e22eadd 100644
--- a/sender.c
+++ b/sender.c
@@ -379,6 +379,16 @@ void send_files(int f_in, int f_out)
}
}
+ if (append_mode > 0 && st.st_size < F_LENGTH(file)) {
+ rprintf(FWARNING, "skipped diminished file: %s\n",
+ full_fname(fname));
+ free_sums(s);
+ close(fd);
+ if (protocol_version >= 30)
+ send_msg_int(MSG_NO_SEND, ndx);
+ continue;
+ }
+
if (st.st_size) {
int32 read_size = MAX(s->blength * 3, MAX_MAP_SIZE);
mbuf = map_file(fd, st.st_size, read_size, s->blength);

View File

@ -1,57 +0,0 @@
diff --git a/util.c b/util.c
index fbbfd8ba..235afa82 100644
--- a/util.c
+++ b/util.c
@@ -342,6 +342,7 @@ int copy_file(const char *source, const char *dest, int ofd, mode_t mode)
if (robust_unlink(dest) && errno != ENOENT) {
int save_errno = errno;
rsyserr(FERROR_XFER, errno, "unlink %s", full_fname(dest));
+ close(ifd);
errno = save_errno;
return -1;
}
diff --git a/lib/pool_alloc.c b/lib/pool_alloc.c
index 5856d591..a70a3f1a 100644
--- a/lib/pool_alloc.c
+++ b/lib/pool_alloc.c
@@ -49,15 +49,15 @@ pool_create(size_t size, size_t quantum, void (*bomb)(const char *), int flags)
{
struct alloc_pool *pool;
- if (!(pool = new0(struct alloc_pool)))
- return NULL;
-
if ((MINALIGN & (MINALIGN - 1)) != 0) {
if (bomb)
(*bomb)("Compiler error: MINALIGN is not a power of 2\n");
return NULL;
}
+ if (!(pool = new0(struct alloc_pool)))
+ return NULL;
+
if (!size)
size = POOL_DEF_EXTENT;
if (!quantum)
diff --git a/batch.c b/batch.c
index 21c632fc..1ab66e90 100644
--- a/batch.c
+++ b/batch.c
@@ -216,7 +216,7 @@ static void write_filter_rules(int fd)
void write_batch_shell_file(int argc, char *argv[], int file_arg_cnt)
{
int fd, i, len, err = 0;
- char *p, filename[MAXPATHLEN];
+ char *p, *p2, filename[MAXPATHLEN];
stringjoin(filename, sizeof filename,
batch_name, ".sh", NULL);
@@ -267,7 +267,7 @@ void write_batch_shell_file(int argc, char *argv[], int file_arg_cnt)
err = 1;
}
}
- if (!(p = check_for_hostspec(argv[argc - 1], &p, &i)))
+ if (!(p = check_for_hostspec(argv[argc - 1], &p2, &i)))
p = argv[argc - 1];
if (write(fd, " ${1:-", 6) != 6
|| write_arg(fd, p) < 0)

View File

@ -1,14 +0,0 @@
diff --git a/match.c b/match.c
index 36e78ed..dfd6af2 100644
--- a/match.c
+++ b/match.c
@@ -147,6 +147,9 @@ static void hash_search(int f,struct sum_struct *s,
int more;
schar *map;
+ // prevent possible memory leaks
+ memset(sum2, 0, sizeof sum2);
+
/* want_i is used to encourage adjacent matches, allowing the RLL
* coding of the output to work more efficiently. */
want_i = 0;

View File

@ -1,36 +0,0 @@
diff --git a/flist.c b/flist.c
index 464d556..087f9da 100644
--- a/flist.c
+++ b/flist.c
@@ -2584,6 +2584,19 @@ struct file_list *recv_file_list(int f, int dir_ndx)
init_hard_links();
#endif
+ if (inc_recurse && dir_ndx >= 0) {
+ if (dir_ndx >= dir_flist->used) {
+ rprintf(FERROR_XFER, "rsync: refusing invalid dir_ndx %u >= %u\n", dir_ndx, dir_flist->used);
+ exit_cleanup(RERR_PROTOCOL);
+ }
+ struct file_struct *file = dir_flist->files[dir_ndx];
+ if (file->flags & FLAG_GOT_DIR_FLIST) {
+ rprintf(FERROR_XFER, "rsync: refusing malicious duplicate flist for dir %d\n", dir_ndx);
+ exit_cleanup(RERR_PROTOCOL);
+ }
+ file->flags |= FLAG_GOT_DIR_FLIST;
+ }
+
flist = flist_new(0, "recv_file_list");
if (inc_recurse) {
diff --git a/rsync.h b/rsync.h
index b357dad..bc9abac 100644
--- a/rsync.h
+++ b/rsync.h
@@ -83,6 +83,7 @@
#define FLAG_SKIP_GROUP (1<<10) /* receiver/generator */
#define FLAG_TIME_FAILED (1<<11)/* generator */
#define FLAG_MOD_NSEC (1<<12) /* sender/receiver/generator */
+#define FLAG_GOT_DIR_FLIST (1<<13)/* sender/receiver/generator - dir_flist only */
/* These flags are passed to functions but not stored. */

View File

@ -1,57 +0,0 @@
diff --git a/testsuite/unsafe-byname.test b/testsuite/unsafe-byname.test
index 75e7201..d2e318e 100644
--- a/testsuite/unsafe-byname.test
+++ b/testsuite/unsafe-byname.test
@@ -40,7 +40,7 @@ test_unsafe ..//../dest from/dir unsafe
test_unsafe .. from/file safe
test_unsafe ../.. from/file unsafe
test_unsafe ..//.. from//file unsafe
-test_unsafe dir/.. from safe
+test_unsafe dir/.. from unsafe
test_unsafe dir/../.. from unsafe
test_unsafe dir/..//.. from unsafe
diff --git a/util.c b/util.c
index da50ff1..f260d39 100644
--- a/util.c
+++ b/util.c
@@ -1318,7 +1318,14 @@ int handle_partial_dir(const char *fname, int create)
*
* "src" is the top source directory currently applicable at the level
* of the referenced symlink. This is usually the symlink's full path
- * (including its name), as referenced from the root of the transfer. */
+ * (including its name), as referenced from the root of the transfer.
+ *
+ * NOTE: this also rejects dest names with a .. component in other
+ * than the first component of the name ie. it rejects names such as
+ * a/b/../x/y. This needs to be done as the leading subpaths 'a' or
+ * 'b' could later be replaced with symlinks such as a link to '.'
+ * resulting in the link being transferred now becoming unsafe
+ */
int unsafe_symlink(const char *dest, const char *src)
{
const char *name, *slash;
@@ -1328,6 +1335,23 @@ int unsafe_symlink(const char *dest, const char *src)
if (!dest || !*dest || *dest == '/')
return 1;
+ // reject destinations with /../ in the name other than at the start of the name
+ const char *dest2 = dest;
+ while (strncmp(dest2, "../", 3) == 0) {
+ dest2 += 3;
+ while (*dest2 == '/') {
+ // allow for ..//..///../foo
+ dest2++;
+ }
+ }
+ if (strstr(dest2, "/../"))
+ return 1;
+
+ // reject if the destination ends in /..
+ const size_t dlen = strlen(dest);
+ if (dlen > 3 && strcmp(&dest[dlen-3], "/..") == 0)
+ return 1;
+
/* find out what our safety margin is */
for (name = src; (slash = strchr(name, '/')) != 0; name = slash+1) {
/* ".." segment starts the count over. "." segment is ignored. */

View File

@ -1,141 +0,0 @@
diff --git a/checksum.c b/checksum.c
index cb21882..66e8089 100644
--- a/checksum.c
+++ b/checksum.c
@@ -406,7 +406,7 @@ void file_checksum(const char *fname, const STRUCT_STAT *st_p, char *sum)
memset(sum, 0, MAX_DIGEST_LEN);
- fd = do_open(fname, O_RDONLY, 0);
+ fd = do_open_checklinks(fname);
if (fd == -1)
return;
diff --git a/generator.c b/generator.c
index 110db28..3f13bb9 100644
--- a/generator.c
+++ b/generator.c
@@ -1867,7 +1867,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
}
/* open the file */
- if ((fd = do_open(fnamecmp, O_RDONLY, 0)) < 0) {
+ if ((fd = do_open_checklinks(fnamecmp)) < 0) {
rsyserr(FERROR, errno, "failed to open %s, continuing",
full_fname(fnamecmp));
pretend_missing:
diff --git a/receiver.c b/receiver.c
index 8031b8f..edfbb21 100644
--- a/receiver.c
+++ b/receiver.c
@@ -775,7 +775,7 @@ int recv_files(int f_in, int f_out, char *local_name)
if (fd1 == -1 && protocol_version < 29) {
if (fnamecmp != fname) {
fnamecmp = fname;
- fd1 = do_open(fnamecmp, O_RDONLY, 0);
+ fd1 = do_open_nofollow(fnamecmp, O_RDONLY);
}
if (fd1 == -1 && basis_dir[0]) {
diff --git a/sender.c b/sender.c
index 2bbff2f..a4d46c3 100644
--- a/sender.c
+++ b/sender.c
@@ -350,7 +350,7 @@ void send_files(int f_in, int f_out)
exit_cleanup(RERR_PROTOCOL);
}
- fd = do_open(fname, O_RDONLY, 0);
+ fd = do_open_checklinks(fname);
if (fd == -1) {
if (errno == ENOENT) {
enum logcode c = am_daemon
diff --git a/syscall.c b/syscall.c
index 47c5ea5..c55ae5f 100644
--- a/syscall.c
+++ b/syscall.c
@@ -45,6 +45,8 @@ extern int preallocate_files;
extern int preallocate_files;
extern int preserve_perms;
extern int preserve_executability;
+extern int copy_links;
+extern int copy_unsafe_links;
#ifndef S_BLKSIZE
# if defined hpux || defined __hpux__ || defined __hpux
@@ -575,3 +575,21 @@ int do_open_nofollow(const char *pathname, int flags)
return fd;
}
+
+/*
+ varient of do_open/do_open_nofollow which does do_open() if the
+ copy_links or copy_unsafe_links options are set and does
+ do_open_nofollow() otherwise
+
+ This is used to prevent a race condition where an attacker could be
+ switching a file between being a symlink and being a normal file
+
+ The open is always done with O_RDONLY flags
+ */
+int do_open_checklinks(const char *pathname)
+{
+ if (copy_links || copy_unsafe_links) {
+ return do_open(pathname, O_RDONLY, 0);
+ }
+ return do_open_nofollow(pathname, O_RDONLY);
+}
diff --git a/t_unsafe.c b/t_unsafe.c
index 010cac5..e10619a 100644
--- a/t_unsafe.c
+++ b/t_unsafe.c
@@ -28,6 +28,9 @@ int am_root = 0;
int human_readable = 0;
int preserve_perms = 0;
int preserve_executability = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
+
short info_levels[COUNT_INFO], debug_levels[COUNT_DEBUG];
int
diff --git a/tls.c b/tls.c
index e6b0708..858f8f1 100644
--- a/tls.c
+++ b/tls.c
@@ -49,6 +49,9 @@ int list_only = 0;
int preserve_executability = 0;
int preallocate_files = 0;
int inplace = 0;
+int safe_symlinks = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
#ifdef SUPPORT_XATTRS
diff --git a/trimslash.c b/trimslash.c
index 1ec928c..f2774cd 100644
--- a/trimslash.c
+++ b/trimslash.c
@@ -26,6 +26,8 @@ int am_root = 0;
int preserve_executability = 0;
int preallocate_files = 0;
int inplace = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
int
main(int argc, char **argv)
diff --git a/util.c b/util.c
index f260d39..d84bc41 100644
--- a/util.c
+++ b/util.c
@@ -365,7 +365,7 @@ int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode)
int len; /* Number of bytes read into `buf'. */
OFF_T prealloc_len = 0, offset = 0;
- if ((ifd = do_open(source, O_RDONLY, 0)) < 0) {
+ if ((ifd = do_open_nofollow(source, O_RDONLY)) < 0) {
int save_errno = errno;
rsyserr(FERROR_XFER, errno, "open %s", full_fname(source));
errno = save_errno;

View File

@ -1,27 +0,0 @@
From 797e17fc4a6f15e3b1756538a9f812b63942686f Mon Sep 17 00:00:00 2001
From: Andrew Tridgell <andrew@tridgell.net>
Date: Sat, 23 Aug 2025 17:26:53 +1000
Subject: [PATCH] fixed an invalid access to files array
this was found by Calum Hutton from Rapid7. It is a real bug, but
analysis shows it can't be leverged into an exploit. Worth fixing
though.
Many thanks to Calum and Rapid7 for finding and reporting this
---
sender.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/sender.c b/sender.c
index a4d46c39e..b1588b701 100644
--- a/sender.c
+++ b/sender.c
@@ -262,6 +262,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)) {

View File

@ -1,54 +0,0 @@
diff --git a/zlib/inftrees.c b/zlib/inftrees.c
index 44d89cf2..571e8100 100644
--- a/zlib/inftrees.c
+++ b/zlib/inftrees.c
@@ -54,7 +54,7 @@ unsigned short FAR *work;
code FAR *next; /* next available space in table */
const unsigned short FAR *base; /* base value table to use */
const unsigned short FAR *extra; /* extra bits table to use */
- int end; /* use base and extra for symbol > end */
+ unsigned match; /* use base and extra for symbol >= match */
unsigned short count[MAXBITS+1]; /* number of codes of each length */
unsigned short offs[MAXBITS+1]; /* offsets in table for each length */
static const unsigned short lbase[31] = { /* Length codes 257..285 base */
@@ -181,19 +181,17 @@ unsigned short FAR *work;
switch (type) {
case CODES:
base = extra = work; /* dummy value--not used */
- end = 19;
+ match = 20;
break;
case LENS:
base = lbase;
- base -= 257;
extra = lext;
- extra -= 257;
- end = 256;
+ match = 257;
break;
default: /* DISTS */
base = dbase;
extra = dext;
- end = -1;
+ match = 0;
}
/* initialize state for loop */
@@ -216,13 +214,13 @@ unsigned short FAR *work;
for (;;) {
/* create table entry */
here.bits = (unsigned char)(len - drop);
- if ((int)(work[sym]) < end) {
+ if (work[sym] + 1u < match) {
here.op = (unsigned char)0;
here.val = work[sym];
}
- else if ((int)(work[sym]) > end) {
- here.op = (unsigned char)(extra[work[sym]]);
- here.val = base[work[sym]];
+ else if (work[sym] >= match) {
+ here.op = (unsigned char)(extra[work[sym] - match]);
+ here.val = base[work[sym] - match];
}
else {
here.op = (unsigned char)(32 + 64); /* end of block */

View File

@ -1,15 +0,0 @@
diff --git a/xattrs.c b/xattrs.c
index f732fb15..b1b4217e 100644
--- a/xattrs.c
+++ b/xattrs.c
@@ -917,8 +917,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 */

View File

@ -1,610 +0,0 @@
From ba9dd4ec47c6e49f21e5905a91af68aad3c4678e Mon Sep 17 00:00:00 2001
From: Andrew Tridgell <andrew@tridgell.net>
Date: Sun, 24 May 2026 08:48:42 +1000
Subject: [PATCH 61/81] receiver: fix absolute --partial-dir delta resume
(false verification)
A delta (--no-whole-file) resume whose basis is an absolute --partial-dir
looped forever on exit code 23 ("failed verification -- update put into
partial-dir"), stranding the correct data in the partial-dir and never
populating the destination.
Cause: an absolute --partial-dir makes the basis path absolute, but the
receiver opened it with secure_relative_open(NULL, fnamecmp, ...), which by
design rejects an absolute relpath (EINVAL). The basis fd was then -1, so
receive_data() mapped no basis and (because the matched-block sum_update() is
guarded by "if (mapbuf)") computed the whole-file verification checksum over
the literal data only -> a spurious mismatch every run. (The data itself was
correct, since the in-place update leaves the matched basis bytes in place.)
Under a non-chroot daemon the in-place write went through the same call and
failed outright.
Fix: add secure_basis_open(), which treats an operator-trusted absolute basis
path as (trusted directory + confined leaf) -- the same way secure_relative_open
already trusts an absolute basedir while keeping O_NOFOLLOW on the leaf -- and
use it for both the basis read and the inplace-partial write. The strict
"reject absolute relpath" contract of secure_relative_open is left intact.
Defense-in-depth: receive_data() now treats a block-match token with no mapped
basis as a protocol inconsistency (it can only arise from a basis that the
generator opened but the receiver could not), failing cleanly instead of
silently dropping those bytes from the verify checksum or the output.
Thanks to @sylvain-ilm for the report (#724, #725).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit eee05c177aeef1abc6e421e47cbf4af9a8135eb5)
---
receiver.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 54 insertions(+), 3 deletions(-)
diff --git a/receiver.c b/receiver.c
index 89d2515b..56341a68 100644
--- a/receiver.c
+++ b/receiver.c
@@ -83,6 +83,44 @@ static int updating_basis_or_equiv;
#define MAX_UNIQUE_NUMBER 999999
#define MAX_UNIQUE_LOOP 100
+/* Open a basis/output path that may legitimately be an operator-trusted
+ * ABSOLUTE path -- e.g. an absolute --partial-dir ("a directory reserved for
+ * partial-dir work") or --backup-dir. secure_relative_open() deliberately
+ * rejects an absolute relpath, so feeding it the whole absolute partialptr
+ * (with a NULL basedir) returns EINVAL: the basis fd is then -1, no basis is
+ * mapped, and receive_data() omits every matched block from the whole-file
+ * verification checksum -> a spurious "failed verification" that strands the
+ * (correct) data in the partial-dir forever.
+ *
+ * The operator's directory is trusted; only the leaf basename is peer-supplied.
+ * So when basedir is NULL and relpath is absolute, split it into its directory
+ * (trusted) and leaf and confine just the leaf -- exactly how secure_relative_
+ * open already trusts an absolute basedir while O_NOFOLLOW-confining the leaf.
+ * Anything else is a straight pass-through that preserves the strict contract. */
+static int secure_basis_open(const char *basedir, const char *relpath, int flags, mode_t mode)
+{
+ if (!basedir && relpath && *relpath == '/') {
+ const char *slash = strrchr(relpath, '/');
+ const char *leaf = slash + 1;
+ char dirbuf[MAXPATHLEN];
+ const char *dir;
+ if (slash == relpath)
+ dir = "/";
+ else {
+ size_t dlen = slash - relpath;
+ if (dlen >= sizeof dirbuf) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ memcpy(dirbuf, relpath, dlen);
+ dirbuf[dlen] = '\0';
+ dir = dirbuf;
+ }
+ return secure_relative_open(dir, leaf, flags, mode);
+ }
+ return secure_relative_open(basedir, relpath, flags, mode);
+}
+
/* get_tmpname() - create a tmp filename for a given filename
*
* If a tmpdir is defined, use that as the directory to put it in. Otherwise,
@@ -364,6 +402,18 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
stats.matched_data += len;
+ /* A block match can only be honored if we actually mapped the
+ * basis. If we didn't (basis open failed), the sender should
+ * never have been told a basis existed -- treat it as a protocol
+ * inconsistency rather than silently omitting these bytes from
+ * the verification checksum (which yields a spurious failure) or
+ * leaving a hole in the output. */
+ if (!mapbuf) {
+ rprintf(FERROR, "got a block match with no basis file for %s [%s]\n",
+ full_fname(fname), who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
+
if (DEBUG_GTE(DELTASUM, 3)) {
rprintf(FINFO,
"chunk[%d] of size %ld at %s offset=%s%s\n",
@@ -793,8 +843,9 @@ int recv_files(int f_in, int f_out, char *local_name)
fnamecmp = fname;
}
- /* open the file */
- fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
+ /* open the file (secure_basis_open tolerates an operator-trusted
+ * absolute fnamecmp, e.g. an absolute --partial-dir basis) */
+ fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0);
if (fd1 == -1 && protocol_version < 29) {
if (fnamecmp != fname) {
@@ -884,7 +935,7 @@ int recv_files(int f_in, int f_out, char *local_name)
* attacker could switch a directory to a symlink between
* path validation and file open. */
if (use_secure_symlinks)
- fd2 = secure_relative_open(NULL, fname, O_WRONLY|O_CREAT, 0600);
+ fd2 = secure_basis_open(NULL, fname, O_WRONLY|O_CREAT, 0600);
else
fd2 = do_open(fname, O_WRONLY|O_CREAT, 0600);
if (fd2 == -1) {
--
2.53.0
From ab7d8e9df3dd1f2d2acc56c49c7d0c70420f8883 Mon Sep 17 00:00:00 2001
From: Andrew Tridgell <andrew@tridgell.net>
Date: Wed, 3 Jun 2026 20:48:10 +1000
Subject: [PATCH 63/81] sender: open a module-root-absolute path for a `path =
/` module (#897)
A daemon module with path=/ makes F_PATHNAME absolute, so the secure_path built
for the content open starts with '/'. secure_relative_open() rejects an
absolute relpath with EINVAL, so a use-chroot=no daemon with path=/ could not
send any file ('failed to open ...: Invalid argument (22)') -- a regression
from 3.4.2. Strip leading slashes to a module-relative path; resolution stays
confined beneath module_dir.
Thanks to @moonlitbugs for the report (#897).
(cherry picked from commit 9886a06610370b3219ea9a29753388e261f4853d)
---
sender.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/sender.c b/sender.c
index 033f87e5..32913af0 100644
--- a/sender.c
+++ b/sender.c
@@ -362,6 +362,7 @@ void send_files(int f_in, int f_out)
* Reconstruct the full path relative to module_dir
* from F_PATHNAME (path) and f_name (fname). */
char secure_path[MAXPATHLEN];
+ const char *relp;
int slen = snprintf(secure_path, sizeof secure_path, "%s%s%s", path, slash, fname);
if (slen >= (int)sizeof secure_path) {
io_error |= IOERR_GENERAL;
@@ -371,7 +372,13 @@ void send_files(int f_in, int f_out)
send_msg_int(MSG_NO_SEND, ndx);
continue;
}
- fd = secure_relative_open(module_dir, secure_path, O_RDONLY, 0);
+ /* A module with `path = /` makes F_PATHNAME absolute, so the
+ * joined path starts with '/'; strip leading slashes to a
+ * module-relative path that secure_relative_open accepts (#897). */
+ relp = secure_path;
+ while (*relp == '/')
+ relp++;
+ fd = secure_relative_open(module_dir, relp, O_RDONLY, 0);
} else {
fd = do_open_checklinks(fname);
}
--
2.53.0
From 57128940b1c63c726cdedd5a4531d2ff1ed7c5e3 Mon Sep 17 00:00:00 2001
From: Andrew Tridgell <andrew@tridgell.net>
Date: Wed, 3 Jun 2026 20:48:10 +1000
Subject: [PATCH 64/81] syscall/receiver: honour a relative alt-basis dir on a
daemon receiver (#915)
The symlink-race hardening routed the receiver's basis open through
secure_relative_open(), which rejects any '..' -- so a sibling
--link-dest=../01 on a use-chroot=no daemon was silently ignored and every file
re-transferred (#915/#928, a regression from 3.4.1).
Narrow the confinement to the sanitizing daemon (am_daemon && !am_chrooted) and
re-anchor it at the module root, the real trust boundary: secure_relative_open()
prefixes the cwd's module-relative path (from rsync's logical curr_dir[], a
guaranteed lexical prefix of module_dir) and resolves beneath module_dir, so
RESOLVE_BENEATH permits an in-module '..' climb while still rejecting one that
escapes the module. secure_basis_open() opens with a bare do_open() in the
non-sanitizing cases. t_stub.c gains weak curr_dir[]/curr_dir_len for the
helpers (via #pragma weak on non-GNU compilers, where rsync.h erases
__attribute__).
Two tests: link-dest-relative-basis asserts the in-module '..' is honoured;
link-dest-module-escape asserts a --link-dest=../../OUTSIDE climb that leaves
the module is refused (not hard-linked to an outside file). See upstream
PR #930.
Thanks to @fufu65 (#915) and @JetAppsClark (#928) for the reports.
(cherry picked from commit 948edffb43b56216bcc9932955481ef80a6a69f1)
---
receiver.c | 23 +++++++++++++-
syscall.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++++-----
t_stub.c | 2 ++
util1.c | 4 +--
4 files changed, 109 insertions(+), 11 deletions(-)
diff --git a/receiver.c b/receiver.c
index 56341a68..e199914f 100644
--- a/receiver.c
+++ b/receiver.c
@@ -99,6 +99,27 @@ static int updating_basis_or_equiv;
* Anything else is a straight pass-through that preserves the strict contract. */
static int secure_basis_open(const char *basedir, const char *relpath, int flags, mode_t mode)
{
+ extern int am_daemon, am_chrooted;
+
+ /* The confined resolver is only needed for the sanitizing daemon
+ * (am_daemon && !am_chrooted, i.e. use_secure_symlinks). Local /
+ * remote-shell mode has no module boundary, and "use chroot = yes" makes
+ * the kernel root the boundary, so there an alt-dest basis like
+ * --link-dest=../01 must resolve against the cwd as a bare open did before
+ * the hardening (confining it would reject the legitimate sibling "..",
+ * #915). */
+ if (!am_daemon || am_chrooted) {
+ if (basedir) {
+ char fullpath[MAXPATHLEN];
+ if (pathjoin(fullpath, sizeof fullpath, basedir, relpath) >= sizeof fullpath) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ return do_open(fullpath, flags, mode);
+ }
+ return do_open(relpath, flags, mode);
+ }
+
if (!basedir && relpath && *relpath == '/') {
const char *slash = strrchr(relpath, '/');
const char *leaf = slash + 1;
@@ -859,7 +880,7 @@ int recv_files(int f_in, int f_out, char *local_name)
/* pre-29 allowed only one alternate basis */
basedir = basis_dir[0];
fnamecmp = fname;
- fd1 = secure_relative_open(basedir, fnamecmp, O_RDONLY, 0);
+ fd1 = secure_basis_open(basedir, fnamecmp, O_RDONLY, 0);
}
}
diff --git a/syscall.c b/syscall.c
index 47777350..4f456807 100644
--- a/syscall.c
+++ b/syscall.c
@@ -1761,13 +1761,68 @@ static int secure_relative_open_resolve_beneath(const char *basedir, const char
}
#endif
+/* The logical current directory (maintained by change_dir() in util1.c).
+ * Defined here -- rather than in util1.c -- so the test helpers that link
+ * syscall.o but not util1.o (tls, trimslash) get the definition without a
+ * weak-symbol fallback, which is not portable to PE/COFF targets (Cygwin). */
+char curr_dir[MAXPATHLEN];
+unsigned int curr_dir_len;
+
int secure_relative_open(const char *basedir, const char *relpath, int flags, mode_t mode)
{
+ extern int am_daemon, am_chrooted;
+ extern char *module_dir;
+ extern unsigned int module_dirlen;
+ char modrel_buf[MAXPATHLEN];
+ int reanchored = 0;
+
if (!relpath || relpath[0] == '/') {
// must be a relative path
errno = EINVAL;
return -1;
}
+
+ /* Sanitizing daemon only (am_daemon && !am_chrooted). Here we have chdir'd
+ * into a sub-dir of the module (the transfer destination), so a relative
+ * alt-dest like "../01" may legitimately climb to a sibling that is still
+ * inside the module (#915). Confining beneath the cwd would reject that
+ * climb. Re-anchor at the module root -- the real trust boundary -- by
+ * prefixing the cwd's module-relative path (from rsync's logical curr_dir[],
+ * a guaranteed lexical prefix of module_dir, unlike getcwd()) and resolving
+ * beneath module_dir; RESOLVE_BENEATH then allows in-module climbs and still
+ * rejects escapes. Only for paths that contain "..". module_dirlen is 0 for
+ * a `path = /` module (clientserver.c), so we gate on module_dir, not its
+ * length, to cover that case too -- the prefix check below treats
+ * module_dirlen 0 as "module root is /". */
+ if (am_daemon && !am_chrooted
+ && module_dir && module_dir[0] == '/'
+ && (basedir == NULL || basedir[0] != '/')
+ && (path_has_dotdot_component(relpath)
+ || (basedir && path_has_dotdot_component(basedir)))) {
+ const char *p;
+ int n;
+ if (curr_dir_len >= module_dirlen
+ && strncmp(curr_dir, module_dir, module_dirlen) == 0
+ && (curr_dir[module_dirlen] == '\0' || curr_dir[module_dirlen] == '/')) {
+ for (p = curr_dir + module_dirlen; *p == '/'; p++) {}
+ if (basedir)
+ n = snprintf(modrel_buf, sizeof modrel_buf, "%s%s%s/%s",
+ p, *p ? "/" : "", basedir, relpath);
+ else
+ n = snprintf(modrel_buf, sizeof modrel_buf, "%s%s%s",
+ p, *p ? "/" : "", relpath);
+ if (n < 0 || n >= (int)sizeof modrel_buf) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ basedir = module_dir; /* absolute, operator-trusted anchor */
+ relpath = modrel_buf;
+ reanchored = 1;
+ }
+ /* else: cwd not under module root as expected -- fall through to the
+ * front-door rejection below (fail safe). */
+ }
+
/* Reject any path with a literal ".." component (bare "..",
* "../foo", "foo/..", "foo/../bar", "subdir/.."). The previous
* substring-based check caught only "../" prefix and "/../"
@@ -1776,14 +1831,19 @@ int secure_relative_open(const char *basedir, const char *relpath, int flags, mo
* and pre-5.6 Linux. RESOLVE_BENEATH on Linux/FreeBSD/macOS
* catches some of these in-kernel with EXDEV, but the front
* door must reject them consistently with EINVAL across all
- * platforms so callers can rely on the validation. */
- if (path_has_dotdot_component(relpath)) {
- errno = EINVAL;
- return -1;
- }
- if (basedir && basedir[0] != '/' && path_has_dotdot_component(basedir)) {
- errno = EINVAL;
- return -1;
+ * platforms so callers can rely on the validation. Skipped for a
+ * re-anchored path: its ".." is deliberate, stays within the module,
+ * and is adjudicated by RESOLVE_BENEATH below (the portable fallback
+ * re-rejects it -- see there). */
+ if (!reanchored) {
+ if (path_has_dotdot_component(relpath)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (basedir && basedir[0] != '/' && path_has_dotdot_component(basedir)) {
+ errno = EINVAL;
+ return -1;
+ }
}
#ifdef __linux__
@@ -1800,6 +1860,21 @@ int secure_relative_open(const char *basedir, const char *relpath, int flags, mo
return secure_relative_open_resolve_beneath(basedir, relpath, flags, mode);
#endif
+ /* Portable fallback only (no kernel RESOLVE_BENEATH): the per-component
+ * O_NOFOLLOW walk below can't adjudicate ".." safely, so reject it here --
+ * even for a re-anchored path. This re-breaks --link-dest=../01 on
+ * openat2/O_RESOLVE_BENEATH-less platforms (NetBSD/OpenBSD/Solaris/Cygwin/
+ * pre-5.6 Linux), trading function for safety; on the kernel paths above
+ * RESOLVE_BENEATH already allowed the in-module climb. */
+ if (path_has_dotdot_component(relpath)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (basedir && basedir[0] != '/' && path_has_dotdot_component(basedir)) {
+ errno = EINVAL;
+ return -1;
+ }
+
#if !defined(O_NOFOLLOW) || !defined(O_DIRECTORY) || !defined(AT_FDCWD)
// really old system, all we can do is live with the risks
if (!basedir) {
diff --git a/util.c b/util.c
index 36c1b68c..12361057 100644
--- a/util.c
+++ b/util.c
@@ -41,8 +41,8 @@ extern filter_rule_list daemon_filter_list;
int sanitize_paths = 0;
-char curr_dir[MAXPATHLEN];
-unsigned int curr_dir_len;
+extern char curr_dir[MAXPATHLEN]; /* defined in syscall.c */
+extern unsigned int curr_dir_len;
int curr_dir_depth; /* This is only set for a sanitizing daemon. */
/* Set a fd into nonblocking mode. */
--
2.53.0
From 005d118f2d25ceaf7680f5b7bb92d70bacf97660 Mon Sep 17 00:00:00 2001
From: pterror <pterrorbird@gmail.com>
Date: Fri, 5 Jun 2026 17:24:05 +1000
Subject: [PATCH 75/81] receiver: fix NULL deref on the delta discard path
receive_data() crashed a receiver that was merely DISCARDING a file's
delta stream. discard_receive_data() calls receive_data() with
fname == NULL and fd == -1, so size_r == 0 and mapbuf == NULL. A normal
block-MATCH token (against a block the basis and source share) then
reaches the !mapbuf branch added in 31fbb17d ("receiver: fix absolute
--partial-dir delta resume"), which calls full_fname(fname). full_fname()
dereferences its argument unconditionally (util1.c: `if (*fn == '/')`),
so fname == NULL faults there -> receiver SIGSEGV.
This is a normal-operation crash with a stock cooperating sender, not an
adversarial one. The generator hands the sender real block sums whenever
the basis is readable and we're in delta mode; the receiver only decides
to discard afterwards, when its output cannot be produced -- e.g. the
destination directory is not writable (mkstemp fails), the basis turns
out to be a directory, or a --partial-dir resume is skipped. A MATCH
token arriving during that discard hit the NULL deref.
The 31fbb17d branch is correct only for a REAL output transfer (fd != -1,
fname valid): there, a block match with no mapped basis is a genuine
protocol inconsistency (the generator promised a basis the receiver could
not open), and honoring it would silently omit those bytes from the
verification checksum or leave a hole, so hard-erroring -- and
full_fname(fname) -- is right. It conflated that with the discard path.
The discriminator is fd, not mapbuf: on the discard path fd == -1 always;
on the real-output inconsistency fd != -1. Scope the "no basis file"
protocol error to fd != -1 (where fname is non-NULL and full_fname is
safe) and, on the discard path (fd == -1), absorb the matched bytes
benignly (offset += len; continue) -- symmetric with the literal-token
handling just above, and restoring the pre-31fbb17d behavior. The
real-transfer inconsistency check is preserved unchanged.
(cherry picked from commit 26f13bc148a0aa5d00496543cdfe6024fa269a11)
---
receiver.c | 34 +++++++++++++++++++++++++---------
1 file changed, 25 insertions(+), 9 deletions(-)
diff --git a/receiver.c b/receiver.c
index e199914f..e1f1fb38 100644
--- a/receiver.c
+++ b/receiver.c
@@ -423,16 +423,32 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
stats.matched_data += len;
- /* A block match can only be honored if we actually mapped the
- * basis. If we didn't (basis open failed), the sender should
- * never have been told a basis existed -- treat it as a protocol
- * inconsistency rather than silently omitting these bytes from
- * the verification checksum (which yields a spurious failure) or
- * leaving a hole in the output. */
+ /* A block match with no mapped basis is a protocol inconsistency
+ * ONLY when we are actually producing output (fd != -1): the
+ * generator told the sender a basis existed but the receiver could
+ * not open it, so honoring the match would silently omit these
+ * bytes from the verification checksum (a spurious failure) or
+ * leave a hole in the output. Fail cleanly in that case.
+ *
+ * On the DISCARD path (fd == -1, fname == NULL) there is no output
+ * and no verification: discard_receive_data() deliberately drains a
+ * delta the receiver never intends to write (basis fstat failed,
+ * basis is a directory, output open failed, batch skip, ...). The
+ * sender does not know the data is being discarded and streams an
+ * ordinary delta, so a match token here is NORMAL protocol, not
+ * malformed. Absorb it benignly (advance the offset and continue),
+ * as the pre-existing "if (mapbuf)" guards did before this check was
+ * added in 31fbb17d -- erroring would wrongly break legitimate
+ * transfers, and full_fname(fname) with fname==NULL would
+ * dereference NULL (a receiver crash on a normal transfer). */
if (!mapbuf) {
- rprintf(FERROR, "got a block match with no basis file for %s [%s]\n",
- full_fname(fname), who_am_i());
- exit_cleanup(RERR_PROTOCOL);
+ if (fd != -1) {
+ rprintf(FERROR, "got a block match with no basis file for %s [%s]\n",
+ full_fname(fname), who_am_i());
+ exit_cleanup(RERR_PROTOCOL);
+ }
+ offset += len;
+ continue;
}
if (DEBUG_GTE(DELTASUM, 3)) {
--
2.53.0
From 0a5fa00fdcbacbebb89daca0ae68ae320f22dc74 Mon Sep 17 00:00:00 2001
From: Andrew Tridgell <andrew@tridgell.net>
Date: Tue, 5 May 2026 16:48:16 +1000
Subject: [PATCH 46/81] receiver: add parent_ndx<0 guard, mirroring 797e17f
Commit 797e17f ("fixed an invalid access to files array") added a
parent_ndx < 0 guard to send_files() in sender.c, but the visually-
identical block in recv_files() in receiver.c was not updated. A
malicious rsync:// server can therefore drive any connecting client
into the same out-of-bounds dir_flist->files[-1] read followed by a
file_struct dereference in f_name() one line later.
Reach: protocol-30+ default (inc_recurse) makes flist.c:2745 set
parent_ndx = -1 on the first received flist when the sender omits a
leading "." entry; rsync.c flist_for_ndx() does not reject ndx == 0
in that state because the range check evaluates 0 < 0 = false; and
read_ndx_and_attrs() only validates ndx with the ITEM_TRANSFER bit
set, so iflags=ITEM_IS_NEW (or any other non-transfer iflag word)
bypasses the check.
Apply the same guard receiver-side. Confirmed: the same PoC (a
minimal Python rsyncd that handshakes with CF_INC_RECURSE, sends a
no-leading-"." flist, and emits ndx=0 with ITEM_IS_NEW) crashes
unpatched 3.4.2 with SEGV_MAPERR si_addr=0x4101a-class in the
receiver child; with this guard it exits cleanly with code 2
(RERR_PROTOCOL).
The attack surface delta over the sender variant is large:
the original was malicious-client -> daemon, this is
malicious-server -> any rsync client doing a normal rsync://
or remote-shell pull.
Reported by Pratham Gupta (alchemy1729).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
generator.c | 4 ++++
io.c | 3 +++
receiver.c | 7 ++++++-
sender.c | 2 ++
4 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/generator.c b/generator.c
index b80eb2e3..38f5ad33 100644
--- a/generator.c
+++ b/generator.c
@@ -2146,6 +2146,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 +2176,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)
diff --git a/io.c b/io.c
index 8d1cf7f2..2d94c1f4 100644
--- a/io.c
+++ b/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]);
diff --git a/receiver.c b/receiver.c
index 63e5cedb..0a993e0f 100644
--- a/receiver.c
+++ b/receiver.c
@@ -467,7 +467,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));
@@ -604,6 +607,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);
diff --git a/sender.c b/sender.c
index 99f431fe..033f87e5 100644
--- a/sender.c
+++ b/sender.c
@@ -140,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;
--
2.53.0

File diff suppressed because it is too large Load Diff

View File

@ -1,174 +0,0 @@
From 9ec57dd61762175a5fef11b66f36cbe6bd451178 Mon Sep 17 00:00:00 2001
From: Andrew Tridgell <andrew@tridgell.net>
Date: Wed, 29 Apr 2026 11:10:59 +1000
Subject: [PATCH] token: harden compressed-token decoding against integer
overflow
The receiver's three compressed-token decoders --
recv_deflated_token (zlib), recv_zstd_token, and
recv_compressed_token (lz4) -- accumulated rx_token (a 32-bit
signed counter) without overflow checking. A malicious sender
could craft a compressed-token stream that walked rx_token past
INT32_MAX, with careful manipulation leaking process memory
contents to the wire (environment variables, passwords, heap
pointers, library pointers -- significantly weakening ASLR
and facilitating further exploitation).
Cap rx_token at MAX_TOKEN_INDEX = 0x7ffffffe. Fold the
bookkeeping into recv_compressed_token_num() and
recv_compressed_token_run() shared by all three decoders. Reject
negative or out-of-range token values explicitly. Also cap the
simple_recv_token literal-block length at the source: any
wire-supplied length > CHUNK_SIZE is ill-formed (the matching
simple_send_token never writes a chunk larger than CHUNK_SIZE),
so reject before looping on attacker-controlled bytes.
Reach: an authenticated daemon connection with compression
enabled (the default for protocols >= 30 when both peers
advertise it). Disabling compression on the daemon
("refuse options = compress" in rsyncd.conf) is the available
workaround.
Reporter: Omar Elsayed (seks99x).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
receiver.c | 11 ++++++++-
token.c | 68 ++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 63 insertions(+), 16 deletions(-)
diff --git a/receiver.c b/receiver.c
index 6044836..59f9759 100644
--- a/receiver.c
+++ b/receiver.c
@@ -305,7 +305,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);
@@ -313,6 +318,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));
diff --git a/token.c b/token.c
index f1299ee..fb722a5 100644
--- a/token.c
+++ b/token.c
@@ -228,6 +228,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;
}
@@ -441,9 +449,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)
{
@@ -535,17 +586,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;
@@ -565,10 +606,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();
}
}
}
--
2.52.0

View File

@ -1,49 +0,0 @@
commit af6118d98b3482cbcfc223bf2a0777bc19eccb02
Author: Wayne Davison <wayne@opencoder.net>
Date: Sun Apr 26 18:02:17 2020 -0700
Allow a missing parent dir when --delete-missing-args was specified.
diff --git a/generator.c b/generator.c
index 3c50f63f..b90c7ccd 100644
--- a/generator.c
+++ b/generator.c
@@ -1277,10 +1277,16 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
&& (*dn != '.' || dn[1]) /* Avoid an issue with --relative and the "." dir. */
&& (!prior_dir_file || strcmp(dn, f_name(prior_dir_file, NULL)) != 0)
&& flist_find_name(cur_flist, dn, 1) < 0) {
- rprintf(FERROR,
- "ABORTING due to invalid path from sender: %s/%s\n",
- dn, file->basename);
- exit_cleanup(RERR_PROTOCOL);
+ /* The --delete-missing-args option can actually put invalid entries into
+ * the file list, so if that option was specified, we'll just complain about
+ * it and allow it. */
+ if (missing_args == 2 && file->mode == 0)
+ rprintf(FERROR, "WARNING: parent dir is absent in the file list: %s\n", dn);
+ else {
+ rprintf(FERROR, "ABORTING due to invalid path from sender: %s/%s\n",
+ dn, file->basename);
+ exit_cleanup(RERR_PROTOCOL);
+ }
}
if (relative_paths && !implied_dirs
&& do_stat(dn, &sx.st) < 0) {
@@ -1383,7 +1389,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
added_perms = 0;
if (is_dir < 0) {
if (!(preserve_times & PRESERVE_DIR_TIMES))
- return;
+ goto cleanup;
/* In inc_recurse mode we want to make sure any missing
* directories get created while we're still processing
* the parent dir (which allows us to touch the parent
@@ -1525,7 +1531,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
"ignoring unsafe symlink \"%s\" -> \"%s\"\n",
fname, sl);
}
- return;
+ goto cleanup;
}
if (statret == 0) {
char lnk[MAXPATHLEN];

View File

@ -1,13 +0,0 @@
diff --git a/exclude.c.old b/exclude.c
index 232249f..2f6dccc 100644
--- a/exclude.c.old
+++ b/exclude.c
@@ -1575,6 +1575,8 @@ char *get_rule_prefix(filter_rule *rule, const char *pat, int for_xfer,
}
if (rule->rflags & FILTRULE_EXCLUDE_SELF)
*op++ = 'e';
+ if (rule->rflags & FILTRULE_XATTR)
+ *op++ = 'x';
if (rule->rflags & FILTRULE_SENDER_SIDE
&& (!for_xfer || protocol_version >= 29))
*op++ = 's';

View File

@ -1,37 +0,0 @@
diff --git a/loadparm.c b/loadparm.c
index 029f358f..534e7b63 100644
--- a/loadparm.c
+++ b/loadparm.c
@@ -449,7 +449,7 @@ static struct parm_struct parm_table[] =
};
/* Initialise the Default all_vars structure. */
-static void reset_all_vars(void)
+void reset_daemon_vars(void)
{
memcpy(&Vars, &Defaults, sizeof Vars);
}
@@ -872,7 +872,7 @@ int lp_load(char *pszFname, int globals_only)
{
bInGlobalSection = True;
- reset_all_vars();
+ reset_daemon_vars();
/* We get sections first, so have to start 'behind' to make up. */
iSectionIndex = -1;
diff --git a/main.c b/main.c
index 1328c504..9af9e5d3 100644
--- a/main.c
+++ b/main.c
@@ -1681,6 +1681,10 @@ int main(int argc,char *argv[])
memset(&stats, 0, sizeof(stats));
+ /* Even a non-daemon runs needs the default config values to be set, e.g.
+ * lp_dont_compress() is queried when no --skip-compress option is set. */
+ reset_daemon_vars();
+
if (argc < 2) {
usage(FERROR);
exit_cleanup(RERR_SYNTAX);

View File

@ -1,122 +0,0 @@
diff --git a/fileio.c b/fileio.c
index b183e20..72d6076 100644
--- a/fileio.c
+++ b/fileio.c
@@ -34,6 +34,7 @@
#define ALIGNED_LENGTH(len) ((((len) - 1) | (ALIGN_BOUNDRY-1)) + 1)
extern int sparse_files;
+extern int sparse_files_block_size;
OFF_T preallocated_len = 0;
@@ -147,7 +148,7 @@ int write_file(int f, int use_seek, OFF_T offset, const char *buf, int len)
while (len > 0) {
int r1;
if (sparse_files > 0) {
- int len1 = MIN(len, SPARSE_WRITE_SIZE);
+ int len1 = MIN(len, sparse_files_block_size ? sparse_files_block_size : SPARSE_WRITE_SIZE);
r1 = write_sparse(f, use_seek, offset, buf, len1);
offset += r1;
} else {
diff --git a/options.c b/options.c
index 195672e..d08c05a 100644
--- a/options.c
+++ b/options.c
@@ -76,6 +76,7 @@ int remove_source_files = 0;
int one_file_system = 0;
int protocol_version = PROTOCOL_VERSION;
int sparse_files = 0;
+long sparse_files_block_size = 0;
int preallocate_files = 0;
int do_compression = 0;
int def_compress_level = NOT_SPECIFIED;
@@ -717,6 +718,7 @@ void usage(enum logcode F)
rprintf(F," --fake-super store/recover privileged attrs using xattrs\n");
#endif
rprintf(F," -S, --sparse turn sequences of nulls into sparse blocks\n");
+ rprintf(F," --sparse-block=SIZE set block size used to handle sparse files\n");
#ifdef SUPPORT_PREALLOCATION
rprintf(F," --preallocate allocate dest files before writing them\n");
#else
@@ -927,6 +929,7 @@ static struct poptOption long_options[] = {
{"sparse", 'S', POPT_ARG_VAL, &sparse_files, 1, 0, 0 },
{"no-sparse", 0, POPT_ARG_VAL, &sparse_files, 0, 0, 0 },
{"no-S", 0, POPT_ARG_VAL, &sparse_files, 0, 0, 0 },
+ {"sparse-block", 0, POPT_ARG_LONG, &sparse_files_block_size, 0, 0, 0 },
{"preallocate", 0, POPT_ARG_NONE, &preallocate_files, 0, 0, 0},
{"inplace", 0, POPT_ARG_VAL, &inplace, 1, 0, 0 },
{"no-inplace", 0, POPT_ARG_VAL, &inplace, 0, 0, 0 },
diff --git a/options.c b/options.c
index b12da55..5a27452 100644
--- a/options.c
+++ b/options.c
@@ -2606,6 +2606,12 @@ void server_options(char **args, int *argc_p)
args[ac++] = arg;
}
+ if (sparse_files_block_size) {
+ if (asprintf(&arg, "--sparse-block=%lu", sparse_files_block_size) < 0)
+ goto oom;
+ args[ac++] = arg;
+ }
+
if (io_timeout) {
if (asprintf(&arg, "--timeout=%d", io_timeout) < 0)
goto oom;
diff --git a/rsync.yo b/rsync.yo
--- a/rsync.yo
+++ b/rsync.yo
@@ -377,6 +377,7 @@ to the detailed description below for a complete description. verb(
--super receiver attempts super-user activities
--fake-super store/recover privileged attrs using xattrs
-S, --sparse turn sequences of nulls into sparse blocks
+ --sparse-block=SIZE set block size used to handle sparse files
--preallocate allocate dest files before writing
-n, --dry-run perform a trial run with no changes made
-W, --whole-file copy files whole (w/o delta-xfer algorithm)
@@ -1299,6 +1300,15 @@ If combined with bf(--sparse), the file will only have sparse blocks (as
opposed to allocated sequences of null bytes) if the kernel version and
filesystem type support creating holes in the allocated data.
+dit(bf(--sparse-block=SIZE)) Change the block size used to handle sparse files
+to SIZE bytes. This option only has an effect if the bf(--sparse) (bf(-S))
+option was also specified. The default block size used by rsync to detect a
+file hole is 1024 bytes; when the receiver writes data to the destination file
+and option bf(--sparse) is used, rsync checks every 1024-bytes chunk to detect
+if they are actually filled with data or not. With certain filesystems,
+optimized to receive data streams for example, enlarging this block size can
+strongly increase performance. The option can be used to tune this block size.
+
dit(bf(-n, --dry-run)) This makes rsync perform a trial run that doesn't
make any changes (and produces mostly the same output as a real run). It
is most commonly used in combination with the bf(-v, --verbose) and/or
diff --git a/rsync.1 b/rsync.1
index 855dd47..1d7af3c 100644
--- a/rsync.1
+++ b/rsync.1
@@ -454,6 +454,7 @@ to the detailed description below for a complete description.
\-\-super receiver attempts super\-user activities
\-\-fake\-super store/recover privileged attrs using xattrs
\-S, \-\-sparse turn sequences of nulls into sparse blocks
+ \-\-sparse-block=SIZE set block size used to handle sparse files
\-\-preallocate allocate dest files before writing
\-n, \-\-dry\-run perform a trial run with no changes made
\-W, \-\-whole\-file copy files whole (w/o delta\-xfer algorithm)
@@ -1493,6 +1493,16 @@ If combined with \fB\-\-sparse\fP, the file will only have sparse blocks (as
opposed to allocated sequences of null bytes) if the kernel version and
filesystem type support creating holes in the allocated data.
.IP
+.IP "\fB\-\-sparse\-block=SIZE\fP"
+Change the block size used to handle sparse files
+to SIZE bytes. This option only has an effect if the \fB\-\-sparse\fP (\fB\-S\fP)
+option was also specified. The default block size used by rsync to detect a
+file hole is 1024 bytes; when the receiver writes data to the destination file
+and option \fB\-\-sparse\fP is used, rsync checks every 1024\-bytes chunk to detect
+if they are actually filled with data or not. With certain filesystems,
+optimized to receive data streams for example, enlarging this block size can
+strongly increase performance. The option can be used to tune this block size.
+.IP
.IP "\fB\-n, \-\-dry\-run\fP"
This makes rsync perform a trial run that doesn\(cq\&t
make any changes (and produces mostly the same output as a real run). It

View File

@ -1,218 +0,0 @@
diff --git a/exclude.c b/exclude.c
index d36a105e..da25661b 100644
--- a/exclude.c
+++ b/exclude.c
@@ -33,18 +33,15 @@ extern int recurse;
extern int local_server;
extern int prune_empty_dirs;
extern int ignore_perishable;
-extern int old_style_args;
extern int relative_paths;
extern int delete_mode;
extern int delete_excluded;
extern int cvs_exclude;
extern int sanitize_paths;
extern int protocol_version;
-extern int read_batch;
-extern int list_only;
+extern int trust_sender_args;
extern int module_id;
-extern char *filesfrom_host;
extern char curr_dir[MAXPATHLEN];
extern unsigned int curr_dir_len;
extern unsigned int module_dirlen;
@@ -55,6 +52,7 @@ filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" };
filter_rule_list implied_filter_list = { .debug_type = " [implied]" };
int saw_xattr_filter = 0;
+int trust_sender_args = 0;
int trust_sender_filter = 0;
/* Need room enough for ":MODS " prefix plus some room to grow. */
@@ -377,7 +375,7 @@ void add_implied_include(const char *arg, int skip_daemon_module)
int slash_cnt = 0;
const char *cp;
char *p;
- if (am_server || old_style_args || list_only || read_batch || filesfrom_host != NULL)
+ if (trust_sender_args)
return;
if (partial_string_len) {
arg_len = strlen(arg);
diff --git a/main.c b/main.c
index 6721ceb7..9ebfbea7 100644
--- a/main.c
+++ b/main.c
@@ -89,7 +89,6 @@ extern int backup_dir_len;
extern BOOL shutting_down;
extern int backup_dir_len;
extern int basis_dir_cnt;
-extern int trust_sender_filter;
extern struct stats stats;
extern char *stdout_format;
extern char *logfile_format;
@@ -636,7 +635,6 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
#ifdef ICONV_CONST
setup_iconv();
#endif
- trust_sender_filter = 1;
} else if (local_server) {
/* If the user didn't request --[no-]whole-file, force
* it on, but only if we're not batch processing. */
diff --git a/options.c b/options.c
index e7a9fcae..4feeb7e0 100644
--- a/options.c
+++ b/options.c
@@ -27,6 +27,8 @@
extern int local_server;
extern int sanitize_paths;
extern int daemon_over_rsh;
+extern int trust_sender_args;
+extern int trust_sender_filter;
extern unsigned int module_dirlen;
extern filter_rule_list filter_list;
extern filter_rule_list daemon_filter_list;
@@ -64,6 +66,7 @@ int preserve_atimes = 0;
static int daemon_opt; /* sets am_daemon after option error-reporting */
static int omit_dir_times = 0;
static int omit_link_times = 0;
+int trust_sender = 0;
static int F_option_cnt = 0;
static int modify_window_set;
static int itemize_changes = 0;
@@ -788,6 +791,7 @@ static struct poptOption long_options[] = {
{"protect-args", 's', POPT_ARG_VAL, &protect_args, 1, 0, 0},
{"no-protect-args", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0},
{"no-s", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0},
+ {"trust-sender", 0, POPT_ARG_VAL, &trust_sender, 1, 0, 0},
{"numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 1, 0, 0 },
{"no-numeric-ids", 0, POPT_ARG_VAL, &numeric_ids, 0, 0, 0 },
{"usermap", 0, POPT_ARG_STRING, 0, OPT_USERMAP, 0, 0 },
@@ -2465,6 +2469,11 @@ int parse_arguments(int *argc_p, const char ***argv_p)
}
}
+ if (trust_sender || am_server || read_batch)
+ trust_sender_args = trust_sender_filter = 1;
+ else if (old_style_args || filesfrom_host != NULL)
+ trust_sender_args = 1;
+
am_starting_up = 0;
return 1;
@@ -2438,9 +2438,7 @@ char *safe_arg(const char *opt, const char *arg)
char *ret;
if (!protect_args && old_style_args < 2 && (!old_style_args || (!is_filename_arg && opt != SPLIT_ARG_WHEN_OLD))) {
const char *f;
- if (!old_style_args && *arg == '~'
- && ((relative_paths && !strstr(arg, "/./"))
- || !strchr(arg, '/'))) {
+ if (!trust_sender_args && *arg == '~' && (relative_paths || !strchr(arg, '/'))) {
extras++;
escape_leading_tilde = 1;
}
diff --git a/rsync.1.old b/rsync.1
index 839f5ad..6882cf5 100644
--- a/rsync.1.old
+++ b/rsync.1
@@ -182,9 +182,39 @@ particular rsync daemon by leaving off the module name:
\f(CWrsync somehost.mydomain.com::\fP
.RE
-.PP
-See the following section for more details.
-.PP
+.SH "MULTI-HOST SECURITY"
+
+.PP
+Rsync takes steps to ensure that the file requests that are shared in a
+transfer are protected against various security issues. Most of the potential
+problems arise on the receiving side where rsync takes steps to ensure that the
+list of files being transferred remains within the bounds of what was
+requested.
+.PP
+Toward this end, rsync 3.1.2 and later have aborted when a file list contains
+an absolute or relative path that tries to escape out of the top of the
+transfer. Also, beginning with version 3.2.5, rsync does two more safety
+checks of the file list to (1) ensure that no extra source arguments were added
+into the transfer other than those that the client requested and (2) ensure
+that the file list obeys the exclude rules that we sent to the sender.
+.PP
+For those that don't yet have a 3.2.5 client rsync, it is safest to do a copy
+into a dedicated destination directory for the remote files rather than
+requesting the remote content get mixed in with other local content. For
+example, doing an rsync copy into your home directory is potentially unsafe on
+an older rsync if the remote rsync is being controlled by a bad actor:
+.PP
+.RS
+\f(CWrsync \-aiv host:dir1 ~\fP
+.RE
+.PP
+A safer command would be:
+.RS
+\f(CWrsync \-aiv host:dir1 ~/host-files\fP
+.RE
+.PP
+See the \fB\-\-trust\-sender\fP option for additional details.
+
.SH "ADVANCED USAGE"
.PP
@@ -519,6 +549,7 @@ to the detailed description below for a complete description.
\-0, \-\-from0 all *from/filter files are delimited by 0s
\-\-old\-args disable the modern arg-protection idiom
\-s, \-\-protect\-args no space\-splitting; wildcard chars only
+ \-\-trust\-sender trust the remote sender's file list
\-\-address=ADDRESS bind address for outgoing socket to daemon
\-\-port=PORT specify double\-colon alternate port number
\-\-sockopts=OPTIONS specify custom TCP options
@@ -2119,6 +2150,49 @@ This option conflicts with the \fB\-\-old\-args\fP option.
Note that this option is incompatible with the use of the restricted rsync
script (`rrsync`) since it hides options from the script's inspection.
.IP
+.IP "\fB\-\-trust\-sender\fP"
+This option disables two extra validation checks that a local client
+performs on the file list generated by a remote sender. This option should
+only be used if you trust the sender to not put something malicious in the
+file list (something that could possibly be done via a modified rsync, a
+modified shell, or some other similar manipulation).
+.IP
+Normally, the rsync client (as of version 3.2.5) runs two extra validation
+checks when pulling files from a remote rsync:
+.RS
+.IP o
+It verifies that additional arg items didn't get added at the top of the
+transfer.
+.IP o
+It verifies that none of the items in the file list are names that should
+have been excluded (if filter rules were specified).
+.RE
+.IP
+Note that various options can turn off one or both of these checks if the
+option interferes with the validation. For instance:
+.RS
+.IP o
+Using a per-directory filter file reads filter rules that only the server
+knows about, so the filter checking is disabled.
+.IP o
+Using the \fB\-\-old\-args\fP option allows the sender to manipulate the
+requested args, so the arg checking is disabled.
+.IP o
+Reading the files-from list from the server side means that the client
+doesn't know the arg list, so the arg checking is disabled.
+.IP o
+Using \fB\-\-read\-batch\fP disables both checks since the batch file's
+contents will have been verified when it was created.
+.RE
+.IP
+This option may help an under-powered client server if the extra pattern
+matching is slowing things down on a huge transfer. It can also be used
+work around a currently-unknown bug in the verification logic for a transfer
+from a trusted sender.
+.IP
+When using this option it is a good idea to specify a dedicated destination
+directory, as discussed in the \(dq\&MULTI-HOST SECURITY\(dq\& section.
+.IP
.IP "\fB\-T, \-\-temp\-dir=DIR\fP"
This option instructs rsync to use DIR as a
scratch directory when creating temporary copies of the files transferred

View File

@ -0,0 +1,12 @@
diff --git a/runtests.sh.old b/runtests.sh
index ecb383e..1cd1d1a 100755
--- a/runtests.sh.old
+++ b/runtests.sh
@@ -276,6 +276,7 @@ do
case "$testscript" in
*hardlinks*) TESTRUN_TIMEOUT=600 ;;
+ *default-acls*) continue ;;
*) TESTRUN_TIMEOUT=300 ;;
esac

View File

@ -0,0 +1,22 @@
From Mon Sep 17 00:00:00 2001
From: Matt McCutchen <matt@mattmccutchen.net>
Date: Wed, 26 Aug 2020 12:16:08 -0400
rsync-ssl: Verify the hostname in the certificate when using openssl.
---
rsync-ssl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rsync-ssl b/rsync-ssl
index 8101975a..46701af1 100755
--- a/rsync-ssl
+++ b/rsync-ssl
@@ -129,7 +129,7 @@ function rsync_ssl_helper {
fi
if [[ $RSYNC_SSL_TYPE == openssl ]]; then
- exec $RSYNC_SSL_OPENSSL s_client $caopt $certopt -quiet -verify_quiet -servername $hostname -connect $hostname:$port
+ exec $RSYNC_SSL_OPENSSL s_client $caopt $certopt -quiet -verify_quiet -servername $hostname -verify_hostname $hostname -connect $hostname:$port
elif [[ $RSYNC_SSL_TYPE == gnutls ]]; then
exec $RSYNC_SSL_GNUTLS --logfile=/dev/null $gnutls_cert_opt $gnutls_opts $hostname:$port
else

View File

@ -0,0 +1,36 @@
diff --git a/rsync.1.md b/rsync.1.md
index ec971ecc..7bb4c5a1 100644
--- a/rsync.1.md
+++ b/rsync.1.md
@@ -949,7 +949,9 @@ your home directory (remove the '=' for that).
existing content in the file (it only verifies the content that it is
appending). Rsync skips any files that exist on the receiving side that
are not shorter than the associated file on the sending side (which means
- that new files are trasnferred).
+ that new files are trasnferred). It also skips any files whose size on the
+ sending side gets shorter during the send negotiations (rsync warns about a
+ "diminished" file when this happens).
This does not interfere with the updating of a file's non-content
attributes (e.g. permissions, ownership, etc.) when the file does not need
diff --git a/sender.c b/sender.c
index 94761c26..9cfca134 100644
--- a/sender.c
+++ b/sender.c
@@ -362,6 +362,16 @@ void send_files(int f_in, int f_out)
exit_cleanup(RERR_FILEIO);
}
+ if (append_mode > 0 && st.st_size < F_LENGTH(file)) {
+ rprintf(FWARNING, "skipped diminished file: %s\n",
+ full_fname(fname));
+ free_sums(s);
+ close(fd);
+ if (protocol_version >= 30)
+ send_msg_int(MSG_NO_SEND, ndx);
+ continue;
+ }
+
if (IS_DEVICE(st.st_mode) && st.st_size == 0)
st.st_size = get_device_size(fd, fname);

View File

@ -0,0 +1,48 @@
diff --git a/rsync.c b/rsync.c
index bcecac63..ff9489be 100644
--- a/rsync.c
+++ b/rsync.c
@@ -63,8 +63,7 @@ extern char *iconv_opt;
#define UPDATED_ATIME (1<<3)
#define UPDATED_ACLS (1<<4)
#define UPDATED_MODE (1<<5)
-
-#define UPDATED_TIMES (UPDATED_MTIME|UPDATED_ATIME)
+#define UPDATED_CRTIME (1<<6)
#ifdef ICONV_CONST
iconv_t ic_chck = (iconv_t)-1;
@@ -576,10 +575,11 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
set_xattr(fname, file, fnamecmp, sxp);
#endif
- if (!preserve_times
- || (!(preserve_times & PRESERVE_DIR_TIMES) && S_ISDIR(sxp->st.st_mode))
- || (!(preserve_times & PRESERVE_LINK_TIMES) && S_ISLNK(sxp->st.st_mode)))
- flags |= ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME;
+ if (!preserve_times)
+ flags |= ATTRS_SKIP_MTIME | (atimes_ndx ? 0 : ATTRS_SKIP_ATIME) | (crtimes_ndx ? 0 : ATTRS_SKIP_CRTIME);
+ else if ((!(preserve_times & PRESERVE_DIR_TIMES) && S_ISDIR(sxp->st.st_mode))
+ || (!(preserve_times & PRESERVE_LINK_TIMES) && S_ISLNK(sxp->st.st_mode)))
+ flags |= ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME;
else if (sxp != &sx2)
memcpy(&sx2.st, &sxp->st, sizeof (sx2.st));
if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
@@ -606,7 +606,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
updated |= UPDATED_ATIME;
}
}
- if (updated & UPDATED_TIMES) {
+ if (updated & (UPDATED_MTIME|UPDATED_ATIME)) {
int ret = set_times(fname, &sx2.st);
if (ret < 0) {
rsyserr(FERROR_XFER, errno, "failed to set times on %s",
@@ -614,7 +614,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
goto cleanup;
}
if (ret > 0) { /* ret == 1 if symlink could not be set */
- updated &= ~UPDATED_TIMES;
+ updated &= ~(UPDATED_MTIME|UPDATED_ATIME);
file->flags |= FLAG_TIME_FAILED;
}
}

View File

@ -1,8 +1,8 @@
diff --git a/exclude.c b/exclude.c diff --git a/exclude.c b/exclude.c
index 7989fb3..13c4253 100644 index e095744..7906caa 100644
--- a/exclude.c --- a/exclude.c
+++ b/exclude.c +++ b/exclude.c
@@ -24,18 +24,26 @@ @@ -25,18 +25,26 @@
extern int am_server; extern int am_server;
extern int am_sender; extern int am_sender;
@ -29,7 +29,7 @@ index 7989fb3..13c4253 100644
extern char curr_dir[MAXPATHLEN]; extern char curr_dir[MAXPATHLEN];
extern unsigned int curr_dir_len; extern unsigned int curr_dir_len;
extern unsigned int module_dirlen; extern unsigned int module_dirlen;
@@ -43,8 +51,10 @@ extern unsigned int module_dirlen; @@ -44,8 +51,10 @@ extern unsigned int module_dirlen;
filter_rule_list filter_list = { .debug_type = "" }; filter_rule_list filter_list = { .debug_type = "" };
filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" }; filter_rule_list cvs_filter_list = { .debug_type = " [global CVS]" };
filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" }; filter_rule_list daemon_filter_list = { .debug_type = " [daemon]" };
@ -40,7 +40,7 @@ index 7989fb3..13c4253 100644
/* Need room enough for ":MODS " prefix plus some room to grow. */ /* Need room enough for ":MODS " prefix plus some room to grow. */
#define MAX_RULE_PREFIX (16) #define MAX_RULE_PREFIX (16)
@@ -293,6 +303,233 @@ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_ @@ -288,6 +297,233 @@ static void add_rule(filter_rule_list *listp, const char *pat, unsigned int pat_
} }
} }
@ -143,9 +143,9 @@ index 7989fb3..13c4253 100644
+ arg = cp + 3; + arg = cp + 3;
+ } else if ((cp = strrchr(arg, '/')) != NULL) { + } else if ((cp = strrchr(arg, '/')) != NULL) {
+ arg = cp + 1; + arg = cp + 1;
+ if (*arg == '.' && arg[1] == '\0')
+ arg++;
+ } + }
+ if (*arg == '.' && arg[1] == '\0')
+ arg++;
+ arg_len = strlen(arg); + arg_len = strlen(arg);
+ if (arg_len) { + if (arg_len) {
+ if (strpbrk(arg, "*[?")) { + if (strpbrk(arg, "*[?")) {
@ -274,7 +274,7 @@ index 7989fb3..13c4253 100644
/* This frees any non-inherited items, leaving just inherited items on the list. */ /* This frees any non-inherited items, leaving just inherited items on the list. */
static void pop_filter_list(filter_rule_list *listp) static void pop_filter_list(filter_rule_list *listp)
{ {
@@ -709,11 +946,12 @@ static void report_filter_result(enum logcode code, char const *name, @@ -702,11 +938,12 @@ static void report_filter_result(enum logcode code, char const *name,
filter_rule const *ent, filter_rule const *ent,
int name_flags, const char *type) int name_flags, const char *type)
{ {
@ -290,7 +290,7 @@ index 7989fb3..13c4253 100644
static char *actions[2][2] static char *actions[2][2]
= { {"show", "hid"}, {"risk", "protect"} }; = { {"show", "hid"}, {"risk", "protect"} };
const char *w = who_am_i(); const char *w = who_am_i();
@@ -721,7 +959,7 @@ static void report_filter_result(enum logcode code, char const *name, @@ -714,7 +951,7 @@ static void report_filter_result(enum logcode code, char const *name,
: name_flags & NAME_IS_DIR ? "directory" : name_flags & NAME_IS_DIR ? "directory"
: "file"; : "file";
rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n", rprintf(code, "[%s] %sing %s %s because of pattern %s%s%s\n",
@ -299,7 +299,7 @@ index 7989fb3..13c4253 100644
t, name, ent->pattern, t, name, ent->pattern,
ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type); ent->rflags & FILTRULE_DIRECTORY ? "/" : "", type);
} }
@@ -894,6 +1132,7 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr, @@ -886,6 +1123,7 @@ static filter_rule *parse_rule_tok(const char **rulestr_ptr,
} }
switch (ch) { switch (ch) {
case ':': case ':':
@ -308,10 +308,10 @@ index 7989fb3..13c4253 100644
| FILTRULE_FINISH_SETUP; | FILTRULE_FINISH_SETUP;
/* FALL THROUGH */ /* FALL THROUGH */
diff --git a/flist.c b/flist.c diff --git a/flist.c b/flist.c
index 499440c..630d685 100644 index 5a1e424..4e9dd10 100644
--- a/flist.c --- a/flist.c
+++ b/flist.c +++ b/flist.c
@@ -70,6 +70,7 @@ extern int need_unsorted_flist; @@ -72,6 +72,7 @@ extern int need_unsorted_flist;
extern int sender_symlink_iconv; extern int sender_symlink_iconv;
extern int output_needs_newline; extern int output_needs_newline;
extern int sender_keeps_checksum; extern int sender_keeps_checksum;
@ -319,7 +319,7 @@ index 499440c..630d685 100644
extern int unsort_ndx; extern int unsort_ndx;
extern uid_t our_uid; extern uid_t our_uid;
extern struct stats stats; extern struct stats stats;
@@ -80,8 +81,7 @@ extern char curr_dir[MAXPATHLEN]; @@ -82,8 +83,7 @@ extern char curr_dir[MAXPATHLEN];
extern struct chmod_mode_struct *chmod_modes; extern struct chmod_mode_struct *chmod_modes;
@ -329,7 +329,7 @@ index 499440c..630d685 100644
#ifdef ICONV_OPTION #ifdef ICONV_OPTION
extern int filesfrom_convert; extern int filesfrom_convert;
@@ -904,6 +904,19 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x @@ -971,6 +971,19 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
exit_cleanup(RERR_UNSUPPORTED); exit_cleanup(RERR_UNSUPPORTED);
} }
@ -350,10 +350,10 @@ index 499440c..630d685 100644
if (one_file_system) { if (one_file_system) {
/* Room to save the dir's device for -x */ /* Room to save the dir's device for -x */
diff --git a/io.c b/io.c diff --git a/io.c b/io.c
index 59105ba..3aea50f 100644 index b50a066..6d0a389 100644
--- a/io.c --- a/io.c
+++ b/io.c +++ b/io.c
@@ -374,6 +374,7 @@ static void forward_filesfrom_data(void) @@ -373,6 +373,7 @@ static void forward_filesfrom_data(void)
free_xbuf(&ff_xb); free_xbuf(&ff_xb);
if (ff_reenable_multiplex >= 0) if (ff_reenable_multiplex >= 0)
io_start_multiplex_out(ff_reenable_multiplex); io_start_multiplex_out(ff_reenable_multiplex);
@ -361,7 +361,7 @@ index 59105ba..3aea50f 100644
} }
return; return;
} }
@@ -415,6 +416,7 @@ static void forward_filesfrom_data(void) @@ -414,6 +415,7 @@ static void forward_filesfrom_data(void)
while (s != eob) { while (s != eob) {
if (*s++ == '\0') { if (*s++ == '\0') {
ff_xb.len = s - sob - 1; ff_xb.len = s - sob - 1;
@ -369,7 +369,7 @@ index 59105ba..3aea50f 100644
if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0) if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0)
exit_cleanup(RERR_PROTOCOL); /* impossible? */ exit_cleanup(RERR_PROTOCOL); /* impossible? */
write_buf(iobuf.out_fd, s-1, 1); /* Send the '\0'. */ write_buf(iobuf.out_fd, s-1, 1); /* Send the '\0'. */
@@ -430,6 +432,7 @@ static void forward_filesfrom_data(void) @@ -429,6 +431,7 @@ static void forward_filesfrom_data(void)
ff_lastchar = '\0'; ff_lastchar = '\0';
else { else {
/* Handle a partial string specially, saving any incomplete chars. */ /* Handle a partial string specially, saving any incomplete chars. */
@ -377,7 +377,7 @@ index 59105ba..3aea50f 100644
flags &= ~ICB_INCLUDE_INCOMPLETE; flags &= ~ICB_INCLUDE_INCOMPLETE;
if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0) { if (iconvbufs(ic_send, &ff_xb, &iobuf.out, flags) < 0) {
if (errno == E2BIG) if (errno == E2BIG)
@@ -446,13 +449,17 @@ static void forward_filesfrom_data(void) @@ -445,13 +448,17 @@ static void forward_filesfrom_data(void)
char *f = ff_xb.buf + ff_xb.pos; char *f = ff_xb.buf + ff_xb.pos;
char *t = ff_xb.buf; char *t = ff_xb.buf;
char *eob = f + len; char *eob = f + len;
@ -396,10 +396,10 @@ index 59105ba..3aea50f 100644
if ((len = t - ff_xb.buf) != 0) { if ((len = t - ff_xb.buf) != 0) {
/* This will not circle back to perform_io() because we only get /* This will not circle back to perform_io() because we only get
diff --git a/main.c b/main.c diff --git a/main.c b/main.c
index 6113563..abe2ebf 100644 index 46b97b5..f124a2d 100644
--- a/main.c --- a/main.c
+++ b/main.c +++ b/main.c
@@ -42,6 +42,7 @@ extern int output_needs_newline; @@ -48,6 +48,7 @@ extern int called_from_signal_handler;
extern int need_messages_from_generator; extern int need_messages_from_generator;
extern int kluge_around_eof; extern int kluge_around_eof;
extern int got_xfer_error; extern int got_xfer_error;
@ -407,15 +407,15 @@ index 6113563..abe2ebf 100644
extern int msgs2stderr; extern int msgs2stderr;
extern int module_id; extern int module_id;
extern int read_only; extern int read_only;
@@ -78,6 +79,7 @@ extern BOOL flist_receiving_enabled; @@ -87,6 +88,7 @@ extern BOOL shutting_down;
extern BOOL shutting_down;
extern int backup_dir_len; extern int backup_dir_len;
extern int basis_dir_cnt; extern int basis_dir_cnt;
extern int default_af_hint;
+extern int trust_sender_filter; +extern int trust_sender_filter;
extern struct stats stats; extern struct stats stats;
extern char *stdout_format; extern char *stdout_format;
extern char *logfile_format; extern char *logfile_format;
@@ -93,7 +95,7 @@ extern char curr_dir[MAXPATHLEN]; @@ -102,7 +104,7 @@ extern char curr_dir[MAXPATHLEN];
extern char backup_dir_buf[MAXPATHLEN]; extern char backup_dir_buf[MAXPATHLEN];
extern char *basis_dir[MAX_BASIS_DIRS+1]; extern char *basis_dir[MAX_BASIS_DIRS+1];
extern struct file_list *first_flist; extern struct file_list *first_flist;
@ -424,7 +424,7 @@ index 6113563..abe2ebf 100644
uid_t our_uid; uid_t our_uid;
gid_t our_gid; gid_t our_gid;
@@ -503,11 +505,7 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in @@ -611,11 +613,7 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n"); rprintf(FERROR, "internal: args[] overflowed in do_cmd()\n");
exit_cleanup(RERR_SYNTAX); exit_cleanup(RERR_SYNTAX);
} }
@ -437,7 +437,7 @@ index 6113563..abe2ebf 100644
remote_argc--; remote_argc--;
} }
} }
@@ -534,6 +532,7 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in @@ -642,6 +640,7 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
#ifdef ICONV_CONST #ifdef ICONV_CONST
setup_iconv(); setup_iconv();
#endif #endif
@ -445,7 +445,7 @@ index 6113563..abe2ebf 100644
} else if (local_server) { } else if (local_server) {
/* If the user didn't request --[no-]whole-file, force /* If the user didn't request --[no-]whole-file, force
* it on, but only if we're not batch processing. */ * it on, but only if we're not batch processing. */
@@ -943,6 +942,7 @@ static int do_recv(int f_in, int f_out, char *local_name) @@ -1080,6 +1079,7 @@ static int do_recv(int f_in, int f_out, char *local_name)
} }
am_generator = 1; am_generator = 1;
@ -453,8 +453,8 @@ index 6113563..abe2ebf 100644
flist_receiving_enabled = True; flist_receiving_enabled = True;
io_end_multiplex_in(MPLX_SWITCHING); io_end_multiplex_in(MPLX_SWITCHING);
@@ -1340,6 +1340,10 @@ static int start_client(int argc, char *argv[]) @@ -1475,6 +1475,10 @@ static int start_client(int argc, char *argv[])
remote_argc = argc = 1; rsync_port = 0;
} }
+ /* A local transfer doesn't unbackslash anything, so leave the args alone. */ + /* A local transfer doesn't unbackslash anything, so leave the args alone. */
@ -464,28 +464,28 @@ index 6113563..abe2ebf 100644
if (!rsync_port && remote_argc && !**remote_argv) /* Turn an empty arg into a dot dir. */ if (!rsync_port && remote_argc && !**remote_argv) /* Turn an empty arg into a dot dir. */
*remote_argv = "."; *remote_argv = ".";
@@ -1358,6 +1362,8 @@ static int start_client(int argc, char *argv[]) @@ -1500,6 +1504,8 @@ static int start_client(int argc, char *argv[])
char *dummy_host; char *dummy_host;
int dummy_port = rsync_port; int dummy_port = rsync_port;
int i; int i;
+ if (filesfrom_fd < 0) + if (filesfrom_fd < 0)
+ add_implied_include(remote_argv[0], daemon_over_rsh); + add_implied_include(remote_argv[0], daemon_connection);
/* For remote source, any extra source args must have either /* For remote source, any extra source args must have either
* the same hostname or an empty hostname. */ * the same hostname or an empty hostname. */
for (i = 1; i < remote_argc; i++) { for (i = 1; i < remote_argc; i++) {
@@ -1381,6 +1387,7 @@ static int start_client(int argc, char *argv[]) @@ -1523,6 +1529,7 @@ static int start_client(int argc, char *argv[])
if (!rsync_port && !*arg) /* Turn an empty arg into a dot dir. */ if (!rsync_port && !*arg) /* Turn an empty arg into a dot dir. */
arg = "."; arg = ".";
remote_argv[i] = arg; remote_argv[i] = arg;
+ add_implied_include(arg, daemon_over_rsh); + add_implied_include(arg, daemon_connection);
} }
} }
diff --git a/receiver.c b/receiver.c diff --git a/receiver.c b/receiver.c
index d6a48f1..c0aa893 100644 index 9df603f..3182e2d 100644
--- a/receiver.c --- a/receiver.c
+++ b/receiver.c +++ b/receiver.c
@@ -577,10 +577,13 @@ int recv_files(int f_in, int f_out, char *local_name) @@ -584,10 +584,13 @@ int recv_files(int f_in, int f_out, char *local_name)
if (DEBUG_GTE(RECV, 1)) if (DEBUG_GTE(RECV, 1))
rprintf(FINFO, "recv_files(%s)\n", fname); rprintf(FINFO, "recv_files(%s)\n", fname);
@ -504,7 +504,7 @@ index d6a48f1..c0aa893 100644
#ifdef SUPPORT_XATTRS #ifdef SUPPORT_XATTRS
diff --git a/options.c b/options.c diff --git a/options.c b/options.c
index 43e8257..aaf8cc9 100644 index 3e530c2..7582236 100644
--- a/options.c --- a/options.c
+++ b/options.c +++ b/options.c
@@ -99,6 +99,7 @@ int filesfrom_fd = -1; @@ -99,6 +99,7 @@ int filesfrom_fd = -1;
@ -514,8 +514,8 @@ index 43e8257..aaf8cc9 100644
+int old_style_args = -1; +int old_style_args = -1;
int human_readable = 1; int human_readable = 1;
int recurse = 0; int recurse = 0;
int allow_inc_recurse = 1; int mkpath_dest_arg = 0;
@@ -277,7 +278,7 @@ static struct output_struct debug_words[COUNT_DEBUG+1] = { @@ -287,7 +288,7 @@ static struct output_struct debug_words[COUNT_DEBUG+1] = {
DEBUG_WORD(DELTASUM, W_SND|W_REC, "Debug delta-transfer checksumming (levels 1-4)"), DEBUG_WORD(DELTASUM, W_SND|W_REC, "Debug delta-transfer checksumming (levels 1-4)"),
DEBUG_WORD(DUP, W_REC, "Debug weeding of duplicate names"), DEBUG_WORD(DUP, W_REC, "Debug weeding of duplicate names"),
DEBUG_WORD(EXIT, W_CLI|W_SRV, "Debug exit events (levels 1-3)"), DEBUG_WORD(EXIT, W_CLI|W_SRV, "Debug exit events (levels 1-3)"),
@ -524,16 +524,16 @@ index 43e8257..aaf8cc9 100644
DEBUG_WORD(FLIST, W_SND|W_REC, "Debug file-list operations (levels 1-4)"), DEBUG_WORD(FLIST, W_SND|W_REC, "Debug file-list operations (levels 1-4)"),
DEBUG_WORD(FUZZY, W_REC, "Debug fuzzy scoring (levels 1-2)"), DEBUG_WORD(FUZZY, W_REC, "Debug fuzzy scoring (levels 1-2)"),
DEBUG_WORD(GENR, W_REC, "Debug generator functions"), DEBUG_WORD(GENR, W_REC, "Debug generator functions"),
@@ -824,7 +825,7 @@ enum {OPT_VERSION = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM, @@ -575,7 +576,7 @@ enum {OPT_SERVER = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE, OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_BLOCK_SIZE,
- OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_STDERR,
+ OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT, OPT_OLD_ARGS, - OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS,
OPT_SERVER, OPT_REFUSED_BASE = 9000}; + OPT_OLD_COMPRESS, OPT_NEW_COMPRESS, OPT_NO_COMPRESS, OPT_OLD_ARGS,
OPT_STOP_AFTER, OPT_STOP_AT,
OPT_REFUSED_BASE = 9000};
static struct poptOption long_options[] = { @@ -779,6 +780,8 @@ static struct poptOption long_options[] = {
@@ -1011,6 +1012,8 @@ static struct poptOption long_options[] = {
{"files-from", 0, POPT_ARG_STRING, &files_from, 0, 0, 0 }, {"files-from", 0, POPT_ARG_STRING, &files_from, 0, 0, 0 },
{"from0", '0', POPT_ARG_VAL, &eol_nulls, 1, 0, 0}, {"from0", '0', POPT_ARG_VAL, &eol_nulls, 1, 0, 0},
{"no-from0", 0, POPT_ARG_VAL, &eol_nulls, 0, 0, 0}, {"no-from0", 0, POPT_ARG_VAL, &eol_nulls, 0, 0, 0},
@ -542,8 +542,8 @@ index 43e8257..aaf8cc9 100644
{"protect-args", 's', POPT_ARG_VAL, &protect_args, 1, 0, 0}, {"protect-args", 's', POPT_ARG_VAL, &protect_args, 1, 0, 0},
{"no-protect-args", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0}, {"no-protect-args", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0},
{"no-s", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0}, {"no-s", 0, POPT_ARG_VAL, &protect_args, 0, 0, 0},
@@ -1577,6 +1580,13 @@ int parse_arguments(int *argc_p, const char ***argv_p) @@ -1605,6 +1608,13 @@ int parse_arguments(int *argc_p, const char ***argv_p)
do_compression++; compress_choice = NULL;
break; break;
+ case OPT_OLD_ARGS: + case OPT_OLD_ARGS:
@ -556,8 +556,8 @@ index 43e8257..aaf8cc9 100644
case 'M': case 'M':
arg = poptGetOptArg(pc); arg = poptGetOptArg(pc);
if (*arg != '-') { if (*arg != '-') {
@@ -1829,6 +1839,21 @@ int parse_arguments(int *argc_p, const char ***argv_p) @@ -1914,6 +1924,21 @@ int parse_arguments(int *argc_p, const char ***argv_p)
} max_alloc = size;
} }
+ if (old_style_args < 0) { + if (old_style_args < 0) {
@ -578,7 +578,7 @@ index 43e8257..aaf8cc9 100644
if (protect_args < 0) { if (protect_args < 0) {
if (am_server) if (am_server)
protect_args = 0; protect_args = 0;
@@ -2381,6 +2406,71 @@ int parse_arguments(int *argc_p, const char ***argv_p) @@ -2451,6 +2476,71 @@ int parse_arguments(int *argc_p, const char ***argv_p)
} }
@ -650,7 +650,7 @@ index 43e8257..aaf8cc9 100644
/** /**
* Construct a filtered list of options to pass through from the * Construct a filtered list of options to pass through from the
* client to the server. * client to the server.
@@ -2556,9 +2646,7 @@ void server_options(char **args, int *argc_p) @@ -2633,9 +2723,7 @@ void server_options(char **args, int *argc_p)
set++; set++;
else else
set = iconv_opt; set = iconv_opt;
@ -661,7 +661,7 @@ index 43e8257..aaf8cc9 100644
} }
#endif #endif
@@ -2625,23 +2713,17 @@ void server_options(char **args, int *argc_p) @@ -2701,33 +2789,24 @@ void server_options(char **args, int *argc_p)
} }
if (backup_dir) { if (backup_dir) {
@ -689,9 +689,21 @@ index 43e8257..aaf8cc9 100644
+ if (checksum_choice) + if (checksum_choice)
+ args[ac++] = safe_arg("--checksum-choice", checksum_choice); + args[ac++] = safe_arg("--checksum-choice", checksum_choice);
if (do_compression == CPRES_ZLIBX)
args[ac++] = "--new-compress";
else if (compress_choice && do_compression == CPRES_ZLIB)
args[ac++] = "--old-compress";
- else if (compress_choice) {
- if (asprintf(&arg, "--compress-choice=%s", compress_choice) < 0)
- goto oom;
- args[ac++] = arg;
- }
+ else if (compress_choice)
+ args[ac++] = safe_arg("--compress-choice", compress_choice);
if (am_sender) { if (am_sender) {
if (max_delete > 0) { if (max_delete > 0) {
@@ -2650,14 +2732,10 @@ void server_options(char **args, int *argc_p) @@ -2736,14 +2815,10 @@ void server_options(char **args, int *argc_p)
args[ac++] = arg; args[ac++] = arg;
} else if (max_delete == 0) } else if (max_delete == 0)
args[ac++] = "--max-delete=-1"; args[ac++] = "--max-delete=-1";
@ -710,7 +722,7 @@ index 43e8257..aaf8cc9 100644
if (delete_before) if (delete_before)
args[ac++] = "--delete-before"; args[ac++] = "--delete-before";
else if (delete_during == 2) else if (delete_during == 2)
@@ -2681,11 +2759,8 @@ void server_options(char **args, int *argc_p) @@ -2767,17 +2842,12 @@ void server_options(char **args, int *argc_p)
if (do_stats) if (do_stats)
args[ac++] = "--stats"; args[ac++] = "--stats";
} else { } else {
@ -723,8 +735,16 @@ index 43e8257..aaf8cc9 100644
+ args[ac++] = safe_arg("--skip-compress", skip_compress); + args[ac++] = safe_arg("--skip-compress", skip_compress);
} }
- if (max_alloc_arg && max_alloc != DEFAULT_MAX_ALLOC) {
- args[ac++] = "--max-alloc";
- args[ac++] = max_alloc_arg;
- }
+ if (max_alloc_arg && max_alloc != DEFAULT_MAX_ALLOC)
+ args[ac++] = safe_arg("--max-alloc", max_alloc_arg);
/* --delete-missing-args needs the cooperation of both sides, but /* --delete-missing-args needs the cooperation of both sides, but
@@ -2711,7 +2786,7 @@ void server_options(char **args, int *argc_p) * the sender can handle --ignore-missing-args by itself. */
@@ -2802,7 +2872,7 @@ void server_options(char **args, int *argc_p)
if (partial_dir && am_sender) { if (partial_dir && am_sender) {
if (partial_dir != tmp_partialdir) { if (partial_dir != tmp_partialdir) {
args[ac++] = "--partial-dir"; args[ac++] = "--partial-dir";
@ -733,7 +753,7 @@ index 43e8257..aaf8cc9 100644
} }
if (delay_updates) if (delay_updates)
args[ac++] = "--delay-updates"; args[ac++] = "--delay-updates";
@@ -2734,17 +2809,11 @@ void server_options(char **args, int *argc_p) @@ -2825,17 +2895,11 @@ void server_options(char **args, int *argc_p)
args[ac++] = "--use-qsort"; args[ac++] = "--use-qsort";
if (am_sender) { if (am_sender) {
@ -755,7 +775,7 @@ index 43e8257..aaf8cc9 100644
if (ignore_existing) if (ignore_existing)
args[ac++] = "--ignore-existing"; args[ac++] = "--ignore-existing";
@@ -2755,7 +2824,7 @@ void server_options(char **args, int *argc_p) @@ -2846,7 +2910,7 @@ void server_options(char **args, int *argc_p)
if (tmpdir) { if (tmpdir) {
args[ac++] = "--temp-dir"; args[ac++] = "--temp-dir";
@ -764,16 +784,16 @@ index 43e8257..aaf8cc9 100644
} }
if (basis_dir[0]) { if (basis_dir[0]) {
@@ -2765,7 +2834,7 @@ void server_options(char **args, int *argc_p) @@ -2856,7 +2920,7 @@ void server_options(char **args, int *argc_p)
*/ */
for (i = 0; i < basis_dir_cnt; i++) { for (i = 0; i < basis_dir_cnt; i++) {
args[ac++] = dest_option; args[ac++] = alt_dest_opt(0);
- args[ac++] = basis_dir[i]; - args[ac++] = basis_dir[i];
+ args[ac++] = safe_arg("", basis_dir[i]); + args[ac++] = safe_arg("", basis_dir[i]);
} }
} }
} }
@@ -2790,7 +2859,7 @@ void server_options(char **args, int *argc_p) @@ -2877,7 +2941,7 @@ void server_options(char **args, int *argc_p)
if (files_from && (!am_sender || filesfrom_host)) { if (files_from && (!am_sender || filesfrom_host)) {
if (filesfrom_host) { if (filesfrom_host) {
args[ac++] = "--files-from"; args[ac++] = "--files-from";
@ -782,7 +802,7 @@ index 43e8257..aaf8cc9 100644
if (eol_nulls) if (eol_nulls)
args[ac++] = "--from0"; args[ac++] = "--from0";
} else { } else {
@@ -2830,7 +2899,7 @@ void server_options(char **args, int *argc_p) @@ -2923,7 +2987,7 @@ void server_options(char **args, int *argc_p)
exit_cleanup(RERR_SYNTAX); exit_cleanup(RERR_SYNTAX);
} }
for (j = 1; j <= remote_option_cnt; j++) for (j = 1; j <= remote_option_cnt; j++)
@ -792,10 +812,10 @@ index 43e8257..aaf8cc9 100644
*argc_p = ac; *argc_p = ac;
diff --git a/clientserver.c b/clientserver.c diff --git a/clientserver.c b/clientserver.c
index e2e2dc0..c18c024 100644 index 48c15a6..feca9c8 100644
--- a/clientserver.c --- a/clientserver.c
+++ b/clientserver.c +++ b/clientserver.c
@@ -45,6 +45,7 @@ extern int protocol_version; @@ -47,6 +47,7 @@ extern int protocol_version;
extern int io_timeout; extern int io_timeout;
extern int no_detach; extern int no_detach;
extern int write_batch; extern int write_batch;
@ -803,7 +823,7 @@ index e2e2dc0..c18c024 100644
extern int default_af_hint; extern int default_af_hint;
extern int logfile_format_has_i; extern int logfile_format_has_i;
extern int logfile_format_has_o_or_i; extern int logfile_format_has_o_or_i;
@@ -255,20 +256,45 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char @@ -288,20 +289,45 @@ int start_inband_exchange(int f_in, int f_out, const char *user, int argc, char
sargs[sargc++] = "."; sargs[sargc++] = ".";
@ -857,87 +877,90 @@ index e2e2dc0..c18c024 100644
argc--; argc--;
} }
diff --git a/rsync.1 b/rsync.1 diff --git a/rsync.1 b/rsync.1
index cf2f573..839f5ad 100644 index e0e13cf..e363827 100644
--- a/rsync.1 --- a/rsync.1
+++ b/rsync.1 +++ b/rsync.1
@@ -197,7 +197,7 @@ or with the hostname omitted. For instance, all these work: @@ -194,32 +194,27 @@ the hostname omitted. For instance, all these work:
.br .nf
\f(CWrsync \-av host::modname/file{1,2} host::modname/file3 /dest/\fP rsync -av host:file1 :file2 host:file{3,4} /dest/
.br rsync -av host::modname/file{1,2} host::modname/file3 /dest/
-\f(CWrsync \-av host::modname/file1 ::modname/file{3,4}\fP -rsync -av host::modname/file1 ::modname/file{3,4}
+\f(CWrsync \-av host::modname/file1 ::modname/file{3,4} /dest/\fP +rsync -av host::modname/file1 ::modname/file{3,4} /dest/
.fi
.RE .RE
.P
.PP -Older versions of rsync required using quoted spaces in the SRC, like these
@@ -211,18 +211,23 @@ examples: -examples:
.RE
.PP
-This word\-splitting still works (by default) in the latest rsync, but is
-not as easy to use as the first method.
-.PP
-If you need to transfer a filename that contains whitespace, you can either
-specify the \fB\-\-protect\-args\fP (\fB\-s\fP) option, or you\(cq\&ll need to escape
-the whitespace in a way that the remote shell will understand. For
-instance:
-.PP
-.RS
-\f(CWrsync \-av host:'\&file\e name\e with\e spaces'\& /dest\fP
+Starting this version of rsync, filenames are passed to a remote shell +Starting this version of rsync, filenames are passed to a remote shell
+in such a way as to preserve the characters you give it. +in such a way as to preserve the characters you give it.
+Thus, if you ask for a file with spaces in the name, that's what the +Thus, if you ask for a file with spaces in the name, that's what the
+remote rsync looks for: +remote rsync looks for:
+.PP .RS 4
+.RS .P
+\f(CWrsync \-aiv host:'\&a simple file.pdf'\& /dest/\fP .nf
-rsync -av host:'dir1/file1 dir2/file2' /dest
-rsync host::'modname/dir1/file1 modname/dir2/file2' /dest
+rsync -aiv host:'a simple file.pdf' /dest/
.fi
.RE .RE
.P
+.PP -This word-splitting still works (by default) in the latest rsync, but is not as
-easy to use as the first method.
-.P
-If you need to transfer a filename that contains whitespace, you can either
-specify the \fB\-\-protect-args\fP (\fB\-s\fP) option, or you'll need to escape the
-whitespace in a way that the remote shell will understand. For instance:
-.RS 4
-.P
-.nf
-rsync -av host:'file\\ name\\ with\\ spaces' /dest
-.fi
-.RE
+If you use scripts that have been written to manually apply extra quoting to +If you use scripts that have been written to manually apply extra quoting to
+the remote rsync args (or to require remote arg splitting), you can ask rsync +the remote rsync args (or to require remote arg splitting), you can ask rsync
+to let your script handle the extra escaping. This is done by either adding +to let your script handle the extra escaping. This is done by either adding
+the \fB\-\-old\-args\fP option to the rsync runs in the script (which requires +the \fB\-\-old\-args\fP option to the rsync runs in the script (which requires
+a new rsync) or exporting \fBRSYNC_OLD_ARGS\fP=1 and \fBRSYNC_PROTECT_ARGS\fP=0 +a new rsync) or exporting \fBRSYNC_OLD_ARGS\fP=1 and \fBRSYNC_PROTECT_ARGS\fP=0
+(which works with old or new rsync versions). +(which works with old or new rsync versions).
+ .P
.PP
.SH "CONNECTING TO AN RSYNC DAEMON" .SH "CONNECTING TO AN RSYNC DAEMON"
.P
@@ -429,6 +434,7 @@ to the detailed description below for a complete description. @@ -427,6 +422,7 @@ detailed description below for a complete description.
\-\-append append data onto shorter files --append append data onto shorter files
\-\-append\-verify \-\-append w/old data in file checksum --append-verify --append w/old data in file checksum
\-d, \-\-dirs transfer directories without recursing --dirs, -d transfer directories without recursing
+ \-\-old\-dirs, \-\-old\-d works like --dirs when talking to old rsync +--old-dirs, --old-d works like --dirs when talking to old rsync
\-l, \-\-links copy symlinks as symlinks --mkpath create the destination's path component
\-L, \-\-copy\-links transform symlink into referent file/dir --links, -l copy symlinks as symlinks
\-\-copy\-unsafe\-links only \(dq\&unsafe\(dq\& symlinks are transformed --copy-links, -L transform symlink into referent file/dir
@@ -511,6 +517,7 @@ to the detailed description below for a complete description. @@ -515,6 +511,7 @@ detailed description below for a complete description.
\-\-include\-from=FILE read include patterns from FILE --include-from=FILE read include patterns from FILE
\-\-files\-from=FILE read list of source\-file names from FILE --files-from=FILE read list of source-file names from FILE
\-0, \-\-from0 all *from/filter files are delimited by 0s --from0, -0 all *-from/filter files are delimited by 0s
+ \-\-old\-args disable the modern arg-protection idiom +--old-args disable the modern arg-protection idiom
\-s, \-\-protect\-args no space\-splitting; wildcard chars only --protect-args, -s no space-splitting; wildcard chars only
\-\-address=ADDRESS bind address for outgoing socket to daemon --copy-as=USER[:GROUP] specify user & optional group for the copy
\-\-port=PORT specify double\-colon alternate port number --address=ADDRESS bind address for outgoing socket to daemon
@@ -1857,10 +1864,10 @@ Be cautious using this, as it is possible to toggle an option that will cause @@ -1950,11 +1947,10 @@ Be cautious using this, as it is possible to toggle an option that will
rsync to have a different idea about what data to expect next over the socket, cause rsync to have a different idea about what data to expect next over
and that will make it fail in a cryptic fashion. the socket, and that will make it fail in a cryptic fashion.
.IP .IP
-Note that it is best to use a separate \fB\-\-remote\-option\fP for each option you -Note that it is best to use a separate \fB\-\-remote-option\fP for each option
-want to pass. This makes your useage compatible with the \fB\-\-protect\-args\fP -you want to pass. This makes your usage compatible with the
-option. If that option is off, any spaces in your remote options will be split -\fB\-\-protect-args\fP option. If that option is off, any spaces in your remote
-by the remote shell unless you take steps to protect them. -options will be split by the remote shell unless you take steps to protect
-them.
+Note that you should use a separate \fB\-M\fP for each remote option you +Note that you should use a separate \fB\-M\fP for each remote option you
+want to pass. On older rsync versions, the presence of any spaces in the +want to pass. On older rsync versions, the presence of any spaces in the
+remote-option arg could cause it to be split into separate remote args, but +remote-option arg could cause it to be split into separate remote args, but
+this requires the use of \fB\-\-old\-args\fP in this version of rsync. +this requires the use of \fB\-\-old\-args\fP in this version of rsync.
.IP .IP
When performing a local transfer, the \(dq\&local\(dq\& side is the sender and the When performing a local transfer, the "local" side is the sender and the
\(dq\&remote\(dq\& side is the receiver. "remote" side is the receiver.
@@ -2054,32 +2061,64 @@ merged files specified in a \fB\-\-filter\fP rule. @@ -2169,26 +2165,64 @@ terminated by a null ('\\0') character, not a NL, CR, or CR+LF. This
It does not affect \fB\-\-cvs\-exclude\fP (since all names read from a .cvsignore affects \fB\-\-exclude-from\fP, \fB\-\-include-from\fP, \fB\-\-files-from\fP, and any merged
file are split on whitespace). files specified in a \fB\-\-filter\fP rule. It does not affect \fB\-\-cvs-exclude\fP
.IP (since all names read from a .cvsignore file are split on whitespace).
+.IP "\fB\-\-old\-args\fP" +.IP "\fB\-\-old\-args\fP"
+This option tells rsync to stop trying to protect the arg values from +This option tells rsync to stop trying to protect the arg values from
+unintended word-splitting or other misinterpretation by using its new +unintended word-splitting or other misinterpretation by using its new
@ -967,42 +990,38 @@ index cf2f573..839f5ad 100644
+.IP +.IP
+This option conflicts with the \fB\-\-protect\-args\fP option. +This option conflicts with the \fB\-\-protect\-args\fP option.
+.IP +.IP
.IP "\fB\-s, \-\-protect\-args\fP" .IP "\fB\-\-protect-args\fP, \fB\-s\fP"
-This option sends all filenames and most options to This option sends all filenames and most options to the remote rsync
-the remote rsync without allowing the remote shell to interpret them. This -without allowing the remote shell to interpret them. This means that
-means that spaces are not split in names, and any non\-wildcard special -spaces are not split in names, and any non-wildcard special characters are
-characters are not translated (such as ~, $, ;, &, etc.). Wildcards are -not translated (such as \fB~\fP, \fB$\fP, \fB;\fP, \fB&\fP, etc.). Wildcards are expanded
-expanded on the remote host by rsync (instead of the shell doing it). -on the remote host by rsync (instead of the shell doing it).
+This option sends all filenames and most options to the remote rsync
+without allowing the remote shell to interpret them. Wildcards are +without allowing the remote shell to interpret them. Wildcards are
+expanded on the remote host by rsync instead of the shell doing it. +expanded on the remote host by rsync instead of the shell doing it.
+.IP +.IP
+This is similar to the new-style backslash-escaping of args that was added +This is similar to the new-style backslash-escaping of args that was added
+in this version of rsync, but supports some extra features and doesn't +in this version of rsync, but supports some extra features and doesn't
+rely on backslash escaping in the remote shell. +rely on backslash escaping in the remote shell.
.IP .IP
If you use this option with \fB\-\-iconv\fP, the args related to the remote If you use this option with \fB\-\-iconv\fP, the args related to the remote side
side will also be translated will also be translated from the local to the remote character-set. The
from the local to the remote character\-set. The translation happens before translation happens before wild-cards are expanded. See also the
wild\-cards are expanded. See also the \fB\-\-files\-from\fP option. \fB\-\-files-from\fP option.
.IP .IP
-You may also control this option via the RSYNC_PROTECT_ARGS environment -You may also control this option via the RSYNC_PROTECT_ARGS environment
-variable. If this variable has a non\-zero value, this option will be enabled -variable. If this variable has a non-zero value, this option will be
-enabled by default, otherwise it will be disabled by default. Either state
+You may also control this setting via the RSYNC_PROTECT_ARGS environment +You may also control this setting via the RSYNC_PROTECT_ARGS environment
+variable. If it has a non-zero value, this setting will be enabled +variable. If it has a non-zero value, this setting will be enabled
by default, otherwise it will be disabled by default. Either state is +by default, otherwise it will be disabled by default. Either state
overridden by a manually specified positive or negative version of this option is overridden by a manually specified positive or negative version of this
(note that \fB\-\-no\-s\fP and \fB\-\-no\-protect\-args\fP are the negative versions). option (note that \fB\-\-no-s\fP and \fB\-\-no-protect-args\fP are the negative
-Since this option was first introduced in 3.0.0, you\(cq\&ll need to make sure it\(cq\&s -versions). Since this option was first introduced in 3.0.0, you'll need to
-disabled if you ever need to interact with a remote rsync that is older than -make sure it's disabled if you ever need to interact with a remote rsync
-that. -that is older than that.
-.IP +versions). This environment variable is also superseded by a non-zero
-Rsync can also be configured (at build time) to have this option enabled by +\fBRSYNC_OLD_ARGS\fP export.
-default (with is overridden by both the environment and the command\-line). +.IP
-This option will eventually become a new default setting at some
-as\-yet\-undetermined point in the future.
+This environment variable is also superseded by a non-zero \fBRSYNC_OLD_ARGS\fP export.
.IP
+You may need to disable this option when interacting with an older rsync +You may need to disable this option when interacting with an older rsync
+(one prior to 3.0.0). +(one prior to 3.0.0).
+.IP +.IP
@ -1010,38 +1029,39 @@ index cf2f573..839f5ad 100644
+.IP +.IP
+Note that this option is incompatible with the use of the restricted rsync +Note that this option is incompatible with the use of the restricted rsync
+script (`rrsync`) since it hides options from the script's inspection. +script (`rrsync`) since it hides options from the script's inspection.
+.IP .IP
.IP "\fB\-T, \-\-temp\-dir=DIR\fP" Rsync can also be configured (at build time) to have this option enabled by
This option instructs rsync to use DIR as a default (with is overridden by both the environment and the command-line).
scratch directory when creating temporary copies of the files transferred @@ -2675,7 +2708,10 @@ super-user (see also the \fB\-\-fake-super\fP option). For the \fB\-\-groupmap\
@@ -2371,7 +2410,11 @@ as a super\-user (see also the \fB\-\-fake\-super\fP option). For the \fB\-\-gr option to have any effect, the \fB\-g\fP (\fB\-\-groups\fP) option must be used (or
option to have any effect, the \fB\-g\fP (\fB\-\-groups\fP) option must be used implied), and the receiver will need to have permissions to set that group.
(or implied), and the receiver will need to have permissions to set that .IP
group. -If your shell complains about the wildcards, use \fB\-\-protect-args\fP (\fB\-s\fP).
-.IP
+.IP
+An older rsync client may need to use \fB\-\-protect\-args\fP (\fB\-s\fP) +An older rsync client may need to use \fB\-\-protect\-args\fP (\fB\-s\fP)
+to avoid a complaint about wildcard characters, but a modern rsync handles +to avoid a complaint about wildcard characters, but a modern rsync handles
+this automatically. +this automatically.
+.IP +.IP
.IP "\fB\-\-chown=USER:GROUP\fP" .IP "\fB\-\-chown=USER:GROUP\fP"
This option forces all files to be owned by USER This option forces all files to be owned by USER with group GROUP. This is
with group GROUP. This is a simpler interface than using \fB\-\-usermap\fP and a simpler interface than using \fB\-\-usermap\fP and \fB\-\-groupmap\fP directly, but
@@ -2382,6 +2425,10 @@ be omitted, but if USER is empty, a leading colon must be supplied. @@ -2685,8 +2721,11 @@ will occur. If GROUP is empty, the trailing colon may be omitted, but if
.IP USER is empty, a leading colon must be supplied.
If you specify \(dq\&\-\-chown=foo:bar, this is exactly the same as specifying .IP
\(dq\&\-\-usermap=*:foo \-\-groupmap=*:bar\(dq\&, only easier. If you specify "\fB\-\-chown=foo:bar\fP", this is exactly the same as specifying
-"\fB\-\-usermap=*:foo\ \-\-groupmap=*:bar\fP", only easier. If your shell complains
-about the wildcards, use \fB\-\-protect-args\fP (\fB\-s\fP).
+"\fB\-\-usermap=*:foo\ \-\-groupmap=*:bar\fP", only easier.
+.IP +.IP
+An older rsync client may need to use \fB\-\-protect\-args\fP (\fB\-s\fP) to avoid a +An older rsync client may need to use \fB\-\-protect\-args\fP (\fB\-s\fP) to avoid a
+complaint about wildcard characters, but a modern rsync handles this +complaint about wildcard characters, but a modern rsync handles this
+automatically. +automatically.
.IP .IP "\fB\-\-timeout=SECONDS\fP"
.IP "\fB\-\-timeout=TIMEOUT\fP" This option allows you to set a maximum I/O timeout in seconds. If no data
This option allows you to set a maximum I/O is transferred for the specified time then rsync will exit. The default is
@@ -3983,10 +4030,24 @@ more details. @@ -4233,10 +4272,24 @@ The CVSIGNORE environment variable supplements any ignore patterns in
.IP "\fBRSYNC_ICONV\fP" .IP "\fBRSYNC_ICONV\fP"
Specify a default \fB\-\-iconv\fP setting using this Specify a default \fB\-\-iconv\fP setting using this environment variable. (First
environment variable. (First supported in 3.0.0.) supported in 3.0.0.)
+.IP "\fBRSYNC_OLD_ARGS\fP" +.IP "\fBRSYNC_OLD_ARGS\fP"
+Specify a "1" if you want the \fB\-\-old\-args\fP option to be enabled by default, +Specify a "1" if you want the \fB\-\-old\-args\fP option to be enabled by default,
+a "2" (or more) if you want it to be enabled in the option-repeated state, +a "2" (or more) if you want it to be enabled in the option-repeated state,
@ -1052,14 +1072,14 @@ index cf2f573..839f5ad 100644
+This variable is ignored if \fB\-\-old\-args\fP, \fB\-\-no\-old\-args\fP, or +This variable is ignored if \fB\-\-old\-args\fP, \fB\-\-no\-old\-args\fP, or
+\fB\-\-protect\-args\fP is specified on the command line. +\fB\-\-protect\-args\fP is specified on the command line.
.IP "\fBRSYNC_PROTECT_ARGS\fP" .IP "\fBRSYNC_PROTECT_ARGS\fP"
Specify a non\-zero numeric value if you want the Specify a non-zero numeric value if you want the \fB\-\-protect-args\fP option to
\fB\-\-protect\-args\fP option to be enabled by default, or a zero value to make be enabled by default, or a zero value to make sure that it is disabled by
sure that it is disabled by default. (First supported in 3.1.0.) default. (First supported in 3.1.0.)
+.IP +.IP
+This variable is ignored if \fB\-\-protect\-args\fP, \fB\-\-no\-protect\-args\fP, +This variable is ignored if \fB\-\-protect\-args\fP, \fB\-\-no\-protect\-args\fP,
+or \fB\-\-old\-args\fP is specified on the command line. +or \fB\-\-old\-args\fP is specified on the command line.
+.IP +.IP
+This variable is ignored if \fBRSYNC_OLD_ARGS\fP is set to a non-zero value. +This variable is ignored if \fBRSYNC_OLD_ARGS\fP is set to a non-zero value.
.IP "\fBRSYNC_RSH\fP" .IP "\fBRSYNC_RSH\fP"
The RSYNC_RSH environment variable allows you to The RSYNC_RSH environment variable allows you to override the default shell
override the default shell used as the transport for rsync. Command line used as the transport for rsync. Command line options are permitted after

View File

@ -0,0 +1,96 @@
From ce51c1b0ae4c0cc35fa14f69271776e3e3ae6354 Mon Sep 17 00:00:00 2001
From: Natanael Copa <ncopa@alpinelinux.org>
Date: Wed, 21 Jul 2021 16:38:22 +0200
Subject: [PATCH 1/3] Add regression test for --delay-updates (#192)
to test https://github.com/WayneD/rsync/issues/192
---
testsuite/delay-updates.test | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 testsuite/delay-updates.test
diff --git a/testsuite/delay-updates.test b/testsuite/delay-updates.test
new file mode 100644
index 000000000..54961dfa9
--- /dev/null
+++ b/testsuite/delay-updates.test
@@ -0,0 +1,21 @@
+#! /bin/sh
+
+# Test rsync --delay-updates
+
+. "$suitedir/rsync.fns"
+
+mkdir "$fromdir"
+
+echo 1 > "$fromdir/foo"
+
+checkit "$RSYNC -avvv --delay-updates \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
+
+mkdir "$todir/.~tmp~"
+echo 2 > "$todir/.~tmp~/foo"
+touch -r .. "$todir/.~tmp~/foo" "$todir/foo"
+echo 3 > "$fromdir/foo"
+
+checkit "$RSYNC -avvv --delay-updates \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
From 5a4ea7e468ae53c09b98803da3519727becb48ad Mon Sep 17 00:00:00 2001
From: Natanael Copa <ncopa@alpinelinux.org>
Date: Thu, 22 Jul 2021 13:30:17 +0200
Subject: [PATCH 2/3] Fix regression with --delay-updates (#192)
Fixes regression introduced with commit 3a7bf54ad520 (A resumed
partial-dir file is transferred in-place.)
Fixes https://github.com/WayneD/rsync/issues/192
---
receiver.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/receiver.c b/receiver.c
index e85c4779c..b5020d074 100644
--- a/receiver.c
+++ b/receiver.c
@@ -881,7 +881,7 @@ int recv_files(int f_in, int f_out, char *local_name)
do_unlink(partialptr);
handle_partial_dir(partialptr, PDIR_DELETE);
}
- } else if (keep_partial && partialptr && !one_inplace) {
+ } else if (keep_partial && partialptr && (!one_inplace || delay_updates)) {
if (!handle_partial_dir(partialptr, PDIR_CREATE)) {
rprintf(FERROR,
"Unable to create partial-dir for %s -- discarding %s.\n",
From 15ec7de5503c57860fb73ea6e4a349f1e70b72db Mon Sep 17 00:00:00 2001
From: Wayne Davison <wayne@opencoder.net>
Date: Wed, 28 Jul 2021 09:03:10 -0700
Subject: [PATCH 3/3] Tweak rsync options.
---
testsuite/delay-updates.test | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/testsuite/delay-updates.test b/testsuite/delay-updates.test
index 54961dfa9..5896a9c7c 100644
--- a/testsuite/delay-updates.test
+++ b/testsuite/delay-updates.test
@@ -8,14 +8,14 @@ mkdir "$fromdir"
echo 1 > "$fromdir/foo"
-checkit "$RSYNC -avvv --delay-updates \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
+checkit "$RSYNC -aiv --delay-updates \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
mkdir "$todir/.~tmp~"
echo 2 > "$todir/.~tmp~/foo"
touch -r .. "$todir/.~tmp~/foo" "$todir/foo"
echo 3 > "$fromdir/foo"
-checkit "$RSYNC -avvv --delay-updates \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
+checkit "$RSYNC -aiv --delay-updates \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
# The script would have aborted on error, so getting here means we've won.
exit 0

View File

@ -42,7 +42,7 @@ index 13c4253..232249f 100644
char *p; char *p;
if (am_server || old_style_args || list_only || read_batch || filesfrom_host != NULL) if (am_server || old_style_args || list_only || read_batch || filesfrom_host != NULL)
@@ -407,6 +413,7 @@ void add_implied_include(const char *arg, int skip_daemon_module) @@ -407,6 +413,7 @@ void add_implied_include(const char *arg, int skip_daemon_module)
arg++; }
arg_len = strlen(arg); arg_len = strlen(arg);
if (arg_len) { if (arg_len) {
+ char *new_pat; + char *new_pat;

View File

@ -0,0 +1,53 @@
diff --git a/syscall.c b/syscall.c
index b9c3b4ef..11d10e4a 100644
--- a/syscall.c
+++ b/syscall.c
@@ -227,27 +227,35 @@ int do_open(const char *pathname, int flags, mode_t mode)
#ifdef HAVE_CHMOD
int do_chmod(const char *path, mode_t mode)
{
+ static int switch_step = 0;
int code;
if (dry_run) return 0;
RETURN_ERROR_IF_RO_OR_LO;
+ switch (switch_step) {
#ifdef HAVE_LCHMOD
- code = lchmod(path, mode & CHMOD_BITS);
-#else
- if (S_ISLNK(mode)) {
+#include "case_N.h"
+ if ((code = lchmod(path, mode & CHMOD_BITS)) == 0 || errno != ENOTSUP)
+ break;
+ switch_step++;
+#endif
+
+#include "case_N.h"
+ if (S_ISLNK(mode)) {
# if defined HAVE_SETATTRLIST
- struct attrlist attrList;
- uint32_t m = mode & CHMOD_BITS; /* manpage is wrong: not mode_t! */
+ struct attrlist attrList;
+ uint32_t m = mode & CHMOD_BITS; /* manpage is wrong: not mode_t! */
- memset(&attrList, 0, sizeof attrList);
- attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
- attrList.commonattr = ATTR_CMN_ACCESSMASK;
- code = setattrlist(path, &attrList, &m, sizeof m, FSOPT_NOFOLLOW);
+ memset(&attrList, 0, sizeof attrList);
+ attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
+ attrList.commonattr = ATTR_CMN_ACCESSMASK;
+ code = setattrlist(path, &attrList, &m, sizeof m, FSOPT_NOFOLLOW);
# else
- code = 1;
+ code = 1;
# endif
- } else
- code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
-#endif /* !HAVE_LCHMOD */
+ } else
+ code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
+ break;
+ }
if (code != 0 && (preserve_perms || preserve_executability))
return code;
return 0;

View File

@ -0,0 +1,24 @@
From f5a9a1013873580d0ad2ae4f5c5038c324d71bfe Mon Sep 17 00:00:00 2001
From: Wayne Davison <wayne@opencoder.net>
Date: Mon, 21 Feb 2022 14:19:31 -0800
Subject: [PATCH] Fix possible array deref using invalid index.
---
copy-devices.diff | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/patches/copy-devices.diff b/patches/copy-devices.diff
index 797d046..4138474 100644
--- a/patches/copy-devices.diff
+++ b/patches/copy-devices.diff
@@ -111,8 +111,8 @@ diff --git a/rsync.c b/rsync.c
if (iflags & ITEM_TRANSFER) {
int i = ndx - cur_flist->ndx_start;
- if (i < 0 || !S_ISREG(cur_flist->files[i]->mode)) {
-+ struct file_struct *file = cur_flist->files[i];
-+ if (i < 0 || !(S_ISREG(file->mode) || (copy_devices && IS_DEVICE(file->mode)))) {
++ if (i < 0
++ || !(S_ISREG(cur_flist->files[i]->mode) || (copy_devices && IS_DEVICE(cur_flist->files[i]->mode)))) {
rprintf(FERROR,
"received request to transfer non-regular file: %d [%s]\n",
ndx, who_am_i());

View File

@ -1,10 +0,0 @@
--- rsync-3.0.9/rsync.1 2011-09-23 18:42:26.000000000 +0200
+++ rsync-3.0.9/rsync.1 2012-09-19 10:40:19.698802861 +0200
@@ -445,6 +445,7 @@
\-o, \-\-owner preserve owner (super\-user only)
\-g, \-\-group preserve group
\-\-devices preserve device files (super\-user only)
+ \-\-copy-devices copy device contents as regular file
\-\-specials preserve special files
\-D same as \-\-devices \-\-specials
\-t, \-\-times preserve modification times

View File

@ -1,6 +1,7 @@
%global _hardened_build 1 %global _hardened_build 1
%define isprerelease 0 %define isprerelease 0
%define _lto_cflags %{nil}
%if %isprerelease %if %isprerelease
%define prerelease pre1 %define prerelease pre1
@ -8,10 +9,9 @@
Summary: A program for synchronizing files over a network Summary: A program for synchronizing files over a network
Name: rsync Name: rsync
Version: 3.1.3 Version: 3.2.3
Release: 27%{?dist} Release: 20%{?dist}
Group: Applications/Internet URL: https://rsync.samba.org/
URL: http://rsync.samba.org/
Source0: https://download.samba.org/pub/rsync/src/rsync-%{version}%{?prerelease}.tar.gz Source0: https://download.samba.org/pub/rsync/src/rsync-%{version}%{?prerelease}.tar.gz
Source1: https://download.samba.org/pub/rsync/src/rsync-patches-%{version}%{?prerelease}.tar.gz Source1: https://download.samba.org/pub/rsync/src/rsync-patches-%{version}%{?prerelease}.tar.gz
@ -21,54 +21,28 @@ Source4: rsyncd.conf
Source5: rsyncd.sysconfig Source5: rsyncd.sysconfig
Source6: rsyncd@.service Source6: rsyncd@.service
BuildRequires: make
BuildRequires: gcc gcc-c++
BuildRequires: libacl-devel, libattr-devel, autoconf, popt-devel, systemd BuildRequires: libacl-devel, libattr-devel, autoconf, popt-devel, systemd
#Requires: zlib BuildRequires: lz4-devel openssl-devel libzstd-devel
#Added virtual provide for zlib due to https://fedoraproject.org/wiki/Bundled_Libraries?rd=Packaging:Bundled_Libraries #Added virtual provide for zlib due to https://fedoraproject.org/wiki/Bundled_Libraries?rd=Packaging:Bundled_Libraries
Provides: bundled(zlib) = 1.2.8 Provides: bundled(zlib) = 1.2.8
License: GPLv3+ License: GPLv3+
Patch0: rsync-man.patch #Added temporarily until new rebase
Patch1: rsync-3.0.6-iconv-logging.patch Patch0: rsync-3.2.2-ssl-verify-hostname.patch
Patch2: rsync-3.1.3-covscan.patch #Added due to rhbz#1873975 - default-acls test fail on s390x due to libacl
Patch3: rsync-3.1.2-remove-symlinks.patch Patch1: rsync-3.2.2-runtests.patch
Patch4: rsync-3.1.2-vvv-hang.patch Patch2: rsync-3.2.3-lchmod.patch
Patch5: rsync-3.1.3-ignore-missing.patch Patch3: rsync-3.2.3-append-mode.patch
Patch6: rsync-3.1.3-append-check.patch Patch4: rsync-3.2.3-xattr.patch
Patch7: rsync-3.1.3-skip-compress.patch Patch5: rsync-3.2.3-segfault.patch
Patch8: rsync-3.1.3-xattr.patch Patch6: rsync-3.2.3-atimes.patch
Patch9: rsync-3.1.3-cve-2018-25032.patch Patch7: rsync-3.1.3-cve-2018-25032.patch
Patch10: rsync-3.1.3-sparse-block.patch Patch8: rsync-3.2.3-cve-2022-37434.patch
Patch11: rsync-3.1.3-cve-2022-29154.patch Patch9: rsync-3.2.3-cve-2022-29154.patch
Patch12: rsync-3.1.3-cve-2022-37434.patch Patch10: rsync-3.2.3-filtering-rules.patch
Patch13: rsync-3.1.3-filtering-rules.patch Patch11: rsync-3.2.3-delay-updates.patch
Patch14: rsync-3.1.3-missing-xattr-filter.patch
Patch15: rsync-3.1.3-cve-2024-12085.patch
Patch16: rsync-3.1.3-cve-2024-12087.patch
Patch17: rsync-3.1.3-cve-2024-12088.patch
Patch18: rsync-3.1.3-cve-2024-12747.patch
# a fix for CVE-2016-9840 in zlib but marked as CVE-2025-4638 for a different component
Patch19: rsync-3.1.3-cve-2025-4638.patch
Patch20: rsync-3.1.3-trust-sender.patch
Patch21: rsync-3.1.3-cve-2025-10158.patch
# https://github.com/RsyncProject/rsync/commit/bb0a8118c2d2ab01140bac5e4e327e5e1ef90c9c
Patch22: rsync-3.1.3-cve-2026-41035.patch
# https://github.com/RsyncProject/rsync/commit/1a5ad81add1004354a3d8ba841b94ffe19cd2505
# https://github.com/RsyncProject/rsync/commit/99b36291d06ca66229942c7a525a1f5566f10c85
# https://github.com/RsyncProject/rsync/commit/72d1cf1c288e5c526e906db2edafbf3d55762668
# https://github.com/RsyncProject/rsync/commit/61d987c54a472d88855c5fbef3a4c7b51696f93a
# https://github.com/RsyncProject/rsync/commit/24852cda3db38e2f2cd78a13703373c77f75f4d5
# https://github.com/RsyncProject/rsync/commit/d22b6bc7d1b1d7be9df1c0c6db1599cb7d5fd82c
# https://github.com/RsyncProject/rsync/commit/39b3074a1ab18705cd685fe0659fc958c8cd3db5
# https://github.com/RsyncProject/rsync/commit/a277a06b1017b4cf6bb0fe33d5823869ed02dfd9
Patch23: rsync-3.1.3-fix-cve-2026-29518.patch
# Backporting a couple of regression fixes
# https://github.com/RsyncProject/rsync/commit/f6b39cca
# https://github.com/RsyncProject/rsync/commit/5ce33659
# https://github.com/RsyncProject/rsync/commit/3526884f
# https://github.com/RsyncProject/rsync/commit/7192db98
Patch24: rsync-3.1.3-fix-cve-2026-29518-regressions.patch
# https://github.com/RsyncProject/rsync/commit/c44c90e9460c666c965446a8c0957f0b9fa4c66a
Patch25: rsync-3.1.3-fix-cve-2026-43618.patch
%description %description
Rsync uses a reliable algorithm to bring remote and host files into Rsync uses a reliable algorithm to bring remote and host files into
@ -99,48 +73,34 @@ package provides the anonymous rsync service.
%setup -q -b 1 %setup -q -b 1
%endif %endif
#Needed for compatibility with previous patched rsync versions
patch -p1 -i patches/acls.diff
patch -p1 -i patches/xattrs.diff
#Enable --copy-devices parameter #Enable --copy-devices parameter
patch -p1 -i patches/copy-devices.diff patch -p1 -i patches/copy-devices.diff
%patch0 -p1 -b .man %patch0 -p1 -b .verify-hostname
%patch1 -p1 -b .iconv %patch1 -p1 -b .runtests
%patch2 -p1 -b .covscan %patch2 -p1 -b .lchmod
%patch3 -p1 -b .symlinks %patch3 -p1 -b .append-mode
%patch4 -p1 -b .vvv %patch4 -p1 -b .xattr
%patch5 -p1 -b .missing %patch5 -p1 -b .segfault
%patch6 -p1 -b .append %patch6 -p1 -b .atimes
%patch7 -p1 -b .skip-compress %patch7 -p1 -b .cve-2018-25032
%patch8 -p1 -b .xattr %patch8 -p1 -b .cve-2022-37434
%patch9 -p1 -b .cve-2018-25032 %patch9 -p1 -b .cve-2022-29154
%patch10 -p1 -b .spars-block %patch10 -p1 -b .filtering-rules
%patch11 -p1 -b .cve-2022-29154 %patch11 -p1 -b .delay-updates
%patch12 -p1 -b .cve-2022-37434
%patch13 -p1 -b .filtering-rules
%patch14 -p1 -b .xattr-filter
%patch15 -p1 -b .cve-2024-12085
%patch16 -p1 -b .cve-2024-12087
%patch17 -p1 -b .cve-2024-12088
%patch18 -p1 -b .cve-2024-12747
%patch19 -p1 -b .cve-2025-4638
%patch20 -p1 -b .trust-sender
%patch21 -p1 -b .cve-2025-10158
%patch22 -p1 -b .cve-2026-41035
%patch23 -p1 -b .cve-2026-29518
%patch24 -p1 -b .cve-2026-29518-regressions
%patch25 -p1 -b .cve-2026-43618
%build %build
%configure %configure --disable-xxhash
# --with-included-zlib=no temporary disabled because of #1043965 # --with-included-zlib=no temporary disabled because of #1043965
make %{?_smp_mflags} %{make_build}
%check
make check
chmod -x support/*
%install %install
%makeinstall INSTALLCMD='install -p' INSTALLMAN='install -p' %{make_install} INSTALLCMD='install -p' INSTALLMAN='install -p'
install -D -m644 %{SOURCE3} $RPM_BUILD_ROOT/%{_unitdir}/rsyncd.service install -D -m644 %{SOURCE3} $RPM_BUILD_ROOT/%{_unitdir}/rsyncd.service
install -D -m644 %{SOURCE2} $RPM_BUILD_ROOT/%{_unitdir}/rsyncd.socket install -D -m644 %{SOURCE2} $RPM_BUILD_ROOT/%{_unitdir}/rsyncd.socket
@ -148,21 +108,17 @@ install -D -m644 %{SOURCE4} $RPM_BUILD_ROOT/%{_sysconfdir}/rsyncd.conf
install -D -m644 %{SOURCE5} $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/rsyncd install -D -m644 %{SOURCE5} $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/rsyncd
install -D -m644 %{SOURCE6} $RPM_BUILD_ROOT/%{_unitdir}/rsyncd@.service install -D -m644 %{SOURCE6} $RPM_BUILD_ROOT/%{_unitdir}/rsyncd@.service
%check
make check
#scripts in support/* are needed to run upstream tests but after install these should not be executable
chmod -x support/*
%files %files
%{!?_licensedir:%global license %%doc}
%license COPYING %license COPYING
%doc NEWS OLDNEWS README support/ tech_report.tex %doc support/ tech_report.tex
%{_bindir}/%{name} %{_bindir}/%{name}
%{_bindir}/%{name}-ssl
%{_mandir}/man1/%{name}.1* %{_mandir}/man1/%{name}.1*
%{_mandir}/man1/%{name}-ssl.1*
%files daemon
%{_mandir}/man5/rsyncd.conf.5* %{_mandir}/man5/rsyncd.conf.5*
%config(noreplace) %{_sysconfdir}/rsyncd.conf %config(noreplace) %{_sysconfdir}/rsyncd.conf
%files daemon
%config(noreplace) %{_sysconfdir}/sysconfig/rsyncd %config(noreplace) %{_sysconfdir}/sysconfig/rsyncd
%{_unitdir}/rsyncd.socket %{_unitdir}/rsyncd.socket
%{_unitdir}/rsyncd.service %{_unitdir}/rsyncd.service
@ -178,86 +134,105 @@ chmod -x support/*
%systemd_postun_with_restart rsyncd.service %systemd_postun_with_restart rsyncd.service
%changelog %changelog
* Mon Jun 15 2026 Michal Ruprich <mruprich@redhat.com> - 3.1.3-27 * Thu Oct 19 2023 Alex Iribarren <Alex.Iribarren@cern.ch> - 3.2.3-20
- Integer overflow in compressed-token decoding (CVE-2026-43618) - Resolves: RHEL-14228 - rsync regression with --delay-updates
- Resolves: RHEL-174951
* Thu May 28 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 3.1.3-26 * Wed Nov 02 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-19
- Resolves: RHEL-174950 - CVE-2026-29518 - TOCTOU symlink race in - Resolves: #2139349 - rsync error: protocol incompatibility when using rsync-3.2.3-18.el9
non-chrooted daemon modules
* Tue May 05 2026 Michal Ruprich <mruprich@redhat.com> - 3.1.3-25 * Thu Aug 25 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-18
- Resolves: RHEL-169141 - CVE-2026-41035 - Use-after-free vulnerability in extended attribute handling - Resolves: #2111177 - remote arbitrary files write inside the directories of connecting peers
* Wed Mar 11 2026 Michal Ruprich <mruprich@redhat.com> - 3.1.3-24 * Thu Aug 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-17
- Resolves: RHEL-152887 - CVE-2025-10158 - Out of bounds array access via negative index - Resolves: #2116669 - zlib: a heap-based buffer over-read or buffer overflow in inflate in inflate.c via a large gzip header extra field
* Wed May 28 2025 Michal Ruprich <mruprich@redhat.com> - 3.1.3-23 * Wed May 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-16
- Resolves: RHEL-52004 - Slowness in rsync due to extra validation steps - Related: #2081296 - Adding ci.fmf for separation of testing results
* Mon May 26 2025 Michal Ruprich <mruprich@redhat.com> - 3.1.3-22 * Wed May 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-15
- Resolves: RHEL-91519 - Improper Pointer Arithmetic in pcl - Related: #2081296 - Disabling STI
* Tue Feb 04 2025 Michal Ruprich <mruprich@redhat.com> - 3.1.3-21 * Wed May 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-14
- Resolves: RHEL-70207 - Path traversal vulnerability in rsync - Resolves: #2071514 - A flaw found in zlib when compressing (not decompressing) certain inputs
* Mon Feb 03 2025 Michal Ruprich <mruprich@redhat.com> - 3.1.3-20 * Wed May 11 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-13
- Resolves: RHEL-70207 - Path traversal vulnerability in rsync - Resolves: #2079639 - rsync --atimes doesn't work
- Resolves: RHEL-70209 - --safe-links option bypass leads to path traversal
- Resolves: RHEL-72502 - Race Condition in rsync Handling Symbolic Links
- Resolves: RHEL-70157 - Info Leak via Uninitialized Stack Contents
* Wed Nov 02 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-19.1 * Tue May 03 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-12
- Resolves: #2139118 - rsync-daemon fail on 3.1.3 - Resolves: #2081296 - Enable fmf tests in centos stream
* Thu Aug 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-19 * Tue Apr 26 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-11
- Resolves: #2116668 - zlib: a heap-based buffer over-read or buffer overflow in inflate in inflate.c via a large gzip header extra field - Resolves: #2053198 - rsync segmentation fault
* Mon Aug 15 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-18 * Fri Apr 22 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-10
- Resolves: #2111175 - remote arbitrary files write inside the directories of connecting peers - Resolves: #2077431 - Read-only files that have changed xattrs fail to allow xattr changes
* Mon Aug 08 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-17 * Tue Aug 10 2021 Mohan Boddu <mboddu@redhat.com> - 3.2.3-9
- Related: #2043753 - New option should not be sent to the server every time - Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
Related: rhbz#1991688
* Thu Jul 28 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-16 * Wed Jun 16 2021 Mohan Boddu <mboddu@redhat.com> - 3.2.3-8
- Resolves: #2043753 - [RFE] Improve defaults for sparse file buffering - Rebuilt for RHEL 9 BETA for openssl 3.0
Related: rhbz#1971065
* Tue Apr 12 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-15 * Mon May 31 2021 Michal Ruprich <mruprich@redhat.com> - 3.2.3-7
- Resolves: #2071513 - A flaw in zlib-1.2.11 when compressing (not decompressing!) certain inputs - Resolves: #1955008 - rsync segfaults in --append mode when file on sender is large (> 2GB) and gets truncated
* Mon Oct 11 2021 Michal Ruprich <mruprich@redhat.com> - 3.1.3-14 * Fri Apr 16 2021 Mohan Boddu <mboddu@redhat.com> - 3.2.3-6
- Related: #1907443 - Adding fmf plans to run tests with tmt - Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937
* Mon Sep 27 2021 Tomas Korbar <tkorbar@redhat.com> - 3.1.3-13 * Wed Jan 27 2021 Fedora Release Engineering <releng@fedoraproject.org> - 3.2.3-5
- Resolves: #1907443 - Read-only files that have changed xattrs fail to allow xattr changes - Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
* Fri Dec 18 2020 Michal Ruprich <mruprich@redhat.com> - 3.1.3-12 * Tue Dec 08 2020 Michal Ruprich <mruprich@redhat.com> - 3.2.3-4
- Resolves: #1816528 - Defaults for --skip-compress are not working, everything is being compressed - Resolves: #1894485 - rsync is unable to set permissions when chrooted
- Getting rid of deprecated makeinstall macro
* Thu Nov 05 2020 Tomas Korbar <tkorbar@redhat.com> - 3.1.3-11 * Fri Nov 20 2020 Michal Ruprich <mruprich@redhat.com> - 3.2.3-3
- Resolves: #1855981 - rsync segfaults in --append mode - Disabling LTO as a temporary measure for rhbz#1898912
* Thu Nov 05 2020 Tomas Korbar <tkorbar@redhat.com> - 3.1.3-10 * Thu Nov 19 2020 Michal Ruprich <mruprich@redhat.com> - 3.2.3-2
- Resolves: #1727093 - rsync: "ABORTING due to invalid path from sender" - Use make macros
- https://fedoraproject.org/wiki/Changes/UseMakeBuildInstallMacro
* Mon Aug 24 2020 Michal Ruprich <mruprich@redhat.com> - 3.1.3-9 * Mon Aug 31 2020 Michal Ruprich <mruprich@redhat.com> - 3.2.3-1
- Resolves: #1667436 - rsyncd.service fails to start at boot if address is configured - New version 3.2.3
- Removed upstream patches acls.diff and xattrs.diff
* Wed Jun 10 2020 Michal Ruprich <mruprich@redhat.com> - 3.1.3-8 * Sat Aug 01 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.2.2-3
- Resolves: #1775561 - rsync 3.1.2 hangs when run with -vvv to sync a large repository - Second attempt - Rebuilt for
https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Tue Oct 29 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-7 * Wed Jul 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.2.2-2
- Resolves: #1693162 - remove-source-files fails with symlinks - Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Tue Apr 16 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-6 * Tue Jul 21 2020 Michal Ruprich <michalruprich@gmail.com> - 3.2.2-1
- Resolves: #1602683 - Please review important issues found by covscan - New version 3.2.2
* Tue Apr 16 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-5 * Thu Jan 30 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.1.3-11
- Resolves: #1656761 - [FJ8.0 Bug]: [REG] The rsync command is terminated with SIGSEGV - Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
* Wed Oct 03 2018 Michal Ruprich <mruprich@redhat.com> - 3.1.3-4 * Thu Oct 10 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-10
- Resolves: #1635631 - Remove --noatime option from rsync - Enabling upstream test suite during build rhbz#1533846
Cleaning spec file
* Fri Jul 26 2019 Fedora Release Engineering <releng@fedoraproject.org> - 3.1.3-9
- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild
* Mon Apr 15 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-8
- Resolves: #1452187 - move man page rsyncd.conf(5) from rsync-daemon to rsync package
- Moving the config file as well
* Tue Mar 19 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-7
- Resolves: #1683737 - [abrt] rsync: utf8_internal_loop(): rsync killed by SIGSEGV
* Sat Feb 02 2019 Fedora Release Engineering <releng@fedoraproject.org> - 3.1.3-6
- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
* Wed Jan 02 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-5
- Fix for rhbz#1586346 - rsyncd.service fails to start at boot if address is configured
* Sat Jul 14 2018 Fedora Release Engineering <releng@fedoraproject.org> - 3.1.3-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Fri Feb 09 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 3.1.3-3 * Fri Feb 09 2018 Igor Gnatenko <ignatenkobrain@fedoraproject.org> - 3.1.3-3
- Escape macros in %%changelog - Escape macros in %%changelog