Skip to content

Commit

Permalink
pythongh-113280: Always close the socket if SSLSocket creation failed
Browse files Browse the repository at this point in the history
  • Loading branch information
serhiy-storchaka committed Jan 27, 2024
1 parent 7a47054 commit 575fdc4
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 54 deletions.
107 changes: 53 additions & 54 deletions Lib/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,65 +1000,61 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
)
self = cls.__new__(cls, **kwargs)
super(SSLSocket, self).__init__(**kwargs)
sock_timeout = sock.gettimeout()
sock.detach()
try:
sock_timeout = sock.gettimeout()
sock.detach()

self._context = context
self._session = session
self._closed = False
self._sslobj = None
self.server_side = server_side
self.server_hostname = context._encode_hostname(server_hostname)
self.do_handshake_on_connect = do_handshake_on_connect
self.suppress_ragged_eofs = suppress_ragged_eofs
self._context = context
self._session = session
self._closed = False
self._sslobj = None
self.server_side = server_side
self.server_hostname = context._encode_hostname(server_hostname)
self.do_handshake_on_connect = do_handshake_on_connect
self.suppress_ragged_eofs = suppress_ragged_eofs

# See if we are connected
try:
self.getpeername()
except OSError as e:
if e.errno != errno.ENOTCONN:
raise
connected = False
blocking = self.getblocking()
self.setblocking(False)
# See if we are connected
try:
# We are not connected so this is not supposed to block, but
# testing revealed otherwise on macOS and Windows so we do
# the non-blocking dance regardless. Our raise when any data
# is found means consuming the data is harmless.
notconn_pre_handshake_data = self.recv(1)
self.getpeername()
except OSError as e:
# EINVAL occurs for recv(1) on non-connected on unix sockets.
if e.errno not in (errno.ENOTCONN, errno.EINVAL):
if e.errno != errno.ENOTCONN:
raise
notconn_pre_handshake_data = b''
self.setblocking(blocking)
if notconn_pre_handshake_data:
# This prevents pending data sent to the socket before it was
# closed from escaping to the caller who could otherwise
# presume it came through a successful TLS connection.
reason = "Closed before TLS handshake with data in recv buffer."
notconn_pre_handshake_data_error = SSLError(e.errno, reason)
# Add the SSLError attributes that _ssl.c always adds.
notconn_pre_handshake_data_error.reason = reason
notconn_pre_handshake_data_error.library = None
try:
self.close()
except OSError:
pass
connected = False
blocking = self.getblocking()
self.setblocking(False)
try:
raise notconn_pre_handshake_data_error
finally:
# Explicitly break the reference cycle.
notconn_pre_handshake_data_error = None
else:
connected = True
# We are not connected so this is not supposed to block, but
# testing revealed otherwise on macOS and Windows so we do
# the non-blocking dance regardless. Our raise when any data
# is found means consuming the data is harmless.
notconn_pre_handshake_data = self.recv(1)
except OSError as e:
# EINVAL occurs for recv(1) on non-connected on unix sockets.
if e.errno not in (errno.ENOTCONN, errno.EINVAL):
raise
notconn_pre_handshake_data = b''
self.setblocking(blocking)
if notconn_pre_handshake_data:
# This prevents pending data sent to the socket before it was
# closed from escaping to the caller who could otherwise
# presume it came through a successful TLS connection.
reason = "Closed before TLS handshake with data in recv buffer."
notconn_pre_handshake_data_error = SSLError(e.errno, reason)
# Add the SSLError attributes that _ssl.c always adds.
notconn_pre_handshake_data_error.reason = reason
notconn_pre_handshake_data_error.library = None
try:
raise notconn_pre_handshake_data_error
finally:
# Explicitly break the reference cycle.
notconn_pre_handshake_data_error = None
else:
connected = True

self.settimeout(sock_timeout) # Must come after setblocking() calls.
self._connected = connected
if connected:
# create the SSL object
try:
self.settimeout(sock_timeout) # Must come after setblocking() calls.
self._connected = connected
if connected:
# create the SSL object
self._sslobj = self._context._wrap_socket(
self, server_side, self.server_hostname,
owner=self, session=self._session,
Expand All @@ -1069,9 +1065,12 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
# non-blocking
raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
self.do_handshake()
except (OSError, ValueError):
except:
try:
self.close()
raise
except OSError:
pass
raise
return self

@property
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a leak of open socket in rare cases when error occurred in
:class:`ssl.SSLSocket` creation.

0 comments on commit 575fdc4

Please sign in to comment.