Fix CVE-2026-43618: integer overflow in compressed-token decoding
Backport upstream commit 901041dddc9a to fix CVE-2026-43618, an integer overflow in compressed-token decoding in rsync. The patch hardens the receiver's three compressed-token decoders (zlib, zstd, lz4) in token.c by replacing inline token decoding with a shared recv_compressed_token_num() function that includes proper overflow protection. It also adds bounds checking in receiver.c and simple_recv_token to reject malformed token values, preventing potential memory leaks to the wire. Upstream patch: https://github.com/RsyncProject/rsync/commit/901041dd.patch Added a followup commit: https://github.com/RsyncProject/rsync/commit/11e3e239 Resolves: RHEL-174939
This commit is contained in:
parent
cc69bbdd82
commit
15f6d55f9a
232
rsync-3.2.5-fix-cve-2026-43618.patch
Normal file
232
rsync-3.2.5-fix-cve-2026-43618.patch
Normal file
@ -0,0 +1,232 @@
|
||||
From 2433971801c8814f2514a97c57dd27b12db1a243 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 | 102 ++++++++++++++++++++++++++++++-----------------------
|
||||
2 files changed, 67 insertions(+), 46 deletions(-)
|
||||
|
||||
diff --git a/receiver.c b/receiver.c
|
||||
index 6c74468d..f42e764c 100644
|
||||
--- a/receiver.c
|
||||
+++ b/receiver.c
|
||||
@@ -310,7 +310,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);
|
||||
|
||||
@@ -318,6 +323,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 f5a41c98..7e295326 100644
|
||||
--- a/token.c
|
||||
+++ b/token.c
|
||||
@@ -286,6 +286,10 @@ static int32 simple_recv_token(int f, char **data)
|
||||
int32 i = read_int(f);
|
||||
if (i <= 0)
|
||||
return i;
|
||||
+ /* A literal run may exceed CHUNK_SIZE: some peers (e.g. the
|
||||
+ * acrosync library) use a 64k block size. The loop below reads
|
||||
+ * the run CHUNK_SIZE bytes at a time, so read_buf never writes
|
||||
+ * past the static CHUNK_SIZE buffer regardless of i. */
|
||||
residue = i;
|
||||
}
|
||||
|
||||
@@ -488,9 +496,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)
|
||||
{
|
||||
@@ -581,17 +632,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;
|
||||
@@ -611,10 +652,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -823,17 +861,7 @@ static int32 recv_zstd_token(int f, char **data)
|
||||
return 0;
|
||||
}
|
||||
/* here we have a token of some kind */
|
||||
- if (flag & TOKEN_REL) {
|
||||
- rx_token += flag & 0x3f;
|
||||
- flag >>= 6;
|
||||
- } else
|
||||
- rx_token = read_int(f);
|
||||
- if (flag & 1) {
|
||||
- rx_run = read_byte(f);
|
||||
- rx_run += read_byte(f) << 8;
|
||||
- recv_state = r_running;
|
||||
- }
|
||||
- return -1 - rx_token;
|
||||
+ return recv_compressed_token_num(f, flag);
|
||||
|
||||
case r_inflated: /* zstd doesn't get into this state */
|
||||
break;
|
||||
@@ -864,10 +892,7 @@ static int32 recv_zstd_token(int f, char **data)
|
||||
break;
|
||||
|
||||
case r_running:
|
||||
- ++rx_token;
|
||||
- if (--rx_run == 0)
|
||||
- recv_state = r_idle;
|
||||
- return -1 - rx_token;
|
||||
+ return recv_compressed_token_run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -987,17 +1012,7 @@ static int32 recv_compressed_token(int f, char **data)
|
||||
}
|
||||
|
||||
/* here we have a token of some kind */
|
||||
- if (flag & TOKEN_REL) {
|
||||
- rx_token += flag & 0x3f;
|
||||
- flag >>= 6;
|
||||
- } else
|
||||
- rx_token = read_int(f);
|
||||
- if (flag & 1) {
|
||||
- rx_run = read_byte(f);
|
||||
- rx_run += read_byte(f) << 8;
|
||||
- recv_state = r_running;
|
||||
- }
|
||||
- return -1 - rx_token;
|
||||
+ return recv_compressed_token_num(f, flag);
|
||||
|
||||
case r_inflating:
|
||||
avail_out = LZ4_decompress_safe(next_in, dbuf, avail_in, size);
|
||||
@@ -1013,10 +1028,7 @@ static int32 recv_compressed_token(int f, char **data)
|
||||
break;
|
||||
|
||||
case r_running:
|
||||
- ++rx_token;
|
||||
- if (--rx_run == 0)
|
||||
- recv_state = r_idle;
|
||||
- return -1 - rx_token;
|
||||
+ return recv_compressed_token_run();
|
||||
}
|
||||
}
|
||||
}
|
||||
--
|
||||
2.52.0
|
||||
|
||||
10
rsync.spec
10
rsync.spec
@ -10,7 +10,7 @@
|
||||
Summary: A program for synchronizing files over a network
|
||||
Name: rsync
|
||||
Version: 3.2.5
|
||||
Release: 8%{?dist}
|
||||
Release: 9%{?dist}
|
||||
URL: https://rsync.samba.org/
|
||||
|
||||
Source0: https://download.samba.org/pub/rsync/src/rsync-%{version}%{?prerelease}.tar.gz
|
||||
@ -73,6 +73,9 @@ Patch13: rsync-3.2.5-fix-cve-2026-29518.patch
|
||||
# https://github.com/RsyncProject/rsync/commit/3526884f
|
||||
# https://github.com/RsyncProject/rsync/commit/7192db98
|
||||
Patch14: rsync-3.2.5-fix-cve-2026-29518-regressions.patch
|
||||
# https://github.com/RsyncProject/rsync/commit/901041dd
|
||||
# https://github.com/RsyncProject/rsync/commit/11e3e239
|
||||
Patch15: rsync-3.2.5-fix-cve-2026-43618.patch
|
||||
|
||||
%description
|
||||
Rsync uses a reliable algorithm to bring remote and host files into
|
||||
@ -126,6 +129,7 @@ may be used to setup a restricted rsync users via ssh logins.
|
||||
%patch 12 -p1 -b .cve-2024-12086
|
||||
%patch 13 -p1 -b .cve-2026-29518
|
||||
%patch 14 -p1 -b .cve-2026-29518-regressions
|
||||
%patch 15 -p1 -b .cve-2026-43618
|
||||
|
||||
%build
|
||||
%configure --disable-xxhash --with-rrsync
|
||||
@ -176,6 +180,10 @@ install -D -m644 %{SOURCE6} $RPM_BUILD_ROOT/%{_unitdir}/rsyncd@.service
|
||||
%systemd_postun_with_restart rsyncd.service
|
||||
|
||||
%changelog
|
||||
* Tue Jun 23 2026 Michal Ruprich <mruprich@redhat.com> - 3.2.5-9
|
||||
- Fix integer overflow in compressed-token decoding (CVE-2026-43618)
|
||||
- Resolves: RHEL-174939
|
||||
|
||||
* Tue Jun 23 2026 Michal Ruprich <mruprich@redhat.com> - 3.2.5-8
|
||||
- Fix TOCTOU symlink race in daemon no-chroot mode (CVE-2026-29518)
|
||||
- Resolves: RHEL-174953
|
||||
|
||||
Loading…
Reference in New Issue
Block a user