Fix CVE-2026-43618: integer overflow in compressed-token decoding
Backport upstream commit c44c90e9460c to fix CVE-2026-43618, an integer overflow in compressed-token decoding. The patch adds MAX_TOKEN_INDEX bounds checking and new helper functions (recv_compressed_token_num, recv_compressed_token_run, invalid_compressed_token) to harden the recv_deflated_token decoder against overflow of the rx_token counter. It also caps simple_recv_token literal-block length at CHUNK_SIZE and adds a safety check for NULL data pointers in receiver.c. The patch was adapted for rsync 3.1.3 which only has the zlib decoder (recv_deflated_token), skipping hunks for zstd/lz4 decoders not present in this version.
Upstream patches:
c44c90e946.patch
Triage Decision Justification: CVE-2026-43618 describes a remote memory disclosure vulnerability via integer overflow in rsync's compressed-token decoding. The receiver's compressed-token decoders 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, leaking process memory contents (environment variables, passwords, heap pointers) and weakening ASLR. Upstream commit c44c90e9460c666c965446a8c0957f0b9fa4c66a on master, authored by Andrew Tridgell on 2026-04-29, directly fixes this vulnerability by: (1) capping rx_token at MAX_TOKEN_INDEX = 0x7ffffffe, (2) introducing shared recv_compressed_token_num() and recv_compressed_token_run() functions with comprehensive validation, (3) rejecting negative or out-of-range token values explicitly, and (4) capping simple_recv_token literal-block length at CHUNK_SIZE. While the patch does not explicitly mention CVE-2026-43618, its commit message exactly describes the vulnerability: 'integer overflow in compressed-token decoding' causing 'remote memory disclosure', matching the CVE summary precisely. Note: RHEL 8 ships rsync 3.1.3 which only has recv_deflated_token() (no zstd/lz4), so the patch will require manual adaptation for the older codebase. The upstream patch also supersedes an earlier precursor commit (359e539a, 'reject negative token values') which is already incorporated into and replaced by the comprehensive c44c90e9 fix.
Resolves: RHEL-174951
Backporting steps:
Successfully backported upstream commit c44c90e9460c666c965446a8c0957f0b9fa4c66a (CVE-2026-43618) to rsync 3.1.3 on c8s.
The upstream patch hardens compressed-token decoding against integer overflow in token.c and adds safety checks in receiver.c. The patch targets a newer rsync version that includes zstd and lz4 compression support (recv_zstd_token and recv_compressed_token functions), which don't exist in v3.1.3.
Conflict resolution:
Hunks for receiver.c applied cleanly (offset -13 lines).
Hunks adding the new helper functions (invalid_compressed_token, recv_compressed_token_num, recv_compressed_token_run, MAX_TOKEN_INDEX) and the simple_recv_token CHUNK_SIZE check applied cleanly.
Hunk #3 (replacing inline token parsing in recv_deflated_token with recv_compressed_token_num call) failed because the v3.1.3 code differs slightly from the newer version (no rx_token < 0 check in original). Manually replaced the old inline token parsing code with the new recv_compressed_token_num(f, flag) call.
Hunk #4 (r_running case in recv_deflated_token using recv_compressed_token_run) applied cleanly.
Hunks for recv_zstd_token and recv_compressed_token (lz4) were correctly skipped as those functions don't exist in v3.1.3 - only recv_deflated_token (zlib) is present.
The core security fix (overflow protection via MAX_TOKEN_INDEX bounds checking and the new helper functions) is fully applied to the only compression decoder that exists in this version.
This commit is contained in:
parent
bdabab39cb
commit
df410d7cbf
174
rsync-3.1.3-fix-cve-2026-43618.patch
Normal file
174
rsync-3.1.3-fix-cve-2026-43618.patch
Normal file
@ -0,0 +1,174 @@
|
||||
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
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
Summary: A program for synchronizing files over a network
|
||||
Name: rsync
|
||||
Version: 3.1.3
|
||||
Release: 26%{?dist}
|
||||
Release: 27%{?dist}
|
||||
Group: Applications/Internet
|
||||
URL: http://rsync.samba.org/
|
||||
|
||||
@ -67,6 +67,8 @@ Patch23: rsync-3.1.3-fix-cve-2026-29518.patch
|
||||
# https://github.com/RsyncProject/rsync/commit/3526884f
|
||||
# https://github.com/RsyncProject/rsync/commit/7192db98
|
||||
Patch24: rsync-3.1.3-fix-cve-2026-29518-regressions.patch
|
||||
# https://github.com/RsyncProject/rsync/commit/c44c90e9460c666c965446a8c0957f0b9fa4c66a
|
||||
Patch25: rsync-3.1.3-fix-cve-2026-43618.patch
|
||||
|
||||
%description
|
||||
Rsync uses a reliable algorithm to bring remote and host files into
|
||||
@ -129,6 +131,7 @@ patch -p1 -i patches/copy-devices.diff
|
||||
%patch22 -p1 -b .cve-2026-41035
|
||||
%patch23 -p1 -b .cve-2026-29518
|
||||
%patch24 -p1 -b .cve-2026-29518-regressions
|
||||
%patch25 -p1 -b .cve-2026-43618
|
||||
|
||||
%build
|
||||
%configure
|
||||
@ -175,6 +178,10 @@ 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 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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user