The hardening in c44c90e9 added a check in simple_recv_token() rejecting
any uncompressed literal-run length > CHUNK_SIZE (32k). That assumption
breaks interoperability: other rsync implementations -- e.g. the acrosync
library used by the iOS "PhotoBackup" app -- use a 64k block size and
send literal runs of 65536 bytes, which 3.4.3+ now rejects with
"invalid uncompressed token length 65536".
The check was unnecessary: simple_recv_token() already reads the run
CHUNK_SIZE bytes at a time via the residue loop (n = MIN(CHUNK_SIZE,
residue)), so read_buf() never writes past the static CHUNK_SIZE buffer
regardless of the wire-supplied length. Drop the check to restore
interop; the compressed-token integer-overflow fix from c44c90e9 (the
MAX_TOKEN_INDEX / rx_token caps) is left unchanged.
Fixes#1002
Reported-by: Jack Whitham
send_deflated_token() adds a matched block to the compressor history with
deflate(Z_INSERT_ONLY). Our bundled zlib implements Z_INSERT_ONLY (it
produces no output and consumes the input in one call), but a build
against a system zlib lacks it and falls back to Z_SYNC_FLUSH (see the top
of the file), which emits a flush block into obuf. For a large
incompressible matched token that block exceeds AVAIL_OUT_SIZE(CHUNK_SIZE),
so deflate returned with avail_in != 0 and the transfer aborted:
"deflate on token returned 0 (N bytes left)" at token.c
The insert output is never sent -- the receiver rebuilds the matching
history itself in see_deflate_token() -- so loop, resetting the output
buffer, and discard it. Drain with the same condition as the data loop
above: until the input is consumed AND avail_out != 0. Stopping at
avail_in == 0 alone can leave pending output in the deflate stream (a
full output buffer with bytes still buffered), which would then be emitted
by the next real deflate send and corrupt the stream. A bundled-zlib
build still finishes in one iteration.
Fixes: #951
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>
Validate that token numbers read from compressed streams are
non-negative. A negative token value would cause the return value
of recv_*_token() to become positive, which callers interpret as
literal data length, but no data pointer is set on this code path.
While this only causes the receiver to crash (which is process-isolated
and only affects the attacker's own connection), it's still undefined
behavior.
Reported-by: Will Sergeant <wlsergeant@gmail.com>
The compression level of the first file in the transfer no longer sets
the level for all files that follow it. Document that per-file level
switching has no current effect (except for a global "dont compress = *"
rule in the daemon).
- Use -pedantic-errors with gcc to make an array-init fatal.
- Fix all the extra warnings that gcc outputs due to this option.
- Also add -Wno-pedantic to gcc if we're using the internal popt
code (since it has lots of pedantic issues).
- All the memory-allocation macros now auto-check for failure and exit
with a failure message that incudes the caller's file and lineno
info. This includes strdup().
- Added the `--max-alloc=SIZE` option to be able to override the memory
allocator's sanity-check limit. It defaults to 1G (as before).
Fixes bugzilla bug 12769.
- Add the zlibx (external-code compatible) compression name.
- Re-enable zlib support with the external library so it can be
tried as a fallback if zlibx isn't available.
- Add --compress-choice=STR (aka -zz=STR) option.
- Make --cc=STR an alias for --checksum-choice=STR.
- Hook up the new compression negotiation logic.
Adding new-style compression that only compresses the literal data that
is sent over the wire and not also matching file data that was not sent.
This new-style compression is compatible with external zlib instances,
and will eventually become the default (once enough time has passed that
all servers support the --new-compress and --old-compress options).
NOTE: if you build rsync with an external zlib (i.e. if you specified
configure --with-included-zlib=no) you will ONLY get support for the
--new-compress option! A client will treat -z as uncompressed (with a
warning) and a server will exit with an error (unless -zz was used).