mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-06-18 02:51:01 -04:00
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>
This commit is contained in:
11
receiver.c
11
receiver.c
@@ -318,7 +318,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);
|
||||
|
||||
@@ -326,6 +331,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));
|
||||
|
||||
117
token.c
117
token.c
@@ -292,6 +292,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;
|
||||
}
|
||||
|
||||
@@ -494,9 +502,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)
|
||||
{
|
||||
@@ -587,22 +638,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 (rx_token < 0) {
|
||||
rprintf(FERROR, "invalid token number in compressed stream\n");
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -622,10 +658,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -836,22 +869,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 (rx_token < 0) {
|
||||
rprintf(FERROR, "invalid token number in compressed stream\n");
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
}
|
||||
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;
|
||||
@@ -882,10 +900,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1005,22 +1020,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 (rx_token < 0) {
|
||||
rprintf(FERROR, "invalid token number in compressed stream\n");
|
||||
exit_cleanup(RERR_PROTOCOL);
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -1036,10 +1036,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user