Skip to content

Commit f8cba75

Browse files
miss-islingtonserhiy-storchakagraingert
authored
[3.11] gh-113280: Always close socket if SSLSocket creation failed (GH-114659) (GH-114996)
(cherry picked from commit 0ea3662) Co-authored-by: Serhiy Storchaka <[email protected]> Co-authored-by: Thomas Grainger <[email protected]>
1 parent 55e5ae7 commit f8cba75

File tree

3 files changed

+78
-64
lines changed

3 files changed

+78
-64
lines changed

Lib/ssl.py

Lines changed: 53 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,71 +1031,67 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
10311031
if context.check_hostname and not server_hostname:
10321032
raise ValueError("check_hostname requires server_hostname")
10331033

1034+
sock_timeout = sock.gettimeout()
10341035
kwargs = dict(
10351036
family=sock.family, type=sock.type, proto=sock.proto,
10361037
fileno=sock.fileno()
10371038
)
10381039
self = cls.__new__(cls, **kwargs)
10391040
super(SSLSocket, self).__init__(**kwargs)
1040-
sock_timeout = sock.gettimeout()
10411041
sock.detach()
1042-
1043-
self._context = context
1044-
self._session = session
1045-
self._closed = False
1046-
self._sslobj = None
1047-
self.server_side = server_side
1048-
self.server_hostname = context._encode_hostname(server_hostname)
1049-
self.do_handshake_on_connect = do_handshake_on_connect
1050-
self.suppress_ragged_eofs = suppress_ragged_eofs
1051-
1052-
# See if we are connected
1042+
# Now SSLSocket is responsible for closing the file descriptor.
10531043
try:
1054-
self.getpeername()
1055-
except OSError as e:
1056-
if e.errno != errno.ENOTCONN:
1057-
raise
1058-
connected = False
1059-
blocking = self.getblocking()
1060-
self.setblocking(False)
1044+
self._context = context
1045+
self._session = session
1046+
self._closed = False
1047+
self._sslobj = None
1048+
self.server_side = server_side
1049+
self.server_hostname = context._encode_hostname(server_hostname)
1050+
self.do_handshake_on_connect = do_handshake_on_connect
1051+
self.suppress_ragged_eofs = suppress_ragged_eofs
1052+
1053+
# See if we are connected
10611054
try:
1062-
# We are not connected so this is not supposed to block, but
1063-
# testing revealed otherwise on macOS and Windows so we do
1064-
# the non-blocking dance regardless. Our raise when any data
1065-
# is found means consuming the data is harmless.
1066-
notconn_pre_handshake_data = self.recv(1)
1055+
self.getpeername()
10671056
except OSError as e:
1068-
# EINVAL occurs for recv(1) on non-connected on unix sockets.
1069-
if e.errno not in (errno.ENOTCONN, errno.EINVAL):
1057+
if e.errno != errno.ENOTCONN:
10701058
raise
1071-
notconn_pre_handshake_data = b''
1072-
self.setblocking(blocking)
1073-
if notconn_pre_handshake_data:
1074-
# This prevents pending data sent to the socket before it was
1075-
# closed from escaping to the caller who could otherwise
1076-
# presume it came through a successful TLS connection.
1077-
reason = "Closed before TLS handshake with data in recv buffer."
1078-
notconn_pre_handshake_data_error = SSLError(e.errno, reason)
1079-
# Add the SSLError attributes that _ssl.c always adds.
1080-
notconn_pre_handshake_data_error.reason = reason
1081-
notconn_pre_handshake_data_error.library = None
1082-
try:
1083-
self.close()
1084-
except OSError:
1085-
pass
1059+
connected = False
1060+
blocking = self.getblocking()
1061+
self.setblocking(False)
10861062
try:
1087-
raise notconn_pre_handshake_data_error
1088-
finally:
1089-
# Explicitly break the reference cycle.
1090-
notconn_pre_handshake_data_error = None
1091-
else:
1092-
connected = True
1063+
# We are not connected so this is not supposed to block, but
1064+
# testing revealed otherwise on macOS and Windows so we do
1065+
# the non-blocking dance regardless. Our raise when any data
1066+
# is found means consuming the data is harmless.
1067+
notconn_pre_handshake_data = self.recv(1)
1068+
except OSError as e:
1069+
# EINVAL occurs for recv(1) on non-connected on unix sockets.
1070+
if e.errno not in (errno.ENOTCONN, errno.EINVAL):
1071+
raise
1072+
notconn_pre_handshake_data = b''
1073+
self.setblocking(blocking)
1074+
if notconn_pre_handshake_data:
1075+
# This prevents pending data sent to the socket before it was
1076+
# closed from escaping to the caller who could otherwise
1077+
# presume it came through a successful TLS connection.
1078+
reason = "Closed before TLS handshake with data in recv buffer."
1079+
notconn_pre_handshake_data_error = SSLError(e.errno, reason)
1080+
# Add the SSLError attributes that _ssl.c always adds.
1081+
notconn_pre_handshake_data_error.reason = reason
1082+
notconn_pre_handshake_data_error.library = None
1083+
try:
1084+
raise notconn_pre_handshake_data_error
1085+
finally:
1086+
# Explicitly break the reference cycle.
1087+
notconn_pre_handshake_data_error = None
1088+
else:
1089+
connected = True
10931090

1094-
self.settimeout(sock_timeout) # Must come after setblocking() calls.
1095-
self._connected = connected
1096-
if connected:
1097-
# create the SSL object
1098-
try:
1091+
self.settimeout(sock_timeout) # Must come after setblocking() calls.
1092+
self._connected = connected
1093+
if connected:
1094+
# create the SSL object
10991095
self._sslobj = self._context._wrap_socket(
11001096
self, server_side, self.server_hostname,
11011097
owner=self, session=self._session,
@@ -1106,9 +1102,12 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
11061102
# non-blocking
11071103
raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
11081104
self.do_handshake()
1109-
except (OSError, ValueError):
1105+
except:
1106+
try:
11101107
self.close()
1111-
raise
1108+
except OSError:
1109+
pass
1110+
raise
11121111
return self
11131112

11141113
@property

Lib/test/test_ssl.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2419,14 +2419,15 @@ def _test_get_server_certificate(test, host, port, cert=None):
24192419
sys.stdout.write("\nVerified certificate for %s:%s is\n%s\n" % (host, port ,pem))
24202420

24212421
def _test_get_server_certificate_fail(test, host, port):
2422-
try:
2423-
pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE)
2424-
except ssl.SSLError as x:
2425-
#should fail
2426-
if support.verbose:
2427-
sys.stdout.write("%s\n" % x)
2428-
else:
2429-
test.fail("Got server certificate %s for %s:%s!" % (pem, host, port))
2422+
with warnings_helper.check_no_resource_warning(test):
2423+
try:
2424+
pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE)
2425+
except ssl.SSLError as x:
2426+
#should fail
2427+
if support.verbose:
2428+
sys.stdout.write("%s\n" % x)
2429+
else:
2430+
test.fail("Got server certificate %s for %s:%s!" % (pem, host, port))
24302431

24312432

24322433
from test.ssl_servers import make_https_server
@@ -3239,6 +3240,16 @@ def test_check_hostname_idn(self):
32393240
server_hostname="python.example.org") as s:
32403241
with self.assertRaises(ssl.CertificateError):
32413242
s.connect((HOST, server.port))
3243+
with ThreadedEchoServer(context=server_context, chatty=True) as server:
3244+
with warnings_helper.check_no_resource_warning(self):
3245+
with self.assertRaises(UnicodeError):
3246+
context.wrap_socket(socket.socket(),
3247+
server_hostname='.pythontest.net')
3248+
with ThreadedEchoServer(context=server_context, chatty=True) as server:
3249+
with warnings_helper.check_no_resource_warning(self):
3250+
with self.assertRaises(UnicodeDecodeError):
3251+
context.wrap_socket(socket.socket(),
3252+
server_hostname=b'k\xf6nig.idn.pythontest.net')
32423253

32433254
def test_wrong_cert_tls12(self):
32443255
"""Connecting when the server rejects the client's certificate
@@ -5116,7 +5127,8 @@ def call_after_accept(conn_to_client):
51165127
self.assertIsNone(wrap_error.library, msg="attr must exist")
51175128
finally:
51185129
# gh-108342: Explicitly break the reference cycle
5119-
wrap_error = None
5130+
with warnings_helper.check_no_resource_warning(self):
5131+
wrap_error = None
51205132
server = None
51215133

51225134
def test_https_client_non_tls_response_ignored(self):
@@ -5165,7 +5177,8 @@ def call_after_accept(conn_to_client):
51655177
# socket; that fails if the connection is broken. It may seem pointless
51665178
# to test this. It serves as an illustration of something that we never
51675179
# want to happen... properly not happening.
5168-
with self.assertRaises(OSError):
5180+
with warnings_helper.check_no_resource_warning(self), \
5181+
self.assertRaises(OSError):
51695182
connection.request("HEAD", "/test", headers={"Host": "localhost"})
51705183
response = connection.getresponse()
51715184

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a leak of open socket in rare cases when error occurred in
2+
:class:`ssl.SSLSocket` creation.

0 commit comments

Comments
 (0)