From c6cf8c2d3e664164fe8ede6a905a86eb9752aafe Mon Sep 17 00:00:00 2001 From: devdanzin <74280297+devdanzin@users.noreply.github.com> Date: Sat, 5 Sep 2026 11:42:14 -0300 Subject: [PATCH] --oom-fuzz reported fusil's own bomb as a CPython contract violation `oom_call`'s `except SystemError` exists to catch a PyCFunction contract violation under allocation failure. But `SystemError` is one of the twelve `_BOMB_EXCEPTIONS`, so every bomb built with `exc=None` -- `FailingIterator`, `SuperBomb`, `DescriptorBomb`, `HiddenNameType`, and the rest -- raises it about one firing in twelve, and the handler dutifully reported fusil's own object as a finding with no C code involved at all. The marker printed only the label, never the exception, so the two were indistinguishable in a kept crash dir. That is what let it go unnoticed: an --oom-foreign fleet of 68,463 sessions kept 410 SystemError dirs -- 74% of its entire output -- and all 418 of their resolved labels had a random-exception bomb passed to the firing call. Seven dirs replayed under the real shim, chosen to include the hardest residue cases (`builtins.tuple`, `builtins.frozenset`, `re.Scanner`), and every one was a bomb: `SystemError('fusil iter bomb')` or `SystemError('fusil superbomb via __eq__')`. A control -- the same handler around a pure-Python callee handed a bomb, no extension module anywhere -- fires the marker at 15/200, matching SystemError's 1-in-12 share of the bomb tuple. Every bomb message starts with "fusil " (samples/bomb_objects.py), which is the discriminator, and the repr is now printed so a genuine one stays triageable from stdout alone. Replaying the session behind one of those kept dirs goes from 4 markers to 0. Removing SystemError from `_BOMB_EXCEPTIONS` would have been the wrong fix: a slot raising an unexpected SystemError is exactly the hostile input that finds C code mishandling it -- the bug is reporting it as the target's. The handler is emitted as source into every generated script, so the new tests exec the emitted definition and drive it rather than asserting on snapshot text: a bomb SystemError is suppressed, a target's is still reported with its repr, and one merely mentioning "fusil" further into the message still reports. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01WPBSmN87d2BqUnrDbojUT1 --- fusil/python/write_python_code.py | 28 ++++++++--- tests/python/test_oom_fuzz.py | 84 ++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 8 deletions(-) diff --git a/fusil/python/write_python_code.py b/fusil/python/write_python_code.py index 0d183b0..66d01bb 100644 --- a/fusil/python/write_python_code.py +++ b/fusil/python/write_python_code.py @@ -722,6 +722,16 @@ def compare_results(a, b): self.emptyLine() if self.options.oom_fuzz: + # The emitted `except SystemError` handlers below filter out a bomb's OWN + # exception. SystemError is one of `_BOMB_EXCEPTIONS`, so every bomb built with + # `exc=None` (FailingIterator, SuperBomb, DescriptorBomb, HiddenNameType, ...) + # picks it about one firing in twelve -- and the handler then reported a + # "contract violation" for fusil's own object, with no C code involved at all. + # Because the marker printed only the label and never the exception, the two were + # indistinguishable in a crash dir: an --oom-foreign fleet (68k sessions) kept 410 + # such dirs, 74% of its total output, every one of them traced back to a bomb. + # Every bomb message starts with "fusil " (samples/bomb_objects.py), which is the + # discriminator; the repr is now printed so a genuine one stays triageable. self.write_block( 0, f""" @@ -735,8 +745,9 @@ def oom_call(label, func, *args, **kwargs): # sweep) identifies which invocation was running if a crash follows -- # more reliable than the faulthandler frame, which is often an # incidental allocation rather than the fuzzed target. MemoryError is - # the expected outcome and is swallowed silently; SystemError is - # surfaced (PyCFunction contract violations); a real crash + # the expected outcome and is swallowed silently; a SystemError the + # target itself raised is surfaced (PyCFunction contract violations), + # while one carrying a bomb's own "fusil ..." message is not; a real crash # (segfault/abort) terminates the process, the signal fusil scores. # The inner finally DISARMS injection (set_nomemory with an unreachable start) # so the except clauses allocate freely, WITHOUT swapping the allocator -- the @@ -756,8 +767,11 @@ def oom_call(label, func, *args, **kwargs): _set_nomemory(_OOM_DISABLE, 0) except MemoryError: pass - except SystemError: - print("[OOM] SystemError in " + label, file=stderr) + except SystemError as _exc: + # Skip a bomb's own exception (message "fusil ..."); report the + # repr so a real one is triageable from stdout alone. + if not str(_exc).startswith("fusil "): + print("[OOM] SystemError in " + label + ": " + repr(_exc), file=stderr) except BaseException: pass """, @@ -802,8 +816,10 @@ def oom_run(label, thunk, window=_OOM_WINDOW): _set_nomemory(_OOM_DISABLE, 0) except MemoryError: pass - except SystemError: - print("[OOM-SEQ] SystemError in " + label, file=stderr) + except SystemError as _exc: + # Same bomb filter as oom_call above. + if not str(_exc).startswith("fusil "): + print("[OOM-SEQ] SystemError in " + label + ": " + repr(_exc), file=stderr) except BaseException: pass """, diff --git a/tests/python/test_oom_fuzz.py b/tests/python/test_oom_fuzz.py index 29f3a8b..8396ee3 100644 --- a/tests/python/test_oom_fuzz.py +++ b/tests/python/test_oom_fuzz.py @@ -151,10 +151,11 @@ def test_oom_mode_emits_harness_and_sweeps(self): self.assertNotIn("_remove_mem_hooks()", src) # Per-call marker (the pinpointing signal) and exception policy: - # MemoryError swallowed silently, SystemError surfaced. + # MemoryError swallowed silently, SystemError surfaced -- unless it is a bomb's own + # (see BombSystemErrorFilterTests, which drives the emitted handler). self.assertIn('print("[OOM] " + label', src) self.assertIn("except MemoryError:", src) - self.assertIn("except SystemError:", src) + self.assertIn("except SystemError as _exc:", src) # The old noisy "print every exception type" surfacing is gone. self.assertNotIn("print(type(_err).__name__)", src) @@ -352,5 +353,84 @@ def test_default_mode_uses_testcapi_not_shim(self): self.assertNotIn("fusil_malloc_arm", src) +class BombSystemErrorFilterTests(unittest.TestCase): + """The emitted ``oom_call`` must not report a BOMB's own SystemError. + + ``SystemError`` is one of ``_BOMB_EXCEPTIONS``, so every bomb built with ``exc=None`` + (``FailingIterator``, ``SuperBomb``, ``DescriptorBomb``, ``HiddenNameType`` ...) raises it + about one firing in twelve. The handler used to print ``[OOM] SystemError in