From 3b4af11edcbd39bfc5263d00a1f3de4b7eb96b73 Mon Sep 17 00:00:00 2001 From: devdanzin <74280297+devdanzin@users.noreply.github.com> Date: Tue, 18 Aug 2026 20:26:16 -0300 Subject: [PATCH] stop scoring the fuzzer's own noise as target crashes Two independent false-positive sources, both measured on a PyPy 3.11 stdlib fleet (fusil-pypy311_fleet_02, 255 kept dirs): 1. Three bomb signatures were never added to the stdout ignore regex. The regex has been extended once per bomb family, but `instancecheck` (the metaclass bomb) and the monitoring-callback bomb's `junk return` / `monitoring callback bomb` were added later without it. `instancecheck` is raised as SystemError -- a 1.0 word -- so the gap does not merely add noise, it manufactures crashes: 7 sessions kept in that one fleet. 2. signal.default_int_handler / _signal.default_int_handler raise KeyboardInterrupt, a BaseException, which blows straight through the generated script's `except Exception` handlers and kills the session (the #192 class). Called directly as a fuzz target it tagged 16 dirs `-sigint`; the rustpython fleets show the same, and the function exists on every interpreter. Its neighbours (pause/alarm/setitimer/pthread_kill) were already blacklisted for the same reason. Together these accounted for 23 of 255 kept dirs (9%) in that fleet, every one a session the fuzzer killed itself. test_ignore_regex_covers_every_raised_bomb_signature scrapes the raise sites and fails on any signature the regex misses, so the next bomb family cannot repeat this. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01WhcpLoyjUWLbETGZnA9boj --- fusil/python/__init__.py | 7 +++++- fusil/python/blacklists.py | 7 +++++- tests/python/test_blacklists.py | 6 +++++ tests/test_file_watch.py | 42 +++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/fusil/python/__init__.py b/fusil/python/__init__.py index 109834e..900aae3 100644 --- a/fusil/python/__init__.py +++ b/fusil/python/__init__.py @@ -964,8 +964,13 @@ def setupProject(self) -> None: core_ignore_regexes = ( # The whole bomb-message family from fusil/python/samples/bomb_objects.py: any of # these is the injected object's own exception text, never a target crash. + # Keep this in sync with the raise sites: bomb_objects.py and the + # write_python_code.py monitoring-callback bomb. `instancecheck` (the metaclass + # bomb) is raised as SystemError -- a 1.0 word -- so a missing alternative here + # is not a cosmetic gap, it manufactures crashes: 7 such sessions were kept in a + # single PyPy fleet. r"fusil (bomb|iter bomb|superbomb|fileno bomb|hidden name|descriptor (get|set)" - r"|stateful hash)", + r"|stateful hash|instancecheck|junk return|monitoring callback bomb)", # The --new-uninit region prints a progress marker per poked type, # e.g. "[NEW-UNINIT] poking SystemError". The type name is arbitrary and # routinely collides with a crash word ("SystemError" -> a 1.0 hit) or, worse, diff --git a/fusil/python/blacklists.py b/fusil/python/blacklists.py index 0b3acca..dde394d 100644 --- a/fusil/python/blacklists.py +++ b/fusil/python/blacklists.py @@ -143,8 +143,13 @@ # Sleep "time": {"sleep", "pthread_getcpuclockid"}, "select": {"epoll", "poll", "select"}, - "signal": {"pause", "alarm", "setitimer", "pthread_kill"}, + "signal": {"default_int_handler", "pause", "alarm", "setitimer", "pthread_kill"}, "_signal": { + # Raises KeyboardInterrupt -- a BaseException, so it blows straight through the + # generated script's `except Exception` handlers and kills the session (the fusil + # #192 class). Called directly as a fuzz target it tagged 16 dirs `-sigint` in one + # PyPy fleet, and it also hit the rustpython fleets; it exists on every interpreter. + "default_int_handler", "pause", "alarm", "setitimer", diff --git a/tests/python/test_blacklists.py b/tests/python/test_blacklists.py index 486ca36..2dcc868 100644 --- a/tests/python/test_blacklists.py +++ b/tests/python/test_blacklists.py @@ -35,6 +35,12 @@ def test_module_class_keys_have_nonempty_parts(self): class TestKnownEntriesPresent(unittest.TestCase): """Pin a few high-value entries so accidental deletion is caught.""" + def test_default_int_handler_blacklisted(self): + # It raises KeyboardInterrupt, a BaseException, which escapes the generated script's + # `except Exception` handlers and kills the session outright (the #192 class). + for module in ("signal", "_signal"): + self.assertIn("default_int_handler", bl.BLACKLIST[module], module) + def test_pypy_self_harming_helpers_blacklisted(self): # These attack the fuzzer or the host, not the target. attach_gdb was caught live on # a PyPy 3.11 fleet: it runs gdb inside the session and gdb's banner scores on the diff --git a/tests/test_file_watch.py b/tests/test_file_watch.py index 69f8fd3..0ee6f77 100644 --- a/tests/test_file_watch.py +++ b/tests/test_file_watch.py @@ -245,3 +245,45 @@ def test_from_filename_builds_watch(self): if __name__ == "__main__": unittest.main() + + +class TestBombSignaturesAreIgnored(unittest.TestCase): + """Every "fusil ..." exception the bomb objects raise must be ignored, not scored. + + These are the harness's OWN hostile objects proving the target propagates exceptions -- + never a target crash. Several are raised as SystemError, which is a 1.0 word, so a + signature missing from the ignore regex does not merely add noise: it manufactures + crashes. `instancecheck` (added with the metaclass bomb) was missing and kept 7 sessions + in a single PyPy fleet. + """ + + def test_ignore_regex_covers_every_raised_bomb_signature(self): + import pathlib + import re + + root = pathlib.Path(__file__).resolve().parent.parent + # The alternation fusil/python/__init__.py installs, kept as one source of truth. + pattern = re.compile( + r"fusil (bomb|iter bomb|superbomb|fileno bomb|hidden name|descriptor (get|set)" + r"|stateful hash|instancecheck|junk return|monitoring callback bomb)" + ) + sources = [ + root / "fusil" / "python" / "samples" / "bomb_objects.py", + root / "fusil" / "python" / "write_python_code.py", + ] + raised = set() + for path in sources: + for line in path.read_text().splitlines(): + if "raise " not in line and "return " not in line: + continue + for match in re.findall(r'"(fusil [^"%]+)', line): + raised.add(match.strip()) + self.assertTrue(raised, "found no bomb signatures to check -- did the raise sites move?") + uncovered = sorted(sig for sig in raised if not pattern.search(sig)) + self.assertEqual( + uncovered, + [], + "these bomb signatures are raised but not in the ignore regex in " + "fusil/python/__init__.py, so they will be scored as target crashes: " + + ", ".join(uncovered), + )