Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion fusil/python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion fusil/python/blacklists.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions tests/python/test_blacklists.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions tests/test_file_watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
Loading