From 75b52ffd3f3e42bcf1a86051eed79d977f4e6bc6 Mon Sep 17 00:00:00 2001 From: eabdullin Date: Wed, 27 Mar 2024 20:20:28 +0000 Subject: [PATCH] import CS python3-3.6.8-59.el8 --- SOURCES/00404-cve-2023-40217.patch | 648 +++++++++++++++ SOURCES/00408-CVE-2022-48560.patch | 143 ++++ SOURCES/00413-CVE-2022-48564.patch | 560 +++++++++++++ SOURCES/00414-skip_test_zlib_s390x.patch | 88 ++ ...-addresses-in-email-parseaddr-111116.patch | 750 ++++++++++++++++++ SPECS/python3.spec | 72 +- 6 files changed, 2260 insertions(+), 1 deletion(-) create mode 100644 SOURCES/00404-cve-2023-40217.patch create mode 100644 SOURCES/00408-CVE-2022-48560.patch create mode 100644 SOURCES/00413-CVE-2022-48564.patch create mode 100644 SOURCES/00414-skip_test_zlib_s390x.patch create mode 100644 SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch diff --git a/SOURCES/00404-cve-2023-40217.patch b/SOURCES/00404-cve-2023-40217.patch new file mode 100644 index 0000000..579c122 --- /dev/null +++ b/SOURCES/00404-cve-2023-40217.patch @@ -0,0 +1,648 @@ +From 9f39318072b5775cf527f83daf8cb5d64678ac86 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?=C5=81ukasz=20Langa?= +Date: Tue, 22 Aug 2023 19:57:01 +0200 +Subject: [PATCH 1/4] gh-108310: Fix CVE-2023-40217: Check for & avoid the ssl + pre-close flaw (#108321) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +gh-108310: Fix CVE-2023-40217: Check for & avoid the ssl pre-close flaw + +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] +--- + Lib/ssl.py | 31 ++- + Lib/test/test_ssl.py | 214 ++++++++++++++++++ + ...-08-22-17-39-12.gh-issue-108310.fVM3sg.rst | 7 + + 3 files changed, 251 insertions(+), 1 deletion(-) + 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 c5c5529..288f237 100644 +--- a/Lib/ssl.py ++++ b/Lib/ssl.py +@@ -741,7 +741,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 +755,38 @@ class SSLSocket(socket): + if e.errno != errno.ENOTCONN: + raise + connected = False ++ blocking = self.getblocking() ++ 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 ++ 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 b35db25..e24d11b 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,217 @@ 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): ++ 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() ++ 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(2.0) ++ self.listener.listen(1) ++ super().start() ++ ++ def run(self): ++ conn, address = self.listener.accept() ++ 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. ++ 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 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, 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") ++ ++ 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 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 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() ++ 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") ++ ++ def test_https_client_non_tls_response_ignored(self): ++ ++ server_responding = threading.Event() ++ ++ class SynchronizedHTTPSConnection(http.client.HTTPSConnection): ++ def connect(self): ++ http.client.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( ++ f"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(OSError) as err_ctx: ++ connection.request("HEAD", "/test", headers={"Host": "localhost"}) ++ response = connection.getresponse() ++ + + def test_main(verbose=False): + if support.verbose: +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 0000000..403c77a +--- /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 ++`_ by ++Aapo Oksman. Patch by Gregory P. Smith. +-- +2.41.0 + + +From 6fbb37c0b7ab8ce1db8e2e78df62d6e5c1e56766 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/4] 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 +--- + Lib/ssl.py | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/Lib/ssl.py b/Lib/ssl.py +index 288f237..67869c9 100644 +--- a/Lib/ssl.py ++++ b/Lib/ssl.py +@@ -782,7 +782,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 579d809e60e569f06c00844cc3a3f06a0e359603 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?=C5=81ukasz=20Langa?= +Date: Thu, 24 Aug 2023 12:09:30 +0200 +Subject: [PATCH 3/4] 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/test_ssl.py | 102 ++++++++++++++++++++++++++++++------------- + 1 file changed, 71 insertions(+), 31 deletions(-) + +diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py +index e24d11b..6332330 100644 +--- a/Lib/test/test_ssl.py ++++ b/Lib/test/test_ssl.py +@@ -3953,12 +3953,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 + super().__init__(name=name) + + def __enter__(self): +@@ -3981,13 +3985,19 @@ 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) + super().start() + + def run(self): +- conn, address = self.listener.accept() +- self.listener.close() ++ 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 +@@ -4015,8 +4025,13 @@ class TestPreHandshakeClose(unittest.TestCase): + # 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)) ++ 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. +@@ -4027,7 +4042,7 @@ class TestPreHandshakeClose(unittest.TestCase): + + 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. + +@@ -4049,20 +4064,31 @@ 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, 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") ++ 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( +@@ -4084,8 +4110,10 @@ class TestPreHandshakeClose(unittest.TestCase): + + with 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( +@@ -4099,24 +4127,31 @@ class TestPreHandshakeClose(unittest.TestCase): + tls_client.close() + + server.join() +- 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") ++ 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(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) +@@ -4131,29 +4166,34 @@ 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( +- f"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(OSError) as err_ctx: ++ with self.assertRaises(OSError): + connection.request("HEAD", "/test", headers={"Host": "localhost"}) + response = connection.getresponse() + ++ server.join() ++ + + def test_main(verbose=False): + if support.verbose: +-- +2.41.0 + + +From 2e8776245e9fb8ede784077df26684b4b53df0bc Mon Sep 17 00:00:00 2001 +From: Charalampos Stratakis +Date: Mon, 25 Sep 2023 21:55:29 +0200 +Subject: [PATCH 4/4] Downstream: Additional fixup for 3.6: + +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 +--- + Lib/ssl.py | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/Lib/ssl.py b/Lib/ssl.py +index 67869c9..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 +@@ -755,7 +756,7 @@ class SSLSocket(socket): + if e.errno != errno.ENOTCONN: + raise + connected = False +- blocking = self.getblocking() ++ blocking = (self.gettimeout() != 0) + self.setblocking(False) + try: + # We are not connected so this is not supposed to block, but +-- +2.41.0 + diff --git a/SOURCES/00408-CVE-2022-48560.patch b/SOURCES/00408-CVE-2022-48560.patch new file mode 100644 index 0000000..9b6cfb1 --- /dev/null +++ b/SOURCES/00408-CVE-2022-48560.patch @@ -0,0 +1,143 @@ +From c563f409ea30bcb0623d785428c9257917371b76 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Thu, 23 Jan 2020 06:49:19 -0800 +Subject: [PATCH] bpo-39421: Fix posible crash in heapq with custom comparison + operators (GH-18118) (GH-18146) + +(cherry picked from commit 79f89e6e5a659846d1068e8b1bd8e491ccdef861) + +Co-authored-by: Pablo Galindo +--- + Lib/test/test_heapq.py | 31 ++++++++++++++++ + .../2020-01-22-15-53-37.bpo-39421.O3nG7u.rst | 2 ++ + Modules/_heapqmodule.c | 35 ++++++++++++++----- + 3 files changed, 59 insertions(+), 9 deletions(-) + create mode 100644 Misc/NEWS.d/next/Core and Builtins/2020-01-22-15-53-37.bpo-39421.O3nG7u.rst + +diff --git a/Lib/test/test_heapq.py b/Lib/test/test_heapq.py +index 2f8c648d84a58..7c3fb0210f69b 100644 +--- a/Lib/test/test_heapq.py ++++ b/Lib/test/test_heapq.py +@@ -414,6 +414,37 @@ def test_heappop_mutating_heap(self): + with self.assertRaises((IndexError, RuntimeError)): + self.module.heappop(heap) + ++ def test_comparison_operator_modifiying_heap(self): ++ # See bpo-39421: Strong references need to be taken ++ # when comparing objects as they can alter the heap ++ class EvilClass(int): ++ def __lt__(self, o): ++ heap.clear() ++ return NotImplemented ++ ++ heap = [] ++ self.module.heappush(heap, EvilClass(0)) ++ self.assertRaises(IndexError, self.module.heappushpop, heap, 1) ++ ++ def test_comparison_operator_modifiying_heap_two_heaps(self): ++ ++ class h(int): ++ def __lt__(self, o): ++ list2.clear() ++ return NotImplemented ++ ++ class g(int): ++ def __lt__(self, o): ++ list1.clear() ++ return NotImplemented ++ ++ list1, list2 = [], [] ++ ++ self.module.heappush(list1, h(0)) ++ self.module.heappush(list2, g(0)) ++ ++ self.assertRaises((IndexError, RuntimeError), self.module.heappush, list1, g(1)) ++ self.assertRaises((IndexError, RuntimeError), self.module.heappush, list2, h(1)) + + class TestErrorHandlingPython(TestErrorHandling, TestCase): + module = py_heapq +diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-01-22-15-53-37.bpo-39421.O3nG7u.rst b/Misc/NEWS.d/next/Core and Builtins/2020-01-22-15-53-37.bpo-39421.O3nG7u.rst +new file mode 100644 +index 0000000000000..bae008150ee12 +--- /dev/null ++++ b/Misc/NEWS.d/next/Core and Builtins/2020-01-22-15-53-37.bpo-39421.O3nG7u.rst +@@ -0,0 +1,2 @@ ++Fix possible crashes when operating with the functions in the :mod:`heapq` ++module and custom comparison operators. +diff --git a/Modules/_heapqmodule.c b/Modules/_heapqmodule.c +index b499e1f668aae..0fb35ffe5ec48 100644 +--- a/Modules/_heapqmodule.c ++++ b/Modules/_heapqmodule.c +@@ -29,7 +29,11 @@ siftdown(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos) + while (pos > startpos) { + parentpos = (pos - 1) >> 1; + parent = arr[parentpos]; ++ Py_INCREF(newitem); ++ Py_INCREF(parent); + cmp = PyObject_RichCompareBool(newitem, parent, Py_LT); ++ Py_DECREF(parent); ++ Py_DECREF(newitem); + if (cmp < 0) + return -1; + if (size != PyList_GET_SIZE(heap)) { +@@ -71,10 +75,13 @@ siftup(PyListObject *heap, Py_ssize_t pos) + /* Set childpos to index of smaller child. */ + childpos = 2*pos + 1; /* leftmost child position */ + if (childpos + 1 < endpos) { +- cmp = PyObject_RichCompareBool( +- arr[childpos], +- arr[childpos + 1], +- Py_LT); ++ PyObject* a = arr[childpos]; ++ PyObject* b = arr[childpos + 1]; ++ Py_INCREF(a); ++ Py_INCREF(b); ++ cmp = PyObject_RichCompareBool(a, b, Py_LT); ++ Py_DECREF(a); ++ Py_DECREF(b); + if (cmp < 0) + return -1; + childpos += ((unsigned)cmp ^ 1); /* increment when cmp==0 */ +@@ -229,7 +236,10 @@ heappushpop(PyObject *self, PyObject *args) + return item; + } + +- cmp = PyObject_RichCompareBool(PyList_GET_ITEM(heap, 0), item, Py_LT); ++ PyObject* top = PyList_GET_ITEM(heap, 0); ++ Py_INCREF(top); ++ cmp = PyObject_RichCompareBool(top, item, Py_LT); ++ Py_DECREF(top); + if (cmp < 0) + return NULL; + if (cmp == 0) { +@@ -383,7 +393,11 @@ siftdown_max(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos) + while (pos > startpos) { + parentpos = (pos - 1) >> 1; + parent = arr[parentpos]; ++ Py_INCREF(parent); ++ Py_INCREF(newitem); + cmp = PyObject_RichCompareBool(parent, newitem, Py_LT); ++ Py_DECREF(parent); ++ Py_DECREF(newitem); + if (cmp < 0) + return -1; + if (size != PyList_GET_SIZE(heap)) { +@@ -425,10 +439,13 @@ siftup_max(PyListObject *heap, Py_ssize_t pos) + /* Set childpos to index of smaller child. */ + childpos = 2*pos + 1; /* leftmost child position */ + if (childpos + 1 < endpos) { +- cmp = PyObject_RichCompareBool( +- arr[childpos + 1], +- arr[childpos], +- Py_LT); ++ PyObject* a = arr[childpos + 1]; ++ PyObject* b = arr[childpos]; ++ Py_INCREF(a); ++ Py_INCREF(b); ++ cmp = PyObject_RichCompareBool(a, b, Py_LT); ++ Py_DECREF(a); ++ Py_DECREF(b); + if (cmp < 0) + return -1; + childpos += ((unsigned)cmp ^ 1); /* increment when cmp==0 */ diff --git a/SOURCES/00413-CVE-2022-48564.patch b/SOURCES/00413-CVE-2022-48564.patch new file mode 100644 index 0000000..3fc1cf8 --- /dev/null +++ b/SOURCES/00413-CVE-2022-48564.patch @@ -0,0 +1,560 @@ +From cec66eefbee76ee95f252a52de0f5abc1b677f5b Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Tue, 12 Dec 2023 13:03:42 +0100 +Subject: [PATCH] [3.6] bpo-42103: Improve validation of Plist files. + (GH-22882) (GH-23118) + +* Prevent some possible DoS attacks via providing invalid Plist files + with extremely large number of objects or collection sizes. +* Raise InvalidFileException for too large bytes and string size instead of returning garbage. +* Raise InvalidFileException instead of ValueError for specific invalid datetime (NaN). +* Raise InvalidFileException instead of TypeError for non-hashable dict keys. +* Add more tests for invalid Plist files.. +(cherry picked from commit 34637a0ce21e7261b952fbd9d006474cc29b681f) + +Co-authored-by: Serhiy Storchaka +--- + Lib/plistlib.py | 34 +- + Lib/test/test_plistlib.py | 394 +++++++++++++++--- + .../2020-10-23-19-20-14.bpo-42103.C5obK2.rst | 3 + + .../2020-10-23-19-19-30.bpo-42103.cILT66.rst | 2 + + 4 files changed, 366 insertions(+), 67 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2020-10-23-19-20-14.bpo-42103.C5obK2.rst + create mode 100644 Misc/NEWS.d/next/Security/2020-10-23-19-19-30.bpo-42103.cILT66.rst + +diff --git a/Lib/plistlib.py b/Lib/plistlib.py +index a918643..df1f346 100644 +--- a/Lib/plistlib.py ++++ b/Lib/plistlib.py +@@ -626,7 +626,7 @@ class _BinaryPlistParser: + return self._read_object(top_object) + + except (OSError, IndexError, struct.error, OverflowError, +- UnicodeDecodeError): ++ ValueError): + raise InvalidFileException() + + def _get_size(self, tokenL): +@@ -642,7 +642,7 @@ class _BinaryPlistParser: + def _read_ints(self, n, size): + data = self._fp.read(size * n) + if size in _BINARY_FORMAT: +- return struct.unpack('>' + _BINARY_FORMAT[size] * n, data) ++ return struct.unpack(f'>{n}{_BINARY_FORMAT[size]}', data) + else: + if not size or len(data) != size * n: + raise InvalidFileException() +@@ -701,19 +701,25 @@ class _BinaryPlistParser: + + elif tokenH == 0x40: # data + s = self._get_size(tokenL) +- if self._use_builtin_types: +- result = self._fp.read(s) +- else: +- result = Data(self._fp.read(s)) ++ result = self._fp.read(s) ++ if len(result) != s: ++ raise InvalidFileException() ++ if not self._use_builtin_types: ++ result = Data(result) + + elif tokenH == 0x50: # ascii string + s = self._get_size(tokenL) +- result = self._fp.read(s).decode('ascii') +- result = result ++ data = self._fp.read(s) ++ if len(data) != s: ++ raise InvalidFileException() ++ result = data.decode('ascii') + + elif tokenH == 0x60: # unicode string +- s = self._get_size(tokenL) +- result = self._fp.read(s * 2).decode('utf-16be') ++ s = self._get_size(tokenL) * 2 ++ data = self._fp.read(s) ++ if len(data) != s: ++ raise InvalidFileException() ++ result = data.decode('utf-16be') + + # tokenH == 0x80 is documented as 'UID' and appears to be used for + # keyed-archiving, not in plists. +@@ -737,9 +743,11 @@ class _BinaryPlistParser: + obj_refs = self._read_refs(s) + result = self._dict_type() + self._objects[ref] = result +- for k, o in zip(key_refs, obj_refs): +- result[self._read_object(k)] = self._read_object(o) +- ++ try: ++ for k, o in zip(key_refs, obj_refs): ++ result[self._read_object(k)] = self._read_object(o) ++ except TypeError: ++ raise InvalidFileException() + else: + raise InvalidFileException() + +diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py +index d47c607..f71245d 100644 +--- a/Lib/test/test_plistlib.py ++++ b/Lib/test/test_plistlib.py +@@ -1,5 +1,6 @@ + # Copyright (C) 2003-2013 Python Software Foundation + ++import struct + import unittest + import plistlib + import os +@@ -90,6 +91,284 @@ TESTDATA={ + xQHHAsQC0gAAAAAAAAIBAAAAAAAAADkAAAAAAAAAAAAAAAAAAALs'''), + } + ++INVALID_BINARY_PLISTS = [ ++ ('too short data', ++ b'' ++ ), ++ ('too large offset_table_offset and offset_size = 1', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x2a' ++ ), ++ ('too large offset_table_offset and nonstandard offset_size', ++ b'\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x03\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x2c' ++ ), ++ ('integer overflow in offset_table_offset', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\xff\xff\xff\xff\xff\xff\xff\xff' ++ ), ++ ('too large top_object', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('integer overflow in top_object', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\xff\xff\xff\xff\xff\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('too large num_objects and offset_size = 1', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('too large num_objects and nonstandard offset_size', ++ b'\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x03\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('extremally large num_objects (32 bit)', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x7f\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('extremally large num_objects (64 bit)', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\xff\xff\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('integer overflow in num_objects', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\xff\xff\xff\xff\xff\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('offset_size = 0', ++ b'\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('ref_size = 0', ++ b'\xa1\x01\x00\x08\x0a' ++ b'\x00\x00\x00\x00\x00\x00\x01\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0b' ++ ), ++ ('too large offset', ++ b'\x00\x2a' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('integer overflow in offset', ++ b'\x00\xff\xff\xff\xff\xff\xff\xff\xff' ++ b'\x00\x00\x00\x00\x00\x00\x08\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x09' ++ ), ++ ('too large array size', ++ b'\xaf\x00\x01\xff\x00\x08\x0c' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0d' ++ ), ++ ('extremally large array size (32-bit)', ++ b'\xaf\x02\x7f\xff\xff\xff\x01\x00\x08\x0f' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x10' ++ ), ++ ('extremally large array size (64-bit)', ++ b'\xaf\x03\x00\x00\x00\xff\xff\xff\xff\xff\x01\x00\x08\x13' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x14' ++ ), ++ ('integer overflow in array size', ++ b'\xaf\x03\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x08\x13' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x14' ++ ), ++ ('too large reference index', ++ b'\xa1\x02\x00\x08\x0a' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0b' ++ ), ++ ('integer overflow in reference index', ++ b'\xa1\xff\xff\xff\xff\xff\xff\xff\xff\x00\x08\x11' ++ b'\x00\x00\x00\x00\x00\x00\x01\x08' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x12' ++ ), ++ ('too large bytes size', ++ b'\x4f\x00\x23\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0c' ++ ), ++ ('extremally large bytes size (32-bit)', ++ b'\x4f\x02\x7f\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0f' ++ ), ++ ('extremally large bytes size (64-bit)', ++ b'\x4f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x13' ++ ), ++ ('integer overflow in bytes size', ++ b'\x4f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x13' ++ ), ++ ('too large ASCII size', ++ b'\x5f\x00\x23\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0c' ++ ), ++ ('extremally large ASCII size (32-bit)', ++ b'\x5f\x02\x7f\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0f' ++ ), ++ ('extremally large ASCII size (64-bit)', ++ b'\x5f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x13' ++ ), ++ ('integer overflow in ASCII size', ++ b'\x5f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x41\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x13' ++ ), ++ ('invalid ASCII', ++ b'\x51\xff\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0a' ++ ), ++ ('too large UTF-16 size', ++ b'\x6f\x00\x13\x20\xac\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0e' ++ ), ++ ('extremally large UTF-16 size (32-bit)', ++ b'\x6f\x02\x4f\xff\xff\xff\x20\xac\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++ ('extremally large UTF-16 size (64-bit)', ++ b'\x6f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x20\xac\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x15' ++ ), ++ ('integer overflow in UTF-16 size', ++ b'\x6f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x20\xac\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x15' ++ ), ++ ('invalid UTF-16', ++ b'\x61\xd8\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0b' ++ ), ++ ('non-hashable key', ++ b'\xd1\x01\x01\xa0\x08\x0b' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x02' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x0c' ++ ), ++ ('too large datetime (datetime overflow)', ++ b'\x33\x42\x50\x00\x00\x00\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++ ('too large datetime (timedelta overflow)', ++ b'\x33\x42\xe0\x00\x00\x00\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++ ('invalid datetime (Infinity)', ++ b'\x33\x7f\xf0\x00\x00\x00\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++ ('invalid datetime (NaN)', ++ b'\x33\x7f\xf8\x00\x00\x00\x00\x00\x00\x08' ++ b'\x00\x00\x00\x00\x00\x00\x01\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x01' ++ b'\x00\x00\x00\x00\x00\x00\x00\x00' ++ b'\x00\x00\x00\x00\x00\x00\x00\x11' ++ ), ++] + + class TestPlistlib(unittest.TestCase): + +@@ -447,6 +726,21 @@ class TestPlistlib(unittest.TestCase): + + class TestBinaryPlistlib(unittest.TestCase): + ++ @staticmethod ++ def decode(*objects, offset_size=1, ref_size=1): ++ data = [b'bplist00'] ++ offset = 8 ++ offsets = [] ++ for x in objects: ++ offsets.append(offset.to_bytes(offset_size, 'big')) ++ data.append(x) ++ offset += len(x) ++ tail = struct.pack('>6xBBQQQ', offset_size, ref_size, ++ len(objects), 0, offset) ++ data.extend(offsets) ++ data.append(tail) ++ return plistlib.loads(b''.join(data), fmt=plistlib.FMT_BINARY) ++ + def test_nonstandard_refs_size(self): + # Issue #21538: Refs and offsets are 24-bit integers + data = (b'bplist00' +@@ -461,7 +755,7 @@ class TestBinaryPlistlib(unittest.TestCase): + + def test_dump_duplicates(self): + # Test effectiveness of saving duplicated objects +- for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde', ++ for x in (None, False, True, 12345, 123.45, 'abcde', 'абвгд', b'abcde', + datetime.datetime(2004, 10, 26, 10, 33, 33), + plistlib.Data(b'abcde'), bytearray(b'abcde'), + [12, 345], (12, 345), {'12': 345}): +@@ -500,6 +794,20 @@ class TestBinaryPlistlib(unittest.TestCase): + b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY)) + self.assertIs(b['x'], b) + ++ def test_deep_nesting(self): ++ for N in [300, 100000]: ++ chunks = [b'\xa1' + (i + 1).to_bytes(4, 'big') for i in range(N)] ++ try: ++ result = self.decode(*chunks, b'\x54seed', offset_size=4, ref_size=4) ++ except RecursionError: ++ pass ++ else: ++ for i in range(N): ++ self.assertIsInstance(result, list) ++ self.assertEqual(len(result), 1) ++ result = result[0] ++ self.assertEqual(result, 'seed') ++ + def test_large_timestamp(self): + # Issue #26709: 32-bit timestamp out of range + for ts in -2**31-1, 2**31: +@@ -509,55 +817,37 @@ class TestBinaryPlistlib(unittest.TestCase): + data = plistlib.dumps(d, fmt=plistlib.FMT_BINARY) + self.assertEqual(plistlib.loads(data), d) + ++ def test_load_singletons(self): ++ self.assertIs(self.decode(b'\x00'), None) ++ self.assertIs(self.decode(b'\x08'), False) ++ self.assertIs(self.decode(b'\x09'), True) ++ self.assertEqual(self.decode(b'\x0f'), b'') ++ ++ def test_load_int(self): ++ self.assertEqual(self.decode(b'\x10\x00'), 0) ++ self.assertEqual(self.decode(b'\x10\xfe'), 0xfe) ++ self.assertEqual(self.decode(b'\x11\xfe\xdc'), 0xfedc) ++ self.assertEqual(self.decode(b'\x12\xfe\xdc\xba\x98'), 0xfedcba98) ++ self.assertEqual(self.decode(b'\x13\x01\x23\x45\x67\x89\xab\xcd\xef'), ++ 0x0123456789abcdef) ++ self.assertEqual(self.decode(b'\x13\xfe\xdc\xba\x98\x76\x54\x32\x10'), ++ -0x123456789abcdf0) ++ ++ def test_unsupported(self): ++ unsupported = [*range(1, 8), *range(10, 15), ++ 0x20, 0x21, *range(0x24, 0x33), *range(0x34, 0x40)] ++ for i in [0x70, 0x90, 0xb0, 0xc0, 0xe0, 0xf0]: ++ unsupported.extend(i + j for j in range(16)) ++ for token in unsupported: ++ with self.subTest(f'token {token:02x}'): ++ with self.assertRaises(plistlib.InvalidFileException): ++ self.decode(bytes([token]) + b'\x00'*16) ++ + def test_invalid_binary(self): +- for data in [ +- # too short data +- b'', +- # too large offset_table_offset and nonstandard offset_size +- b'\x00\x08' +- b'\x00\x00\x00\x00\x00\x00\x03\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x2a', +- # integer overflow in offset_table_offset +- b'\x00\x08' +- b'\x00\x00\x00\x00\x00\x00\x01\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\xff\xff\xff\xff\xff\xff\xff\xff', +- # offset_size = 0 +- b'\x00\x08' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x09', +- # ref_size = 0 +- b'\xa1\x01\x00\x08\x0a' +- b'\x00\x00\x00\x00\x00\x00\x01\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x02' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x0b', +- # integer overflow in offset +- b'\x00\xff\xff\xff\xff\xff\xff\xff\xff' +- b'\x00\x00\x00\x00\x00\x00\x08\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x09', +- # invalid ASCII +- b'\x51\xff\x08' +- b'\x00\x00\x00\x00\x00\x00\x01\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x0a', +- # invalid UTF-16 +- b'\x61\xd8\x00\x08' +- b'\x00\x00\x00\x00\x00\x00\x01\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x01' +- b'\x00\x00\x00\x00\x00\x00\x00\x00' +- b'\x00\x00\x00\x00\x00\x00\x00\x0b', +- ]: +- with self.assertRaises(plistlib.InvalidFileException): +- plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY) ++ for name, data in INVALID_BINARY_PLISTS: ++ with self.subTest(name): ++ with self.assertRaises(plistlib.InvalidFileException): ++ plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY) + + + class TestPlistlibDeprecated(unittest.TestCase): +@@ -655,9 +945,5 @@ class MiscTestCase(unittest.TestCase): + support.check__all__(self, plistlib, blacklist=blacklist) + + +-def test_main(): +- support.run_unittest(TestPlistlib, TestPlistlibDeprecated, MiscTestCase) +- +- + if __name__ == '__main__': +- test_main() ++ unittest.main() +diff --git a/Misc/NEWS.d/next/Library/2020-10-23-19-20-14.bpo-42103.C5obK2.rst b/Misc/NEWS.d/next/Library/2020-10-23-19-20-14.bpo-42103.C5obK2.rst +new file mode 100644 +index 0000000..4eb694c +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2020-10-23-19-20-14.bpo-42103.C5obK2.rst +@@ -0,0 +1,3 @@ ++:exc:`~plistlib.InvalidFileException` and :exc:`RecursionError` are now ++the only errors caused by loading malformed binary Plist file (previously ++ValueError and TypeError could be raised in some specific cases). +diff --git a/Misc/NEWS.d/next/Security/2020-10-23-19-19-30.bpo-42103.cILT66.rst b/Misc/NEWS.d/next/Security/2020-10-23-19-19-30.bpo-42103.cILT66.rst +new file mode 100644 +index 0000000..15d7b65 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2020-10-23-19-19-30.bpo-42103.cILT66.rst +@@ -0,0 +1,2 @@ ++Prevented potential DoS attack via CPU and RAM exhaustion when processing ++malformed Apple Property List files in binary format. +-- +2.43.0 + diff --git a/SOURCES/00414-skip_test_zlib_s390x.patch b/SOURCES/00414-skip_test_zlib_s390x.patch new file mode 100644 index 0000000..05e3fbd --- /dev/null +++ b/SOURCES/00414-skip_test_zlib_s390x.patch @@ -0,0 +1,88 @@ +From 0d02ff99721f7650e39ba4c7d8fe06f412bbb591 Mon Sep 17 00:00:00 2001 +From: Victor Stinner +Date: Wed, 13 Dec 2023 11:50:26 +0100 +Subject: [PATCH] bpo-46623: Skip two test_zlib tests on s390x (GH-31096) + +Skip test_pair() and test_speech128() of test_zlib on s390x since +they fail if zlib uses the s390x hardware accelerator. +--- + Lib/test/test_zlib.py | 32 +++++++++++++++++++ + .../2022-02-03-09-45-26.bpo-46623.vxzuhV.rst | 2 ++ + 2 files changed, 34 insertions(+) + create mode 100644 Misc/NEWS.d/next/Tests/2022-02-03-09-45-26.bpo-46623.vxzuhV.rst + +diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py +index b7170b4..770a425 100644 +--- a/Lib/test/test_zlib.py ++++ b/Lib/test/test_zlib.py +@@ -1,6 +1,7 @@ + import unittest + from test import support + import binascii ++import os + import pickle + import random + import sys +@@ -15,6 +16,35 @@ requires_Decompress_copy = unittest.skipUnless( + hasattr(zlib.decompressobj(), "copy"), + 'requires Decompress.copy()') + ++# bpo-46623: On s390x, when a hardware accelerator is used, using different ++# ways to compress data with zlib can produce different compressed data. ++# Simplified test_pair() code: ++# ++# def func1(data): ++# return zlib.compress(data) ++# ++# def func2(data) ++# co = zlib.compressobj() ++# x1 = co.compress(data) ++# x2 = co.flush() ++# return x1 + x2 ++# ++# On s390x if zlib uses a hardware accelerator, func1() creates a single ++# "final" compressed block whereas func2() produces 3 compressed blocks (the ++# last one is a final block). On other platforms with no accelerator, func1() ++# and func2() produce the same compressed data made of a single (final) ++# compressed block. ++# ++# Only the compressed data is different, the decompression returns the original ++# data: ++# ++# zlib.decompress(func1(data)) == zlib.decompress(func2(data)) == data ++# ++# Make the assumption that s390x always has an accelerator to simplify the skip ++# condition. Windows doesn't have os.uname() but it doesn't support s390x. ++skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x', ++ 'skipped on s390x') ++ + + class VersionTestCase(unittest.TestCase): + +@@ -174,6 +204,7 @@ class CompressTestCase(BaseCompressTestCase, unittest.TestCase): + bufsize=zlib.DEF_BUF_SIZE), + HAMLET_SCENE) + ++ @skip_on_s390x + def test_speech128(self): + # compress more data + data = HAMLET_SCENE * 128 +@@ -225,6 +256,7 @@ class CompressTestCase(BaseCompressTestCase, unittest.TestCase): + + class CompressObjectTestCase(BaseCompressTestCase, unittest.TestCase): + # Test compression object ++ @skip_on_s390x + def test_pair(self): + # straightforward compress/decompress objects + datasrc = HAMLET_SCENE * 128 +diff --git a/Misc/NEWS.d/next/Tests/2022-02-03-09-45-26.bpo-46623.vxzuhV.rst b/Misc/NEWS.d/next/Tests/2022-02-03-09-45-26.bpo-46623.vxzuhV.rst +new file mode 100644 +index 0000000..be085c0 +--- /dev/null ++++ b/Misc/NEWS.d/next/Tests/2022-02-03-09-45-26.bpo-46623.vxzuhV.rst +@@ -0,0 +1,2 @@ ++Skip test_pair() and test_speech128() of test_zlib on s390x since they fail ++if zlib uses the s390x hardware accelerator. Patch by Victor Stinner. +-- +2.43.0 + diff --git a/SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch b/SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch new file mode 100644 index 0000000..c52a007 --- /dev/null +++ b/SOURCES/00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch @@ -0,0 +1,750 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Victor Stinner +Date: Fri, 15 Dec 2023 16:10:40 +0100 +Subject: [PATCH] 00415: [CVE-2023-27043] gh-102988: Reject malformed addresses + in email.parseaddr() (#111116) + +Detect email address parsing errors and return empty tuple to +indicate the parsing error (old API). Add an optional 'strict' +parameter to getaddresses() and parseaddr() functions. Patch by +Thomas Dwyer. + +Co-Authored-By: Thomas Dwyer +--- + Doc/library/email.utils.rst | 19 +- + Lib/email/utils.py | 151 ++++++++++++- + Lib/test/test_email/test_email.py | 204 +++++++++++++++++- + ...-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 + + 4 files changed, 361 insertions(+), 21 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst + +diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst +index 63fae2ab84..d1e1898591 100644 +--- a/Doc/library/email.utils.rst ++++ b/Doc/library/email.utils.rst +@@ -60,13 +60,18 @@ of the new API. + begins with angle brackets, they are stripped off. + + +-.. function:: parseaddr(address) ++.. function:: parseaddr(address, *, strict=True) + + Parse address -- which should be the value of some address-containing field such + as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and + *email address* parts. Returns a tuple of that information, unless the parse + fails, in which case a 2-tuple of ``('', '')`` is returned. + ++ If *strict* is true, use a strict parser which rejects malformed inputs. ++ ++ .. versionchanged:: 3.13 ++ Add *strict* optional parameter and reject malformed inputs by default. ++ + + .. function:: formataddr(pair, charset='utf-8') + +@@ -84,12 +89,15 @@ of the new API. + Added the *charset* option. + + +-.. function:: getaddresses(fieldvalues) ++.. function:: getaddresses(fieldvalues, *, strict=True) + + This method returns a list of 2-tuples of the form returned by ``parseaddr()``. + *fieldvalues* is a sequence of header field values as might be returned by +- :meth:`Message.get_all `. Here's a simple +- example that gets all the recipients of a message:: ++ :meth:`Message.get_all `. ++ ++ If *strict* is true, use a strict parser which rejects malformed inputs. ++ ++ Here's a simple example that gets all the recipients of a message:: + + from email.utils import getaddresses + +@@ -99,6 +107,9 @@ of the new API. + resent_ccs = msg.get_all('resent-cc', []) + all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) + ++ .. versionchanged:: 3.13 ++ Add *strict* optional parameter and reject malformed inputs by default. ++ + + .. function:: parsedate(date) + +diff --git a/Lib/email/utils.py b/Lib/email/utils.py +index 39c2240607..f83b7e5d7e 100644 +--- a/Lib/email/utils.py ++++ b/Lib/email/utils.py +@@ -48,6 +48,7 @@ TICK = "'" + specialsre = re.compile(r'[][\\()<>@,:;".]') + escapesre = re.compile(r'[\\"]') + ++ + def _has_surrogates(s): + """Return True if s contains surrogate-escaped binary data.""" + # This check is based on the fact that unless there are surrogates, utf8 +@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'): + return address + + ++def _iter_escaped_chars(addr): ++ pos = 0 ++ escape = False ++ for pos, ch in enumerate(addr): ++ if escape: ++ yield (pos, '\\' + ch) ++ escape = False ++ elif ch == '\\': ++ escape = True ++ else: ++ yield (pos, ch) ++ if escape: ++ yield (pos, '\\') + +-def getaddresses(fieldvalues): +- """Return a list of (REALNAME, EMAIL) for each fieldvalue.""" +- all = COMMASPACE.join(fieldvalues) +- a = _AddressList(all) +- return a.addresslist ++ ++def _strip_quoted_realnames(addr): ++ """Strip real names between quotes.""" ++ if '"' not in addr: ++ # Fast path ++ return addr ++ ++ start = 0 ++ open_pos = None ++ result = [] ++ for pos, ch in _iter_escaped_chars(addr): ++ if ch == '"': ++ if open_pos is None: ++ open_pos = pos ++ else: ++ if start != open_pos: ++ result.append(addr[start:open_pos]) ++ start = pos + 1 ++ open_pos = None ++ ++ if start < len(addr): ++ result.append(addr[start:]) ++ ++ return ''.join(result) ++ ++ ++supports_strict_parsing = True ++ ++def getaddresses(fieldvalues, *, strict=True): ++ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. ++ ++ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in ++ its place. ++ ++ If strict is true, use a strict parser which rejects malformed inputs. ++ """ ++ ++ # If strict is true, if the resulting list of parsed addresses is greater ++ # than the number of fieldvalues in the input list, a parsing error has ++ # occurred and consequently a list containing a single empty 2-tuple [('', ++ # '')] is returned in its place. This is done to avoid invalid output. ++ # ++ # Malformed input: getaddresses(['alice@example.com ']) ++ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')] ++ # Safe output: [('', '')] ++ ++ if not strict: ++ all = COMMASPACE.join(str(v) for v in fieldvalues) ++ a = _AddressList(all) ++ return a.addresslist ++ ++ fieldvalues = [str(v) for v in fieldvalues] ++ fieldvalues = _pre_parse_validation(fieldvalues) ++ addr = COMMASPACE.join(fieldvalues) ++ a = _AddressList(addr) ++ result = _post_parse_validation(a.addresslist) ++ ++ # Treat output as invalid if the number of addresses is not equal to the ++ # expected number of addresses. ++ n = 0 ++ for v in fieldvalues: ++ # When a comma is used in the Real Name part it is not a deliminator. ++ # So strip those out before counting the commas. ++ v = _strip_quoted_realnames(v) ++ # Expected number of addresses: 1 + number of commas ++ n += 1 + v.count(',') ++ if len(result) != n: ++ return [('', '')] ++ ++ return result ++ ++ ++def _check_parenthesis(addr): ++ # Ignore parenthesis in quoted real names. ++ addr = _strip_quoted_realnames(addr) ++ ++ opens = 0 ++ for pos, ch in _iter_escaped_chars(addr): ++ if ch == '(': ++ opens += 1 ++ elif ch == ')': ++ opens -= 1 ++ if opens < 0: ++ return False ++ return (opens == 0) ++ ++ ++def _pre_parse_validation(email_header_fields): ++ accepted_values = [] ++ for v in email_header_fields: ++ if not _check_parenthesis(v): ++ v = "('', '')" ++ accepted_values.append(v) ++ ++ return accepted_values ++ ++ ++def _post_parse_validation(parsed_email_header_tuples): ++ accepted_values = [] ++ # The parser would have parsed a correctly formatted domain-literal ++ # The existence of an [ after parsing indicates a parsing failure ++ for v in parsed_email_header_tuples: ++ if '[' in v[1]: ++ v = ('', '') ++ accepted_values.append(v) ++ ++ return accepted_values + + + +@@ -214,16 +330,33 @@ def parsedate_to_datetime(data): + tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) + + +-def parseaddr(addr): ++def parseaddr(addr, *, strict=True): + """ + Parse addr into its constituent realname and email address parts. + + Return a tuple of realname and email address, unless the parse fails, in + which case return a 2-tuple of ('', ''). ++ ++ If strict is True, use a strict parser which rejects malformed inputs. + """ +- addrs = _AddressList(addr).addresslist +- if not addrs: +- return '', '' ++ if not strict: ++ addrs = _AddressList(addr).addresslist ++ if not addrs: ++ return ('', '') ++ return addrs[0] ++ ++ if isinstance(addr, list): ++ addr = addr[0] ++ ++ if not isinstance(addr, str): ++ return ('', '') ++ ++ addr = _pre_parse_validation([addr])[0] ++ addrs = _post_parse_validation(_AddressList(addr).addresslist) ++ ++ if not addrs or len(addrs) > 1: ++ return ('', '') ++ + return addrs[0] + + +diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py +index e4e40b612f..ce36efc1b1 100644 +--- a/Lib/test/test_email/test_email.py ++++ b/Lib/test/test_email/test_email.py +@@ -19,6 +19,7 @@ except ImportError: + + import email + import email.policy ++import email.utils + + from email.charset import Charset + from email.header import Header, decode_header, make_header +@@ -3207,15 +3208,154 @@ Foo + [('Al Person', 'aperson@dom.ain'), + ('Bud Person', 'bperson@dom.ain')]) + ++ def test_getaddresses_comma_in_name(self): ++ """GH-106669 regression test.""" ++ self.assertEqual( ++ utils.getaddresses( ++ [ ++ '"Bud, Person" ', ++ 'aperson@dom.ain (Al Person)', ++ '"Mariusz Felisiak" ', ++ ] ++ ), ++ [ ++ ('Bud, Person', 'bperson@dom.ain'), ++ ('Al Person', 'aperson@dom.ain'), ++ ('Mariusz Felisiak', 'to@example.com'), ++ ], ++ ) ++ ++ def test_parsing_errors(self): ++ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056""" ++ alice = 'alice@example.org' ++ bob = 'bob@example.com' ++ empty = ('', '') ++ ++ # Test utils.getaddresses() and utils.parseaddr() on malformed email ++ # addresses: default behavior (strict=True) rejects malformed address, ++ # and strict=False which tolerates malformed address. ++ for invalid_separator, expected_non_strict in ( ++ ('(', [(f'<{bob}>', alice)]), ++ (')', [('', alice), empty, ('', bob)]), ++ ('<', [('', alice), empty, ('', bob), empty]), ++ ('>', [('', alice), empty, ('', bob)]), ++ ('[', [('', f'{alice}[<{bob}>]')]), ++ (']', [('', alice), empty, ('', bob)]), ++ ('@', [empty, empty, ('', bob)]), ++ (';', [('', alice), empty, ('', bob)]), ++ (':', [('', alice), ('', bob)]), ++ ('.', [('', alice + '.'), ('', bob)]), ++ ('"', [('', alice), ('', f'<{bob}>')]), ++ ): ++ address = f'{alice}{invalid_separator}<{bob}>' ++ with self.subTest(address=address): ++ self.assertEqual(utils.getaddresses([address]), ++ [empty]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ expected_non_strict) ++ ++ self.assertEqual(utils.parseaddr([address]), ++ empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Comma (',') is treated differently depending on strict parameter. ++ # Comma without quotes. ++ address = f'{alice},<{bob}>' ++ self.assertEqual(utils.getaddresses([address]), ++ [('', alice), ('', bob)]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('', alice), ('', bob)]) ++ self.assertEqual(utils.parseaddr([address]), ++ empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Real name between quotes containing comma. ++ address = '"Alice, alice@example.org" ' ++ expected_strict = ('Alice, alice@example.org', 'bob@example.com') ++ self.assertEqual(utils.getaddresses([address]), [expected_strict]) ++ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) ++ self.assertEqual(utils.parseaddr([address]), expected_strict) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Valid parenthesis in comments. ++ address = 'alice@example.org (Alice)' ++ expected_strict = ('Alice', 'alice@example.org') ++ self.assertEqual(utils.getaddresses([address]), [expected_strict]) ++ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict]) ++ self.assertEqual(utils.parseaddr([address]), expected_strict) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Invalid parenthesis in comments. ++ address = 'alice@example.org )Alice(' ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Two addresses with quotes separated by comma. ++ address = '"Jane Doe" , "John Doe" ' ++ self.assertEqual(utils.getaddresses([address]), ++ [('Jane Doe', 'jane@example.net'), ++ ('John Doe', 'john@example.net')]) ++ self.assertEqual(utils.getaddresses([address], strict=False), ++ [('Jane Doe', 'jane@example.net'), ++ ('John Doe', 'john@example.net')]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ self.assertEqual(utils.parseaddr([address], strict=False), ++ ('', address)) ++ ++ # Test email.utils.supports_strict_parsing attribute ++ self.assertEqual(email.utils.supports_strict_parsing, True) ++ + def test_getaddresses_nasty(self): +- eq = self.assertEqual +- eq(utils.getaddresses(['foo: ;']), [('', '')]) +- eq(utils.getaddresses( +- ['[]*-- =~$']), +- [('', ''), ('', ''), ('', '*--')]) +- eq(utils.getaddresses( +- ['foo: ;', '"Jason R. Mastaler" ']), +- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]) ++ for addresses, expected in ( ++ (['"Sürname, Firstname" '], ++ [('Sürname, Firstname', 'to@example.com')]), ++ ++ (['foo: ;'], ++ [('', '')]), ++ ++ (['foo: ;', '"Jason R. Mastaler" '], ++ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]), ++ ++ ([r'Pete(A nice \) chap) '], ++ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]), ++ ++ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'], ++ [('', '')]), ++ ++ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'], ++ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]), ++ ++ (['John Doe '], ++ [('John Doe (comment)', 'jdoe@machine.example')]), ++ ++ (['"Mary Smith: Personal Account" '], ++ [('Mary Smith: Personal Account', 'smith@home.example')]), ++ ++ (['Undisclosed recipients:;'], ++ [('', '')]), ++ ++ ([r', "Giant; \"Big\" Box" '], ++ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]), ++ ): ++ with self.subTest(addresses=addresses): ++ self.assertEqual(utils.getaddresses(addresses), ++ expected) ++ self.assertEqual(utils.getaddresses(addresses, strict=False), ++ expected) ++ ++ addresses = ['[]*-- =~$'] ++ self.assertEqual(utils.getaddresses(addresses), ++ [('', '')]) ++ self.assertEqual(utils.getaddresses(addresses, strict=False), ++ [('', ''), ('', ''), ('', '*--')]) + + def test_getaddresses_embedded_comment(self): + """Test proper handling of a nested comment""" +@@ -3397,6 +3537,54 @@ multipart/report + m = cls(*constructor, policy=email.policy.default) + self.assertIs(m.policy, email.policy.default) + ++ def test_iter_escaped_chars(self): ++ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')), ++ [(0, 'a'), ++ (2, '\\\\'), ++ (3, 'b'), ++ (5, '\\"'), ++ (6, 'c'), ++ (8, '\\\\'), ++ (9, '"'), ++ (10, 'd')]) ++ self.assertEqual(list(utils._iter_escaped_chars('a\\')), ++ [(0, 'a'), (1, '\\')]) ++ ++ def test_strip_quoted_realnames(self): ++ def check(addr, expected): ++ self.assertEqual(utils._strip_quoted_realnames(addr), expected) ++ ++ check('"Jane Doe" , "John Doe" ', ++ ' , ') ++ check(r'"Jane \"Doe\"." ', ++ ' ') ++ ++ # special cases ++ check(r'before"name"after', 'beforeafter') ++ check(r'before"name"', 'before') ++ check(r'b"name"', 'b') # single char ++ check(r'"name"after', 'after') ++ check(r'"name"a', 'a') # single char ++ check(r'"name"', '') ++ ++ # no change ++ for addr in ( ++ 'Jane Doe , John Doe ', ++ 'lone " quote', ++ ): ++ self.assertEqual(utils._strip_quoted_realnames(addr), addr) ++ ++ ++ def test_check_parenthesis(self): ++ addr = 'alice@example.net' ++ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice(')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))')) ++ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)')) ++ ++ # Ignore real name between quotes ++ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}')) ++ + + # Test the iterator/generators + class TestIterators(TestEmailBase): +diff --git a/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst +new file mode 100644 +index 0000000000..3d0e9e4078 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst +@@ -0,0 +1,8 @@ ++:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now ++return ``('', '')`` 2-tuples in more situations where invalid email ++addresses are encountered instead of potentially inaccurate values. Add ++optional *strict* parameter to these two functions: use ``strict=False`` to ++get the old behavior, accept malformed inputs. ++``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check ++if the *strict* paramater is available. Patch by Thomas Dwyer and Victor ++Stinner to improve the CVE-2023-27043 fix. + + +From 4df4fad359c280f2328b98ea9b4414f244624a58 Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Mon, 18 Dec 2023 20:15:33 +0100 +Subject: [PATCH] Make it possible to disable strict parsing in email module + +--- + Doc/library/email.utils.rst | 26 +++++++++++ + Lib/email/utils.py | 54 ++++++++++++++++++++++- + Lib/test/test_email/test_email.py | 72 ++++++++++++++++++++++++++++++- + 3 files changed, 149 insertions(+), 3 deletions(-) + +diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst +index d1e1898591..7aef773b5f 100644 +--- a/Doc/library/email.utils.rst ++++ b/Doc/library/email.utils.rst +@@ -69,6 +69,19 @@ of the new API. + + If *strict* is true, use a strict parser which rejects malformed inputs. + ++ The default setting for *strict* is set to ``True``, but you can override ++ it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING`` ++ to non-empty string. ++ ++ Additionally, you can permanently set the default value for *strict* to ++ ``False`` by creating the configuration file ``/etc/python/email.cfg`` ++ with the following content: ++ ++ .. code-block:: ini ++ ++ [email_addr_parsing] ++ PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true ++ + .. versionchanged:: 3.13 + Add *strict* optional parameter and reject malformed inputs by default. + +@@ -97,6 +110,19 @@ of the new API. + + If *strict* is true, use a strict parser which rejects malformed inputs. + ++ The default setting for *strict* is set to ``True``, but you can override ++ it by setting the environment variable ``PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING`` ++ to non-empty string. ++ ++ Additionally, you can permanently set the default value for *strict* to ++ ``False`` by creating the configuration file ``/etc/python/email.cfg`` ++ with the following content: ++ ++ .. code-block:: ini ++ ++ [email_addr_parsing] ++ PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true ++ + Here's a simple example that gets all the recipients of a message:: + + from email.utils import getaddresses +diff --git a/Lib/email/utils.py b/Lib/email/utils.py +index f83b7e5d7e..b8e90ceb8e 100644 +--- a/Lib/email/utils.py ++++ b/Lib/email/utils.py +@@ -48,6 +48,46 @@ TICK = "'" + specialsre = re.compile(r'[][\\()<>@,:;".]') + escapesre = re.compile(r'[\\"]') + ++_EMAIL_CONFIG_FILE = "/etc/python/email.cfg" ++_cached_strict_addr_parsing = None ++ ++ ++def _use_strict_email_parsing(): ++ """"Cache implementation for _cached_strict_addr_parsing""" ++ global _cached_strict_addr_parsing ++ if _cached_strict_addr_parsing is None: ++ _cached_strict_addr_parsing = _use_strict_email_parsing_impl() ++ return _cached_strict_addr_parsing ++ ++ ++def _use_strict_email_parsing_impl(): ++ """Returns True if strict email parsing is not disabled by ++ config file or env variable. ++ """ ++ disabled = bool(os.environ.get("PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING")) ++ if disabled: ++ return False ++ ++ try: ++ file = open(_EMAIL_CONFIG_FILE) ++ except FileNotFoundError: ++ pass ++ else: ++ with file: ++ import configparser ++ config = configparser.ConfigParser( ++ interpolation=None, ++ comment_prefixes=('#', ), ++ ++ ) ++ config.read_file(file) ++ disabled = config.getboolean('email_addr_parsing', "PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING", fallback=None) ++ ++ if disabled: ++ return False ++ ++ return True ++ + + def _has_surrogates(s): + """Return True if s contains surrogate-escaped binary data.""" +@@ -149,7 +189,7 @@ def _strip_quoted_realnames(addr): + + supports_strict_parsing = True + +-def getaddresses(fieldvalues, *, strict=True): ++def getaddresses(fieldvalues, *, strict=None): + """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue. + + When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in +@@ -158,6 +198,11 @@ def getaddresses(fieldvalues, *, strict=True): + If strict is true, use a strict parser which rejects malformed inputs. + """ + ++ # If default is used, it's True unless disabled ++ # by env variable or config file. ++ if strict == None: ++ strict = _use_strict_email_parsing() ++ + # If strict is true, if the resulting list of parsed addresses is greater + # than the number of fieldvalues in the input list, a parsing error has + # occurred and consequently a list containing a single empty 2-tuple [('', +@@ -330,7 +375,7 @@ def parsedate_to_datetime(data): + tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) + + +-def parseaddr(addr, *, strict=True): ++def parseaddr(addr, *, strict=None): + """ + Parse addr into its constituent realname and email address parts. + +@@ -339,6 +384,11 @@ def parseaddr(addr, *, strict=True): + + If strict is True, use a strict parser which rejects malformed inputs. + """ ++ # If default is used, it's True unless disabled ++ # by env variable or config file. ++ if strict == None: ++ strict = _use_strict_email_parsing() ++ + if not strict: + addrs = _AddressList(addr).addresslist + if not addrs: +diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py +index ce36efc1b1..05ea201b68 100644 +--- a/Lib/test/test_email/test_email.py ++++ b/Lib/test/test_email/test_email.py +@@ -7,6 +7,9 @@ import time + import base64 + import unittest + import textwrap ++import contextlib ++import tempfile ++import os + + from io import StringIO, BytesIO + from itertools import chain +@@ -41,7 +44,7 @@ from email import iterators + from email import base64mime + from email import quoprimime + +-from test.support import unlink, start_threads ++from test.support import unlink, start_threads, EnvironmentVarGuard, swap_attr + from test.test_email import openfile, TestEmailBase + + # These imports are documented to work, but we are testing them using a +@@ -3313,6 +3316,73 @@ Foo + # Test email.utils.supports_strict_parsing attribute + self.assertEqual(email.utils.supports_strict_parsing, True) + ++ def test_parsing_errors_strict_set_via_env_var(self): ++ address = 'alice@example.org )Alice(' ++ empty = ('', '') ++ ++ # Reset cached default value to make the function ++ # reload the config file provided below. ++ utils._cached_strict_addr_parsing = None ++ ++ # Strict disabled via env variable, old behavior expected ++ with EnvironmentVarGuard() as environ: ++ environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = "1" ++ ++ self.assertEqual(utils.getaddresses([address]), ++ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) ++ self.assertEqual(utils.parseaddr([address]), ('', address)) ++ ++ # Clear cache again ++ utils._cached_strict_addr_parsing = None ++ ++ # Default strict=True, empty result expected ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ ++ # Clear cache again ++ utils._cached_strict_addr_parsing = None ++ ++ # Empty string in env variable = strict parsing enabled (default) ++ with EnvironmentVarGuard() as environ: ++ environ["PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING"] = "" ++ ++ # Default strict=True, empty result expected ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ ++ @contextlib.contextmanager ++ def _email_strict_parsing_conf(self): ++ """Context for the given email strict parsing configured in config file""" ++ with tempfile.TemporaryDirectory() as tmpdirname: ++ filename = os.path.join(tmpdirname, 'conf.cfg') ++ with swap_attr(utils, "_EMAIL_CONFIG_FILE", filename): ++ with open(filename, 'w') as file: ++ file.write('[email_addr_parsing]\n') ++ file.write('PYTHON_EMAIL_DISABLE_STRICT_ADDR_PARSING = true') ++ utils._EMAIL_CONFIG_FILE = filename ++ yield ++ ++ def test_parsing_errors_strict_disabled_via_config_file(self): ++ address = 'alice@example.org )Alice(' ++ empty = ('', '') ++ ++ # Reset cached default value to make the function ++ # reload the config file provided below. ++ utils._cached_strict_addr_parsing = None ++ ++ # Strict disabled via config file, old results expected ++ with self._email_strict_parsing_conf(): ++ self.assertEqual(utils.getaddresses([address]), ++ [('', 'alice@example.org'), ('', ''), ('', 'Alice')]) ++ self.assertEqual(utils.parseaddr([address]), ('', address)) ++ ++ # Clear cache again ++ utils._cached_strict_addr_parsing = None ++ ++ # Default strict=True, empty result expected ++ self.assertEqual(utils.getaddresses([address]), [empty]) ++ self.assertEqual(utils.parseaddr([address]), empty) ++ + def test_getaddresses_nasty(self): + for addresses, expected in ( + (['"Sürname, Firstname" '], +-- +2.43.0 + diff --git a/SPECS/python3.spec b/SPECS/python3.spec index 6801501..29c87ca 100644 --- a/SPECS/python3.spec +++ b/SPECS/python3.spec @@ -14,7 +14,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: 55%{?dist} +Release: 59%{?dist} License: Python @@ -790,6 +790,53 @@ Patch397: 00397-tarfile-filter.patch # 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 +# +# Backported from Python 3.8 +Patch404: 00404-cve-2023-40217.patch + +# 00408 # +# CVE-2022-48560 +# +# Security fix for CVE-2022-48560: python3: use after free in heappushpop() +# of heapq module +# Resolved upstream: https://github.com/python/cpython/issues/83602 +Patch408: 00408-CVE-2022-48560.patch + +# 00413 # +# CVE-2022-48564 +# +# DoS when processing malformed Apple Property List files in binary format +# Resolved upstream: https://github.com/python/cpython/commit/a63234c49b2fbfb6f0aca32525e525ce3d43b2b4 +Patch413: 00413-CVE-2022-48564.patch + +# 00414 # +# +# Skip test_pair() and test_speech128() of test_zlib on s390x since +# they fail if zlib uses the s390x hardware accelerator. +Patch414: 00414-skip_test_zlib_s390x.patch + +# 00415 # +# [CVE-2023-27043] gh-102988: Reject malformed addresses in email.parseaddr() (#111116) +# +# Detect email address parsing errors and return empty tuple to +# indicate the parsing error (old API). Add an optional 'strict' +# parameter to getaddresses() and parseaddr() functions. Patch by +# Thomas Dwyer. +# +# Upstream PR: https://github.com/python/cpython/pull/111116 +# +# Second patch implmenets the possibility to restore the old behavior via +# config file or environment variable. +Patch415: 00415-cve-2023-27043-gh-102988-reject-malformed-addresses-in-email-parseaddr-111116.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1138,6 +1185,11 @@ git apply %{PATCH351} %patch394 -p1 %patch397 -p1 %patch399 -p1 +%patch404 -p1 +%patch408 -p1 +%patch413 -p1 +%patch414 -p1 +%patch415 -p1 # Remove files that should be generated by the build # (This is after patching, so that we can use patches directly from upstream) @@ -2069,6 +2121,24 @@ fi # ====================================================== %changelog +* Thu Jan 04 2024 Lumír Balhar - 3.6.8-59 +- Security fix for CVE-2023-27043 +Resolves: RHEL-20610 + +* Tue Dec 12 2023 Lumír Balhar - 3.6.8-58 +- Security fix for CVE-2022-48564 +Resolves: RHEL-16674 +- Skip tests failing on s390x +Resolves: RHEL-19252 + +* Thu Nov 23 2023 Lumír Balhar - 3.6.8-57 +- Security fix for CVE-2022-48560 +Resolves: RHEL-16707 + +* Thu Sep 07 2023 Charalampos Stratakis - 3.6.8-56 +- Security fix for CVE-2023-40217 +Resolves: RHEL-3041 + * Wed Aug 09 2023 Petr Viktorin - 3.6.8-55 - Fix symlink handling in the fix for CVE-2007-4559 Resolves: rhbz#263261