fix(parity): review-wave fixes (basis stats over-report, filter-rule bounds)
- receiver stats: exclude basis-dir materializations (--link-dest/--copy-dest) from created/literal tallies; rsync reports 0 for a basis hit, so a fresh --link-dest --stats run now matches (differential test_link_dest_stats_matches_rsync) - filter wire block: reject a pattern above the glob evaluation bound and lower MAX_FILTER_RULES to 1024, so a crafted rule list cannot amplify delete-walk glob work or install a rule that silently never protects - negative tests for over-cap count and over-long pattern - README: correct --delete default, --stats/--progress description, add --delete-commit; RSYNC_COMPAT stale version labels/overclaim fixed
This commit is contained in:
@@ -1055,6 +1055,13 @@ static bool send_protect_entries(int fd, const Config* c) {
|
||||
bool ok = send_int(fd, rules->count);
|
||||
for (int i = 0; ok && i < rules->count; i++) {
|
||||
const FilterRule* r = rules->items[i];
|
||||
/* Mirror the receiver's limit so the peer never receives a rule it will
|
||||
reject as a protocol error. */
|
||||
if (r->pattern && strlen(r->pattern) > MAX_PROTECT_PATTERN_LEN) {
|
||||
log_message(LOG_LEVEL_ERROR, "filter pattern exceeds %d bytes", MAX_PROTECT_PATTERN_LEN);
|
||||
filter_rule_list_free(rules);
|
||||
return false;
|
||||
}
|
||||
ok = send_int(fd, (int)r->action) && send_int(fd, (int)r->sides) &&
|
||||
send_int(fd, r->anchored ? 1 : 0) && send_int(fd, r->dir_only ? 1 : 0) &&
|
||||
send_int(fd, r->negate ? 1 : 0) && send_str(fd, r->owner ? r->owner : "") &&
|
||||
@@ -1098,6 +1105,15 @@ static bool receive_protect_entries(int fd, Config* c, ConfigStringBudget* budge
|
||||
free(pattern);
|
||||
goto fail;
|
||||
}
|
||||
/* A pattern too long to be evaluated by glob_match against a PATH_MAX path
|
||||
would silently fail to match and leave a protect rule inert (fail-open:
|
||||
the entry is then deleted). Reject it up front as a protocol error
|
||||
rather than accept a rule that can never shield anything. */
|
||||
if (strlen(pattern) > MAX_PROTECT_PATTERN_LEN) {
|
||||
free(owner);
|
||||
free(pattern);
|
||||
goto fail;
|
||||
}
|
||||
size_t bytes = strlen(owner) + strlen(pattern);
|
||||
if (bytes > MAX_FILTER_BYTES - pattern_bytes) {
|
||||
free(owner);
|
||||
|
||||
+9
-1
@@ -1068,8 +1068,16 @@ typedef struct Config {
|
||||
* count and the aggregate pattern+owner bytes are each capped so a hostile
|
||||
* peer cannot pin unbounded pre-auth memory; both are validated strictly on
|
||||
* receive (alongside the per-string ConfigStringBudget). */
|
||||
#define MAX_FILTER_RULES 4096
|
||||
/* A peer may supply protect rules; cap the list so a crafted config cannot make
|
||||
* the receiver's delete walk evaluate an unbounded number of glob patterns per
|
||||
* destination entry (glob_match is O(pattern x path)). 1024 is far above any
|
||||
* legitimate selection. */
|
||||
#define MAX_FILTER_RULES 1024
|
||||
#define MAX_FILTER_BYTES (256 * 1024)
|
||||
/* glob_match's DP is capped at 64 Mi work units; a pattern longer than this
|
||||
* could exceed the cap against a PATH_MAX path and silently stop matching,
|
||||
* leaving a protect rule inert. Reject such a rule at receive time. */
|
||||
#define MAX_PROTECT_PATTERN_LEN 8192
|
||||
|
||||
/* Upper bound on the number of --skip-compress suffixes accepted from the wire.
|
||||
* Each suffix is an independent wire string (up to MAX_STRING_SIZE = 64 KiB), so
|
||||
|
||||
@@ -1038,6 +1038,13 @@ void receiver_stats_note_saved(ReceiverStats* stats, const File* file, bool crea
|
||||
unsigned created_dirs) {
|
||||
if (!stats || !file)
|
||||
return;
|
||||
/* A basis-dir hit (--link-dest/--copy-dest) materializes bytes the sender
|
||||
* never transferred. rsync reports no literal data and no created entry for
|
||||
* such a file, and does not count the parent directories it creates only to
|
||||
* hold it, so exclude the whole entry from the receiver tallies. */
|
||||
bool basis_sourced = file->basis_link != NULL || file->basis_copy != NULL;
|
||||
if (basis_sourced)
|
||||
return;
|
||||
bool is_sibling = file->link_group != 0 && !file->link_first;
|
||||
if (!file->is_dir && !file->is_symlink && !file->is_special && !is_sibling) {
|
||||
unsigned long long literal = file->literal_bytes;
|
||||
|
||||
Reference in New Issue
Block a user