From ef0023d78ce5ad2f48248e931534fbd00d0ef931 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sun, 18 Oct 2026 04:13:32 +0200 Subject: [PATCH] fix: key the manifest hash cache by its check The cache of manifest hashes was keyed by the database's id without holding the database, so a freed database replaced at the same address got another scan's verified hashes back. The key now carries the sha1 that database expects for the path. --- scripts/generate_pack.py | 7 +++++-- tests/test_install_experience.py | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 31984c0b..6eb06929 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -3236,13 +3236,16 @@ def _stable_hashes(local_path: str, db: dict) -> dict: refused rather than published. """ stat = os.stat(local_path) - key = (os.path.normpath(local_path), stat.st_size, stat.st_mtime_ns, id(db)) + expected = _db_sha1_by_path(db).get(os.path.normpath(local_path)) + # Keyed by what was checked, the sha1 this database expects there, never + # by id(db): a database freed and replaced at the same address answered + # with hashes verified against the first one. + key = (os.path.normpath(local_path), stat.st_size, stat.st_mtime_ns, expected) known = _STABLE_HASHES.get(key) if known is not None: # Verified against the scan once: twelve manifests name a file up # to a dozen times, and rehashing it each time spent 163 s of 218. return known - expected = _db_sha1_by_path(db).get(os.path.normpath(local_path)) hashes = compute_hashes(local_path, _MANIFEST_HASHES) for _attempt in range(2): if expected is None or hashes["sha1"] == expected: diff --git a/tests/test_install_experience.py b/tests/test_install_experience.py index 289bdf6a..71d71111 100644 --- a/tests/test_install_experience.py +++ b/tests/test_install_experience.py @@ -212,6 +212,16 @@ class ManifestHashesMatchTheScan(unittest.TestCase): generate_pack._stable_hashes(str(path), bad) self.assertEqual(generate_pack._stable_hashes(str(path), {"files": {}})["sha1"], real) + def test_the_cache_is_keyed_by_what_was_checked(self): + """Keyed by id(db), a database freed and replaced at the same address + got hashes verified against the first one back, unchecked.""" + source = (REPO_ROOT / "scripts" / "generate_pack.py").read_text(encoding="utf-8") + body = source[source.index("def _stable_hashes("):] + body = body[: body.index("\ndef ")] + key_line = next(line for line in body.splitlines() if line.strip().startswith("key =")) + self.assertNotIn("id(", key_line) + self.assertIn("expected", key_line) + class RetriesWait(unittest.TestCase): def test_a_server_error_is_retried_after_the_delay_it_asks(self):