Skip to content

Reject a None loop in Server's constructor instead of crashing later - #762

Open
afonsojanu wants to merge 1 commit into
MagicStack:masterfrom
afonsojanu:fix/server-none-loop-crash
Open

Reject a None loop in Server's constructor instead of crashing later#762
afonsojanu wants to merge 1 commit into
MagicStack:masterfrom
afonsojanu:fix/server-none-loop-crash

Conversation

@afonsojanu

Copy link
Copy Markdown

Fixes #760

uvloop.loop.Server is only ever constructed internally with Server(self), but the constructor accepted anything since Loop loop doesn't say not None. As reported, Server(None) builds fine, and calling close() on it segfaults instead of raising, because self._loop is a typed cdef attribute and Cython's attribute access on it skips the usual None check that a plain Python object reference would get. The crash happens deep inside _unref(), well past where the bad input was actually supplied.

Traced the actual path with the ASan trace from the issue: close() calls _unref() in its finally block, which does self._loop._servers.discard(self). With self._loop set to None, that ends up dereferencing garbage instead of raising AttributeError the way ordinary Python code would, which lines up with the PyType_IsSubtype / __Pyx_PySet_Discard frames in the report.

Marking the parameter Loop loop not None makes the constructor reject bad input immediately with a clean TypeError, which is what the reporter expected in the first place. Checked both call sites in loop.pyx (create_server and the Unix socket path) and they already pass self, a real Loop, so this doesn't affect any real usage.

Reproduced the crash locally first (built from source with the constructor unchanged, confirmed Server(None); server.close() segfaults with exit code 139) before writing the fix, then confirmed the same reproducer raises a clean TypeError afterward. Added test_server_with_none_loop_raises_instead_of_crashing in tests/test_regr1.py, run in a subprocess since without the fix it takes down the whole interpreter rather than failing normally. Reverting just the constructor change makes that test fail with the SIGSEGV assertion, confirming it actually exercises the bug.

Ran the full tests/test_tcp.py suite (112 tests) afterward with no regressions, and flake8 on the touched test file is clean.

uvloop.loop.Server is constructed internally with Loop(self) in a
couple of places, but nothing stopped it from being called directly
with None. That's not a realistic usage pattern, but Cython's typed
attribute access on self._loop skips the usual None check once it's
stored, so calling close() on a Server built this way segfaults deep
inside _unref() instead of raising anything.

Marking the loop parameter not None makes the constructor itself
reject it right away with a clean TypeError, matching how the type
is already declared everywhere it's used internally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Directly constructed uvloop.loop.Server(None) segfaults in close()

1 participant