Skip to content

Commit 33dcd36

Browse files
committed
gh-95494: Fix transport EOF handling in OpenSSL 3.0
GH-25309 enabled SSL_OP_IGNORE_UNEXPECTED_EOF by default, with a comment that it restores OpenSSL 1.1.1 behavior, but this wasn't quite right. That option causes OpenSSL to treat transport EOF as the same as close_notify (i.e. SSL_ERROR_ZERO_RETURN), whereas Python actually has distinct SSLEOFError and SSLZeroReturnError exceptions. (The latter is usually mapped to a zero return from read.) In OpenSSL 1.1.1, the ssl module would raise them for transport EOF and close_notify, respectively. In OpenSSL 3.0, both act like close_notify. Fix this by, instead, just detecting SSL_R_UNEXPECTED_EOF_WHILE_READING and mapping that to the other exception type. There doesn't seem to have been any unit test of this error, so fill in the missing one. This had to be done with the BIO path because it's actually slightly tricky to simulate a transport EOF with Python's fd based APIs. (If you instruct the server to close the socket, it gets confused, probably because the server's SSL object is still referencing the now dead fd?)
1 parent 9ec6abf commit 33dcd36

File tree

2 files changed

+24
-7
lines changed

2 files changed

+24
-7
lines changed

Lib/test/test_ssl.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ def data_file(*name):
153153
OP_SINGLE_ECDH_USE = getattr(ssl, "OP_SINGLE_ECDH_USE", 0)
154154
OP_CIPHER_SERVER_PREFERENCE = getattr(ssl, "OP_CIPHER_SERVER_PREFERENCE", 0)
155155
OP_ENABLE_MIDDLEBOX_COMPAT = getattr(ssl, "OP_ENABLE_MIDDLEBOX_COMPAT", 0)
156-
OP_IGNORE_UNEXPECTED_EOF = getattr(ssl, "OP_IGNORE_UNEXPECTED_EOF", 0)
157156

158157
# Ubuntu has patched OpenSSL and changed behavior of security level 2
159158
# see https://bugs.python.org/issue41561#msg389003
@@ -960,8 +959,7 @@ def test_options(self):
960959
# SSLContext also enables these by default
961960
default |= (OP_NO_COMPRESSION | OP_CIPHER_SERVER_PREFERENCE |
962961
OP_SINGLE_DH_USE | OP_SINGLE_ECDH_USE |
963-
OP_ENABLE_MIDDLEBOX_COMPAT |
964-
OP_IGNORE_UNEXPECTED_EOF)
962+
OP_ENABLE_MIDDLEBOX_COMPAT)
965963
self.assertEqual(default, ctx.options)
966964
with warnings_helper.check_warnings():
967965
ctx.options |= ssl.OP_NO_TLSv1
@@ -2120,6 +2118,20 @@ def test_bio_read_write_data(self):
21202118
self.assertEqual(buf, b'foo\n')
21212119
self.ssl_io_loop(sock, incoming, outgoing, sslobj.unwrap)
21222120

2121+
def test_transport_eof(self):
2122+
client_context, server_context, hostname = testing_context()
2123+
with socket.socket(socket.AF_INET) as sock:
2124+
sock.connect(self.server_addr)
2125+
incoming = ssl.MemoryBIO()
2126+
outgoing = ssl.MemoryBIO()
2127+
sslobj = client_context.wrap_bio(incoming, outgoing,
2128+
server_hostname=hostname)
2129+
self.ssl_io_loop(sock, incoming, outgoing, sslobj.do_handshake)
2130+
2131+
# Simulate EOF from the transport.
2132+
incoming.write_eof()
2133+
self.assertRaises(ssl.SSLEOFError, sslobj.read)
2134+
21232135

21242136
@support.requires_resource('network')
21252137
class NetworkedTests(unittest.TestCase):

Modules/_ssl.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,15 @@ PySSL_SetError(PySSLSocket *sslsock, int ret, const char *filename, int lineno)
662662
ERR_GET_REASON(e) == SSL_R_CERTIFICATE_VERIFY_FAILED) {
663663
type = state->PySSLCertVerificationErrorObject;
664664
}
665+
#if defined(SSL_R_UNEXPECTED_EOF_WHILE_READING)
666+
/* OpenSSL 3.0 changed transport EOF from SSL_ERROR_SYSCALL with
667+
* zero return value to SSL_ERROR_SSL with a special error code. */
668+
if (ERR_GET_LIB(e) == ERR_LIB_SSL &&
669+
ERR_GET_REASON(e) == SSL_R_UNEXPECTED_EOF_WHILE_READING) {
670+
type = state->PySSLEOFErrorObject;
671+
errstr = "EOF occurred in violation of protocol";
672+
}
673+
#endif
665674
break;
666675
}
667676
default:
@@ -3110,10 +3119,6 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
31103119
#endif
31113120
#ifdef SSL_OP_SINGLE_ECDH_USE
31123121
options |= SSL_OP_SINGLE_ECDH_USE;
3113-
#endif
3114-
#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF
3115-
/* Make OpenSSL 3.0.0 behave like 1.1.1 */
3116-
options |= SSL_OP_IGNORE_UNEXPECTED_EOF;
31173122
#endif
31183123
SSL_CTX_set_options(self->ctx, options);
31193124

0 commit comments

Comments
 (0)