-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
Ignore specific errors when closing ssl connections #84132
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
Comments
When a connection wrapped in ssl is closed, sometimes the ssl library reports an error, which I believe should be ignored. The error code is The explanation for openssl reporting this error is here:
This situation is easily achieved, because of network delays. Just because we sent "close notify", doesn't mean the other end has received it, and even if it did, there could still be return data in flight. Reproducer is here: https://gist.github.com/dimaqq/087c66dd7b4a85a669a00221dc3792ea |
Reproducer: """ Reproducer for BPO-39951 host = "nghttp2.org"
port = 443
ssl_context = ssl.create_default_context()
ssl_context.options |= ssl.OP_NO_TLSv1
ssl_context.options |= ssl.OP_NO_TLSv1_1
ssl_context.set_alpn_protocols(["h2"])
# Captured from an HTTP/2 client
DATA = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n\x00\x00*\x04\x00\x00\x00\x00\x00\x00\x01\x00\x00\x10\x00\x00\x02\x00\x00\x00\x00\x00\x04\x00\x00\xff\xff\x00\x05\x00\x00@\x00\x00\x08\x00\x00\x00\x00\x00\x03\x00\x10\x00\x00\x00\x06\x00\x00\xff\xff\x00\x00\x04\x08\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x00\x00'
async def test():
r, w = await asyncio.open_connection(host, port, ssl=ssl_context)
info = w.get_extra_info("ssl_object")
assert info, "HTTP/2 server is required"
proto = info.selected_alpn_protocol()
assert proto == "h2", "Failed to negotiate HTTP/2"
w.write(DATA)
w.close()
await w.wait_closed()
asyncio.run(test()) Test on macOS, using cpython builds from python.org: … > python3.7 repro-bpo-39951.py
Traceback (most recent call last):
File "repro-bpo-39951.py", line 34, in <module>
asyncio.run(test())
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
return future.result()
File "repro-bpo-39951.py", line 31, in test
await w.wait_closed()
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/streams.py", line 323, in wait_closed
await self._protocol._closed
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/sslproto.py", line 530, in data_received
ssldata, appdata = self._sslpipe.feed_ssldata(data)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/sslproto.py", line 207, in feed_ssldata
self._sslobj.unwrap()
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ssl.py", line 778, in unwrap
return self._sslobj.shutdown()
ssl.SSLError: [SSL: KRB5_S_INIT] application data after close notify (_ssl.c:2629)
… > python3.8 repro-bpo-39951.py
Traceback (most recent call last):
File "repro-bpo-39951.py", line 34, in <module>
asyncio.run(test())
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
return future.result()
File "repro-bpo-39951.py", line 31, in test
await w.wait_closed()
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/streams.py", line 359, in wait_closed
await self._protocol._get_close_waiter(self)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/sslproto.py", line 529, in data_received
ssldata, appdata = self._sslpipe.feed_ssldata(data)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/sslproto.py", line 207, in feed_ssldata
self._sslobj.unwrap()
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py", line 948, in unwrap
return self._sslobj.shutdown()
ssl.SSLError: [SSL: KRB5_S_INIT] application data after close notify (_ssl.c:2730)
… > python3.9 repro-bpo-39951.py
Traceback (most recent call last):
File "/.../repro-bpo-39951.py", line 34, in <module>
asyncio.run(test())
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/.../repro-bpo-39951.py", line 31, in test
await w.wait_closed()
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/streams.py", line 359, in wait_closed
await self._protocol._get_close_waiter(self)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/sslproto.py", line 529, in data_received
ssldata, appdata = self._sslpipe.feed_ssldata(data)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/sslproto.py", line 207, in feed_ssldata
self._sslobj.unwrap()
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/ssl.py", line 948, in unwrap
return self._sslobj.shutdown()
ssl.SSLError: [SSL: KRB5_S_INIT] application data after close notify (_ssl.c:2730) Test on Linux (python:3.8.1-alpine3.11): / # python repro.py
Traceback (most recent call last):
File "repro.py", line 33, in <module>
asyncio.run(test())
File "/usr/local/lib/python3.8/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 612, in run_until_complete
return future.result()
File "repro.py", line 30, in test
await w.wait_closed()
File "/usr/local/lib/python3.8/asyncio/streams.py", line 359, in wait_closed
await self._protocol._get_close_waiter(self)
File "/usr/local/lib/python3.8/asyncio/sslproto.py", line 529, in data_received
ssldata, appdata = self._sslpipe.feed_ssldata(data)
File "/usr/local/lib/python3.8/asyncio/sslproto.py", line 207, in feed_ssldata
self._sslobj.unwrap()
File "/usr/local/lib/python3.8/ssl.py", line 948, in unwrap
return self._sslobj.shutdown()
ssl.SSLError: [SSL: KRB5_S_INIT] application data after close notify (_ssl.c:2730) |
Let's close this in favour of https://bugs.python.org/issue39953 which has a pending pull request #19082 |
Sorry I was too fast to close this. 39953 is about error codes. This bug is about having an error at all. I believe that the code in question should pass without error, even in the presence of network delays. |
https://bugs.python.org/issue39953 has landed and the errors are now more sensible: Python 3.7.8 [SSL: KRB5_S_INIT] application data after close notify I guess I shall work on a patch to ignore this specific error when closing the connection. I would appreciate guidance on half-closed state that, it seems, is possible since TLS 1.3 🤔 |
This should/will be fixed by #62175 I think - like suggested in the OpenSSL comments, the proposed change will always try to run SSL_read() before SSL_shutdown(), even after the close_notify is sent. |
@fantix alas, no: ~/cpython (asvetlov--new-ssl|✚1) [1] > ./python.exe ~/repro-39951.py
Traceback (most recent call last):
File "/Users/dima.tisnek/repro-39951.py", line 33, in <module>
asyncio.run(test())
File "/Users/dima.tisnek/cpython/Lib/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "/Users/dima.tisnek/cpython/Lib/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/Users/dima.tisnek/repro-39951.py", line 30, in test
await w.wait_closed()
File "/Users/dima.tisnek/cpython/Lib/asyncio/streams.py", line 359, in wait_closed
await self._protocol._get_close_waiter(self)
File "/Users/dima.tisnek/cpython/Lib/asyncio/sslproto.py", line 638, in _do_shutdown
self._sslobj.unwrap()
File "/Users/dima.tisnek/cpython/Lib/ssl.py", line 948, in unwrap
return self._sslobj.shutdown()
ssl.SSLError: [SSL: KRB5_S_INIT] application data after close notify (_ssl.c:2730) |
Added 3.10 target. |
3.7 is in security fix-only mode. APPLICATION_DATA_AFTER_CLOSE_NOTIFY is a protocol violation in any TLS version. It's not related to TLS 1.3. The error occurs when one side wants to close the connection, but the other sides keeps sending user data. It's bug in higher level application could. The ssl module cannot ignore this error condition. |
Thank you, Christian, for removing 3.7 target, I was not up to date on Python support schedule. Regarding protocol violation, let me explain what I've dug up so far... I am not an expert, please feel free to correct me. TLS up to 1.2 TLS 1.3 and later |
TLS 1.2 has one-way close notify. For example typical HTTP clients like curl send a close_notify and then shut down the TCP connection. HTTP servers may not reply with close_notify or may not wait for the client to confirm the server-side close notify. Python's ssl module does not support one-way close yet. It's an unfortunate limitation of the API that predates my involvement in the ssl module. The unwrap() methods always performs a blocking two-way shutdown. unwrap() calls SSL_shutdown() twice to downgrade a TLS connection to a plain TCP connection. The unwrap() API also requires cooperation from both parties. https://tools.ietf.org/html/rfc5246#section-7.2.1 |
The bug should be fixed in 3.9 and 3.10 maintenance branches, too. |
The TODO is fixed on all supported versions python/cpython#84132
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: