Skip to content

Attach the passed socket before awaiting connection_made - #786

Open
bozhko-egor wants to merge 2 commits into
MagicStack:masterfrom
bozhko-egor:fix-create-connection-sock-early-close
Open

bozhko-egor wants to merge 2 commits into
MagicStack:masterfrom
bozhko-egor:fix-create-connection-sock-early-close

Conversation

@bozhko-egor

@bozhko-egor bozhko-egor commented Oct 26, 2026 •

Copy link
Copy Markdown

Fixes #785

create_connection(sock=) and create_unix_connection(sock=) attached the passed socket to the transport only after await waiter. If the peer resets the connection before the TLS ClientHello is written, connection_lost() closes the transport before the coroutine resumes. The socket was then never detached: it kept the closed fd number and closed it again when garbage-collected, possibly closing an unrelated connection that reused the fd.

This PR attaches the socket right after tr._open(), so every close path detaches it, as on the success path. The existing sock.detach() in the except branch is unchanged; if the socket is already detached, it returns -1.

The same bug happens without TLS when the protocol aborts the transport in connection_made(): create_connection(sock=) then returns normally, but the socket keeps the closed fd number.

Tests:

  • test_create_connection_sock_abort_detaches (tests/test_tcp.py) and test_create_unix_connection_sock_abort_detaches (tests/test_unix.py): the protocol calls transport.abort() in connection_made().
  • test_create_connection_ssl_sock_reset_detaches (tests/test_tcp.py): the server resets the connection with SO_LINGER 0, and the client calls create_connection(sock=, ssl=) after the RST has arrived.
  • test_create_unix_connection_ssl_sock_closed_detaches (tests/test_unix.py): the same for a Unix socket, where the ClientHello write fails with EPIPE.

All tests check sock.fileno() == -1 and run for uvloop and asyncio. On master the uvloop variants fail with 13 != -1 on Linux. With this change they pass (also with PYTHONASYNCIODEBUG=1), and the full test suite passes (python:3.12-slim, 535 tests).

AI disclosure: the fix and tests were made with AI assistance (Claude). I ran them as described above.

If the peer resets the connection before the TLS ClientHello is
written, connection_lost() closes the transport before
create_connection(sock=) resumes. The socket was attached only after
`await waiter`, so it was never detached: it kept the closed fd number
and closed it again when garbage-collected, possibly closing an
unrelated connection that reused the fd.

Attach the socket right after `tr._open()`, so every close path
detaches it. The same change is applied to create_unix_connection().

Fixes MagicStack#785
The same bug happens without TLS when the protocol aborts the
transport in connection_made(): create_connection(sock=) returns
normally, but the socket keeps the closed fd number.
@fed239

fed239 commented Oct 27, 2026

Copy link
Copy Markdown

@1st1 @elprans @fantix @graingert we need your help with this PR. We've spot the vulnerable behaviour in our application and we trust it affects other applications, so I'm calling for immediate attention. Thank you!

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.

create_connection(sock=, ssl=): reset before TLS handshake causes fd double-close and response leakage

2 participants