diff --git a/00404-cve-2023-40217.patch b/00404-cve-2023-40217.patch new file mode 100644 index 0000000000000000000000000000000000000000..aee8d86d545e5e60f18b0edd5b4b085cac5890a9 --- /dev/null +++ b/00404-cve-2023-40217.patch @@ -0,0 +1,340 @@ +diff --git a/Lib/ssl.py b/Lib/ssl.py +index c5c5529..daedc82 100644 +--- a/Lib/ssl.py ++++ b/Lib/ssl.py +@@ -690,6 +690,7 @@ class SSLSocket(socket): + suppress_ragged_eofs=True, npn_protocols=None, ciphers=None, + server_hostname=None, + _context=None, _session=None): ++ self._sslobj = None + + if _context: + self._context = _context +@@ -741,7 +742,7 @@ class SSLSocket(socket): + type=sock.type, + proto=sock.proto, + fileno=sock.fileno()) +- self.settimeout(sock.gettimeout()) ++ sock_timeout = sock.gettimeout() + sock.detach() + elif fileno is not None: + socket.__init__(self, fileno=fileno) +@@ -755,9 +756,42 @@ class SSLSocket(socket): + if e.errno != errno.ENOTCONN: + raise + connected = False ++ blocking = (self.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 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: ++ self.close() ++ except OSError: ++ pass ++ 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._closed = False + self._sslobj = None + self._connected = connected +diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py +index b35db25..f1a797a 100644 +--- a/Lib/test/test_ssl.py ++++ b/Lib/test/test_ssl.py +@@ -3,11 +3,14 @@ + import sys + import unittest + from test import support ++import re + import socket + import select ++import struct + import time + import datetime + import gc ++import http.client + import os + import errno + import pprint +@@ -3940,6 +3943,256 @@ class TestPostHandshakeAuth(unittest.TestCase): + # server cert has not been validated + self.assertEqual(s.getpeercert(), {}) + ++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, 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 ++ super().__init__(name=name) ++ ++ def __enter__(self): ++ self.start() ++ return self ++ ++ def __exit__(self, *args): ++ try: ++ if self.listener: ++ self.listener.close() ++ except OSError: ++ 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(self.timeout) ++ self.listener.listen(1) ++ super().start() ++ ++ def run(self): ++ try: ++ conn, address = self.listener.accept() ++ except TimeoutError: ++ # on timeout, just close the listener ++ return ++ finally: ++ self.listener.close() ++ ++ with conn: ++ if self.call_after_accept(conn): ++ return ++ try: ++ tls_socket = self.ssl_ctx.wrap_socket(conn, server_side=True) ++ except OSError as err: # ssl.SSLError inherits from OSError ++ 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. ++ try: ++ self.skipTest("Could not recreate conditions on {}: \ ++ err={}".format(sys.platform,err)) ++ finally: ++ # gh-108342: Explicitly break the reference cycle ++ err = None ++ ++ # 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(support.SHORT_TIMEOUT): ++ 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 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 ++ server.wrap_error = None ++ try: ++ self.assertEqual(b"", server.received_data) ++ self.assertIsInstance(wrap_error, OSError) # All platforms. ++ self.non_linux_skip_if_other_okay_error(wrap_error) ++ 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( ++ 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 socket.socket() as client: ++ client.connect(server.listener.getsockname()) ++ 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( ++ client, server_hostname="localhost") ++ except OSError as err: # SSLError inherits from OSError ++ wrap_error = err ++ received_data = b"" ++ else: ++ wrap_error = None ++ received_data = tls_client.recv(400) ++ tls_client.close() ++ ++ server.join() ++ try: ++ self.assertEqual(b"", received_data) ++ self.assertIsInstance(wrap_error, OSError) # All platforms. ++ self.non_linux_skip_if_other_okay_error(wrap_error) ++ 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(http.client.HTTPSConnection): ++ def connect(self): ++ # Call clear text HTTP connect(), not the encrypted HTTPS (TLS) ++ # connect(): wrap_socket() is called manually below. ++ http.client.HTTPConnection.connect(self) ++ ++ # Wait for our fault injection server to have done its thing. ++ 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) ++ ++ 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. ++ ++ timeout = 2.0 ++ server = self.SingleConnectionTestServerThread( ++ call_after_accept=call_after_accept, ++ 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( ++ server.listener.getsockname()[0], ++ port=server.port, ++ context=ssl.create_default_context(), ++ 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(OSError): ++ connection.request("HEAD", "/test", headers={"Host": "localhost"}) ++ response = connection.getresponse() ++ ++ server.join() + + def test_main(verbose=False): + if support.verbose: diff --git a/python3.spec b/python3.spec index e6fa602e650f1f7cb5fed7ad1ab04ec9bd756726..b568216516873d98658f049e3923b776e5763666 100644 --- a/python3.spec +++ b/python3.spec @@ -15,7 +15,7 @@ URL: https://www.python.org/ # WARNING When rebasing to a new Python version, # remember to update the python3-docs package as well Version: %{pybasever}.8 -Release: 51%{anolis_release}%{?dist}.1 +Release: 51%{anolis_release}%{?dist}.2 License: Python @@ -200,7 +200,6 @@ BuildRequires: libappstream-glib BuildRequires: libffi-devel BuildRequires: libnsl2-devel BuildRequires: libtirpc-devel -BuildRequires: libGL-devel BuildRequires: libX11-devel BuildRequires: ncurses-devel @@ -778,6 +777,20 @@ Patch394: 00394-cve-2022-45061-cpu-denial-of-service-via-inefficient-idna-decode # Backported from Python 3.12 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 +#Use alternative for self.getblocking(), which was added in Python 3.7 +#see: https://docs.python.org/3/library/socket.html#socket.socket.getblocking +# +# Backported from Python 3.8 +Patch404: 00404-cve-2023-40217.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1130,6 +1143,7 @@ git apply %{PATCH351} %patch387 -p1 %patch394 -p1 %patch399 -p1 +%patch404 -p1 %patch1000 -p1 %patch1001 -p1 @@ -2066,6 +2080,9 @@ fi # ====================================================== %changelog +* Tue Nov 22 2023 ningmingxiao - 3.6.8-51.0.1.2 +- Security fix for CVE-2023-40217 + * Thu Jun 15 2023 zhangbinchen - 3.6.8-51.0.1.1 - Add Anolis platform cherry-pick [9a96461] - Support Loongarch for python3