Skip to content

asyncio.sslproto.SSLProtocol exception handling triggers TypeError: SSLProtocol._abort() takes 1 positional argument but 2 were given #113214

Closed
@mjpieters

Description

@mjpieters

Bug report

Bug description:

There are several pathways to this bug, all which call asyncio.sslproto._SSLProtocolTransport._force_close() with an exception instance:

  • During shutdown, if the flushing state takes too long (asyncio.sslproto.SSLProtocolTransport._check_shutdown_timeout() is called)
  • Anything that triggers a call to 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:

Fatal error on SSL protocol
protocol: <asyncio.sslproto.SSLProtocol object at 0x7fe36f3a1350>
transport: <_SelectorSocketTransport closing fd=6 read=idle write=<idle, bufsize=0>>
Traceback (most recent call last):
  File ".../lib/python3.11/asyncio/sslproto.py", line 644, in _do_shutdown
    self._sslobj.unwrap()
  File ".../lib/python3.11/ssl.py", line 983, in unwrap
    return self._sslobj.shutdown()
           ^^^^^^^^^^^^^^^^^^^^^^^
ssl.SSLError: [SSL: APPLICATION_DATA_AFTER_CLOSE_NOTIFY] application data after close notify (_ssl.c:2702)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../lib/python3.11/asyncio/sslproto.py", line 731, in _do_read
    self._do_read__buffered()
  File ".../lib/python3.11/asyncio/sslproto.py", line 765, in _do_read__buffered
    self._app_protocol_buffer_updated(offset)
  File ".../lib/python3.11/asyncio/sslproto.py", line 445, in buffer_updated
    self._do_shutdown()
  File ".../lib/python3.11/asyncio/sslproto.py", line 648, in _do_shutdown
    self._on_shutdown_complete(exc)
  File ".../lib/python3.11/asyncio/sslproto.py", line 660, in _on_shutdown_complete
    self._fatal_error(shutdown_exc)
  File ".../lib/python3.11/asyncio/sslproto.py", line 911, in _fatal_error
    self._transport._force_close(exc)
  File ".../lib/python3.11/asyncio/sslproto.py", line 252, in _force_close
    self._ssl_protocol._abort(exc)
TypeError: SSLProtocol._abort() takes 1 positional argument but 2 were given

To me, the implementation of _SSLProtocolTransport._force_close() looks like an unfinished copy of the _SSLProtocolTransport.abort() method:

def abort(self):
"""Close the transport immediately.
Buffered data will be lost. No more data will be received.
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()
def _force_close(self, exc):
self._closed = True
self._ssl_protocol._abort(exc)

At any rate, the self._ssl_protocol attribute is an instance of SSLProtocol in the same module, and the _abort() method on that class doesn't accept an exception instance:

def _abort(self):
self._set_state(SSLProtocolState.UNWRAPPED)
if self._transport is not None:
self._transport.abort()

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions