Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ extern int inplace;
extern int append_mode;
extern int make_backups;
extern int csum_length;
extern int xfer_sum_len;
extern int ignore_times;
extern int size_only;
extern OFF_T max_size;
Expand Down Expand Up @@ -697,6 +698,11 @@ static void sum_sizes_sqroot(struct sum_struct *sum, int64 len)
{
int32 blength;
int s2length;
/* The strong sum can be no longer than the negotiated checksum digest:
* a short checksum (e.g. xxh64 = 8 bytes, when xxh128/xxh3 are absent)
* makes xfer_sum_len < SUM_LENGTH, and the sender rejects an s2length
* larger than xfer_sum_len (io.c). */
int max_s2length = MIN(SUM_LENGTH, xfer_sum_len);
int64 l;

if (len < 0) {
Expand Down Expand Up @@ -731,7 +737,7 @@ static void sum_sizes_sqroot(struct sum_struct *sum, int64 len)
if (protocol_version < 27) {
s2length = csum_length;
} else if (csum_length == SUM_LENGTH) {
s2length = SUM_LENGTH;
s2length = max_s2length;
} else {
int32 c;
int b = BLOCKSUM_BIAS;
Expand All @@ -740,7 +746,7 @@ static void sum_sizes_sqroot(struct sum_struct *sum, int64 len)
/* add a bit, subtract rollsum, round up. */
s2length = (b + 1 - 32 + 7) / 8; /* --optimize in compiler-- */
s2length = MAX(s2length, csum_length);
s2length = MIN(s2length, SUM_LENGTH);
s2length = MIN(s2length, max_s2length);
}

sum->flength = len;
Expand Down
52 changes: 52 additions & 0 deletions testsuite/append-shortsum_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""Regression: a short transfer checksum must not over-state the block s2length.

A full-checksum (--append-verify redo) pass computes the strong block sum length
(s2length). The generator used to cap it at SUM_LENGTH (16), the legacy MD4/MD5
digest size, regardless of the negotiated algorithm. Since the sum2 array holds
xfer_sum_len-byte elements and the sender rejects an s2length larger than
xfer_sum_len, a sub-16-byte transfer checksum -- xxh64 (8 bytes), which is what
rsync negotiates when the build's libxxhash lacks xxh128/xxh3 (e.g. Ubuntu
20.04) -- made the sender die with "Invalid checksum length 16 [sender]"
(protocol incompatibility, code 2).

Forcing --checksum-choice=xxh64 reproduces it on any build that has xxhash, so
this guards the fix without needing an old-libxxhash host. Skipped where xxh64
is unavailable (a build without xxhash).
"""

import json

from rsyncfns import (
FROMDIR, TODIR, assert_same, make_data_file, rmtree, run_rsync,
test_skipped,
)

vv = json.loads(run_rsync('-VV', check=True, capture_output=True).stdout)
if 'xxh64' not in vv.get('checksum_list', []):
test_skipped("xxh64 not in this build's checksum list (no xxhash)")

src, dst = FROMDIR, TODIR
rmtree(src)
rmtree(dst)
src.mkdir(parents=True)
dst.mkdir(parents=True)

# Source longer than the destination so --append has bytes to add; the dest is a
# *corrupted* prefix so --append-verify's whole-file check fails and the file is
# redone with a full checksum -- the csum_length == SUM_LENGTH path that emitted
# the over-long s2length.
make_data_file(src / 'f', 40000)
full = (src / 'f').read_bytes()
prefix = bytearray(full[:20000])
prefix[0:64] = b'\x00' * 64
(dst / 'f').write_bytes(bytes(prefix))

# --no-whole-file forces the delta/checksum path regardless of local-vs-remote.
# run_rsync(check=True) fails the test on the non-zero exit the bug produced.
run_rsync('-a', '--append-verify', '--checksum-choice=xxh64', '--no-whole-file',
f'{src}/', f'{dst}/')
assert_same(dst / 'f', src / 'f', label='append-verify xxh64 redo')

print("append-shortsum: --append-verify with an 8-byte (xxh64) checksum no "
"longer overflows the block s2length")
Loading