Blame SOURCES/00404-cve-2023-40217.patch

037c46
From e0477d457e5427ecc895f60d7d7cad0f812c4eff Mon Sep 17 00:00:00 2001
037c46
From: =?UTF-8?q?=C5=81ukasz=20Langa?= <lukasz@langa.pl>
037c46
Date: Tue, 22 Aug 2023 19:53:15 +0200
037c46
Subject: [PATCH 1/3] gh-108310: Fix CVE-2023-40217: Check for & avoid the ssl
037c46
 pre-close flaw (#108315)
037c46
MIME-Version: 1.0
037c46
Content-Type: text/plain; charset=UTF-8
037c46
Content-Transfer-Encoding: 8bit
037c46
037c46
Instances of `ssl.SSLSocket` were vulnerable to a bypass of the TLS handshake
037c46
and included protections (like certificate verification) and treating sent
037c46
unencrypted data as if it were post-handshake TLS encrypted data.
037c46
037c46
The vulnerability is caused when a socket is connected, data is sent by the
037c46
malicious peer and stored in a buffer, and then the malicious peer closes the
037c46
socket within a small timing window before the other peers’ TLS handshake can
037c46
begin. After this sequence of events the closed socket will not immediately
037c46
attempt a TLS handshake due to not being connected but will also allow the
037c46
buffered data to be read as if a successful TLS handshake had occurred.
037c46
037c46
Co-authored-by: Gregory P. Smith [Google LLC] <greg@krypto.org>
037c46
037c46
-----
037c46
037c46
Notable adjustments for Python 2.7:
037c46
Use alternative for self.getblocking(), which was added in Python 3.7
037c46
see: https://docs.python.org/3/library/socket.html#socket.socket.getblocking
037c46
Set self._sslobj early to avoid AttributeError
037c46
Use SSLError where necessary (it is not a subclass of OSError)
037c46
---
037c46
 Lib/ssl.py                                    |  32 +++
037c46
 Lib/test/test_ssl.py                          | 210 ++++++++++++++++++
037c46
 ...-08-22-17-39-12.gh-issue-108310.fVM3sg.rst |   7 +
037c46
 3 files changed, 249 insertions(+)
037c46
 create mode 100644 Misc/NEWS.d/next/Security/2023-08-22-17-39-12.gh-issue-108310.fVM3sg.rst
037c46
037c46
diff --git a/Lib/ssl.py b/Lib/ssl.py
037c46
index 5311321..9627a77 100644
037c46
--- a/Lib/ssl.py
037c46
+++ b/Lib/ssl.py
037c46
@@ -528,6 +528,7 @@ class SSLSocket(socket):
037c46
                  server_hostname=None,
037c46
                  _context=None):
037c46
 
037c46
+        self._sslobj = None
037c46
         self._makefile_refs = 0
037c46
         if _context:
037c46
             self._context = _context
037c46
@@ -583,6 +584,8 @@ class SSLSocket(socket):
037c46
         self.do_handshake_on_connect = do_handshake_on_connect
037c46
         self.suppress_ragged_eofs = suppress_ragged_eofs
037c46
 
037c46
+        sock_timeout = sock.gettimeout()
037c46
+
037c46
         # See if we are connected
037c46
         try:
037c46
             self.getpeername()
037c46
@@ -590,9 +593,38 @@ class SSLSocket(socket):
037c46
             if e.errno != errno.ENOTCONN:
037c46
                 raise
037c46
             connected = False
037c46
+            blocking = (sock.gettimeout() != 0)
037c46
+            self.setblocking(False)
037c46
+            try:
037c46
+                # We are not connected so this is not supposed to block, but
037c46
+                # testing revealed otherwise on macOS and Windows so we do
037c46
+                # the non-blocking dance regardless. Our raise when any data
037c46
+                # is found means consuming the data is harmless.
037c46
+                notconn_pre_handshake_data = self.recv(1)
037c46
+            except socket_error as e:
037c46
+                # EINVAL occurs for recv(1) on non-connected on unix sockets.
037c46
+                if e.errno not in (errno.ENOTCONN, errno.EINVAL):
037c46
+                    raise
037c46
+                notconn_pre_handshake_data = b''
037c46
+            self.setblocking(blocking)
037c46
+            if notconn_pre_handshake_data:
037c46
+                # This prevents pending data sent to the socket before it was
037c46
+                # closed from escaping to the caller who could otherwise
037c46
+                # presume it came through a successful TLS connection.
037c46
+                reason = "Closed before TLS handshake with data in recv buffer"
037c46
+                notconn_pre_handshake_data_error = SSLError(e.errno, reason)
037c46
+                # Add the SSLError attributes that _ssl.c always adds.
037c46
+                notconn_pre_handshake_data_error.reason = reason
037c46
+                notconn_pre_handshake_data_error.library = None
037c46
+                try:
037c46
+                    self.close()
037c46
+                except OSError:
037c46
+                    pass
037c46
+                raise notconn_pre_handshake_data_error
037c46
         else:
037c46
             connected = True
037c46
 
037c46
+        self.settimeout(sock_timeout)  # Must come after setblocking() calls.
037c46
         self._closed = False
037c46
         self._sslobj = None
037c46
         self._connected = connected
037c46
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
037c46
index f6b42a0..8288e0e 100644
037c46
--- a/Lib/test/test_ssl.py
037c46
+++ b/Lib/test/test_ssl.py
037c46
@@ -8,9 +8,11 @@ from test.script_helper import assert_python_ok
037c46
 import asyncore
037c46
 import socket
037c46
 import select
037c46
+import struct
037c46
 import time
037c46
 import datetime
037c46
 import gc
037c46
+import httplib
037c46
 import os
037c46
 import errno
037c46
 import pprint
037c46
@@ -2990,6 +2992,213 @@ else:
037c46
                 self.assertRaises(ValueError, s.read, 1024)
037c46
                 self.assertRaises(ValueError, s.write, b'hello')
037c46
 
037c46
+def set_socket_so_linger_on_with_zero_timeout(sock):
037c46
+    sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
037c46
+
037c46
+
037c46
+class TestPreHandshakeClose(unittest.TestCase):
037c46
+    """Verify behavior of close sockets with received data before to the handshake.
037c46
+    """
037c46
+
037c46
+    class SingleConnectionTestServerThread(threading.Thread):
037c46
+
037c46
+        def __init__(self,  name, call_after_accept):
037c46
+            self.call_after_accept = call_after_accept
037c46
+            self.received_data = b''  # set by .run()
037c46
+            self.wrap_error = None  # set by .run()
037c46
+            self.listener = None  # set by .start()
037c46
+            self.port = None  # set by .start()
037c46
+            threading.Thread.__init__(self, name=name)
037c46
+
037c46
+        def __enter__(self):
037c46
+            self.start()
037c46
+            return self
037c46
+
037c46
+        def __exit__(self, *args):
037c46
+            try:
037c46
+                if self.listener:
037c46
+                    self.listener.close()
037c46
+            except ssl.SSLError:
037c46
+                pass
037c46
+            self.join()
037c46
+            self.wrap_error = None  # avoid dangling references
037c46
+
037c46
+        def start(self):
037c46
+            self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
037c46
+            self.ssl_ctx.verify_mode = ssl.CERT_REQUIRED
037c46
+            self.ssl_ctx.load_verify_locations(cafile=ONLYCERT)
037c46
+            self.ssl_ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
037c46
+            self.listener = socket.socket()
037c46
+            self.port = support.bind_port(self.listener)
037c46
+            self.listener.settimeout(2.0)
037c46
+            self.listener.listen(1)
037c46
+            threading.Thread.start(self)
037c46
+
037c46
+        def run(self):
037c46
+            conn, address = self.listener.accept()
037c46
+            self.listener.close()
037c46
+            with closing(conn):
037c46
+                if self.call_after_accept(conn):
037c46
+                    return
037c46
+                try:
037c46
+                    tls_socket = self.ssl_ctx.wrap_socket(conn, server_side=True)
037c46
+                except ssl.SSLError as err:
037c46
+                    self.wrap_error = err
037c46
+                else:
037c46
+                    try:
037c46
+                        self.received_data = tls_socket.recv(400)
037c46
+                    except OSError:
037c46
+                        pass  # closed, protocol error, etc.
037c46
+
037c46
+    def non_linux_skip_if_other_okay_error(self, err):
037c46
+        if sys.platform == "linux":
037c46
+            return  # Expect the full test setup to always work on Linux.
037c46
+        if (isinstance(err, ConnectionResetError) or
037c46
+            (isinstance(err, OSError) and err.errno == errno.EINVAL) or
037c46
+            re.search('wrong.version.number', getattr(err, "reason", ""), re.I)):
037c46
+            # On Windows the TCP RST leads to a ConnectionResetError
037c46
+            # (ECONNRESET) which Linux doesn't appear to surface to userspace.
037c46
+            # If wrap_socket() winds up on the "if connected:" path and doing
037c46
+            # the actual wrapping... we get an SSLError from OpenSSL. Typically
037c46
+            # WRONG_VERSION_NUMBER. While appropriate, neither is the scenario
037c46
+            # we're specifically trying to test. The way this test is written
037c46
+            # is known to work on Linux. We'll skip it anywhere else that it
037c46
+            # does not present as doing so.
037c46
+            self.skipTest("Could not recreate conditions on {}: \
037c46
+                          err={}".format(sys.platform,err))
037c46
+        # If maintaining this conditional winds up being a problem.
037c46
+        # just turn this into an unconditional skip anything but Linux.
037c46
+        # The important thing is that our CI has the logic covered.
037c46
+
037c46
+    def test_preauth_data_to_tls_server(self):
037c46
+        server_accept_called = threading.Event()
037c46
+        ready_for_server_wrap_socket = threading.Event()
037c46
+
037c46
+        def call_after_accept(unused):
037c46
+            server_accept_called.set()
037c46
+            if not ready_for_server_wrap_socket.wait(2.0):
037c46
+                raise RuntimeError("wrap_socket event never set, test may fail.")
037c46
+            return False  # Tell the server thread to continue.
037c46
+
037c46
+        server = self.SingleConnectionTestServerThread(
037c46
+                call_after_accept=call_after_accept,
037c46
+                name="preauth_data_to_tls_server")
037c46
+        server.__enter__()  # starts it
037c46
+        self.addCleanup(server.__exit__)  # ... & unittest.TestCase stops it.
037c46
+
037c46
+        with closing(socket.socket()) as client:
037c46
+            client.connect(server.listener.getsockname())
037c46
+            # This forces an immediate connection close via RST on .close().
037c46
+            set_socket_so_linger_on_with_zero_timeout(client)
037c46
+            client.setblocking(False)
037c46
+
037c46
+            server_accept_called.wait()
037c46
+            client.send(b"DELETE /data HTTP/1.0\r\n\r\n")
037c46
+            client.close()  # RST
037c46
+
037c46
+        ready_for_server_wrap_socket.set()
037c46
+        server.join()
037c46
+        wrap_error = server.wrap_error
037c46
+        self.assertEqual(b"", server.received_data)
037c46
+        self.assertIsInstance(wrap_error, ssl.SSLError)
037c46
+        self.assertIn("before TLS handshake with data", wrap_error.args[1])
037c46
+        self.assertIn("before TLS handshake with data", wrap_error.reason)
037c46
+        self.assertNotEqual(0, wrap_error.args[0])
037c46
+        self.assertIsNone(wrap_error.library, msg="attr must exist")
037c46
+
037c46
+    def test_preauth_data_to_tls_client(self):
037c46
+        client_can_continue_with_wrap_socket = threading.Event()
037c46
+
037c46
+        def call_after_accept(conn_to_client):
037c46
+            # This forces an immediate connection close via RST on .close().
037c46
+            set_socket_so_linger_on_with_zero_timeout(conn_to_client)
037c46
+            conn_to_client.send(
037c46
+                    b"HTTP/1.0 307 Temporary Redirect\r\n"
037c46
+                    b"Location: https://example.com/someone-elses-server\r\n"
037c46
+                    b"\r\n")
037c46
+            conn_to_client.close()  # RST
037c46
+            client_can_continue_with_wrap_socket.set()
037c46
+            return True  # Tell the server to stop.
037c46
+
037c46
+        server = self.SingleConnectionTestServerThread(
037c46
+                call_after_accept=call_after_accept,
037c46
+                name="preauth_data_to_tls_client")
037c46
+        server.__enter__()  # starts it
037c46
+        self.addCleanup(server.__exit__)  # ... & unittest.TestCase stops it.
037c46
+
037c46
+        # Redundant; call_after_accept sets SO_LINGER on the accepted conn.
037c46
+        set_socket_so_linger_on_with_zero_timeout(server.listener)
037c46
+
037c46
+        with closing(socket.socket()) as client:
037c46
+            client.connect(server.listener.getsockname())
037c46
+            if not client_can_continue_with_wrap_socket.wait(2.0):
037c46
+                self.fail("test server took too long.")
037c46
+            ssl_ctx = ssl.create_default_context()
037c46
+            try:
037c46
+                tls_client = ssl_ctx.wrap_socket(
037c46
+                        client, server_hostname="localhost")
037c46
+            except ssl.SSLError as err:
037c46
+                wrap_error = err
037c46
+                received_data = b""
037c46
+            else:
037c46
+                wrap_error = None
037c46
+                received_data = tls_client.recv(400)
037c46
+                tls_client.close()
037c46
+
037c46
+        server.join()
037c46
+        self.assertEqual(b"", received_data)
037c46
+        self.assertIsInstance(wrap_error, ssl.SSLError)
037c46
+        self.assertIn("before TLS handshake with data", wrap_error.args[1])
037c46
+        self.assertIn("before TLS handshake with data", wrap_error.reason)
037c46
+        self.assertNotEqual(0, wrap_error.args[0])
037c46
+        self.assertIsNone(wrap_error.library, msg="attr must exist")
037c46
+
037c46
+    def test_https_client_non_tls_response_ignored(self):
037c46
+
037c46
+        server_responding = threading.Event()
037c46
+
037c46
+        class SynchronizedHTTPSConnection(httplib.HTTPSConnection):
037c46
+            def connect(self):
037c46
+                httplib.HTTPConnection.connect(self)
037c46
+                # Wait for our fault injection server to have done its thing.
037c46
+                if not server_responding.wait(1.0) and support.verbose:
037c46
+                    sys.stdout.write("server_responding event never set.")
037c46
+                self.sock = self._context.wrap_socket(
037c46
+                        self.sock, server_hostname=self.host)
037c46
+
037c46
+        def call_after_accept(conn_to_client):
037c46
+            # This forces an immediate connection close via RST on .close().
037c46
+            set_socket_so_linger_on_with_zero_timeout(conn_to_client)
037c46
+            conn_to_client.send(
037c46
+                    b"HTTP/1.0 402 Payment Required\r\n"
037c46
+                    b"\r\n")
037c46
+            conn_to_client.close()  # RST
037c46
+            server_responding.set()
037c46
+            return True  # Tell the server to stop.
037c46
+
037c46
+        server = self.SingleConnectionTestServerThread(
037c46
+                call_after_accept=call_after_accept,
037c46
+                name="non_tls_http_RST_responder")
037c46
+        server.__enter__()  # starts it
037c46
+        self.addCleanup(server.__exit__)  # ... & unittest.TestCase stops it.
037c46
+        # Redundant; call_after_accept sets SO_LINGER on the accepted conn.
037c46
+        set_socket_so_linger_on_with_zero_timeout(server.listener)
037c46
+
037c46
+        connection = SynchronizedHTTPSConnection(
037c46
+                "localhost",
037c46
+                port=server.port,
037c46
+                context=ssl.create_default_context(),
037c46
+                timeout=2.0,
037c46
+        )
037c46
+        # There are lots of reasons this raises as desired, long before this
037c46
+        # test was added. Sending the request requires a successful TLS wrapped
037c46
+        # socket; that fails if the connection is broken. It may seem pointless
037c46
+        # to test this. It serves as an illustration of something that we never
037c46
+        # want to happen... properly not happening.
037c46
+        with self.assertRaises(ssl.SSLError) as err_ctx:
037c46
+            connection.request("HEAD", "/test", headers={"Host": "localhost"})
037c46
+            response = connection.getresponse()
037c46
+
037c46
 
037c46
 def test_main(verbose=False):
037c46
     if support.verbose:
037c46
@@ -3024,6 +3233,7 @@ def test_main(verbose=False):
037c46
             raise support.TestFailed("Can't read certificate file %r" % filename)
037c46
 
037c46
     tests = [ContextTests, BasicTests, BasicSocketTests, SSLErrorTests]
037c46
+    tests += [TestPreHandshakeClose]
037c46
 
037c46
     if support.is_resource_enabled('network'):
037c46
         tests.append(NetworkedTests)
037c46
diff --git a/Misc/NEWS.d/next/Security/2023-08-22-17-39-12.gh-issue-108310.fVM3sg.rst b/Misc/NEWS.d/next/Security/2023-08-22-17-39-12.gh-issue-108310.fVM3sg.rst
037c46
new file mode 100644
037c46
index 0000000..403c77a
037c46
--- /dev/null
037c46
+++ b/Misc/NEWS.d/next/Security/2023-08-22-17-39-12.gh-issue-108310.fVM3sg.rst
037c46
@@ -0,0 +1,7 @@
037c46
+Fixed an issue where instances of :class:`ssl.SSLSocket` were vulnerable to
037c46
+a bypass of the TLS handshake and included protections (like certificate
037c46
+verification) and treating sent unencrypted data as if it were
037c46
+post-handshake TLS encrypted data.  Security issue reported as
037c46
+`CVE-2023-40217
037c46
+<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-40217>`_ by
037c46
+Aapo Oksman. Patch by Gregory P. Smith.
037c46
-- 
037c46
2.41.0
037c46
037c46
037c46
From 82032d4fb71fb3a6e7c364b6e93c2c33ff49b1a5 Mon Sep 17 00:00:00 2001
037c46
From: "Miss Islington (bot)"
037c46
 <31488909+miss-islington@users.noreply.github.com>
037c46
Date: Wed, 23 Aug 2023 03:10:56 -0700
037c46
Subject: [PATCH 2/3] gh-108342: Break ref cycle in SSLSocket._create() exc
037c46
 (GH-108344) (#108352)
037c46
037c46
Explicitly break a reference cycle when SSLSocket._create() raises an
037c46
exception. Clear the variable storing the exception, since the
037c46
exception traceback contains the variables and so creates a reference
037c46
cycle.
037c46
037c46
This test leak was introduced by the test added for the fix of GH-108310.
037c46
(cherry picked from commit 64f99350351bc46e016b2286f36ba7cd669b79e3)
037c46
037c46
Co-authored-by: Victor Stinner <vstinner@python.org>
037c46
---
037c46
 Lib/ssl.py | 6 +++++-
037c46
 1 file changed, 5 insertions(+), 1 deletion(-)
037c46
037c46
diff --git a/Lib/ssl.py b/Lib/ssl.py
037c46
index 9627a77..ee3806e 100644
037c46
--- a/Lib/ssl.py
037c46
+++ b/Lib/ssl.py
037c46
@@ -620,7 +620,11 @@ class SSLSocket(socket):
037c46
                     self.close()
037c46
                 except OSError:
037c46
                     pass
037c46
-                raise notconn_pre_handshake_data_error
037c46
+                try:
037c46
+                    raise notconn_pre_handshake_data_error
037c46
+                finally:
037c46
+                    # Explicitly break the reference cycle.
037c46
+                    notconn_pre_handshake_data_error = None
037c46
         else:
037c46
             connected = True
037c46
 
037c46
-- 
037c46
2.41.0
037c46
037c46
037c46
From 960d54566e3da48e9dde1e9c1bd74c89c277244b Mon Sep 17 00:00:00 2001
037c46
From: =?UTF-8?q?=C5=81ukasz=20Langa?= <lukasz@langa.pl>
037c46
Date: Thu, 24 Aug 2023 12:09:30 +0200
037c46
Subject: [PATCH 3/3] gh-108342: Make ssl TestPreHandshakeClose more reliable
037c46
 (GH-108370) (#108408)
037c46
037c46
* In preauth tests of test_ssl, explicitly break reference cycles
037c46
  invoving SingleConnectionTestServerThread to make sure that the
037c46
  thread is deleted. Otherwise, the test marks the environment as
037c46
  altered because the threading module sees a "dangling thread"
037c46
  (SingleConnectionTestServerThread). This test leak was introduced
037c46
  by the test added for the fix of issue gh-108310.
037c46
* Use support.SHORT_TIMEOUT instead of hardcoded 1.0 or 2.0 seconds
037c46
  timeout.
037c46
* SingleConnectionTestServerThread.run() catchs TimeoutError
037c46
* Fix a race condition (missing synchronization) in
037c46
  test_preauth_data_to_tls_client(): the server now waits until the
037c46
  client connect() completed in call_after_accept().
037c46
* test_https_client_non_tls_response_ignored() calls server.join()
037c46
  explicitly.
037c46
* Replace "localhost" with server.listener.getsockname()[0].
037c46
(cherry picked from commit 592bacb6fc0833336c0453e818e9b95016e9fd47)
037c46
---
037c46
 Lib/test/test_ssl.py     | 105 ++++++++++++++++++++++-----------------
037c46
 Lib/test/test_support.py |   2 +
037c46
 2 files changed, 62 insertions(+), 45 deletions(-)
037c46
037c46
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
037c46
index 8288e0e..7795627 100644
037c46
--- a/Lib/test/test_ssl.py
037c46
+++ b/Lib/test/test_ssl.py
037c46
@@ -3002,12 +3002,16 @@ class TestPreHandshakeClose(unittest.TestCase):
037c46
 
037c46
     class SingleConnectionTestServerThread(threading.Thread):
037c46
 
037c46
-        def __init__(self,  name, call_after_accept):
037c46
+        def __init__(self,  name, call_after_accept, timeout=None):
037c46
             self.call_after_accept = call_after_accept
037c46
             self.received_data = b''  # set by .run()
037c46
             self.wrap_error = None  # set by .run()
037c46
             self.listener = None  # set by .start()
037c46
             self.port = None  # set by .start()
037c46
+            if timeout is None:
037c46
+                self.timeout = support.SHORT_TIMEOUT
037c46
+            else:
037c46
+                self.timeout = timeout
037c46
             threading.Thread.__init__(self, name=name)
037c46
 
037c46
         def __enter__(self):
037c46
@@ -3030,13 +3034,22 @@ class TestPreHandshakeClose(unittest.TestCase):
037c46
             self.ssl_ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
037c46
             self.listener = socket.socket()
037c46
             self.port = support.bind_port(self.listener)
037c46
-            self.listener.settimeout(2.0)
037c46
+            self.listener.settimeout(self.timeout)
037c46
             self.listener.listen(1)
037c46
             threading.Thread.start(self)
037c46
 
037c46
         def run(self):
037c46
-            conn, address = self.listener.accept()
037c46
-            self.listener.close()
037c46
+            try:
037c46
+                conn, address = self.listener.accept()
037c46
+            except OSError as e:
037c46
+                if e.errno == errno.ETIMEDOUT:
037c46
+                    # on timeout, just close the listener
037c46
+                    return
037c46
+                else:
037c46
+                    raise
037c46
+            finally:
037c46
+                self.listener.close()
037c46
+
037c46
             with closing(conn):
037c46
                 if self.call_after_accept(conn):
037c46
                     return
037c46
@@ -3050,33 +3063,13 @@ class TestPreHandshakeClose(unittest.TestCase):
037c46
                     except OSError:
037c46
                         pass  # closed, protocol error, etc.
037c46
 
037c46
-    def non_linux_skip_if_other_okay_error(self, err):
037c46
-        if sys.platform == "linux":
037c46
-            return  # Expect the full test setup to always work on Linux.
037c46
-        if (isinstance(err, ConnectionResetError) or
037c46
-            (isinstance(err, OSError) and err.errno == errno.EINVAL) or
037c46
-            re.search('wrong.version.number', getattr(err, "reason", ""), re.I)):
037c46
-            # On Windows the TCP RST leads to a ConnectionResetError
037c46
-            # (ECONNRESET) which Linux doesn't appear to surface to userspace.
037c46
-            # If wrap_socket() winds up on the "if connected:" path and doing
037c46
-            # the actual wrapping... we get an SSLError from OpenSSL. Typically
037c46
-            # WRONG_VERSION_NUMBER. While appropriate, neither is the scenario
037c46
-            # we're specifically trying to test. The way this test is written
037c46
-            # is known to work on Linux. We'll skip it anywhere else that it
037c46
-            # does not present as doing so.
037c46
-            self.skipTest("Could not recreate conditions on {}: \
037c46
-                          err={}".format(sys.platform,err))
037c46
-        # If maintaining this conditional winds up being a problem.
037c46
-        # just turn this into an unconditional skip anything but Linux.
037c46
-        # The important thing is that our CI has the logic covered.
037c46
-
037c46
     def test_preauth_data_to_tls_server(self):
037c46
         server_accept_called = threading.Event()
037c46
         ready_for_server_wrap_socket = threading.Event()
037c46
 
037c46
         def call_after_accept(unused):
037c46
             server_accept_called.set()
037c46
-            if not ready_for_server_wrap_socket.wait(2.0):
037c46
+            if not ready_for_server_wrap_socket.wait(support.SHORT_TIMEOUT):
037c46
                 raise RuntimeError("wrap_socket event never set, test may fail.")
037c46
             return False  # Tell the server thread to continue.
037c46
 
037c46
@@ -3098,18 +3091,28 @@ class TestPreHandshakeClose(unittest.TestCase):
037c46
 
037c46
         ready_for_server_wrap_socket.set()
037c46
         server.join()
037c46
+
037c46
         wrap_error = server.wrap_error
037c46
-        self.assertEqual(b"", server.received_data)
037c46
-        self.assertIsInstance(wrap_error, ssl.SSLError)
037c46
-        self.assertIn("before TLS handshake with data", wrap_error.args[1])
037c46
-        self.assertIn("before TLS handshake with data", wrap_error.reason)
037c46
-        self.assertNotEqual(0, wrap_error.args[0])
037c46
-        self.assertIsNone(wrap_error.library, msg="attr must exist")
037c46
+        try:
037c46
+            self.assertEqual(b"", server.received_data)
037c46
+            self.assertIsInstance(wrap_error, ssl.SSLError)
037c46
+            self.assertIn("before TLS handshake with data", wrap_error.args[1])
037c46
+            self.assertIn("before TLS handshake with data", wrap_error.reason)
037c46
+            self.assertNotEqual(0, wrap_error.args[0])
037c46
+            self.assertIsNone(wrap_error.library, msg="attr must exist")
037c46
+        finally:
037c46
+            # gh-108342: Explicitly break the reference cycle
037c46
+            wrap_error = None
037c46
+            server = None
037c46
 
037c46
     def test_preauth_data_to_tls_client(self):
037c46
+        server_can_continue_with_wrap_socket = threading.Event()
037c46
         client_can_continue_with_wrap_socket = threading.Event()
037c46
 
037c46
         def call_after_accept(conn_to_client):
037c46
+            if not server_can_continue_with_wrap_socket.wait(support.SHORT_TIMEOUT):
037c46
+                print("ERROR: test client took too long")
037c46
+
037c46
             # This forces an immediate connection close via RST on .close().
037c46
             set_socket_so_linger_on_with_zero_timeout(conn_to_client)
037c46
             conn_to_client.send(
037c46
@@ -3131,8 +3134,10 @@ class TestPreHandshakeClose(unittest.TestCase):
037c46
 
037c46
         with closing(socket.socket()) as client:
037c46
             client.connect(server.listener.getsockname())
037c46
-            if not client_can_continue_with_wrap_socket.wait(2.0):
037c46
-                self.fail("test server took too long.")
037c46
+            server_can_continue_with_wrap_socket.set()
037c46
+
037c46
+            if not client_can_continue_with_wrap_socket.wait(support.SHORT_TIMEOUT):
037c46
+                self.fail("test server took too long")
037c46
             ssl_ctx = ssl.create_default_context()
037c46
             try:
037c46
                 tls_client = ssl_ctx.wrap_socket(
037c46
@@ -3146,22 +3151,29 @@ class TestPreHandshakeClose(unittest.TestCase):
037c46
                 tls_client.close()
037c46
 
037c46
         server.join()
037c46
-        self.assertEqual(b"", received_data)
037c46
-        self.assertIsInstance(wrap_error, ssl.SSLError)
037c46
-        self.assertIn("before TLS handshake with data", wrap_error.args[1])
037c46
-        self.assertIn("before TLS handshake with data", wrap_error.reason)
037c46
-        self.assertNotEqual(0, wrap_error.args[0])
037c46
-        self.assertIsNone(wrap_error.library, msg="attr must exist")
037c46
+        try:
037c46
+            self.assertEqual(b"", received_data)
037c46
+            self.assertIsInstance(wrap_error, ssl.SSLError)
037c46
+            self.assertIn("before TLS handshake with data", wrap_error.args[1])
037c46
+            self.assertIn("before TLS handshake with data", wrap_error.reason)
037c46
+            self.assertNotEqual(0, wrap_error.args[0])
037c46
+            self.assertIsNone(wrap_error.library, msg="attr must exist")
037c46
+        finally:
037c46
+            # gh-108342: Explicitly break the reference cycle
037c46
+            wrap_error = None
037c46
+            server = None
037c46
 
037c46
     def test_https_client_non_tls_response_ignored(self):
037c46
-
037c46
         server_responding = threading.Event()
037c46
 
037c46
         class SynchronizedHTTPSConnection(httplib.HTTPSConnection):
037c46
             def connect(self):
037c46
+                # Call clear text HTTP connect(), not the encrypted HTTPS (TLS)
037c46
+                # connect(): wrap_socket() is called manually below.
037c46
                 httplib.HTTPConnection.connect(self)
037c46
+
037c46
                 # Wait for our fault injection server to have done its thing.
037c46
-                if not server_responding.wait(1.0) and support.verbose:
037c46
+                if not server_responding.wait(support.SHORT_TIMEOUT) and support.verbose:
037c46
                     sys.stdout.write("server_responding event never set.")
037c46
                 self.sock = self._context.wrap_socket(
037c46
                         self.sock, server_hostname=self.host)
037c46
@@ -3176,29 +3188,32 @@ class TestPreHandshakeClose(unittest.TestCase):
037c46
             server_responding.set()
037c46
             return True  # Tell the server to stop.
037c46
 
037c46
+        timeout = 2.0
037c46
         server = self.SingleConnectionTestServerThread(
037c46
                 call_after_accept=call_after_accept,
037c46
-                name="non_tls_http_RST_responder")
037c46
+                name="non_tls_http_RST_responder",
037c46
+                timeout=timeout)
037c46
         server.__enter__()  # starts it
037c46
         self.addCleanup(server.__exit__)  # ... & unittest.TestCase stops it.
037c46
         # Redundant; call_after_accept sets SO_LINGER on the accepted conn.
037c46
         set_socket_so_linger_on_with_zero_timeout(server.listener)
037c46
 
037c46
         connection = SynchronizedHTTPSConnection(
037c46
-                "localhost",
037c46
+                server.listener.getsockname()[0],
037c46
                 port=server.port,
037c46
                 context=ssl.create_default_context(),
037c46
-                timeout=2.0,
037c46
+                timeout=timeout,
037c46
         )
037c46
         # There are lots of reasons this raises as desired, long before this
037c46
         # test was added. Sending the request requires a successful TLS wrapped
037c46
         # socket; that fails if the connection is broken. It may seem pointless
037c46
         # to test this. It serves as an illustration of something that we never
037c46
         # want to happen... properly not happening.
037c46
-        with self.assertRaises(ssl.SSLError) as err_ctx:
037c46
+        with self.assertRaises(ssl.SSLError):
037c46
             connection.request("HEAD", "/test", headers={"Host": "localhost"})
037c46
             response = connection.getresponse()
037c46
 
037c46
+        server.join()
037c46
 
037c46
 def test_main(verbose=False):
037c46
     if support.verbose:
037c46
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
037c46
index 98a9275..1536606 100644
037c46
--- a/Lib/test/test_support.py
037c46
+++ b/Lib/test/test_support.py
037c46
@@ -42,6 +42,8 @@ __all__ = ["Error", "TestFailed", "ResourceDenied", "import_module",
037c46
            "import_fresh_module", "threading_cleanup", "reap_children",
037c46
            "strip_python_stderr", "IPV6_ENABLED"]
037c46
 
037c46
+SHORT_TIMEOUT = 30.0  # Added to make backporting from 3.x easier
037c46
+
037c46
 class Error(Exception):
037c46
     """Base class for regression test exceptions."""
037c46
 
037c46
-- 
037c46
2.41.0
037c46