-
-
Notifications
You must be signed in to change notification settings - Fork 31.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
asyncio.sslproto.SSLProtocol exception handling triggers TypeError: SSLProtocol._abort() takes 1 positional argument but 2 were given #113214
Comments
Ah, I just found the trail to UVLoop, and I see that there the implementation of The implementation of
and the implementation in
Is there any reason why the Python port didn't follow this same pattern? |
Hallo Martijn,
I doubt that anyone can answer your question. Andrew has been distracted by a war, and Yury (uvloop creator) by a company. Kumar I believe is in his final year of school. I personally refuse to learn anything about SSL, its complexity just depresses me. Nevertheless, I read your bug description and, well, SSL is still bizarre. I think the best fix at this point is to do as you say -- don't pass the exception to If you don't have a test to trigger the bug, so be it. It's only a three-character fix, and you've convinced me that it doesn't make things worse -- possibly the code will crash a few lines later, but so be it. |
Hi Guido,
There is actually other code that calls I'll try to take a look at the original uvloop PR and may have to face the test suite anyway. If I can't get round to that in the next week I'll cop out and just make a PR for the 3-character change. |
Okay, thanks and good luck! |
After a little bit of time with the code today I understand much better what is going on. First of all: the SSLProtocol implementation is, basically, private to the event loop implementations. It is shared between the proactor and selector event loops, and it has knowledge of the internal private methods of the transport implementations used by each loop. This includes the Either event loop implementation will insert the SSLProtocol in between a normal application protocol object and the socket transport when you include a ssl context. Normally the relationship between protocol, transport and socket is simply this:
but with ssl, this becomes
where The most important detail that I stumbled over is what What goes wrong here is that But my case is special because I was using SSL-over-SSL, in the form of a HTTPS proxy used by aiohttp (passing in the shim
SSLProtocol does know about the internal methods of the transport classes and so calls To correct this, the def _abort(self, exc):
self._set_state(SSLProtocolState.UNWRAPPED)
if self._transport is not None:
self._transport._force_close(exc) Note that this calls There is a bit more to a patch in that there are other calls to the same ff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py
index 3eb65a8a08b..cbb6527d0b2 100644
--- a/Lib/asyncio/sslproto.py
+++ b/Lib/asyncio/sslproto.py
@@ -243,13 +243,12 @@ def abort(self):
The protocol's connection_lost() method will (eventually) be
called with None as its argument.
"""
- self._closed = True
- if self._ssl_protocol is not None:
- self._ssl_protocol._abort()
+ self._force_close(None)
def _force_close(self, exc):
self._closed = True
- self._ssl_protocol._abort(exc)
+ if self._ssl_protocol is not None:
+ self._ssl_protocol._abort(exc)
def _test__append_write_backlog(self, data):
# for test only
@@ -614,7 +613,7 @@ def _start_shutdown(self):
if self._app_transport is not None:
self._app_transport._closed = True
if self._state == SSLProtocolState.DO_HANDSHAKE:
- self._abort()
+ self._abort(None)
else:
self._set_state(SSLProtocolState.FLUSHING)
self._shutdown_timeout_handle = self._loop.call_later(
@@ -661,10 +660,10 @@ def _on_shutdown_complete(self, shutdown_exc):
else:
self._loop.call_soon(self._transport.close)
- def _abort(self):
+ def _abort(self, exc):
self._set_state(SSLProtocolState.UNWRAPPED)
if self._transport is not None:
- self._transport.abort()
+ self._transport._force_close(exc)
# Outgoing flow My next step is to try and port the uvloop |
Thanks for the analysis, that looks convincing. Do you think you can come up with a new test case that exercises this code path? That would be ideal, since it would prove the bug exists in the previous version and is fixed by the patch. We should probably backport this to 3.12 and 3.11, right? |
I certainly was hoping I could; implement a SSL-over-SSL test case, verify that it fails without the change, and succeeds with it.
Yes, I came to this issue via a 3.11 project. |
Right, the test case is actually a lot simpler than I anticipated; closing a wrapped protocol transport is all that's needed:
This reproduces the exception. It actually produces 2 exceptions, but the other is an artefact of the ssl object being a mock and can be silenced by adding a I'll wrap this in a pull request next. |
…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).
…arios (pythonGH-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). (cherry picked from commit 1ff0238) Co-authored-by: Martijn Pieters <[email protected]>
…arios (pythonGH-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). (cherry picked from commit 1ff0238) Co-authored-by: Martijn Pieters <[email protected]>
…narios (GH-113334) (#113339) 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). (cherry picked from commit 1ff0238) Co-authored-by: Martijn Pieters <[email protected]>
…narios (GH-113334) (#113340) 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). (cherry picked from commit 1ff0238) Co-authored-by: Martijn Pieters <[email protected]>
…arios (python#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).
…arios (python#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).
…arios (python#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).
…arios (python#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).
Bug report
Bug description:
There are several pathways to this bug, all which call
asyncio.sslproto._SSLProtocolTransport._force_close()
with an exception instance:asyncio.sslproto.SSLProtocolTransport._check_shutdown_timeout()
is called)asyncio.sslproto.SSLProtocol._fatal_error()
, e.g. SSL handshake timeout or exception, SSL shutdown timeout or exception, an exception during reading, exception raised in the app transport EOF handler, etc.I'm seeing this when using a HTTPS proxy with a aiohttp client session (which wraps TLS in TLS), but I don't think it is specific to that context. I'm seeing these tracebacks:
To me, the implementation of
_SSLProtocolTransport._force_close()
looks like an unfinished copy of the_SSLProtocolTransport.abort()
method:cpython/Lib/asyncio/sslproto.py
Lines 239 to 252 in 1583c40
At any rate, the
self._ssl_protocol
attribute is an instance ofSSLProtocol
in the same module, and the_abort()
method on that class doesn't accept an exception instance:cpython/Lib/asyncio/sslproto.py
Lines 664 to 667 in 1583c40
I find the test suite surrounding the SSL protocol to be dense enough that I can't easily spot how to provide an update there to reproduce this issue more easily, but the fix looks simple enough: don't pass an argument to
_abort()
.CPython versions tested on:
3.11, 3.12, CPython main branch
Operating systems tested on:
Linux
Linked PRs
The text was updated successfully, but these errors were encountered: