Skip to content

Commit 1ff0238

Browse files
authored
GH-113214: Fix SSLProto exception handling in SSL-over-SSL scenarios (#113334)
When wrapped, `_SSLProtocolTransport._force_close(exc)` is called just like in the unwrapped scenario `_SelectorTransport._force_close(exc)` or `_ProactorBasePipeTransport._force_close(exc)` would be called, except here the exception needs to be passed through the `SSLProtocol._abort()` method, which didn't accept an exception object. This commit ensures that this path works, in the same way that the uvloop implementation of SSLProto passes on the exception (on which the current implementation of SSLProto is based).
1 parent a3e8afe commit 1ff0238

File tree

3 files changed

+21
-8
lines changed

3 files changed

+21
-8
lines changed

Lib/asyncio/sslproto.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,12 @@ def abort(self):
243243
The protocol's connection_lost() method will (eventually) be
244244
called with None as its argument.
245245
"""
246-
self._closed = True
247-
if self._ssl_protocol is not None:
248-
self._ssl_protocol._abort()
246+
self._force_close(None)
249247

250248
def _force_close(self, exc):
251249
self._closed = True
252-
self._ssl_protocol._abort(exc)
250+
if self._ssl_protocol is not None:
251+
self._ssl_protocol._abort(exc)
253252

254253
def _test__append_write_backlog(self, data):
255254
# for test only
@@ -614,7 +613,7 @@ def _start_shutdown(self):
614613
if self._app_transport is not None:
615614
self._app_transport._closed = True
616615
if self._state == SSLProtocolState.DO_HANDSHAKE:
617-
self._abort()
616+
self._abort(None)
618617
else:
619618
self._set_state(SSLProtocolState.FLUSHING)
620619
self._shutdown_timeout_handle = self._loop.call_later(
@@ -661,10 +660,10 @@ def _on_shutdown_complete(self, shutdown_exc):
661660
else:
662661
self._loop.call_soon(self._transport.close)
663662

664-
def _abort(self):
663+
def _abort(self, exc):
665664
self._set_state(SSLProtocolState.UNWRAPPED)
666665
if self._transport is not None:
667-
self._transport.abort()
666+
self._transport._force_close(exc)
668667

669668
# Outgoing flow
670669

Lib/test/test_asyncio/test_sslproto.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def connection_made(self, ssl_proto, *, do_handshake=None):
4747
sslobj = mock.Mock()
4848
# emulate reading decompressed data
4949
sslobj.read.side_effect = ssl.SSLWantReadError
50+
sslobj.write.side_effect = ssl.SSLWantReadError
5051
if do_handshake is not None:
5152
sslobj.do_handshake = do_handshake
5253
ssl_proto._sslobj = sslobj
@@ -120,7 +121,19 @@ def test_close_during_handshake(self):
120121
test_utils.run_briefly(self.loop)
121122

122123
ssl_proto._app_transport.close()
123-
self.assertTrue(transport.abort.called)
124+
self.assertTrue(transport._force_close.called)
125+
126+
def test_close_during_ssl_over_ssl(self):
127+
# gh-113214: passing exceptions from the inner wrapped SSL protocol to the
128+
# shim transport provided by the outer SSL protocol should not raise
129+
# attribute errors
130+
outer = self.ssl_protocol(proto=self.ssl_protocol())
131+
self.connection_made(outer)
132+
# Closing the outer app transport should not raise an exception
133+
messages = []
134+
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
135+
outer._app_transport.close()
136+
self.assertEqual(messages, [])
124137

125138
def test_get_extra_info_on_closed_connection(self):
126139
waiter = self.loop.create_future()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix an ``AttributeError`` during asyncio SSL protocol aborts in SSL-over-SSL scenarios.

0 commit comments

Comments
 (0)