From 2a50bc21f76fee87bb38bd591a8e5eee6e0436bc Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 00:35:47 +0200 Subject: [PATCH] fix: stop on a digest an unmoved file contradicts --- scripts/generate_db.py | 18 ++++++++++++------ tests/test_audit_regressions.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/scripts/generate_db.py b/scripts/generate_db.py index 4eaddf74..a62eb09e 100644 --- a/scripts/generate_db.py +++ b/scripts/generate_db.py @@ -80,8 +80,11 @@ def _settled_hashes( A forced run rehashes everything, and this host has given a wrong SHA-1 under load (DSi_Nand_USA.bin read as 834b140f, not ef08ae02). An entry whose mtime and size did not move but whose digest did is hashed a - second time, and the two computations that agree win. The previous run's - value is the independent check the forced run used to throw away. + second time. A second reading that gives the cached value back settles it. + Two readings that agree against it do not: the host has returned the same + wrong digest twice in a row (62b100f2 for the same file), and a value + taken then is cached and read back by every later run. A file rewritten + in place moves its mtime, so the run stops instead of choosing. """ hashes = compute_hashes(filepath) if not cached or cached.get("mtime") != mtime or cached.get("size") != size: @@ -89,15 +92,18 @@ def _settled_hashes( if cached.get("sha1") in (None, hashes["sha1"]): return hashes again = compute_hashes(filepath, cache=False) - if again["sha1"] == hashes["sha1"]: - # Two fresh readings agree: the bytes changed under an unchanged mtime. - return hashes print( f" WARNING: {filepath}: sha1 read as {hashes['sha1'][:8]} then " f"{again['sha1'][:8]}, cached {cached['sha1'][:8]}", file=sys.stderr, ) - return again if again["sha1"] == cached["sha1"] else hashes + if again["sha1"] == cached["sha1"]: + return again + raise RuntimeError( + f"{filepath}: read as {again['sha1'][:8]}, cached as {cached['sha1'][:8]}, " + "with the same mtime and size. Rerun when the host is idle, or " + f"`touch {filepath}` if its bytes really changed." + ) def scan_bios_dir(bios_dir: Path, cache: dict, force: bool) -> tuple[dict, dict, dict]: diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index a604f9c4..337ea1e3 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -5928,6 +5928,20 @@ class AForcedRehashIsCheckedAgainstTheLastOne(unittest.TestCase): self.assertEqual(settled["sha1"], "a" * 40) self.assertIn("WARNING", err.getvalue()) + def test_two_agreeing_readings_against_an_unmoved_file_stop_the_run(self): + """The host has returned the same wrong SHA-1 twice in a row + (DSi_Nand_USA.bin as 62b100f2, 2026-10-22). Taking two agreeing + readings as a rewrite put the wrong value in the cache, where every + later run read it back. An unmoved file is never rewritten silently.""" + from unittest import mock + + from scripts import generate_db + + cached = {"sha1": "a" * 40, "mtime": 1.0, "size": 4} + with mock.patch.object(generate_db, "compute_hashes", lambda *a, **k: {"sha1": "b" * 40}), \ + self.assertRaisesRegex(RuntimeError, "touch"): + generate_db._settled_hashes(Path("x.bin"), cached, 1.0, 4) + def test_a_changed_file_takes_its_new_digest(self): from unittest import mock