From ae637f9c7070c90fcdbe97a2b50f17b3476132ae Mon Sep 17 00:00:00 2001 From: devdanzin <74280297+devdanzin@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:45:12 -0300 Subject: [PATCH 1/3] two more fleet-noise sources: socket's int-as-fd family and logging's level constant Both measured on one PyPy --concurrency-stress fleet (158 827 sessions, 229 kept dirs). Together they account for 177 of those 229 dirs -- 77% of everything the fleet kept was fusil hurting itself or scoring an echo. socket.close/dup/fromfd/send_fds take a RAW INTEGER file descriptor, so any fuzz integer closes or reinterprets a descriptor the interpreter is still using. This is the int-as-FD analogue of the int-as-pointer family already excluded in CTYPES, and it is not a target defect: CPython aborts on it too, measured 1/6 against PyPy's 6/6 (the gap is window width, not correctness). 163 of 229 dirs, in two faces that split exactly on the value passed. 120 closed some other descriptor and were captured with glibc's own `Unexpected error 9 on netlink descriptor 11` -- 11 being socket.AF_ROSE, which the stress region had picked as a shared object. The other 43 were SIGABRTs with an EMPTY stdout, and all 43 of them shared a constant worth 0, 1 or 2: they had closed the child's own stdout or stderr, so the diagnostic had nowhere to go. That is why the bucket looked like two problems. The entry is module-keyed (BLACKLIST["socket"]), not name-based: `close` and `dup` are also methods on socket, file and many other objects, where they take no descriptor and are fine to fuzz. A METHOD_BLACKLIST entry would stop fusil ever closing anything. The second fix is a third face of the self-echo disease PR #265 addressed, and one that survived it. `warnings` prints the source line of whatever frame it fired in: /.../logging/__init__.py:1536: RuntimeWarning: coroutine '...' was never awaited self._log(CRITICAL, msg, args, **kwargs) so any un-awaited coroutine collected while logging is on the stack echoes logging's own source, and `CRITICAL` there is the level constant passed as an argument. It is neither a `File "...", line N, in ...` frame nor a `.critical(` call, so neither #265 rule reaches it: 14 kept dirs across asyncio.base_events, asyncio.streams and asyncio.selector_events. The new rule matches the constant only in an argument position and is case-sensitive, so a real record (`CRITICAL:root:...`) and English prose both still score. --- fusil/python/__init__.py | 16 +++++++++++ fusil/python/blacklists.py | 18 ++++++++++++ tests/python/test_blacklists.py | 24 ++++++++++++++++ tests/test_file_watch.py | 50 +++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/fusil/python/__init__.py b/fusil/python/__init__.py index 6f341e6..f72d650 100644 --- a/fusil/python/__init__.py +++ b/fusil/python/__init__.py @@ -1009,6 +1009,22 @@ def setupProject(self) -> None: # these prefixes; no diagnostic starts with them. r"^(\+\+\+|---|!!!) ", r"\.critical\(", + # Third face of the same disease, and the one that survived PR #265: `warnings` + # prints the source line of whatever frame was running when it fired -- + # + # /.../logging/__init__.py:1536: RuntimeWarning: coroutine '...' was never awaited + # self._log(CRITICAL, msg, args, **kwargs) + # + # -- so any un-awaited coroutine collected while logging is on the stack echoes + # `logging`'s own source, and `CRITICAL` there is the LEVEL CONSTANT being passed + # as an argument, not a diagnostic. 14 kept dirs in one PyPy fleet, across + # asyncio.base_events, asyncio.streams and asyncio.selector_events. + # + # Match the constant only where it sits in an argument position, so a real + # formatted record (`CRITICAL:root:...`) and English prose ("critical error") + # both still score. Case-sensitive on purpose: the all-caps spelling is the + # module constant. + r"[(,]\s*CRITICAL\s*[),]", # 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 efdb978..7a2f575 100644 --- a/fusil/python/blacklists.py +++ b/fusil/python/blacklists.py @@ -67,6 +67,24 @@ "getaddrinfo", "socket", "SocketType", + # The int-as-FD family: the same self-harm shape as the int-as-pointer functions in + # CTYPES above, one layer up. These four take a RAW INTEGER file descriptor + # (`close(integer) -> None`, `dup(integer) -> integer`, `fromfd(fd, family, type)`, + # `send_fds(sock, buffers, fds)`), so handing them any fuzz integer closes or + # reinterprets a descriptor the interpreter is still using. It is not a target defect: + # CPython aborts on it too. + # + # Measured, and it is not a small effect: 163 of 229 kept dirs in one PyPy + # --concurrency-stress fleet (71%) were this, in two faces that split exactly on the + # value passed. 120 closed some other descriptor and were captured with glibc's own + # `Unexpected error 9 on netlink descriptor 11` -- 11 being `socket.AF_ROSE`, which the + # stress region had picked as a shared object. The other 43 were SIGABRTs with an EMPTY + # stdout, and all 43 shared a constant whose value is 0, 1 or 2: they had closed the + # child's own stdout or stderr, so the diagnostic had nowhere to go. + "close", + "dup", + "fromfd", + "send_fds", } POSIX = { "_exit", diff --git a/tests/python/test_blacklists.py b/tests/python/test_blacklists.py index 47f1dd5..fdb6bd7 100644 --- a/tests/python/test_blacklists.py +++ b/tests/python/test_blacklists.py @@ -85,6 +85,30 @@ def test_pypy_blacklist_stays_narrow(self): for keep in ("newdict", "strategy", "internal_repr", "intop", "move_to_end"): self.assertNotIn(keep, bl.BLACKLIST["__pypy__"]) + def test_socket_int_as_fd_functions_blacklisted(self): + """socket.close/dup/fromfd/send_fds take a RAW INTEGER file descriptor. + + Handing them a fuzz integer closes or reinterprets a descriptor the interpreter is + still using -- the int-as-FD analogue of the int-as-pointer functions in CTYPES, and + self-harm rather than a target defect (CPython aborts on it too). It dominated a PyPy + --concurrency-stress fleet: 163 of 229 kept dirs, in two faces that split on the value + passed -- 120 carrying glibc's `Unexpected error 9 on netlink descriptor 11` + (`socket.AF_ROSE == 11`), and 43 SIGABRTs with an EMPTY stdout, every one of which had + been handed a constant worth 0, 1 or 2 and had closed its own stdout or stderr. + """ + self.assertLessEqual({"close", "dup", "fromfd", "send_fds"}, bl.BLACKLIST["socket"]) + + def test_socket_blacklist_keeps_the_socket_object_surface(self): + """Only the MODULE-level int-taking functions are excluded. + + `close` and `dup` are also METHODS on a socket object, where they take no descriptor + and are perfectly safe to fuzz. The entry is module-keyed for exactly that reason -- + putting these names in the name-based METHOD_BLACKLIST would silently stop fusil from + ever closing a socket, a file or anything else with a `close`. + """ + for name in ("close", "dup"): + self.assertNotIn(name, bl.METHOD_BLACKLIST) + def test_sys_trace_hooks_blacklisted(self): self.assertEqual( bl.BLACKLIST["sys"] & {"settrace", "setprofile"}, {"settrace", "setprofile"} diff --git a/tests/test_file_watch.py b/tests/test_file_watch.py index c4c7401..b86b790 100644 --- a/tests/test_file_watch.py +++ b/tests/test_file_watch.py @@ -356,6 +356,56 @@ def test_faulthandler_stack_lines_are_not_swallowed(self): self.assertEqual(w.score, 1.0) +class TestWarningSourceEchoIgnored(unittest.TestCase): + """`warnings` echoes the source of whatever frame it fired in; that source may score. + + The default warning formatter prints two lines -- a header naming a file and line, and + then that line's SOURCE, verbatim: + + /.../logging/__init__.py:1536: RuntimeWarning: coroutine '...' was never awaited + self._log(CRITICAL, msg, args, **kwargs) + + So an un-awaited coroutine collected while `logging` happens to be on the stack echoes + `logging`'s own source, and the `CRITICAL` in it is the level CONSTANT being passed as an + argument -- not a diagnostic. This is a third, distinct route to the same 1.0 word as the + traceback shapes above, and the traceback rules do not cover it: the line is neither a + `File "...", line N, in ...` frame nor a `.critical(` call. 14 kept dirs in one PyPy + fleet came in this way. + + The rule matches the constant only in an argument position, so a real formatted record + and English prose both still score. + """ + + ARG_CONSTANT = r"[(,]\s*CRITICAL\s*[),]" + + def _watch_with_rule(self): + w = _watch(words={"critical": 1.0}) + w.ignoreRegex(self.ARG_CONSTANT) + return w + + def test_logging_source_echo_is_ignored(self): + for line in ( + b" self._log(CRITICAL, msg, args, **kwargs)", + b" if self.isEnabledFor(CRITICAL):", + b" self.log(CRITICAL, msg, *args, **kwargs)", + ): + with self.subTest(line=line): + w = self._watch_with_rule() + self.assertIsNone(w.processLine(line)) + self.assertEqual(w.score, 0.0) + + def test_a_real_record_or_prose_still_scores(self): + for line in ( + b"CRITICAL:root:something exploded", + b"CRITICAL: target failed", + b"a critical error occurred in the target", + ): + with self.subTest(line=line): + w = self._watch_with_rule() + w.processLine(line) + self.assertEqual(w.score, 1.0) + + class TestCookiejarWarningIgnored(unittest.TestCase): """http.cookiejar's own "bug!" warning must not push a boring session over the threshold. From ada9abb21cb599a311d6688fa1e90fda68ccdadf Mon Sep 17 00:00:00 2001 From: devdanzin <74280297+devdanzin@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:53:34 -0300 Subject: [PATCH 2/3] socket int-as-fd: argue from the contract, not from a differential I cannot reproduce The first version of this comment said "CPython aborts on it too", carried over from the snapshot triage, which had measured PyPy 6/6 against CPython 1/6. Re-checking it before shipping: a standalone harness closing fd 11 while sibling threads call getaddrinfo is 0/6 on BOTH interpreters, across two fd ranges and two rates. The window evidently needs the full stress region, so that differential is not a number to stand behind here. Nothing about the disposition changes. `close(integer)` is documented as taking an integer and does what it is told on any interpreter; the captured message is glibc's own __libc_fatal reporting EBADF on a descriptor it owns; the manifests name the constant whose value is that descriptor; and 43 of 43 silent aborts carry a constant worth 0, 1 or 2. The case rests on that, and the comment now says so. --- fusil/python/blacklists.py | 7 +++++-- tests/python/test_blacklists.py | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/fusil/python/blacklists.py b/fusil/python/blacklists.py index 7a2f575..2c2d843 100644 --- a/fusil/python/blacklists.py +++ b/fusil/python/blacklists.py @@ -71,8 +71,11 @@ # CTYPES above, one layer up. These four take a RAW INTEGER file descriptor # (`close(integer) -> None`, `dup(integer) -> integer`, `fromfd(fd, family, type)`, # `send_fds(sock, buffers, fds)`), so handing them any fuzz integer closes or - # reinterprets a descriptor the interpreter is still using. It is not a target defect: - # CPython aborts on it too. + # reinterprets a descriptor the interpreter is still using. It is not a target defect -- + # the contract is the argument: `close(integer)` does what it is told, on any interpreter. + # (A standalone harness closing fd 11 while sibling threads call getaddrinfo did NOT + # reproduce the abort on either interpreter, so the window needs the full stress region; + # the case here rests on the evidence below, not on a differential.) # # Measured, and it is not a small effect: 163 of 229 kept dirs in one PyPy # --concurrency-stress fleet (71%) were this, in two faces that split exactly on the diff --git a/tests/python/test_blacklists.py b/tests/python/test_blacklists.py index fdb6bd7..2b2e764 100644 --- a/tests/python/test_blacklists.py +++ b/tests/python/test_blacklists.py @@ -90,7 +90,8 @@ def test_socket_int_as_fd_functions_blacklisted(self): Handing them a fuzz integer closes or reinterprets a descriptor the interpreter is still using -- the int-as-FD analogue of the int-as-pointer functions in CTYPES, and - self-harm rather than a target defect (CPython aborts on it too). It dominated a PyPy + self-harm rather than a target defect -- the contract is the argument, and `close(integer)` + does what it is told on any interpreter. It dominated a PyPy --concurrency-stress fleet: 163 of 229 kept dirs, in two faces that split on the value passed -- 120 carrying glibc's `Unexpected error 9 on netlink descriptor 11` (`socket.AF_ROSE == 11`), and 43 SIGABRTs with an EMPTY stdout, every one of which had From 1a7814e598c678da1832e4132160a5a41984f046 Mon Sep 17 00:00:00 2001 From: devdanzin <74280297+devdanzin@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:57:05 -0300 Subject: [PATCH 3/3] socket int-as-fd: state the silent-abort evidence as the measured asymmetry 'All 43 shared a 0/1/2 constant' is true but incomplete on its own: 57 of the 120 noisy dirs carry such a constant too, so having one is necessary, not sufficient. The asymmetry is what carries the argument -- of the 100 dirs with a std-fd constant 43 went silent, of the 63 without one none did. --- fusil/python/blacklists.py | 5 +++-- tests/python/test_blacklists.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/fusil/python/blacklists.py b/fusil/python/blacklists.py index 2c2d843..c315882 100644 --- a/fusil/python/blacklists.py +++ b/fusil/python/blacklists.py @@ -82,8 +82,9 @@ # value passed. 120 closed some other descriptor and were captured with glibc's own # `Unexpected error 9 on netlink descriptor 11` -- 11 being `socket.AF_ROSE`, which the # stress region had picked as a shared object. The other 43 were SIGABRTs with an EMPTY - # stdout, and all 43 shared a constant whose value is 0, 1 or 2: they had closed the - # child's own stdout or stderr, so the diagnostic had nowhere to go. + # stdout: they had closed the child's own stdout or stderr, so the diagnostic had nowhere + # to go. Of the 100 dirs whose shared constants include a 0, 1 or 2, 43 went silent; of the + # 63 that do not, none did. "close", "dup", "fromfd", diff --git a/tests/python/test_blacklists.py b/tests/python/test_blacklists.py index 2b2e764..0e07b6e 100644 --- a/tests/python/test_blacklists.py +++ b/tests/python/test_blacklists.py @@ -94,8 +94,9 @@ def test_socket_int_as_fd_functions_blacklisted(self): does what it is told on any interpreter. It dominated a PyPy --concurrency-stress fleet: 163 of 229 kept dirs, in two faces that split on the value passed -- 120 carrying glibc's `Unexpected error 9 on netlink descriptor 11` - (`socket.AF_ROSE == 11`), and 43 SIGABRTs with an EMPTY stdout, every one of which had - been handed a constant worth 0, 1 or 2 and had closed its own stdout or stderr. + (`socket.AF_ROSE == 11`), and 43 SIGABRTs with an EMPTY stdout, which had closed their own + stdout or stderr -- of the 100 dirs carrying a constant worth 0, 1 or 2, 43 went silent; + of the 63 without one, none did. """ self.assertLessEqual({"close", "dup", "fromfd", "send_fds"}, bl.BLACKLIST["socket"])