merge(r2 follow-up): the EACCES blast radius, and r2's falsifier table

design-dev's two follow-up commits on the R2 branch.

167f265 is PRE-EXISTING and his to have found, not his to have caused:
Path.is_file() swallows ENOENT but PROPAGATES EACCES, so one folder with r--
and no x in one booth made booth_items raise — and list_booths calls it for
every booth, so the index 500s for all of them. Identical blast radius to the
0xff filename the bug-hunt panel found, arriving through a different syscall.

39a3cb2 commits R2's own falsifiers as tests/mutations/r2_flow.toml, 18 rows.
Its first run caught three vacuous proofs, which is the fourth time this week
that running the mutation has disagreed with reading the assertion.

# Conflicts:
#	booth/items.py
This commit is contained in:
vh
2026-09-23 10:52:08 -07:00
6 changed files with 244 additions and 6 deletions
+25
View File
@@ -341,6 +341,31 @@ def test_one_unrepresentable_filename_costs_its_own_tile_not_the_booth(tmp_path)
assert [it.rel for it in got] == ["ok.png"]
def test_a_folder_that_lists_but_cannot_be_searched_costs_its_files_not_the_index(tmp_path):
"""Found folding R2's bug-hunt: `Path.is_file()` swallows a missing entry
but PROPAGATES EACCES. A directory with read and no execute permission
lists its names, and every stat under it raises — so one such folder in
one booth took out the index for every booth, the same blast radius as the
unrepresentable filename above. Its files are not items.
Defeating change: calling `is_file()` outside the OSError guard."""
b = tmp_path / "b"
b.mkdir()
(b / "ok.png").write_bytes(b"\x89PNG")
sub = b / "d"
sub.mkdir()
(sub / "x.png").write_bytes(b"\x89PNG")
sub.chmod(0o644) # r--: listable, nothing inside stat-able
try:
with pytest.raises(PermissionError):
(sub / "x.png").stat() # the fixture is live, not assumed
assert [it.rel for it in booth_items(b)] == ["ok.png"]
[row] = list_booths(tmp_path, ttl_seconds=86400)
assert row["name"] == "b" and row["count"] == 1
finally:
sub.chmod(0o755)
def test_a_huge_caption_sidecar_is_not_read_whole(tmp_path):
"""HULDA: `read_text()` pulled the entire sidecar into memory before
`[:CAPTION_MAX]` trimmed it, and the handler catches only OSError — so a