Skip to content

Commit 222ccbc

Browse files
miss-islingtonserhiy-storchakagraingert
authored
[3.12] gh-113280: Always close socket if SSLSocket creation failed (GH-114659) (GH-114995)
(cherry picked from commit 0ea3662) Co-authored-by: Serhiy Storchaka <[email protected]> Co-authored-by: Thomas Grainger <[email protected]>
1 parent 709a293 commit 222ccbc

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
@@ -969,71 +969,67 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
969969
if context.check_hostname and not server_hostname:
970970
raise ValueError("check_hostname requires server_hostname")
971971

972+
sock_timeout = sock.gettimeout()
972973
kwargs = dict(
973974
family=sock.family, type=sock.type, proto=sock.proto,
974975
fileno=sock.fileno()
975976
)
976977
self = cls.__new__(cls, **kwargs)
977978
super(SSLSocket, self).__init__(**kwargs)
978-
sock_timeout = sock.gettimeout()
979979
sock.detach()
980-
981-
self._context = context
982-
self._session = session
983-
self._closed = False
984-
self._sslobj = None
985-
self.server_side = server_side
986-
self.server_hostname = context._encode_hostname(server_hostname)
987-
self.do_handshake_on_connect = do_handshake_on_connect
988-
self.suppress_ragged_eofs = suppress_ragged_eofs
989-
990-
# See if we are connected
980+
# Now SSLSocket is responsible for closing the file descriptor.
991981
try:
992-
self.getpeername()
993-
except OSError as e:
994-
if e.errno != errno.ENOTCONN:
995-
raise
996-
connected = False
997-
blocking = self.getblocking()
998-
self.setblocking(False)
982+
self._context = context
983+
self._session = session
984+
self._closed = False
985+
self._sslobj = None
986+
self.server_side = server_side
987+
self.server_hostname = context._encode_hostname(server_hostname)
988+
self.do_handshake_on_connect = do_handshake_on_connect
989+
self.suppress_ragged_eofs = suppress_ragged_eofs
990+
991+
# See if we are connected
999992
try:
1000-
# We are not connected so this is not supposed to block, but
1001-
# testing revealed otherwise on macOS and Windows so we do
1002-
# the non-blocking dance regardless. Our raise when any data
1003-
# is found means consuming the data is harmless.
1004-
notconn_pre_handshake_data = self.recv(1)
993+
self.getpeername()
1005994
except OSError as e:
1006-
# EINVAL occurs for recv(1) on non-connected on unix sockets.
1007-
if e.errno not in (errno.ENOTCONN, errno.EINVAL):
995+
if e.errno != errno.ENOTCONN:
1008996
raise
1009-
notconn_pre_handshake_data = b''
1010-
self.setblocking(blocking)
1011-
if notconn_pre_handshake_data:
1012-
# This prevents pending data sent to the socket before it was
1013-
# closed from escaping to the caller who could otherwise
1014-
# presume it came through a successful TLS connection.
1015-
reason = "Closed before TLS handshake with data in recv buffer."
1016-
notconn_pre_handshake_data_error = SSLError(e.errno, reason)
1017-
# Add the SSLError attributes that _ssl.c always adds.
1018-
notconn_pre_handshake_data_error.reason = reason
1019-
notconn_pre_handshake_data_error.library = None
1020-
try:
1021-
self.close()
1022-
except OSError:
1023-
pass
997+
connected = False
998+
blocking = self.getblocking()
999+
self.setblocking(False)
10241000
try:
1025-
raise notconn_pre_handshake_data_error
1026-
finally:
1027-
# Explicitly break the reference cycle.
1028-
notconn_pre_handshake_data_error = None
1029-
else:
1030-
connected = True
1001+
# We are not connected so this is not supposed to block, but
1002+
# testing revealed otherwise on macOS and Windows so we do
1003+
# the non-blocking dance regardless. Our raise when any data
1004+
# is found means consuming the data is harmless.
1005+
notconn_pre_handshake_data = self.recv(1)
1006+
except OSError as e:
1007+
# EINVAL occurs for recv(1) on non-connected on unix sockets.
1008+
if e.errno not in (errno.ENOTCONN, errno.EINVAL):
1009+
raise
1010+
notconn_pre_handshake_data = b''
1011+
self.setblocking(blocking)
1012+
if notconn_pre_handshake_data:
1013+
# This prevents pending data sent to the socket before it was
1014+
# closed from escaping to the caller who could otherwise
1015+
# presume it came through a successful TLS connection.
1016+
reason = "Closed before TLS handshake with data in recv buffer."
1017+
notconn_pre_handshake_data_error = SSLError(e.errno, reason)
1018+
# Add the SSLError attributes that _ssl.c always adds.
1019+
notconn_pre_handshake_data_error.reason = reason
1020+
notconn_pre_handshake_data_error.library = None
1021+
try:
1022+
raise notconn_pre_handshake_data_error
1023+
finally:
1024+
# Explicitly break the reference cycle.
1025+
notconn_pre_handshake_data_error = None
1026+
else:
1027+
connected = True
10311028

1032-
self.settimeout(sock_timeout) # Must come after setblocking() calls.
1033-
self._connected = connected
1034-
if connected:
1035-
# create the SSL object
1036-
try:
1029+
self.settimeout(sock_timeout) # Must come after setblocking() calls.
1030+
self._connected = connected
1031+
if connected:
1032+
# create the SSL object
10371033
self._sslobj = self._context._wrap_socket(
10381034
self, server_side, self.server_hostname,
10391035
owner=self, session=self._session,
@@ -1044,9 +1040,12 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
10441040
# non-blocking
10451041
raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
10461042
self.do_handshake()
1047-
except (OSError, ValueError):
1043+
except:
1044+
try:
10481045
self.close()
1049-
raise
1046+
except OSError:
1047+
pass
1048+
raise
10501049
return self
10511050

10521051
@property

Lib/test/test_ssl.py

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

22082208
def _test_get_server_certificate_fail(test, host, port):
2209-
try:
2210-
pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE)
2211-
except ssl.SSLError as x:
2212-
#should fail
2213-
if support.verbose:
2214-
sys.stdout.write("%s\n" % x)
2215-
else:
2216-
test.fail("Got server certificate %s for %s:%s!" % (pem, host, port))
2209+
with warnings_helper.check_no_resource_warning(test):
2210+
try:
2211+
pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE)
2212+
except ssl.SSLError as x:
2213+
#should fail
2214+
if support.verbose:
2215+
sys.stdout.write("%s\n" % x)
2216+
else:
2217+
test.fail("Got server certificate %s for %s:%s!" % (pem, host, port))
22172218

22182219

22192220
from test.ssl_servers import make_https_server
@@ -3026,6 +3027,16 @@ def test_check_hostname_idn(self):
30263027
server_hostname="python.example.org") as s:
30273028
with self.assertRaises(ssl.CertificateError):
30283029
s.connect((HOST, server.port))
3030+
with ThreadedEchoServer(context=server_context, chatty=True) as server:
3031+
with warnings_helper.check_no_resource_warning(self):
3032+
with self.assertRaises(UnicodeError):
3033+
context.wrap_socket(socket.socket(),
3034+
server_hostname='.pythontest.net')
3035+
with ThreadedEchoServer(context=server_context, chatty=True) as server:
3036+
with warnings_helper.check_no_resource_warning(self):
3037+
with self.assertRaises(UnicodeDecodeError):
3038+
context.wrap_socket(socket.socket(),
3039+
server_hostname=b'k\xf6nig.idn.pythontest.net')
30293040

30303041
def test_wrong_cert_tls12(self):
30313042
"""Connecting when the server rejects the client's certificate
@@ -4882,7 +4893,8 @@ def call_after_accept(conn_to_client):
48824893
self.assertIsNone(wrap_error.library, msg="attr must exist")
48834894
finally:
48844895
# gh-108342: Explicitly break the reference cycle
4885-
wrap_error = None
4896+
with warnings_helper.check_no_resource_warning(self):
4897+
wrap_error = None
48864898
server = None
48874899

48884900
def test_https_client_non_tls_response_ignored(self):
@@ -4931,7 +4943,8 @@ def call_after_accept(conn_to_client):
49314943
# socket; that fails if the connection is broken. It may seem pointless
49324944
# to test this. It serves as an illustration of something that we never
49334945
# want to happen... properly not happening.
4934-
with self.assertRaises(OSError):
4946+
with warnings_helper.check_no_resource_warning(self), \
4947+
self.assertRaises(OSError):
49354948
connection.request("HEAD", "/test", headers={"Host": "localhost"})
49364949
response = connection.getresponse()
49374950

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)