diff --git a/fusil/python/blacklists.py b/fusil/python/blacklists.py index 317f4cd..0b3acca 100644 --- a/fusil/python/blacklists.py +++ b/fusil/python/blacklists.py @@ -153,6 +153,31 @@ "sigwaitinfo", "sigtimedwait", }, + # PyPy interpreter internals (__pypy__) that attack the fuzzer or the host rather than + # the target -- the "generated code kills its own session" class (see fusil #192). + # Everything else in __pypy__ is deliberately left fuzzable: it is PyPy-only surface with + # no CPython counterpart, which is the whole point of fuzzing PyPy. + "__pypy__": { + # Spawns an interp-level gdb *inside the session*. Observed live: gdb's own banner + # ("For bug reporting instructions...") lands in the captured stdout and scores on + # the "bug" word, so the session is kept as a crash that never happened. + "attach_gdb", + # "Executes a script of Python code in a given remote Python process." A fuzzer-chosen + # pid means arbitrary code injection into any process on the box -- including sibling + # fleet instances and the fuzzer itself. + "remote_exec", + # Installs a process-global hook invoked on every code-object creation; handing it one + # of fusil's bomb objects makes the rest of the session detonate on unrelated code. + "set_code_callback", + # Blocks: calls PyOS_InputHook() / stops under the reverse debugger. + "pyos_inputhook", + "revdb_stop", + # Documented as "for testing purposes, raise an interpreter-level ValueError. + # Should turn into a SystemError automatically" -- a deliberate self-test helper. + # "SystemError" is a 1.0 crash word, so with --test-private this alone tagged + # EVERY __pypy__ session as a crash. Manufactured signal, never a target bug. + "_internal_crash", + }, "_socket": SOCKET, "socket": SOCKET, "posix": POSIX, diff --git a/tests/python/test_blacklists.py b/tests/python/test_blacklists.py index 8c62f28..486ca36 100644 --- a/tests/python/test_blacklists.py +++ b/tests/python/test_blacklists.py @@ -35,6 +35,21 @@ 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_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 + # "bug" word, manufacturing a crash. remote_exec injects code into an arbitrary pid. + self.assertLessEqual( + {"attach_gdb", "remote_exec", "set_code_callback", "_internal_crash"}, + bl.BLACKLIST["__pypy__"], + ) + + def test_pypy_blacklist_stays_narrow(self): + # __pypy__ is PyPy-only surface with no CPython counterpart -- the reason to fuzz PyPy + # at all. Only the self-harming helpers belong here, never the interesting internals. + for keep in ("newdict", "strategy", "internal_repr", "intop", "move_to_end"): + self.assertNotIn(keep, bl.BLACKLIST["__pypy__"]) + def test_sys_trace_hooks_blacklisted(self): self.assertEqual( bl.BLACKLIST["sys"] & {"settrace", "setprofile"}, {"settrace", "setprofile"}