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:
@@ -382,7 +382,9 @@ _STANDALONE_REFS = {
|
||||
"compare_dest": "--compare-dest",
|
||||
"copy_dest": "--copy-dest",
|
||||
"link_dest": "--link-dest",
|
||||
"link_dest_stats": "--link-dest + --stats",
|
||||
"verify_basis": "--verify-basis (FastSync-only)",
|
||||
"verify_basis_default": "--verify-basis (default quick-check vs rsync)",
|
||||
"added_and_deleted": "--delete across two runs",
|
||||
"added_and_deleted_seed": "--delete across two runs",
|
||||
"one_file_system": "-x/--one-file-system",
|
||||
@@ -510,6 +512,34 @@ def test_link_dest_hardlinks_basis(parity_server_factory):
|
||||
_run_and_check(case_id, result)
|
||||
|
||||
|
||||
@requires_rsync
|
||||
@parity
|
||||
def test_link_dest_stats_matches_rsync(parity_server_factory):
|
||||
"""A basis hit must not be counted as created or literal data: rsync reports
|
||||
zero for both, so FastSync's receiver tallies must too (regression for the
|
||||
basis materialization over-report)."""
|
||||
case_id = "link_dest_stats"
|
||||
src = os.path.join(TEST_DATA_DIR, "parity_linkds_src")
|
||||
rdst = os.path.join(TEST_DATA_DIR, "parity_linkds_rdst")
|
||||
fdst = os.path.join(TEST_DATA_DIR, "parity_linkds_fdst")
|
||||
clean_dir(src)
|
||||
_mk(os.path.join(src, "f.txt"), b"link-basis-content\n")
|
||||
_pin(os.path.join(src, "f.txt"), _OLD_MTIME)
|
||||
server = parity_server_factory(SUPER)
|
||||
rel = os.path.abspath(src).lstrip(os.sep)
|
||||
|
||||
def seed(_src, rroot, froot):
|
||||
_mk(os.path.join(rroot, "basis", "f.txt"), b"link-basis-content\n", _OLD_MTIME)
|
||||
_mk(os.path.join(fdst, "basis", rel, "f.txt"), b"link-basis-content\n", _OLD_MTIME)
|
||||
|
||||
result = H.run_differential(
|
||||
src, rdst, fdst,
|
||||
["-a", "--link-dest=basis", "--stats"],
|
||||
["-a", f"--link-dest={os.path.join(fdst, 'basis')}", "--incremental", "--stats"],
|
||||
server, seed=seed, ignore_paths=("basis",), stdout=H.STDOUT_STATS)
|
||||
_run_and_check(case_id, result, ref="--link-dest + --stats")
|
||||
|
||||
|
||||
@requires_rsync
|
||||
@parity
|
||||
def test_copy_dest_copies_basis(parity_server_factory):
|
||||
|
||||
@@ -2120,6 +2120,48 @@ static void test_config_receive_rejects_oversized_string_budget() {
|
||||
config_delete(over_bytes);
|
||||
}
|
||||
|
||||
/* Pre-auth bounds for the receiver-side filter rule block (protocol 2.28.0).
|
||||
A peer may send `protect`/`risk` rules; the receiver must reject an over-cap
|
||||
count or an over-long pattern before evaluating anything, so a crafted config
|
||||
cannot drive unbounded glob work or install a rule that silently never
|
||||
matches. */
|
||||
static void test_config_receive_rejects_bad_protect_rules() {
|
||||
if (is_running_under_valgrind())
|
||||
return;
|
||||
|
||||
/* Over-cap rule count: one more than MAX_FILTER_RULES rules. */
|
||||
Config* c = config_create();
|
||||
EXPECT_NOT_NULL(c);
|
||||
c->send_directory = str_dup("/src");
|
||||
c->receive_root_directory = str_dup("/dst");
|
||||
c->filters = array_list_create(free);
|
||||
EXPECT_NOT_NULL(c->filters);
|
||||
for (int i = 0; i <= MAX_FILTER_RULES; i++)
|
||||
EXPECT_TRUE(array_list_add(c->filters, str_dup("- *.tmp")));
|
||||
EXPECT_TRUE(roundtrip_config_rejected(c));
|
||||
config_delete(c);
|
||||
|
||||
/* A pattern longer than the receiver's evaluation bound is rejected by the
|
||||
sender (mirroring the receiver's guard) instead of being sent as an inert
|
||||
rule. */
|
||||
c = config_create();
|
||||
EXPECT_NOT_NULL(c);
|
||||
c->send_directory = str_dup("/src");
|
||||
c->receive_root_directory = str_dup("/dst");
|
||||
c->filters = array_list_create(free);
|
||||
EXPECT_NOT_NULL(c->filters);
|
||||
size_t big = MAX_PROTECT_PATTERN_LEN + 1;
|
||||
char* long_rule = malloc(big + 3);
|
||||
EXPECT_NOT_NULL(long_rule);
|
||||
long_rule[0] = '-';
|
||||
long_rule[1] = ' ';
|
||||
memset(long_rule + 2, 'x', big);
|
||||
long_rule[big + 2] = '\0';
|
||||
EXPECT_TRUE(array_list_add(c->filters, long_rule));
|
||||
EXPECT_TRUE(roundtrip_config_rejected(c));
|
||||
config_delete(c);
|
||||
}
|
||||
|
||||
/* identity_copy_as_refused() is the pure, pre-snapshot refusal predicate: a
|
||||
--copy-as is refused when the receiver is not root OR the effective super
|
||||
mode is OFF (an operator veto), and never when --copy-as is unset. */
|
||||
@@ -3304,6 +3346,7 @@ void test_config() {
|
||||
test_config_wire_golden_receive();
|
||||
test_config_wire_receive_bounds();
|
||||
test_config_receive_rejects_overcap_counts();
|
||||
test_config_receive_rejects_bad_protect_rules();
|
||||
test_config_wire_roundtrip_all_fields();
|
||||
test_config_preserve_attribute_wire_roundtrip();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user