Security fix for CVE-2023-40217
Resolves: RHEL-9621
This commit is contained in:
		
							parent
							
								
									c84db09589
								
							
						
					
					
						commit
						68ad882ccc
					
				
							
								
								
									
										640
									
								
								00404-cve-2023-40217.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										640
									
								
								00404-cve-2023-40217.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,640 @@ | |||||||
|  | From 27e9b8a48632696311bd8c4af93ec52a49ba5e09 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: =?UTF-8?q?=C5=81ukasz=20Langa?= <lukasz@langa.pl> | ||||||
|  | Date: Tue, 22 Aug 2023 19:53:15 +0200 | ||||||
|  | Subject: [PATCH 1/3] gh-108310: Fix CVE-2023-40217: Check for & avoid the ssl | ||||||
|  |  pre-close flaw (#108315) | ||||||
|  | MIME-Version: 1.0 | ||||||
|  | Content-Type: text/plain; charset=UTF-8 | ||||||
|  | Content-Transfer-Encoding: 8bit | ||||||
|  | 
 | ||||||
|  | Instances of `ssl.SSLSocket` were vulnerable to a bypass of the TLS handshake | ||||||
|  | and included protections (like certificate verification) and treating sent | ||||||
|  | unencrypted data as if it were post-handshake TLS encrypted data. | ||||||
|  | 
 | ||||||
|  | The vulnerability is caused when a socket is connected, data is sent by the | ||||||
|  | malicious peer and stored in a buffer, and then the malicious peer closes the | ||||||
|  | socket within a small timing window before the other peers’ TLS handshake can | ||||||
|  | begin. After this sequence of events the closed socket will not immediately | ||||||
|  | attempt a TLS handshake due to not being connected but will also allow the | ||||||
|  | buffered data to be read as if a successful TLS handshake had occurred. | ||||||
|  | 
 | ||||||
|  | Co-authored-by: Gregory P. Smith [Google LLC] <greg@krypto.org> | ||||||
|  | 
 | ||||||
|  | -----
 | ||||||
|  | 
 | ||||||
|  | Notable adjustments for Python 2.7: | ||||||
|  | Use alternative for self.getblocking(), which was added in Python 3.7 | ||||||
|  | see: https://docs.python.org/3/library/socket.html#socket.socket.getblocking | ||||||
|  | Set self._sslobj early to avoid AttributeError | ||||||
|  | Use SSLError where necessary (it is not a subclass of OSError) | ||||||
|  | ---
 | ||||||
|  |  Lib/ssl.py                                    |  32 +++ | ||||||
|  |  Lib/test/test_ssl.py                          | 210 ++++++++++++++++++ | ||||||
|  |  ...-08-22-17-39-12.gh-issue-108310.fVM3sg.rst |   7 + | ||||||
|  |  3 files changed, 249 insertions(+) | ||||||
|  |  create mode 100644 Misc/NEWS.d/next/Security/2023-08-22-17-39-12.gh-issue-108310.fVM3sg.rst | ||||||
|  | 
 | ||||||
|  | diff --git a/Lib/ssl.py b/Lib/ssl.py
 | ||||||
|  | index 0bb43a4a4de..de9ce6bc134 100644
 | ||||||
|  | --- a/Lib/ssl.py
 | ||||||
|  | +++ b/Lib/ssl.py
 | ||||||
|  | @@ -523,6 +523,7 @@ class SSLSocket(socket):
 | ||||||
|  |                   server_hostname=None, | ||||||
|  |                   _context=None): | ||||||
|  |   | ||||||
|  | +        self._sslobj = None
 | ||||||
|  |          self._makefile_refs = 0 | ||||||
|  |          if _context: | ||||||
|  |              self._context = _context | ||||||
|  | @@ -573,6 +574,8 @@ class SSLSocket(socket):
 | ||||||
|  |          self.do_handshake_on_connect = do_handshake_on_connect | ||||||
|  |          self.suppress_ragged_eofs = suppress_ragged_eofs | ||||||
|  |   | ||||||
|  | +        sock_timeout = sock.gettimeout()
 | ||||||
|  | +
 | ||||||
|  |          # See if we are connected | ||||||
|  |          try: | ||||||
|  |              self.getpeername() | ||||||
|  | @@ -580,9 +583,38 @@ class SSLSocket(socket):
 | ||||||
|  |              if e.errno != errno.ENOTCONN: | ||||||
|  |                  raise | ||||||
|  |              connected = False | ||||||
|  | +            blocking = (sock.gettimeout() != 0)
 | ||||||
|  | +            self.setblocking(False)
 | ||||||
|  | +            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)
 | ||||||
|  | +            except socket_error 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:
 | ||||||
|  | +                    self.close()
 | ||||||
|  | +                except OSError:
 | ||||||
|  | +                    pass
 | ||||||
|  | +                raise notconn_pre_handshake_data_error
 | ||||||
|  |          else: | ||||||
|  |              connected = True | ||||||
|  |   | ||||||
|  | +        self.settimeout(sock_timeout)  # Must come after setblocking() calls.
 | ||||||
|  |          self._closed = False | ||||||
|  |          self._sslobj = None | ||||||
|  |          self._connected = connected | ||||||
|  | diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
 | ||||||
|  | index ef2e59c1d15..fd657761a05 100644
 | ||||||
|  | --- a/Lib/test/test_ssl.py
 | ||||||
|  | +++ b/Lib/test/test_ssl.py
 | ||||||
|  | @@ -8,9 +8,11 @@ from test.script_helper import assert_python_ok
 | ||||||
|  |  import asyncore | ||||||
|  |  import socket | ||||||
|  |  import select | ||||||
|  | +import struct
 | ||||||
|  |  import time | ||||||
|  |  import datetime | ||||||
|  |  import gc | ||||||
|  | +import httplib
 | ||||||
|  |  import os | ||||||
|  |  import errno | ||||||
|  |  import pprint | ||||||
|  | @@ -3240,6 +3242,213 @@ else:
 | ||||||
|  |                  self.assertRaises(ValueError, s.read, 1024) | ||||||
|  |                  self.assertRaises(ValueError, s.write, b'hello') | ||||||
|  |   | ||||||
|  | +def set_socket_so_linger_on_with_zero_timeout(sock):
 | ||||||
|  | +    sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
 | ||||||
|  | +
 | ||||||
|  | +
 | ||||||
|  | +class TestPreHandshakeClose(unittest.TestCase):
 | ||||||
|  | +    """Verify behavior of close sockets with received data before to the handshake.
 | ||||||
|  | +    """
 | ||||||
|  | +
 | ||||||
|  | +    class SingleConnectionTestServerThread(threading.Thread):
 | ||||||
|  | +
 | ||||||
|  | +        def __init__(self,  name, call_after_accept):
 | ||||||
|  | +            self.call_after_accept = call_after_accept
 | ||||||
|  | +            self.received_data = b''  # set by .run()
 | ||||||
|  | +            self.wrap_error = None  # set by .run()
 | ||||||
|  | +            self.listener = None  # set by .start()
 | ||||||
|  | +            self.port = None  # set by .start()
 | ||||||
|  | +            threading.Thread.__init__(self, name=name)
 | ||||||
|  | +
 | ||||||
|  | +        def __enter__(self):
 | ||||||
|  | +            self.start()
 | ||||||
|  | +            return self
 | ||||||
|  | +
 | ||||||
|  | +        def __exit__(self, *args):
 | ||||||
|  | +            try:
 | ||||||
|  | +                if self.listener:
 | ||||||
|  | +                    self.listener.close()
 | ||||||
|  | +            except ssl.SSLError:
 | ||||||
|  | +                pass
 | ||||||
|  | +            self.join()
 | ||||||
|  | +            self.wrap_error = None  # avoid dangling references
 | ||||||
|  | +
 | ||||||
|  | +        def start(self):
 | ||||||
|  | +            self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
 | ||||||
|  | +            self.ssl_ctx.verify_mode = ssl.CERT_REQUIRED
 | ||||||
|  | +            self.ssl_ctx.load_verify_locations(cafile=ONLYCERT)
 | ||||||
|  | +            self.ssl_ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
 | ||||||
|  | +            self.listener = socket.socket()
 | ||||||
|  | +            self.port = support.bind_port(self.listener)
 | ||||||
|  | +            self.listener.settimeout(2.0)
 | ||||||
|  | +            self.listener.listen(1)
 | ||||||
|  | +            threading.Thread.start(self)
 | ||||||
|  | +
 | ||||||
|  | +        def run(self):
 | ||||||
|  | +            conn, address = self.listener.accept()
 | ||||||
|  | +            self.listener.close()
 | ||||||
|  | +            with closing(conn):
 | ||||||
|  | +                if self.call_after_accept(conn):
 | ||||||
|  | +                    return
 | ||||||
|  | +                try:
 | ||||||
|  | +                    tls_socket = self.ssl_ctx.wrap_socket(conn, server_side=True)
 | ||||||
|  | +                except ssl.SSLError as err:
 | ||||||
|  | +                    self.wrap_error = err
 | ||||||
|  | +                else:
 | ||||||
|  | +                    try:
 | ||||||
|  | +                        self.received_data = tls_socket.recv(400)
 | ||||||
|  | +                    except OSError:
 | ||||||
|  | +                        pass  # closed, protocol error, etc.
 | ||||||
|  | +
 | ||||||
|  | +    def non_linux_skip_if_other_okay_error(self, err):
 | ||||||
|  | +        if sys.platform == "linux":
 | ||||||
|  | +            return  # Expect the full test setup to always work on Linux.
 | ||||||
|  | +        if (isinstance(err, ConnectionResetError) or
 | ||||||
|  | +            (isinstance(err, OSError) and err.errno == errno.EINVAL) or
 | ||||||
|  | +            re.search('wrong.version.number', getattr(err, "reason", ""), re.I)):
 | ||||||
|  | +            # On Windows the TCP RST leads to a ConnectionResetError
 | ||||||
|  | +            # (ECONNRESET) which Linux doesn't appear to surface to userspace.
 | ||||||
|  | +            # If wrap_socket() winds up on the "if connected:" path and doing
 | ||||||
|  | +            # the actual wrapping... we get an SSLError from OpenSSL. Typically
 | ||||||
|  | +            # WRONG_VERSION_NUMBER. While appropriate, neither is the scenario
 | ||||||
|  | +            # we're specifically trying to test. The way this test is written
 | ||||||
|  | +            # is known to work on Linux. We'll skip it anywhere else that it
 | ||||||
|  | +            # does not present as doing so.
 | ||||||
|  | +            self.skipTest("Could not recreate conditions on {}: \
 | ||||||
|  | +                          err={}".format(sys.platform,err))
 | ||||||
|  | +        # If maintaining this conditional winds up being a problem.
 | ||||||
|  | +        # just turn this into an unconditional skip anything but Linux.
 | ||||||
|  | +        # The important thing is that our CI has the logic covered.
 | ||||||
|  | +
 | ||||||
|  | +    def test_preauth_data_to_tls_server(self):
 | ||||||
|  | +        server_accept_called = threading.Event()
 | ||||||
|  | +        ready_for_server_wrap_socket = threading.Event()
 | ||||||
|  | +
 | ||||||
|  | +        def call_after_accept(unused):
 | ||||||
|  | +            server_accept_called.set()
 | ||||||
|  | +            if not ready_for_server_wrap_socket.wait(2.0):
 | ||||||
|  | +                raise RuntimeError("wrap_socket event never set, test may fail.")
 | ||||||
|  | +            return False  # Tell the server thread to continue.
 | ||||||
|  | +
 | ||||||
|  | +        server = self.SingleConnectionTestServerThread(
 | ||||||
|  | +                call_after_accept=call_after_accept,
 | ||||||
|  | +                name="preauth_data_to_tls_server")
 | ||||||
|  | +        server.__enter__()  # starts it
 | ||||||
|  | +        self.addCleanup(server.__exit__)  # ... & unittest.TestCase stops it.
 | ||||||
|  | +
 | ||||||
|  | +        with closing(socket.socket()) as client:
 | ||||||
|  | +            client.connect(server.listener.getsockname())
 | ||||||
|  | +            # This forces an immediate connection close via RST on .close().
 | ||||||
|  | +            set_socket_so_linger_on_with_zero_timeout(client)
 | ||||||
|  | +            client.setblocking(False)
 | ||||||
|  | +
 | ||||||
|  | +            server_accept_called.wait()
 | ||||||
|  | +            client.send(b"DELETE /data HTTP/1.0\r\n\r\n")
 | ||||||
|  | +            client.close()  # RST
 | ||||||
|  | +
 | ||||||
|  | +        ready_for_server_wrap_socket.set()
 | ||||||
|  | +        server.join()
 | ||||||
|  | +        wrap_error = server.wrap_error
 | ||||||
|  | +        self.assertEqual(b"", server.received_data)
 | ||||||
|  | +        self.assertIsInstance(wrap_error, ssl.SSLError)
 | ||||||
|  | +        self.assertIn("before TLS handshake with data", wrap_error.args[1])
 | ||||||
|  | +        self.assertIn("before TLS handshake with data", wrap_error.reason)
 | ||||||
|  | +        self.assertNotEqual(0, wrap_error.args[0])
 | ||||||
|  | +        self.assertIsNone(wrap_error.library, msg="attr must exist")
 | ||||||
|  | +
 | ||||||
|  | +    def test_preauth_data_to_tls_client(self):
 | ||||||
|  | +        client_can_continue_with_wrap_socket = threading.Event()
 | ||||||
|  | +
 | ||||||
|  | +        def call_after_accept(conn_to_client):
 | ||||||
|  | +            # This forces an immediate connection close via RST on .close().
 | ||||||
|  | +            set_socket_so_linger_on_with_zero_timeout(conn_to_client)
 | ||||||
|  | +            conn_to_client.send(
 | ||||||
|  | +                    b"HTTP/1.0 307 Temporary Redirect\r\n"
 | ||||||
|  | +                    b"Location: https://example.com/someone-elses-server\r\n"
 | ||||||
|  | +                    b"\r\n")
 | ||||||
|  | +            conn_to_client.close()  # RST
 | ||||||
|  | +            client_can_continue_with_wrap_socket.set()
 | ||||||
|  | +            return True  # Tell the server to stop.
 | ||||||
|  | +
 | ||||||
|  | +        server = self.SingleConnectionTestServerThread(
 | ||||||
|  | +                call_after_accept=call_after_accept,
 | ||||||
|  | +                name="preauth_data_to_tls_client")
 | ||||||
|  | +        server.__enter__()  # starts it
 | ||||||
|  | +        self.addCleanup(server.__exit__)  # ... & unittest.TestCase stops it.
 | ||||||
|  | +
 | ||||||
|  | +        # Redundant; call_after_accept sets SO_LINGER on the accepted conn.
 | ||||||
|  | +        set_socket_so_linger_on_with_zero_timeout(server.listener)
 | ||||||
|  | +
 | ||||||
|  | +        with closing(socket.socket()) as client:
 | ||||||
|  | +            client.connect(server.listener.getsockname())
 | ||||||
|  | +            if not client_can_continue_with_wrap_socket.wait(2.0):
 | ||||||
|  | +                self.fail("test server took too long.")
 | ||||||
|  | +            ssl_ctx = ssl.create_default_context()
 | ||||||
|  | +            try:
 | ||||||
|  | +                tls_client = ssl_ctx.wrap_socket(
 | ||||||
|  | +                        client, server_hostname="localhost")
 | ||||||
|  | +            except ssl.SSLError as err:
 | ||||||
|  | +                wrap_error = err
 | ||||||
|  | +                received_data = b""
 | ||||||
|  | +            else:
 | ||||||
|  | +                wrap_error = None
 | ||||||
|  | +                received_data = tls_client.recv(400)
 | ||||||
|  | +                tls_client.close()
 | ||||||
|  | +
 | ||||||
|  | +        server.join()
 | ||||||
|  | +        self.assertEqual(b"", received_data)
 | ||||||
|  | +        self.assertIsInstance(wrap_error, ssl.SSLError)
 | ||||||
|  | +        self.assertIn("before TLS handshake with data", wrap_error.args[1])
 | ||||||
|  | +        self.assertIn("before TLS handshake with data", wrap_error.reason)
 | ||||||
|  | +        self.assertNotEqual(0, wrap_error.args[0])
 | ||||||
|  | +        self.assertIsNone(wrap_error.library, msg="attr must exist")
 | ||||||
|  | +
 | ||||||
|  | +    def test_https_client_non_tls_response_ignored(self):
 | ||||||
|  | +
 | ||||||
|  | +        server_responding = threading.Event()
 | ||||||
|  | +
 | ||||||
|  | +        class SynchronizedHTTPSConnection(httplib.HTTPSConnection):
 | ||||||
|  | +            def connect(self):
 | ||||||
|  | +                httplib.HTTPConnection.connect(self)
 | ||||||
|  | +                # Wait for our fault injection server to have done its thing.
 | ||||||
|  | +                if not server_responding.wait(1.0) and support.verbose:
 | ||||||
|  | +                    sys.stdout.write("server_responding event never set.")
 | ||||||
|  | +                self.sock = self._context.wrap_socket(
 | ||||||
|  | +                        self.sock, server_hostname=self.host)
 | ||||||
|  | +
 | ||||||
|  | +        def call_after_accept(conn_to_client):
 | ||||||
|  | +            # This forces an immediate connection close via RST on .close().
 | ||||||
|  | +            set_socket_so_linger_on_with_zero_timeout(conn_to_client)
 | ||||||
|  | +            conn_to_client.send(
 | ||||||
|  | +                    b"HTTP/1.0 402 Payment Required\r\n"
 | ||||||
|  | +                    b"\r\n")
 | ||||||
|  | +            conn_to_client.close()  # RST
 | ||||||
|  | +            server_responding.set()
 | ||||||
|  | +            return True  # Tell the server to stop.
 | ||||||
|  | +
 | ||||||
|  | +        server = self.SingleConnectionTestServerThread(
 | ||||||
|  | +                call_after_accept=call_after_accept,
 | ||||||
|  | +                name="non_tls_http_RST_responder")
 | ||||||
|  | +        server.__enter__()  # starts it
 | ||||||
|  | +        self.addCleanup(server.__exit__)  # ... & unittest.TestCase stops it.
 | ||||||
|  | +        # Redundant; call_after_accept sets SO_LINGER on the accepted conn.
 | ||||||
|  | +        set_socket_so_linger_on_with_zero_timeout(server.listener)
 | ||||||
|  | +
 | ||||||
|  | +        connection = SynchronizedHTTPSConnection(
 | ||||||
|  | +                "localhost",
 | ||||||
|  | +                port=server.port,
 | ||||||
|  | +                context=ssl.create_default_context(),
 | ||||||
|  | +                timeout=2.0,
 | ||||||
|  | +        )
 | ||||||
|  | +        # There are lots of reasons this raises as desired, long before this
 | ||||||
|  | +        # test was added. Sending the request requires a successful TLS wrapped
 | ||||||
|  | +        # socket; that fails if the connection is broken. It may seem pointless
 | ||||||
|  | +        # to test this. It serves as an illustration of something that we never
 | ||||||
|  | +        # want to happen... properly not happening.
 | ||||||
|  | +        with self.assertRaises(ssl.SSLError) as err_ctx:
 | ||||||
|  | +            connection.request("HEAD", "/test", headers={"Host": "localhost"})
 | ||||||
|  | +            response = connection.getresponse()
 | ||||||
|  | +
 | ||||||
|  |   | ||||||
|  |  def test_main(verbose=False): | ||||||
|  |      if support.verbose: | ||||||
|  | @@ -3274,6 +3483,7 @@ def test_main(verbose=False):
 | ||||||
|  |              raise support.TestFailed("Can't read certificate file %r" % filename) | ||||||
|  |   | ||||||
|  |      tests = [ContextTests, BasicTests, BasicSocketTests, SSLErrorTests] | ||||||
|  | +    tests += [TestPreHandshakeClose]
 | ||||||
|  |   | ||||||
|  |      if support.is_resource_enabled('network'): | ||||||
|  |          tests.append(NetworkedTests) | ||||||
|  | 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
 | ||||||
|  | new file mode 100644 | ||||||
|  | index 00000000000..403c77a9d48
 | ||||||
|  | --- /dev/null
 | ||||||
|  | +++ b/Misc/NEWS.d/next/Security/2023-08-22-17-39-12.gh-issue-108310.fVM3sg.rst
 | ||||||
|  | @@ -0,0 +1,7 @@
 | ||||||
|  | +Fixed an issue where instances of :class:`ssl.SSLSocket` were vulnerable to
 | ||||||
|  | +a bypass of the TLS handshake and included protections (like certificate
 | ||||||
|  | +verification) and treating sent unencrypted data as if it were
 | ||||||
|  | +post-handshake TLS encrypted data.  Security issue reported as
 | ||||||
|  | +`CVE-2023-40217
 | ||||||
|  | +<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-40217>`_ by
 | ||||||
|  | +Aapo Oksman. Patch by Gregory P. Smith.
 | ||||||
|  | -- 
 | ||||||
|  | 2.41.0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | From 8ea38387426d3d2dd4c38f5ab7c26ee0ab0fb242 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: "Miss Islington (bot)" | ||||||
|  |  <31488909+miss-islington@users.noreply.github.com> | ||||||
|  | Date: Wed, 23 Aug 2023 03:10:56 -0700 | ||||||
|  | Subject: [PATCH 2/3] gh-108342: Break ref cycle in SSLSocket._create() exc | ||||||
|  |  (GH-108344) (#108352) | ||||||
|  | 
 | ||||||
|  | Explicitly break a reference cycle when SSLSocket._create() raises an | ||||||
|  | exception. Clear the variable storing the exception, since the | ||||||
|  | exception traceback contains the variables and so creates a reference | ||||||
|  | cycle. | ||||||
|  | 
 | ||||||
|  | This test leak was introduced by the test added for the fix of GH-108310. | ||||||
|  | (cherry picked from commit 64f99350351bc46e016b2286f36ba7cd669b79e3) | ||||||
|  | 
 | ||||||
|  | Co-authored-by: Victor Stinner <vstinner@python.org> | ||||||
|  | ---
 | ||||||
|  |  Lib/ssl.py | 6 +++++- | ||||||
|  |  1 file changed, 5 insertions(+), 1 deletion(-) | ||||||
|  | 
 | ||||||
|  | diff --git a/Lib/ssl.py b/Lib/ssl.py
 | ||||||
|  | index de9ce6bc134..ac734b4f2ad 100644
 | ||||||
|  | --- a/Lib/ssl.py
 | ||||||
|  | +++ b/Lib/ssl.py
 | ||||||
|  | @@ -610,7 +610,11 @@ class SSLSocket(socket):
 | ||||||
|  |                      self.close() | ||||||
|  |                  except OSError: | ||||||
|  |                      pass | ||||||
|  | -                raise notconn_pre_handshake_data_error
 | ||||||
|  | +                try:
 | ||||||
|  | +                    raise notconn_pre_handshake_data_error
 | ||||||
|  | +                finally:
 | ||||||
|  | +                    # Explicitly break the reference cycle.
 | ||||||
|  | +                    notconn_pre_handshake_data_error = None
 | ||||||
|  |          else: | ||||||
|  |              connected = True | ||||||
|  |   | ||||||
|  | -- 
 | ||||||
|  | 2.41.0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | From 845491ee514d6489466664aeef377fe9155f8e83 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: =?UTF-8?q?=C5=81ukasz=20Langa?= <lukasz@langa.pl> | ||||||
|  | Date: Thu, 24 Aug 2023 12:09:30 +0200 | ||||||
|  | Subject: [PATCH 3/3] [3.8] gh-108342: Make ssl TestPreHandshakeClose more | ||||||
|  |  reliable (GH-108370) (#108408) | ||||||
|  | 
 | ||||||
|  | * In preauth tests of test_ssl, explicitly break reference cycles | ||||||
|  |   invoving SingleConnectionTestServerThread to make sure that the | ||||||
|  |   thread is deleted. Otherwise, the test marks the environment as | ||||||
|  |   altered because the threading module sees a "dangling thread" | ||||||
|  |   (SingleConnectionTestServerThread). This test leak was introduced | ||||||
|  |   by the test added for the fix of issue gh-108310. | ||||||
|  | * Use support.SHORT_TIMEOUT instead of hardcoded 1.0 or 2.0 seconds | ||||||
|  |   timeout. | ||||||
|  | * SingleConnectionTestServerThread.run() catchs TimeoutError | ||||||
|  | * Fix a race condition (missing synchronization) in | ||||||
|  |   test_preauth_data_to_tls_client(): the server now waits until the | ||||||
|  |   client connect() completed in call_after_accept(). | ||||||
|  | * test_https_client_non_tls_response_ignored() calls server.join() | ||||||
|  |   explicitly. | ||||||
|  | * Replace "localhost" with server.listener.getsockname()[0]. | ||||||
|  | (cherry picked from commit 592bacb6fc0833336c0453e818e9b95016e9fd47) | ||||||
|  | ---
 | ||||||
|  |  Lib/test/support/__init__.py |   2 + | ||||||
|  |  Lib/test/test_ssl.py         | 105 ++++++++++++++++++++--------------- | ||||||
|  |  2 files changed, 62 insertions(+), 45 deletions(-) | ||||||
|  | 
 | ||||||
|  | diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
 | ||||||
|  | index ccc11c1b4b0..d6ee9934f04 100644
 | ||||||
|  | --- a/Lib/test/support/__init__.py
 | ||||||
|  | +++ b/Lib/test/support/__init__.py
 | ||||||
|  | @@ -47,6 +47,8 @@ __all__ = ["Error", "TestFailed", "TestDidNotRun", "ResourceDenied", "import_mod
 | ||||||
|  |             "strip_python_stderr", "IPV6_ENABLED", "run_with_tz", | ||||||
|  |             "SuppressCrashReport"] | ||||||
|  |   | ||||||
|  | +SHORT_TIMEOUT = 30.0  # Added to make backporting from 3.x easier
 | ||||||
|  | +
 | ||||||
|  |  class Error(Exception): | ||||||
|  |      """Base class for regression test exceptions.""" | ||||||
|  |   | ||||||
|  | diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
 | ||||||
|  | index fd657761a05..0f5aa37a0c1 100644
 | ||||||
|  | --- a/Lib/test/test_ssl.py
 | ||||||
|  | +++ b/Lib/test/test_ssl.py
 | ||||||
|  | @@ -3252,12 +3252,16 @@ class TestPreHandshakeClose(unittest.TestCase):
 | ||||||
|  |   | ||||||
|  |      class SingleConnectionTestServerThread(threading.Thread): | ||||||
|  |   | ||||||
|  | -        def __init__(self,  name, call_after_accept):
 | ||||||
|  | +        def __init__(self,  name, call_after_accept, timeout=None):
 | ||||||
|  |              self.call_after_accept = call_after_accept | ||||||
|  |              self.received_data = b''  # set by .run() | ||||||
|  |              self.wrap_error = None  # set by .run() | ||||||
|  |              self.listener = None  # set by .start() | ||||||
|  |              self.port = None  # set by .start() | ||||||
|  | +            if timeout is None:
 | ||||||
|  | +                self.timeout = support.SHORT_TIMEOUT
 | ||||||
|  | +            else:
 | ||||||
|  | +                self.timeout = timeout
 | ||||||
|  |              threading.Thread.__init__(self, name=name) | ||||||
|  |   | ||||||
|  |          def __enter__(self): | ||||||
|  | @@ -3280,13 +3284,22 @@ class TestPreHandshakeClose(unittest.TestCase):
 | ||||||
|  |              self.ssl_ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY) | ||||||
|  |              self.listener = socket.socket() | ||||||
|  |              self.port = support.bind_port(self.listener) | ||||||
|  | -            self.listener.settimeout(2.0)
 | ||||||
|  | +            self.listener.settimeout(self.timeout)
 | ||||||
|  |              self.listener.listen(1) | ||||||
|  |              threading.Thread.start(self) | ||||||
|  |   | ||||||
|  |          def run(self): | ||||||
|  | -            conn, address = self.listener.accept()
 | ||||||
|  | -            self.listener.close()
 | ||||||
|  | +            try:
 | ||||||
|  | +                conn, address = self.listener.accept()
 | ||||||
|  | +            except OSError as e:
 | ||||||
|  | +                if e.errno == errno.ETIMEDOUT:
 | ||||||
|  | +                    # on timeout, just close the listener
 | ||||||
|  | +                    return
 | ||||||
|  | +                else:
 | ||||||
|  | +                    raise
 | ||||||
|  | +            finally:
 | ||||||
|  | +                self.listener.close()
 | ||||||
|  | +
 | ||||||
|  |              with closing(conn): | ||||||
|  |                  if self.call_after_accept(conn): | ||||||
|  |                      return | ||||||
|  | @@ -3300,33 +3313,13 @@ class TestPreHandshakeClose(unittest.TestCase):
 | ||||||
|  |                      except OSError: | ||||||
|  |                          pass  # closed, protocol error, etc. | ||||||
|  |   | ||||||
|  | -    def non_linux_skip_if_other_okay_error(self, err):
 | ||||||
|  | -        if sys.platform == "linux":
 | ||||||
|  | -            return  # Expect the full test setup to always work on Linux.
 | ||||||
|  | -        if (isinstance(err, ConnectionResetError) or
 | ||||||
|  | -            (isinstance(err, OSError) and err.errno == errno.EINVAL) or
 | ||||||
|  | -            re.search('wrong.version.number', getattr(err, "reason", ""), re.I)):
 | ||||||
|  | -            # On Windows the TCP RST leads to a ConnectionResetError
 | ||||||
|  | -            # (ECONNRESET) which Linux doesn't appear to surface to userspace.
 | ||||||
|  | -            # If wrap_socket() winds up on the "if connected:" path and doing
 | ||||||
|  | -            # the actual wrapping... we get an SSLError from OpenSSL. Typically
 | ||||||
|  | -            # WRONG_VERSION_NUMBER. While appropriate, neither is the scenario
 | ||||||
|  | -            # we're specifically trying to test. The way this test is written
 | ||||||
|  | -            # is known to work on Linux. We'll skip it anywhere else that it
 | ||||||
|  | -            # does not present as doing so.
 | ||||||
|  | -            self.skipTest("Could not recreate conditions on {}: \
 | ||||||
|  | -                          err={}".format(sys.platform,err))
 | ||||||
|  | -        # If maintaining this conditional winds up being a problem.
 | ||||||
|  | -        # just turn this into an unconditional skip anything but Linux.
 | ||||||
|  | -        # The important thing is that our CI has the logic covered.
 | ||||||
|  | -
 | ||||||
|  |      def test_preauth_data_to_tls_server(self): | ||||||
|  |          server_accept_called = threading.Event() | ||||||
|  |          ready_for_server_wrap_socket = threading.Event() | ||||||
|  |   | ||||||
|  |          def call_after_accept(unused): | ||||||
|  |              server_accept_called.set() | ||||||
|  | -            if not ready_for_server_wrap_socket.wait(2.0):
 | ||||||
|  | +            if not ready_for_server_wrap_socket.wait(support.SHORT_TIMEOUT):
 | ||||||
|  |                  raise RuntimeError("wrap_socket event never set, test may fail.") | ||||||
|  |              return False  # Tell the server thread to continue. | ||||||
|  |   | ||||||
|  | @@ -3348,18 +3341,28 @@ class TestPreHandshakeClose(unittest.TestCase):
 | ||||||
|  |   | ||||||
|  |          ready_for_server_wrap_socket.set() | ||||||
|  |          server.join() | ||||||
|  | +
 | ||||||
|  |          wrap_error = server.wrap_error | ||||||
|  | -        self.assertEqual(b"", server.received_data)
 | ||||||
|  | -        self.assertIsInstance(wrap_error, ssl.SSLError)
 | ||||||
|  | -        self.assertIn("before TLS handshake with data", wrap_error.args[1])
 | ||||||
|  | -        self.assertIn("before TLS handshake with data", wrap_error.reason)
 | ||||||
|  | -        self.assertNotEqual(0, wrap_error.args[0])
 | ||||||
|  | -        self.assertIsNone(wrap_error.library, msg="attr must exist")
 | ||||||
|  | +        try:
 | ||||||
|  | +            self.assertEqual(b"", server.received_data)
 | ||||||
|  | +            self.assertIsInstance(wrap_error, ssl.SSLError)
 | ||||||
|  | +            self.assertIn("before TLS handshake with data", wrap_error.args[1])
 | ||||||
|  | +            self.assertIn("before TLS handshake with data", wrap_error.reason)
 | ||||||
|  | +            self.assertNotEqual(0, wrap_error.args[0])
 | ||||||
|  | +            self.assertIsNone(wrap_error.library, msg="attr must exist")
 | ||||||
|  | +        finally:
 | ||||||
|  | +            # gh-108342: Explicitly break the reference cycle
 | ||||||
|  | +            wrap_error = None
 | ||||||
|  | +            server = None
 | ||||||
|  |   | ||||||
|  |      def test_preauth_data_to_tls_client(self): | ||||||
|  | +        server_can_continue_with_wrap_socket = threading.Event()
 | ||||||
|  |          client_can_continue_with_wrap_socket = threading.Event() | ||||||
|  |   | ||||||
|  |          def call_after_accept(conn_to_client): | ||||||
|  | +            if not server_can_continue_with_wrap_socket.wait(support.SHORT_TIMEOUT):
 | ||||||
|  | +                print("ERROR: test client took too long")
 | ||||||
|  | +
 | ||||||
|  |              # This forces an immediate connection close via RST on .close(). | ||||||
|  |              set_socket_so_linger_on_with_zero_timeout(conn_to_client) | ||||||
|  |              conn_to_client.send( | ||||||
|  | @@ -3381,8 +3384,10 @@ class TestPreHandshakeClose(unittest.TestCase):
 | ||||||
|  |   | ||||||
|  |          with closing(socket.socket()) as client: | ||||||
|  |              client.connect(server.listener.getsockname()) | ||||||
|  | -            if not client_can_continue_with_wrap_socket.wait(2.0):
 | ||||||
|  | -                self.fail("test server took too long.")
 | ||||||
|  | +            server_can_continue_with_wrap_socket.set()
 | ||||||
|  | +
 | ||||||
|  | +            if not client_can_continue_with_wrap_socket.wait(support.SHORT_TIMEOUT):
 | ||||||
|  | +                self.fail("test server took too long")
 | ||||||
|  |              ssl_ctx = ssl.create_default_context() | ||||||
|  |              try: | ||||||
|  |                  tls_client = ssl_ctx.wrap_socket( | ||||||
|  | @@ -3396,22 +3401,29 @@ class TestPreHandshakeClose(unittest.TestCase):
 | ||||||
|  |                  tls_client.close() | ||||||
|  |   | ||||||
|  |          server.join() | ||||||
|  | -        self.assertEqual(b"", received_data)
 | ||||||
|  | -        self.assertIsInstance(wrap_error, ssl.SSLError)
 | ||||||
|  | -        self.assertIn("before TLS handshake with data", wrap_error.args[1])
 | ||||||
|  | -        self.assertIn("before TLS handshake with data", wrap_error.reason)
 | ||||||
|  | -        self.assertNotEqual(0, wrap_error.args[0])
 | ||||||
|  | -        self.assertIsNone(wrap_error.library, msg="attr must exist")
 | ||||||
|  | +        try:
 | ||||||
|  | +            self.assertEqual(b"", received_data)
 | ||||||
|  | +            self.assertIsInstance(wrap_error, ssl.SSLError)
 | ||||||
|  | +            self.assertIn("before TLS handshake with data", wrap_error.args[1])
 | ||||||
|  | +            self.assertIn("before TLS handshake with data", wrap_error.reason)
 | ||||||
|  | +            self.assertNotEqual(0, wrap_error.args[0])
 | ||||||
|  | +            self.assertIsNone(wrap_error.library, msg="attr must exist")
 | ||||||
|  | +        finally:
 | ||||||
|  | +            # gh-108342: Explicitly break the reference cycle
 | ||||||
|  | +            wrap_error = None
 | ||||||
|  | +            server = None
 | ||||||
|  |   | ||||||
|  |      def test_https_client_non_tls_response_ignored(self): | ||||||
|  | -
 | ||||||
|  |          server_responding = threading.Event() | ||||||
|  |   | ||||||
|  |          class SynchronizedHTTPSConnection(httplib.HTTPSConnection): | ||||||
|  |              def connect(self): | ||||||
|  | +                # Call clear text HTTP connect(), not the encrypted HTTPS (TLS)
 | ||||||
|  | +                # connect(): wrap_socket() is called manually below.
 | ||||||
|  |                  httplib.HTTPConnection.connect(self) | ||||||
|  | +
 | ||||||
|  |                  # Wait for our fault injection server to have done its thing. | ||||||
|  | -                if not server_responding.wait(1.0) and support.verbose:
 | ||||||
|  | +                if not server_responding.wait(support.SHORT_TIMEOUT) and support.verbose:
 | ||||||
|  |                      sys.stdout.write("server_responding event never set.") | ||||||
|  |                  self.sock = self._context.wrap_socket( | ||||||
|  |                          self.sock, server_hostname=self.host) | ||||||
|  | @@ -3426,29 +3438,32 @@ class TestPreHandshakeClose(unittest.TestCase):
 | ||||||
|  |              server_responding.set() | ||||||
|  |              return True  # Tell the server to stop. | ||||||
|  |   | ||||||
|  | +        timeout = 2.0
 | ||||||
|  |          server = self.SingleConnectionTestServerThread( | ||||||
|  |                  call_after_accept=call_after_accept, | ||||||
|  | -                name="non_tls_http_RST_responder")
 | ||||||
|  | +                name="non_tls_http_RST_responder",
 | ||||||
|  | +                timeout=timeout)
 | ||||||
|  |          server.__enter__()  # starts it | ||||||
|  |          self.addCleanup(server.__exit__)  # ... & unittest.TestCase stops it. | ||||||
|  |          # Redundant; call_after_accept sets SO_LINGER on the accepted conn. | ||||||
|  |          set_socket_so_linger_on_with_zero_timeout(server.listener) | ||||||
|  |   | ||||||
|  |          connection = SynchronizedHTTPSConnection( | ||||||
|  | -                "localhost",
 | ||||||
|  | +                server.listener.getsockname()[0],
 | ||||||
|  |                  port=server.port, | ||||||
|  |                  context=ssl.create_default_context(), | ||||||
|  | -                timeout=2.0,
 | ||||||
|  | +                timeout=timeout,
 | ||||||
|  |          ) | ||||||
|  |          # There are lots of reasons this raises as desired, long before this | ||||||
|  |          # test was added. Sending the request requires a successful TLS wrapped | ||||||
|  |          # socket; that fails if the connection is broken. It may seem pointless | ||||||
|  |          # to test this. It serves as an illustration of something that we never | ||||||
|  |          # want to happen... properly not happening. | ||||||
|  | -        with self.assertRaises(ssl.SSLError) as err_ctx:
 | ||||||
|  | +        with self.assertRaises(ssl.SSLError):
 | ||||||
|  |              connection.request("HEAD", "/test", headers={"Host": "localhost"}) | ||||||
|  |              response = connection.getresponse() | ||||||
|  |   | ||||||
|  | +        server.join()
 | ||||||
|  |   | ||||||
|  |  def test_main(verbose=False): | ||||||
|  |      if support.verbose: | ||||||
|  | -- 
 | ||||||
|  | 2.41.0 | ||||||
							
								
								
									
										19
									
								
								python2.spec
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								python2.spec
									
									
									
									
									
								
							| @ -104,7 +104,7 @@ Summary: An interpreted, interactive, object-oriented programming language | |||||||
| Name: %{python} | Name: %{python} | ||||||
| # Remember to also rebase python2-docs when changing this: | # Remember to also rebase python2-docs when changing this: | ||||||
| Version: 2.7.18 | Version: 2.7.18 | ||||||
| Release: 14%{?dist} | Release: 15%{?dist} | ||||||
| License: Python | License: Python | ||||||
| Group: Development/Languages | Group: Development/Languages | ||||||
| Requires: %{python}-libs%{?_isa} = %{version}-%{release} | Requires: %{python}-libs%{?_isa} = %{version}-%{release} | ||||||
| @ -826,6 +826,18 @@ Patch394: 00394-cve-2022-45061-cpu-denial-of-service-via-inefficient-idna-decode | |||||||
| # Backported to Python 2 from Python 3.12. | # Backported to Python 2 from Python 3.12. | ||||||
| Patch399: 00399-cve-2023-24329.patch | Patch399: 00399-cve-2023-24329.patch | ||||||
| 
 | 
 | ||||||
|  | # 00404 # | ||||||
|  | # CVE-2023-40217 | ||||||
|  | # | ||||||
|  | # Security fix for CVE-2023-40217: Bypass TLS handshake on closed sockets | ||||||
|  | # Resolved upstream: https://github.com/python/cpython/issues/108310 | ||||||
|  | # Fixups added on top: | ||||||
|  | # https://github.com/python/cpython/pull/108352 | ||||||
|  | # https://github.com/python/cpython/pull/108408 | ||||||
|  | # | ||||||
|  | # Backported from Python 3.8 | ||||||
|  | Patch404: 00404-cve-2023-40217.patch | ||||||
|  | 
 | ||||||
| # (New patches go here ^^^) | # (New patches go here ^^^) | ||||||
| # | # | ||||||
| # When adding new patches to "python2" and "python3" in Fedora, EL, etc., | # When adding new patches to "python2" and "python3" in Fedora, EL, etc., | ||||||
| @ -1163,6 +1175,7 @@ git apply %{PATCH351} | |||||||
| %patch382 -p1 | %patch382 -p1 | ||||||
| %patch394 -p1 | %patch394 -p1 | ||||||
| %patch399 -p1 | %patch399 -p1 | ||||||
|  | %patch404 -p1 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # This shouldn't be necesarry, but is right now (2.2a3) | # This shouldn't be necesarry, but is right now (2.2a3) | ||||||
| @ -2102,6 +2115,10 @@ fi | |||||||
| # ====================================================== | # ====================================================== | ||||||
| 
 | 
 | ||||||
| %changelog | %changelog | ||||||
|  | * Wed Sep 27 2023 Charalampos Stratakis <cstratak@redhat.com> - 2.7.18-15 | ||||||
|  | - Security fix for CVE-2023-40217 | ||||||
|  | Resolves: RHEL-9621 | ||||||
|  | 
 | ||||||
| * Thu Jul 20 2023 Charalampos Stratakis <cstratak@redhat.com> - 2.7.18-14 | * Thu Jul 20 2023 Charalampos Stratakis <cstratak@redhat.com> - 2.7.18-14 | ||||||
| - Security fix for CVE-2023-24329 | - Security fix for CVE-2023-24329 | ||||||
| Resolves: rhbz#2173917 | Resolves: rhbz#2173917 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user