Compare commits

...

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

33 changed files with 430 additions and 7164 deletions

4
.gitignore vendored
View File

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

View File

@ -1,2 +1,2 @@
82e7829c0b3cefbd33c233005341e2073c425629 SOURCES/rsync-3.1.3.tar.gz
74c16510a18ef43d797f9ceba6150f0862568cc0 SOURCES/rsync-patches-3.1.3.tar.gz
26baded8871b9e2406add210cdbfa744c94642d2 SOURCES/rsync-3.2.5.tar.gz
db897ede46e509347a84ca4e0cb02c452eeabdbe SOURCES/rsync-patches-3.2.5.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,343 +0,0 @@
From 5c44459c3b28a9bd3283aaceab7c615f8020c531 Mon Sep 17 00:00:00 2001
From: Mark Adler <madler@alumni.caltech.edu>
Date: Tue, 17 Apr 2018 22:09:22 -0700
Subject: [PATCH] Fix a bug that can crash deflate on some input when using
Z_FIXED.
This bug was reported by Danilo Ramos of Eideticom, Inc. It has
lain in wait 13 years before being found! The bug was introduced
in zlib 1.2.2.2, with the addition of the Z_FIXED option. That
option forces the use of fixed Huffman codes. For rare inputs with
a large number of distant matches, the pending buffer into which
the compressed data is written can overwrite the distance symbol
table which it overlays. That results in corrupted output due to
invalid distances, and can result in out-of-bound accesses,
crashing the application.
The fix here combines the distance buffer and literal/length
buffers into a single symbol buffer. Now three bytes of pending
buffer space are opened up for each literal or length/distance
pair consumed, instead of the previous two bytes. This assures
that the pending buffer cannot overwrite the symbol table, since
the maximum fixed code compressed length/distance is 31 bits, and
since there are four bytes of pending space for every three bytes
of symbol space.
---
deflate.c | 74 ++++++++++++++++++++++++++++++++++++++++---------------
deflate.h | 25 +++++++++----------
trees.c | 50 +++++++++++--------------------------
3 files changed, 79 insertions(+), 70 deletions(-)
diff --git a/zlib/deflate.c b/zlib/deflate.c
index 425babc00..19cba873a 100644
--- a/zlib/deflate.c
+++ b/zlib/deflate.c
@@ -255,11 +255,6 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
int wrap = 1;
static const char my_version[] = ZLIB_VERSION;
- ushf *overlay;
- /* We overlay pending_buf and d_buf+l_buf. This works since the average
- * output size for (length,distance) codes is <= 24 bits.
- */
-
if (version == Z_NULL || version[0] != my_version[0] ||
stream_size != sizeof(z_stream)) {
return Z_VERSION_ERROR;
@@ -329,9 +324,47 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
- overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
- s->pending_buf = (uchf *) overlay;
- s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L);
+ /* We overlay pending_buf and sym_buf. This works since the average size
+ * for length/distance pairs over any compressed block is assured to be 31
+ * bits or less.
+ *
+ * Analysis: The longest fixed codes are a length code of 8 bits plus 5
+ * extra bits, for lengths 131 to 257. The longest fixed distance codes are
+ * 5 bits plus 13 extra bits, for distances 16385 to 32768. The longest
+ * possible fixed-codes length/distance pair is then 31 bits total.
+ *
+ * sym_buf starts one-fourth of the way into pending_buf. So there are
+ * three bytes in sym_buf for every four bytes in pending_buf. Each symbol
+ * in sym_buf is three bytes -- two for the distance and one for the
+ * literal/length. As each symbol is consumed, the pointer to the next
+ * sym_buf value to read moves forward three bytes. From that symbol, up to
+ * 31 bits are written to pending_buf. The closest the written pending_buf
+ * bits gets to the next sym_buf symbol to read is just before the last
+ * code is written. At that time, 31*(n-2) bits have been written, just
+ * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at
+ * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1
+ * symbols are written.) The closest the writing gets to what is unread is
+ * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and
+ * can range from 128 to 32768.
+ *
+ * Therefore, at a minimum, there are 142 bits of space between what is
+ * written and what is read in the overlain buffers, so the symbols cannot
+ * be overwritten by the compressed data. That space is actually 139 bits,
+ * due to the three-bit fixed-code block header.
+ *
+ * That covers the case where either Z_FIXED is specified, forcing fixed
+ * codes, or when the use of fixed codes is chosen, because that choice
+ * results in a smaller compressed block than dynamic codes. That latter
+ * condition then assures that the above analysis also covers all dynamic
+ * blocks. A dynamic-code block will only be chosen to be emitted if it has
+ * fewer bits than a fixed-code block would for the same set of symbols.
+ * Therefore its average symbol length is assured to be less than 31. So
+ * the compressed data for a dynamic block also cannot overwrite the
+ * symbols from which it is being constructed.
+ */
+
+ s->pending_buf = (uchf *) ZALLOC(strm, s->lit_bufsize, 4);
+ s->pending_buf_size = (ulg)s->lit_bufsize * 4;
if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL ||
s->pending_buf == Z_NULL) {
@@ -340,8 +373,12 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
deflateEnd (strm);
return Z_MEM_ERROR;
}
- s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
- s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+ s->sym_buf = s->pending_buf + s->lit_bufsize;
+ s->sym_end = (s->lit_bufsize - 1) * 3;
+ /* We avoid equality with lit_bufsize*3 because of wraparound at 64K
+ * on 16 bit machines and because stored blocks are restricted to
+ * 64K-1 bytes.
+ */
s->level = level;
s->strategy = strategy;
@@ -552,7 +589,7 @@ int ZEXPORT deflatePrime (strm, bits, value)
if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
s = strm->state;
- if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3))
+ if (s->sym_buf < s->pending_out + ((Buf_size + 7) >> 3))
return Z_BUF_ERROR;
do {
put = Buf_size - s->bi_valid;
@@ -1113,7 +1150,6 @@ int ZEXPORT deflateCopy (dest, source)
#else
deflate_state *ds;
deflate_state *ss;
- ushf *overlay;
if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) {
@@ -1133,8 +1169,7 @@ int ZEXPORT deflateCopy (dest, source)
ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte));
ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos));
ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos));
- overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2);
- ds->pending_buf = (uchf *) overlay;
+ ds->pending_buf = (uchf *) ZALLOC(dest, ds->lit_bufsize, 4);
if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL ||
ds->pending_buf == Z_NULL) {
@@ -1148,8 +1183,7 @@ int ZEXPORT deflateCopy (dest, source)
zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size);
ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf);
- ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush);
- ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize;
+ ds->sym_buf = ds->pending_buf + ds->lit_bufsize;
ds->l_desc.dyn_tree = ds->dyn_ltree;
ds->d_desc.dyn_tree = ds->dyn_dtree;
@@ -1771,7 +1771,7 @@ local block_state deflate_fast(s, flush)
FLUSH_BLOCK(s, 1);
return finish_done;
}
- if (s->last_lit)
+ if (s->sym_next)
FLUSH_BLOCK(s, 0);
return block_done;
}
@@ -1912,7 +1912,7 @@ local block_state deflate_slow(s, flush)
FLUSH_BLOCK(s, 1);
return finish_done;
}
- if (s->last_lit)
+ if (s->sym_next)
FLUSH_BLOCK(s, 0);
return block_done;
}
@@ -1987,7 +1987,7 @@ local block_state deflate_rle(s, flush)
FLUSH_BLOCK(s, 1);
return finish_done;
}
- if (s->last_lit)
+ if (s->sym_next)
FLUSH_BLOCK(s, 0);
return block_done;
}
@@ -2026,7 +2026,7 @@ local block_state deflate_huff(s, flush)
FLUSH_BLOCK(s, 1);
return finish_done;
}
- if (s->last_lit)
+ if (s->sym_next)
FLUSH_BLOCK(s, 0);
return block_done;
}
diff --git a/zlib/deflate.h b/zlib/deflate.h
index 23ecdd312..d4cf1a98b 100644
--- a/zlib/deflate.h
+++ b/zlib/deflate.h
@@ -217,7 +217,7 @@ typedef struct internal_state {
/* Depth of each subtree used as tie breaker for trees of equal frequency
*/
- uchf *l_buf; /* buffer for literals or lengths */
+ uchf *sym_buf; /* buffer for distances and literals/lengths */
uInt lit_bufsize;
/* Size of match buffer for literals/lengths. There are 4 reasons for
@@ -239,13 +239,8 @@ typedef struct internal_state {
* - I can't count above 4
*/
- uInt last_lit; /* running index in l_buf */
-
- ushf *d_buf;
- /* Buffer for distances. To simplify the code, d_buf and l_buf have
- * the same number of elements. To use different lengths, an extra flag
- * array would be necessary.
- */
+ uInt sym_next; /* running index in sym_buf */
+ uInt sym_end; /* symbol table full when sym_next reaches this */
ulg opt_len; /* bit length of current block with optimal trees */
ulg static_len; /* bit length of current block with static trees */
@@ -317,20 +317,22 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
# define _tr_tally_lit(s, c, flush) \
{ uch cc = (c); \
- s->d_buf[s->last_lit] = 0; \
- s->l_buf[s->last_lit++] = cc; \
+ s->sym_buf[s->sym_next++] = 0; \
+ s->sym_buf[s->sym_next++] = 0; \
+ s->sym_buf[s->sym_next++] = cc; \
s->dyn_ltree[cc].Freq++; \
- flush = (s->last_lit == s->lit_bufsize-1); \
+ flush = (s->sym_next == s->sym_end); \
}
# define _tr_tally_dist(s, distance, length, flush) \
{ uch len = (length); \
ush dist = (distance); \
- s->d_buf[s->last_lit] = dist; \
- s->l_buf[s->last_lit++] = len; \
+ s->sym_buf[s->sym_next++] = dist; \
+ s->sym_buf[s->sym_next++] = dist >> 8; \
+ s->sym_buf[s->sym_next++] = len; \
dist--; \
s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \
s->dyn_dtree[d_code(dist)].Freq++; \
- flush = (s->last_lit == s->lit_bufsize-1); \
+ flush = (s->sym_next == s->sym_end); \
}
#else
# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c)
diff --git a/zlib/trees.c b/zlib/trees.c
index 4f4a65011..decaeb7c3 100644
--- a/zlib/trees.c
+++ b/zlib/trees.c
@@ -416,7 +416,7 @@ local void init_block(s)
s->dyn_ltree[END_BLOCK].Freq = 1;
s->opt_len = s->static_len = 0L;
- s->last_lit = s->matches = 0;
+ s->sym_next = s->matches = 0;
}
#define SMALLEST 1
@@ -948,7 +948,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
- s->last_lit));
+ s->sym_next / 3));
if (static_lenb <= opt_lenb) opt_lenb = static_lenb;
@@ -1017,8 +1017,9 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc)
unsigned dist; /* distance of matched string */
unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */
{
- s->d_buf[s->last_lit] = (ush)dist;
- s->l_buf[s->last_lit++] = (uch)lc;
+ s->sym_buf[s->sym_next++] = dist;
+ s->sym_buf[s->sym_next++] = dist >> 8;
+ s->sym_buf[s->sym_next++] = lc;
if (dist == 0) {
/* lc is the unmatched char */
s->dyn_ltree[lc].Freq++;
@@ -1033,30 +1034,7 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc)
s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++;
s->dyn_dtree[d_code(dist)].Freq++;
}
-
-#ifdef TRUNCATE_BLOCK
- /* Try to guess if it is profitable to stop the current block here */
- if ((s->last_lit & 0x1fff) == 0 && s->level > 2) {
- /* Compute an upper bound for the compressed length */
- ulg out_length = (ulg)s->last_lit*8L;
- ulg in_length = (ulg)((long)s->strstart - s->block_start);
- int dcode;
- for (dcode = 0; dcode < D_CODES; dcode++) {
- out_length += (ulg)s->dyn_dtree[dcode].Freq *
- (5L+extra_dbits[dcode]);
- }
- out_length >>= 3;
- Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
- s->last_lit, in_length, out_length,
- 100L - out_length*100L/in_length));
- if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1;
- }
-#endif
- return (s->last_lit == s->lit_bufsize-1);
- /* We avoid equality with lit_bufsize because of wraparound at 64K
- * on 16 bit machines and because stored blocks are restricted to
- * 64K-1 bytes.
- */
+ return (s->sym_next == s->sym_end);
}
/* ===========================================================================
@@ -1069,13 +1047,14 @@ local void compress_block(s, ltree, dtree)
{
unsigned dist; /* distance of matched string */
int lc; /* match length or unmatched char (if dist == 0) */
- unsigned lx = 0; /* running index in l_buf */
+ unsigned sx = 0; /* running index in sym_buf */
unsigned code; /* the code to send */
int extra; /* number of extra bits to send */
- if (s->last_lit != 0) do {
- dist = s->d_buf[lx];
- lc = s->l_buf[lx++];
+ if (s->sym_next != 0) do {
+ dist = s->sym_buf[sx++] & 0xff;
+ dist += (unsigned)(s->sym_buf[sx++] & 0xff) << 8;
+ lc = s->sym_buf[sx++];
if (dist == 0) {
send_code(s, lc, ltree); /* send a literal byte */
Tracecv(isgraph(lc), (stderr," '%c' ", lc));
@@ -1100,11 +1079,10 @@ local void compress_block(s, ltree, dtree)
}
} /* literal or match pair ? */
- /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
- Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
- "pendingBuf overflow");
+ /* Check that the overlay between pending_buf and sym_buf is ok: */
+ Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow");
- } while (lx < s->last_lit);
+ } while (sx < s->sym_next);
send_code(s, END_BLOCK, ltree);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +0,0 @@
diff --git a/zlib/inflate.c b/zlib/inflate.c
index e43abd9..bd33c19 100644
--- a/zlib/inflate.c
+++ b/zlib/inflate.c
@@ -740,8 +740,9 @@ int flush;
if (copy > have) copy = have;
if (copy) {
if (state->head != Z_NULL &&
- state->head->extra != Z_NULL) {
- len = state->head->extra_len - state->length;
+ state->head->extra != Z_NULL &&
+ (len = state->head->extra_len - state->length) <
+ state->head->extra_max) {
zmemcpy(state->head->extra + len, next,
len + copy > state->head->extra_max ?
state->head->extra_max - len : copy);

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

@ -1,38 +0,0 @@
diff --git a/xattrs.c b/xattrs.c
index 508649c0..3c549192 100644
--- a/xattrs.c
+++ b/xattrs.c
@@ -1055,7 +1055,7 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
{
rsync_xa_list *glst = rsync_xal_l.items;
item_list *lst;
- int ndx;
+ int ndx, added_write_perm = 0;
if (dry_run)
return 1; /* FIXME: --dry-run needs to compute this value */
@@ -1084,10 +1084,23 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
}
#endif
+ /* If the target file lacks write permission, we try to add it
+ * temporarily so we can change the extended attributes. */
+ if (!am_root
+#ifdef SUPPORT_LINKS
+ && !S_ISLNK(sxp->st.st_mode)
+#endif
+ && access(fname, W_OK) < 0
+ && do_chmod(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR) == 0)
+ added_write_perm = 1;
+
ndx = F_XATTR(file);
glst += ndx;
lst = &glst->xa_items;
- return rsync_xal_set(fname, lst, fnamecmp, sxp);
+ int return_value = rsync_xal_set(fname, lst, fnamecmp, sxp);
+ if (added_write_perm) /* remove the temporary write permission */
+ do_chmod(fname, sxp->st.st_mode);
+ return return_value;
}
#ifdef SUPPORT_ACLS

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

@ -40,9 +40,9 @@ index 13c4253..232249f 100644
+ 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)
@@ -407,6 +413,7 @@ void add_implied_include(const char *arg, int skip_daemon_module)
arg++;
arg++;
arg_len = strlen(arg);
if (arg_len) {
+ char *new_pat;
@ -287,21 +287,6 @@ index 13c4253..232249f 100644
}
/* This is only called by the client. */
diff --git a/options.c b/options.c
index afc33ce..4d0a1a6 100644
--- a/options.c
+++ b/options.c
@@ -2426,7 +2426,9 @@ 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 || !strchr(arg, '/'))) {
+ if (!old_style_args && *arg == '~'
+ && ((relative_paths && !strstr(arg, "/./"))
+ || !strchr(arg, '/'))) {
extras++;
escape_leading_tilde = 1;
}
diff --git a/flist.c b/flist.c
index 630d685..8c2397b 100644
--- a/flist.c

View File

@ -20,13 +20,13 @@ index 464d556..087f9da 100644
+ }
+
flist = flist_new(0, "recv_file_list");
flist_expand(flist, FLIST_START_LARGE);
if (inc_recurse) {
diff --git a/rsync.h b/rsync.h
index b357dad..bc9abac 100644
index f8bd024..fbaf312 100644
--- a/rsync.h
+++ b/rsync.h
@@ -83,6 +83,7 @@
@@ -92,6 +92,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 */

View File

@ -11,10 +11,10 @@ index 75e7201..d2e318e 100644
test_unsafe dir/../.. from unsafe
test_unsafe dir/..//.. from unsafe
diff --git a/util.c b/util.c
diff --git a/util1.c b/util1.c
index da50ff1..f260d39 100644
--- a/util.c
+++ b/util.c
--- a/util1.c
+++ b/util1.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

View File

@ -11,16 +11,38 @@ index cb21882..66e8089 100644
if (fd == -1)
return;
diff --git a/flist.c b/flist.c
index 087f9da..1783253 100644
--- a/flist.c
+++ b/flist.c
@@ -1390,7 +1390,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
if (copy_devices && am_sender && IS_DEVICE(st.st_mode)) {
if (st.st_size == 0) {
- int fd = do_open(fname, O_RDONLY, 0);
+ int fd = do_open_checklinks(fname);
if (fd >= 0) {
st.st_size = get_device_size(fd, fname);
close(fd);
diff --git a/generator.c b/generator.c
index 110db28..3f13bb9 100644
--- a/generator.c
+++ b/generator.c
@@ -1798,7 +1798,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
if (write_devices && IS_DEVICE(sx.st.st_mode) && sx.st.st_size == 0) {
/* This early open into fd skips the regular open below. */
- if ((fd = do_open(fnamecmp, O_RDONLY, 0)) >= 0)
+ if ((fd = do_open_nofollow(fnamecmp, O_RDONLY)) >= 0)
real_sx.st.st_size = sx.st.st_size = get_device_size(fd, fnamecmp);
}
@@ -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) {
- if (fd < 0 && (fd = do_open(fnamecmp, O_RDONLY, 0)) < 0) {
+ if (fd < 0 && (fd = do_open_checklinks(fnamecmp)) < 0) {
rsyserr(FERROR, errno, "failed to open %s, continuing",
full_fname(fnamecmp));
pretend_missing:
@ -29,9 +51,9 @@ 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;
fnamecmp_type = FNAMECMP_FNAME;
- fd1 = do_open(fnamecmp, O_RDONLY, 0);
+ fd1 = do_open_nofollow(fnamecmp, O_RDONLY);
}
@ -49,21 +71,21 @@ index 2bbff2f..a4d46c3 100644
+ fd = do_open_checklinks(fname);
if (fd == -1) {
if (errno == ENOENT) {
enum logcode c = am_daemon
enum logcode c = am_daemon && protocol_version < 28 ? FERROR : FWARNING;
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 open_noatime;
+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)
@@ -714,3 +714,21 @@ int do_open_nofollow(const char *pathname, int flags)
return fd;
}
@ -90,9 +112,9 @@ 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 am_sender = 1;
int read_only = 0;
int list_only = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
+
@ -104,9 +126,9 @@ 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 link_times = 0;
int link_owner = 0;
int nsec_times = 0;
+int safe_symlinks = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
@ -118,18 +140,18 @@ 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 am_sender = 1;
int read_only = 1;
int list_only = 0;
+int copy_links = 0;
+int copy_unsafe_links = 0;
int
main(int argc, char **argv)
diff --git a/util.c b/util.c
diff --git a/util1.c b/util1.c
index f260d39..d84bc41 100644
--- a/util.c
+++ b/util.c
--- a/util1.c
+++ b/util1.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;

View File

@ -0,0 +1,171 @@
diff --git a/NEWS.md b/NEWS.md
index e32600c..e1f7d41 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -92,7 +92,7 @@
export LC_ALL=C.UTF-8
```
- or if iconv translations are needed:
+ or maybe:
```shell
if [ "${LC_ALL:-}" ]; then
@@ -145,11 +145,6 @@
- Avoid a weird failure if you run a local copy with a (useless)
[`--rsh`](rsync.1#opt) option that contains a `V` in the command.
- - Fixed a long-standing compression bug where the compression level of the
- first file transferred affected the level for all future files. Also, the
- per-file compression skipping has apparently never worked, so it is now
- documented as being ineffective.
-
- Fixed a truncate error when a `--write-devices` copy wrote a file onto a
device that was shorter than the device.
diff --git a/rsync.1.md b/rsync.1.md
index f29495f..d06f231 100644
--- a/rsync.1.md
+++ b/rsync.1.md
@@ -2658,6 +2658,9 @@ expand it.
ignore this weirdness unless the rsync server complains and tells you to
specify `-zz`.
+ See also the `--skip-compress` option for the default list of file suffixes
+ that will be transferred with no (or minimal) compression.
+
0. `--compress-choice=STR`, `--zc=STR`
This option can be used to override the automatic negotiation of the
@@ -2703,8 +2706,8 @@ expand it.
> rsync -aiv --zc=zstd --zl=22 host:src/ dest/
For zlib & zlibx compression the valid values are from 1 to 9 with 6 being
- the default. Specifying `--zl=0` turns compression off, and specifying
- `--zl=-1` chooses the default level of 6.
+ the default. Specifying 0 turns compression off, and specifying -1 chooses
+ the default of 6.
For zstd compression the valid values are from -131072 to 22 with 3 being
the default. Specifying 0 chooses the default of 3.
@@ -2723,15 +2726,14 @@ expand it.
0. `--skip-compress=LIST`
- **NOTE:** no compression method currently supports per-file compression
- changes, so this option has no effect.
-
Override the list of file suffixes that will be compressed as little as
possible. Rsync sets the compression level on a per-file basis based on
- the file's suffix. If the compression algorithm has an "off" level, then
- no compression occurs for those files. Other algorithms that support
- changing the streaming level on-the-fly will have the level minimized to
- reduces the CPU usage as much as possible for a matching file.
+ the file's suffix. If the compression algorithm has an "off" level (such
+ as zlib/zlibx) then no compression occurs for those files. Other
+ algorithms that support changing the streaming level on-the-fly will have
+ the level minimized to reduces the CPU usage as much as possible for a
+ matching file. At this time, only zlib & zlibx compression support this
+ changing of levels on a per-file basis.
The **LIST** should be one or more file suffixes (without the dot) separated
by slashes (`/`). You may specify an empty string to indicate that no files
diff --git a/rsyncd.conf.5.md b/rsyncd.conf.5.md
index 8bcbec0..e28c27a 100644
--- a/rsyncd.conf.5.md
+++ b/rsyncd.conf.5.md
@@ -924,9 +924,8 @@ the values of parameters. See the GLOBAL PARAMETERS section for more details.
> refuse options = * !a !delete* delete-after
- A note on refusing "compress": it may be better to set the "[dont compress](#)"
- daemon parameter to "`*`" and ensure that `RSYNC_COMPRESS_LIST=zlib` is set
- in the environment of the daemon in order to disable compression silently
+ A note on refusing "compress": it may be better to set the "dont compress"
+ daemon parameter to "`*`" because that disables compression silently
instead of returning an error that forces the client to remove the `-z`
option.
@@ -958,10 +957,6 @@ the values of parameters. See the GLOBAL PARAMETERS section for more details.
0. `dont compress`
- **NOTE:** This parameter currently has no effect except in one instance: if
- it is set to "`*`" then it minimizes or disables compression for all files
- (for those that don't want to refuse the `--compress` option completely).
-
This parameter allows you to select filenames based on wildcard patterns
that should not be compressed when pulling files from the daemon (no
analogous parameter exists to govern the pushing of files to a daemon).
@@ -972,14 +967,14 @@ the values of parameters. See the GLOBAL PARAMETERS section for more details.
The "dont compress" parameter takes a space-separated list of
case-insensitive wildcard patterns. Any source filename matching one of the
patterns will be compressed as little as possible during the transfer. If
- the compression algorithm has an "off" level, then no compression occurs
- for those files. If an algorithms has the ability to change the level in
- mid-stream, it will be minimized to reduce the CPU usage as much as
- possible.
+ the compression algorithm has an "off" level (such as zlib/zlibx) then no
+ compression occurs for those files. Other algorithms have the level
+ minimized to reduces the CPU usage as much as possible.
See the `--skip-compress` parameter in the **rsync**(1) manpage for the
- list of file suffixes that are skipped by default if this parameter is not
- set.
+ list of file suffixes that are not compressed by default. Specifying a
+ value for the "dont compress" parameter changes the default when the daemon
+ is the sender.
0. `early exec`, `pre-xfer exec`, `post-xfer exec`
diff --git a/token.c b/token.c
index c108b3a..f5a41c9 100644
--- a/token.c
+++ b/token.c
@@ -39,6 +39,7 @@ extern char *skip_compress;
#define Z_INSERT_ONLY Z_SYNC_FLUSH
#endif
+static int compression_level; /* The compression level for the current file. */
static int skip_compression_level; /* The least possible compressing for handling skip-compress files. */
static int per_file_default_level; /* The default level that each new file gets prior to checking its suffix. */
@@ -223,11 +224,9 @@ static void init_set_compression(void)
/* determine the compression level based on a wildcard filename list */
void set_compression(const char *fname)
{
-#if 0 /* No compression algorithms currently allow mid-stream changing of the level. */
const struct suffix_tree *node;
const char *s;
char ltr;
-#endif
if (!do_compression)
return;
@@ -235,7 +234,6 @@ void set_compression(const char *fname)
if (!match_list)
init_set_compression();
-#if 0
compression_level = per_file_default_level;
if (!*match_list && !suftree)
@@ -272,9 +270,6 @@ void set_compression(const char *fname)
if (!(node = node->child))
return;
}
-#else
- (void)fname;
-#endif
}
/* non-compressing recv token */
@@ -366,7 +361,7 @@ send_deflated_token(int f, int32 token, struct map_struct *buf, OFF_T offset, in
tx_strm.next_in = NULL;
tx_strm.zalloc = NULL;
tx_strm.zfree = NULL;
- if (deflateInit2(&tx_strm, per_file_default_level,
+ if (deflateInit2(&tx_strm, compression_level,
Z_DEFLATED, -15, 8,
Z_DEFAULT_STRATEGY) != Z_OK) {
rprintf(FERROR, "compression init failed\n");

View File

@ -0,0 +1,23 @@
diff --git a/Makefile.in b/Makefile.in
index 3cde955..06232f1 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -106,6 +106,9 @@ rsync$(EXEEXT): $(OBJS)
rrsync: support/rrsync
cp -p $(srcdir)/support/rrsync rrsync
+rrsync.1.md: support/rrsync.1.md
+ cp -p $(srcdir)/support/rrsync.1.md rrsync.1.md
+
$(OBJS): $(HEADERS)
$(CHECK_OBJS): $(HEADERS)
tls.o xattrs.o: lib/sysxattrs.h
@@ -269,7 +269,7 @@ rsyncd.conf.5: rsyncd.conf.5.md md-convert version.h Makefile
@$(srcdir)/maybe-make-man rsyncd.conf.5.md
rrsync.1: support/rrsync.1.md md-convert Makefile
- @$(srcdir)/maybe-make-man support/rrsync.1.md
+ @$(srcdir)/maybe-make-man rrsync.1.md
.PHONY: clean
clean: cleantests

View File

@ -0,0 +1,15 @@
diff --git a/main.c b/main.c
index 7222a83..630ca03 100644
--- a/main.c
+++ b/main.c
@@ -1743,7 +1743,9 @@ int main(int argc,char *argv[])
our_gid = MY_GID();
am_root = our_uid == ROOT_UID;
- unset_env_var("DISPLAY");
+ // DISPLAY should only be cleared if SSH_ASKPASS is empty
+ if (!getenv("SSH_ASKPASS"))
+ unset_env_var("DISPLAY");
memset(&stats, 0, sizeof(stats));

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
%define isprerelease 0
%define _lto_cflags %{nil}
%if %isprerelease
%define prerelease pre1
@ -8,10 +9,9 @@
Summary: A program for synchronizing files over a network
Name: rsync
Version: 3.1.3
Release: 27%{?dist}
Group: Applications/Internet
URL: http://rsync.samba.org/
Version: 3.2.5
Release: 4%{?dist}
URL: https://rsync.samba.org/
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
@ -21,54 +21,36 @@ Source4: rsyncd.conf
Source5: rsyncd.sysconfig
Source6: rsyncd@.service
BuildRequires: libacl-devel, libattr-devel, autoconf, popt-devel, systemd
#Requires: zlib
BuildRequires: make
BuildRequires: gcc
BuildRequires: gcc-c++
BuildRequires: libacl-devel
BuildRequires: libattr-devel
BuildRequires: autoconf
BuildRequires: popt-devel
BuildRequires: systemd
BuildRequires: lz4-devel
BuildRequires: openssl-devel
BuildRequires: libzstd-devel
#Added virtual provide for zlib due to https://fedoraproject.org/wiki/Bundled_Libraries?rd=Packaging:Bundled_Libraries
Provides: bundled(zlib) = 1.2.8
License: GPLv3+
Patch0: rsync-man.patch
Patch1: rsync-3.0.6-iconv-logging.patch
Patch2: rsync-3.1.3-covscan.patch
Patch3: rsync-3.1.2-remove-symlinks.patch
Patch4: rsync-3.1.2-vvv-hang.patch
Patch5: rsync-3.1.3-ignore-missing.patch
Patch6: rsync-3.1.3-append-check.patch
Patch7: rsync-3.1.3-skip-compress.patch
Patch8: rsync-3.1.3-xattr.patch
Patch9: rsync-3.1.3-cve-2018-25032.patch
Patch10: rsync-3.1.3-sparse-block.patch
Patch11: rsync-3.1.3-cve-2022-29154.patch
Patch12: rsync-3.1.3-cve-2022-37434.patch
Patch13: rsync-3.1.3-filtering-rules.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
#Added due to rhbz#1873975 - default-acls test fail on s390x due to libacl
Patch1: rsync-3.2.2-runtests.patch
#commonmark would be needed to generate manpage, so we simply copy it
Patch2: rsync-3.2.5-rrsync-man.patch
#A couple of fixes for the new filtering code
Patch3: rsync-3.2.3-filtering-rules.patch
Patch4: rsync-3.2.5-cve-2024-12085.patch
Patch5: rsync-3.2.5-cve-2024-12087.patch
Patch6: rsync-3.2.5-cve-2024-12088.patch
Patch7: rsync-3.2.5-cve-2024-12747.patch
# This is here for RHEL9 lifetime to avoid changes in defaults.
# From RHEL10 this will have to be documented as a different
# behaviour for compression.
Patch8: rsync-3.2.5-default-compression.patch
Patch9: rsync-3.2.5-ssh-askpass.patch
%description
Rsync uses a reliable algorithm to bring remote and host files into
@ -88,6 +70,15 @@ Requires: %{name} = %{version}-%{release}
Rsync can be used to offer read only access to anonymous clients. This
package provides the anonymous rsync service.
%package rrsync
Summary: A script to setup restricted rsync users via ssh logins
BuildArch: noarch
Requires: %{name} = %{version}-%{release}
Requires: %{__python3}
%description rrsync
This subpackage provides rrsync script and its manpage. rrsync
may be used to setup a restricted rsync users via ssh logins.
%prep
# TAG: for pre versions use
@ -99,48 +90,28 @@ package provides the anonymous rsync service.
%setup -q -b 1
%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
patch -p1 -i patches/copy-devices.diff
%patch0 -p1 -b .man
%patch1 -p1 -b .iconv
%patch2 -p1 -b .covscan
%patch3 -p1 -b .symlinks
%patch4 -p1 -b .vvv
%patch5 -p1 -b .missing
%patch6 -p1 -b .append
%patch7 -p1 -b .skip-compress
%patch8 -p1 -b .xattr
%patch9 -p1 -b .cve-2018-25032
%patch10 -p1 -b .spars-block
%patch11 -p1 -b .cve-2022-29154
%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
%patch 1 -p1 -b .runtests
%patch 2 -p1 -b .rrsync-man
%patch 3 -p1 -b .filtering-rules
%patch 4 -p1 -b .cve-2024-12085
%patch 5 -p1 -b .cve-2024-12087
%patch 6 -p1 -b .cve-2024-12088
%patch 7 -p1 -b .cve-2024-12747
%patch 8 -p1 -b .default-compression
%patch 9 -p1 -b .ssh-askpass
%build
%configure
%configure --disable-xxhash --with-rrsync
# --with-included-zlib=no temporary disabled because of #1043965
make %{?_smp_mflags}
%{make_build}
%check
make check
chmod -x support/*
%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 %{SOURCE2} $RPM_BUILD_ROOT/%{_unitdir}/rsyncd.socket
@ -148,26 +119,26 @@ install -D -m644 %{SOURCE4} $RPM_BUILD_ROOT/%{_sysconfdir}/rsyncd.conf
install -D -m644 %{SOURCE5} $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/rsyncd
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
%{!?_licensedir:%global license %%doc}
%license COPYING
%doc NEWS OLDNEWS README support/ tech_report.tex
%doc support/ tech_report.tex
%{_bindir}/%{name}
%{_bindir}/%{name}-ssl
%{_mandir}/man1/%{name}.1*
%files daemon
%{_mandir}/man1/%{name}-ssl.1*
%{_mandir}/man5/rsyncd.conf.5*
%config(noreplace) %{_sysconfdir}/rsyncd.conf
%files daemon
%config(noreplace) %{_sysconfdir}/sysconfig/rsyncd
%{_unitdir}/rsyncd.socket
%{_unitdir}/rsyncd.service
%{_unitdir}/rsyncd@.service
%files rrsync
%{_bindir}/r%{name}
%{_mandir}/man1/r%{name}.1*
%post daemon
%systemd_post rsyncd.service
@ -178,86 +149,123 @@ chmod -x support/*
%systemd_postun_with_restart rsyncd.service
%changelog
* Mon Jun 15 2026 Michal Ruprich <mruprich@redhat.com> - 3.1.3-27
- Integer overflow in compressed-token decoding (CVE-2026-43618)
- Resolves: RHEL-174951
* Thu Oct 09 2025 Michal Ruprich <mruprich@redhat.com> - 3.2.5-4
- Resolves: RHEL-104404 - Do not clear DISPLAY unconditionally
* Thu May 28 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 3.1.3-26
- Resolves: RHEL-174950 - CVE-2026-29518 - TOCTOU symlink race in
non-chrooted daemon modules
* Wed Feb 05 2025 Michal Ruprich <mruprich@redhat.com> - 3.2.5-3
- Resolves: RHEL-70265 - Rebase rsync to 3.2.5
* Tue May 05 2026 Michal Ruprich <mruprich@redhat.com> - 3.1.3-25
- Resolves: RHEL-169141 - CVE-2026-41035 - Use-after-free vulnerability in extended attribute handling
* Wed Jan 29 2025 Michal Ruprich <mruprich@redhat.com> - 3.2.5-2
- Resolves: RHEL-70158 - Info Leak via Uninitialized Stack Contents
- Resolves: RHEL-70208 - Path traversal vulnerability in rsync
- Resolves: RHEL-70210 - --safe-links option bypass leads to path traversal
- Resolves: RHEL-71657 - Race Condition in rsync Handling Symbolic Links
* Wed Mar 11 2026 Michal Ruprich <mruprich@redhat.com> - 3.1.3-24
- Resolves: RHEL-152887 - CVE-2025-10158 - Out of bounds array access via negative index
* Mon Dec 09 2024 Michal Ruprich <mruprich@redhat.com> - 3.2.3-21
- Resolves: RHEL-70265 - Rebase rsync to 3.2.5
- Resolves: RHEL-67142 - Wrong progress reported by rsync when using copy-devices
- Resolves: RHEL-29340 - Slowness in rsync due to extra validation steps.
- Resolves: RHEL-18216 - rysnc script /usr/share/doc/rsync/support/rrsync is unsecure
* Wed May 28 2025 Michal Ruprich <mruprich@redhat.com> - 3.1.3-23
- Resolves: RHEL-52004 - Slowness in rsync due to extra validation steps
* Thu Oct 19 2023 Alex Iribarren <Alex.Iribarren@cern.ch> - 3.2.3-20
- Resolves: RHEL-14228 - rsync regression with --delay-updates
* Mon May 26 2025 Michal Ruprich <mruprich@redhat.com> - 3.1.3-22
- Resolves: RHEL-91519 - Improper Pointer Arithmetic in pcl
* Wed Nov 02 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-19
- Resolves: #2139349 - rsync error: protocol incompatibility when using rsync-3.2.3-18.el9
* Tue Feb 04 2025 Michal Ruprich <mruprich@redhat.com> - 3.1.3-21
- Resolves: RHEL-70207 - Path traversal vulnerability in rsync
* Thu Aug 25 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-18
- Resolves: #2111177 - remote arbitrary files write inside the directories of connecting peers
* Mon Feb 03 2025 Michal Ruprich <mruprich@redhat.com> - 3.1.3-20
- Resolves: RHEL-70207 - Path traversal vulnerability in rsync
- 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
* Thu Aug 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-17
- 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 Nov 02 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-19.1
- Resolves: #2139118 - rsync-daemon fail on 3.1.3
* Wed May 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-16
- Related: #2081296 - Adding ci.fmf for separation of testing results
* Thu Aug 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-19
- Resolves: #2116668 - zlib: a heap-based buffer over-read or buffer overflow in inflate in inflate.c via a large gzip header extra field
* Wed May 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-15
- Related: #2081296 - Disabling STI
* Mon Aug 15 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-18
- Resolves: #2111175 - remote arbitrary files write inside the directories of connecting peers
* Wed May 18 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-14
- Resolves: #2071514 - A flaw found in zlib when compressing (not decompressing) certain inputs
* Mon Aug 08 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-17
- Related: #2043753 - New option should not be sent to the server every time
* Wed May 11 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-13
- Resolves: #2079639 - rsync --atimes doesn't work
* Thu Jul 28 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-16
- Resolves: #2043753 - [RFE] Improve defaults for sparse file buffering
* Tue May 03 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-12
- Resolves: #2081296 - Enable fmf tests in centos stream
* Tue Apr 12 2022 Michal Ruprich <mruprich@redhat.com> - 3.1.3-15
- Resolves: #2071513 - A flaw in zlib-1.2.11 when compressing (not decompressing!) certain inputs
* Tue Apr 26 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-11
- Resolves: #2053198 - rsync segmentation fault
* Mon Oct 11 2021 Michal Ruprich <mruprich@redhat.com> - 3.1.3-14
- Related: #1907443 - Adding fmf plans to run tests with tmt
* Fri Apr 22 2022 Michal Ruprich <mruprich@redhat.com> - 3.2.3-10
- Resolves: #2077431 - Read-only files that have changed xattrs fail to allow xattr changes
* Mon Sep 27 2021 Tomas Korbar <tkorbar@redhat.com> - 3.1.3-13
- Resolves: #1907443 - Read-only files that have changed xattrs fail to allow xattr changes
* Tue Aug 10 2021 Mohan Boddu <mboddu@redhat.com> - 3.2.3-9
- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
Related: rhbz#1991688
* Fri Dec 18 2020 Michal Ruprich <mruprich@redhat.com> - 3.1.3-12
- Resolves: #1816528 - Defaults for --skip-compress are not working, everything is being compressed
* Wed Jun 16 2021 Mohan Boddu <mboddu@redhat.com> - 3.2.3-8
- Rebuilt for RHEL 9 BETA for openssl 3.0
Related: rhbz#1971065
* Thu Nov 05 2020 Tomas Korbar <tkorbar@redhat.com> - 3.1.3-11
- Resolves: #1855981 - rsync segfaults in --append mode
* Mon May 31 2021 Michal Ruprich <mruprich@redhat.com> - 3.2.3-7
- Resolves: #1955008 - rsync segfaults in --append mode when file on sender is large (> 2GB) and gets truncated
* Thu Nov 05 2020 Tomas Korbar <tkorbar@redhat.com> - 3.1.3-10
- Resolves: #1727093 - rsync: "ABORTING due to invalid path from sender"
* Fri Apr 16 2021 Mohan Boddu <mboddu@redhat.com> - 3.2.3-6
- Rebuilt for RHEL 9 BETA on Apr 15th 2021. Related: rhbz#1947937
* Mon Aug 24 2020 Michal Ruprich <mruprich@redhat.com> - 3.1.3-9
- Resolves: #1667436 - rsyncd.service fails to start at boot if address is configured
* Wed Jan 27 2021 Fedora Release Engineering <releng@fedoraproject.org> - 3.2.3-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild
* Wed Jun 10 2020 Michal Ruprich <mruprich@redhat.com> - 3.1.3-8
- Resolves: #1775561 - rsync 3.1.2 hangs when run with -vvv to sync a large repository
* Tue Dec 08 2020 Michal Ruprich <mruprich@redhat.com> - 3.2.3-4
- Resolves: #1894485 - rsync is unable to set permissions when chrooted
- Getting rid of deprecated makeinstall macro
* Tue Oct 29 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-7
- Resolves: #1693162 - remove-source-files fails with symlinks
* Fri Nov 20 2020 Michal Ruprich <mruprich@redhat.com> - 3.2.3-3
- Disabling LTO as a temporary measure for rhbz#1898912
* Tue Apr 16 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-6
- Resolves: #1602683 - Please review important issues found by covscan
* Thu Nov 19 2020 Michal Ruprich <mruprich@redhat.com> - 3.2.3-2
- Use make macros
- https://fedoraproject.org/wiki/Changes/UseMakeBuildInstallMacro
* Tue Apr 16 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-5
- Resolves: #1656761 - [FJ8.0 Bug]: [REG] The rsync command is terminated with SIGSEGV
* Mon Aug 31 2020 Michal Ruprich <mruprich@redhat.com> - 3.2.3-1
- New version 3.2.3
- Removed upstream patches acls.diff and xattrs.diff
* Wed Oct 03 2018 Michal Ruprich <mruprich@redhat.com> - 3.1.3-4
- Resolves: #1635631 - Remove --noatime option from rsync
Cleaning spec file
* Sat Aug 01 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.2.2-3
- Second attempt - Rebuilt for
https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Wed Jul 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.2.2-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild
* Tue Jul 21 2020 Michal Ruprich <michalruprich@gmail.com> - 3.2.2-1
- New version 3.2.2
* Thu Jan 30 2020 Fedora Release Engineering <releng@fedoraproject.org> - 3.1.3-11
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
* Thu Oct 10 2019 Michal Ruprich <mruprich@redhat.com> - 3.1.3-10
- Enabling upstream test suite during build rhbz#1533846
* 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
- Escape macros in %%changelog