From 17723a58f59145d774afc66018129e437518cdaf Mon Sep 17 00:00:00 2001 From: ordinary-jamie <101677823+ordinary-jamie@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:47:41 +1100 Subject: [PATCH 1/2] gh-109534: fix reference leak when SSL handshake fails --- Lib/asyncio/selector_events.py | 4 ++++ Lib/asyncio/sslproto.py | 18 +++++++----------- ...4-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst | 3 +++ 3 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index dcd5e0aa345029..10fbdd76e93f79 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -235,6 +235,10 @@ async def _accept_connection2( await waiter except BaseException: transport.close() + # gh-109534: When an exception is raised by the SSLProtocol object the + # exception set in this future can keep the protocol object alive and + # cause a reference cycle. + waiter = None raise # It's now up to the protocol to handle the connection. diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py index 599e91ba0003d1..4781651e88a7ab 100644 --- a/Lib/asyncio/sslproto.py +++ b/Lib/asyncio/sslproto.py @@ -461,7 +461,7 @@ def eof_received(self): logger.debug("%r received EOF", self) if self._state == SSLProtocolState.DO_HANDSHAKE: - self._on_handshake_complete(ConnectionResetError) + self._on_handshake_complete(ConnectionResetError()) elif self._state == SSLProtocolState.WRAPPED: self._set_state(SSLProtocolState.FLUSHING) @@ -571,21 +571,17 @@ def _on_handshake_complete(self, handshake_exc): self._handshake_timeout_handle = None sslobj = self._sslobj - try: - if handshake_exc is None: - self._set_state(SSLProtocolState.WRAPPED) - else: - raise handshake_exc - + if handshake_exc is None: + self._set_state(SSLProtocolState.WRAPPED) peercert = sslobj.getpeercert() - except Exception as exc: + else: self._set_state(SSLProtocolState.UNWRAPPED) - if isinstance(exc, ssl.CertificateError): + if isinstance(handshake_exc, ssl.CertificateError): msg = 'SSL handshake failed on verifying the certificate' else: msg = 'SSL handshake failed' - self._fatal_error(exc, msg) - self._wakeup_waiter(exc) + self._fatal_error(handshake_exc, msg) + self._wakeup_waiter(handshake_exc) return if self._loop.get_debug(): diff --git a/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst b/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst new file mode 100644 index 00000000000000..fc9a765a230037 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-15-18-42-44.gh-issue-109534.wYaLMZ.rst @@ -0,0 +1,3 @@ +Fix a reference leak in +:class:`asyncio.selector_events.BaseSelectorEventLoop` when SSL handshakes +fail. Patch contributed by Jamie Phan. From e77579de6705a7bb590c179dc37d4d752878c0d4 Mon Sep 17 00:00:00 2001 From: ordinary-jamie <101677823+ordinary-jamie@users.noreply.github.com> Date: Tue, 16 Jan 2024 06:52:09 +1100 Subject: [PATCH 2/2] Revert _on_handshake_complete changes and clear exception ref --- Lib/asyncio/sslproto.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py index 4781651e88a7ab..fa99d4533aa0a6 100644 --- a/Lib/asyncio/sslproto.py +++ b/Lib/asyncio/sslproto.py @@ -461,7 +461,7 @@ def eof_received(self): logger.debug("%r received EOF", self) if self._state == SSLProtocolState.DO_HANDSHAKE: - self._on_handshake_complete(ConnectionResetError()) + self._on_handshake_complete(ConnectionResetError) elif self._state == SSLProtocolState.WRAPPED: self._set_state(SSLProtocolState.FLUSHING) @@ -571,17 +571,22 @@ def _on_handshake_complete(self, handshake_exc): self._handshake_timeout_handle = None sslobj = self._sslobj - if handshake_exc is None: - self._set_state(SSLProtocolState.WRAPPED) + try: + if handshake_exc is None: + self._set_state(SSLProtocolState.WRAPPED) + else: + raise handshake_exc + peercert = sslobj.getpeercert() - else: + except Exception as exc: + handshake_exc = None self._set_state(SSLProtocolState.UNWRAPPED) - if isinstance(handshake_exc, ssl.CertificateError): + if isinstance(exc, ssl.CertificateError): msg = 'SSL handshake failed on verifying the certificate' else: msg = 'SSL handshake failed' - self._fatal_error(handshake_exc, msg) - self._wakeup_waiter(handshake_exc) + self._fatal_error(exc, msg) + self._wakeup_waiter(exc) return if self._loop.get_debug():