Bug#1070218: bookworm-pu: package pypy3/7.3.11+dfsg-2+deb12u2
Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: pypy3@packages.debian.org
Control: affects -1 + src:pypy3
User: release.debian.org@packages.debian.org
Usertags: pu
[ Reason ]
A collection of minor (no-dsa) security updates for pypy3 in bookworm.
This fixes all of the outstanding CVEs (except CVE-2023-27043, which we
are waiting for upstream cPython to commit to a patch for).
[ Impact ]
Minor security issues.
[ Tests ]
All of the patches come from cPython upstream, and have unit tests
included.
While pypy3's testsuite doesn't pass cleanly, none of the new tests are
observed to fail.
[ Risks ]
Changes are relatively straightforward, and all cherry-picked from
cPython's stable releases. They have been published in cPython stable
releases. Where regressions were found, their fixes are included.
[ Checklist ]
[x] *all* changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in (old)stable
[x] the issue is verified as fixed in unstable
[ Changes ]
pypy3 (7.3.11+dfsg-2+deb12u2) bookworm; urgency=medium
.
* Security patches to the standard library:
- Apply cPython upstream security fix for CVE-2023-24329:
Strip C0 control and space characters in urlsplit.
- Apply cPython upstream security fix for CVE-2023-40217:
Avoid bypass TLS of handshake protections on closed sockets.
- Apply cPython upstream security fix for CVE-2023-6597:
tempfile.TemporaryDirectory: fix symlink bug in cleanup.
- Apply cPython upstream security fix for CVE-2024-0450:
Protect zipfile from "quoted-overlap" zipbomb.
diff -Nru pypy3-7.3.11+dfsg/debian/changelog pypy3-7.3.11+dfsg/debian/changelog
--- pypy3-7.3.11+dfsg/debian/changelog 2024-02-01 20:41:13.000000000 -0400
+++ pypy3-7.3.11+dfsg/debian/changelog 2024-05-01 20:39:38.000000000 -0400
@@ -1,3 +1,17 @@
+pypy3 (7.3.11+dfsg-2+deb12u2) bookworm; urgency=medium
+
+ * Security patches to the standard library:
+ - Apply cPython upstream security fix for CVE-2023-24329:
+ Strip C0 control and space characters in urlsplit.
+ - Apply cPython upstream security fix for CVE-2023-40217:
+ Avoid bypass TLS of handshake protections on closed sockets.
+ - Apply cPython upstream security fix for CVE-2023-6597:
+ tempfile.TemporaryDirectory: fix symlink bug in cleanup.
+ - Apply cPython upstream security fix for CVE-2024-0450:
+ Protect zipfile from "quoted-overlap" zipbomb.
+
+ -- Stefano Rivera <stefanor@debian.org> Wed, 01 May 2024 20:39:38 -0400
+
pypy3 (7.3.11+dfsg-2+deb12u1) bookworm; urgency=medium
* Avoid an rpython assertion error in the JIT if integer ranges don't
diff -Nru pypy3-7.3.11+dfsg/debian/patches/CVE-2023-24329-strip-control-chars-urlsplit.patch pypy3-7.3.11+dfsg/debian/patches/CVE-2023-24329-strip-control-chars-urlsplit.patch
--- pypy3-7.3.11+dfsg/debian/patches/CVE-2023-24329-strip-control-chars-urlsplit.patch 1969-12-31 20:00:00.000000000 -0400
+++ pypy3-7.3.11+dfsg/debian/patches/CVE-2023-24329-strip-control-chars-urlsplit.patch 2024-05-01 20:39:38.000000000 -0400
@@ -0,0 +1,145 @@
+From: "Miss Islington (bot)"
+ <31488909+miss-islington@users.noreply.github.com>
+Date: Mon, 22 May 2023 03:42:37 -0700
+Subject: gh-102153: Start stripping C0 control and space chars in `urlsplit`
+ (GH-102508) (GH-104575) (GH-104592) (#104593)
+
+gh-102153: Start stripping C0 control and space chars in `urlsplit` (GH-102508)
+
+`urllib.parse.urlsplit` has already been respecting the WHATWG spec a bit GH-25595.
+
+This adds more sanitizing to respect the "Remove any leading C0 control or space from input" [rule](https://url.spec.whatwg.org/GH-url-parsing:~:text=Remove%20any%20leading%20and%20trailing%20C0%20control%20or%20space%20from%20input.) in response to [CVE-2023-24329](https://nvd.nist.gov/vuln/detail/CVE-2023-24329).
+
+I simplified the docs by eliding the state of the world explanatory
+paragraph in this security release only backport. (people will see
+that in the mainline /3/ docs)
+
+(cherry picked from commit 2f630e1ce18ad2e07428296532a68b11dc66ad10)
+(cherry picked from commit 610cc0ab1b760b2abaac92bd256b96191c46b941)
+(cherry picked from commit f48a96a28012d28ae37a2f4587a780a5eb779946)
+
+Co-authored-by: Illia Volochii <illia.volochii@gmail.com>
+Co-authored-by: Gregory P. Smith [Google] <greg@krypto.org>
+
+Origin: cpython, https://github.com/python/cpython/commit/d7f8a5fe07b0ff3a419ccec434cc405b21a5a304
+---
+ lib-python/3/test/test_urlparse.py | 61 +++++++++++++++++++++++++++++++++++++-
+ lib-python/3/urllib/parse.py | 12 ++++++++
+ 2 files changed, 72 insertions(+), 1 deletion(-)
+
+diff --git a/lib-python/3/test/test_urlparse.py b/lib-python/3/test/test_urlparse.py
+index 31943f3..574da5b 100644
+--- a/lib-python/3/test/test_urlparse.py
++++ b/lib-python/3/test/test_urlparse.py
+@@ -649,6 +649,65 @@ class UrlParseTestCase(unittest.TestCase):
+ self.assertEqual(p.scheme, "http")
+ self.assertEqual(p.geturl(), "http://www.python.org/javascript:alert('msg')/?query=something#fragment")
+
++ def test_urlsplit_strip_url(self):
++ noise = bytes(range(0, 0x20 + 1))
++ base_url = "http://User:Pass@www.python.org:080/doc/?query=yes#frag"
++
++ url = noise.decode("utf-8") + base_url
++ p = urllib.parse.urlsplit(url)
++ self.assertEqual(p.scheme, "http")
++ self.assertEqual(p.netloc, "User:Pass@www.python.org:080")
++ self.assertEqual(p.path, "/doc/")
++ self.assertEqual(p.query, "query=yes")
++ self.assertEqual(p.fragment, "frag")
++ self.assertEqual(p.username, "User")
++ self.assertEqual(p.password, "Pass")
++ self.assertEqual(p.hostname, "www.python.org")
++ self.assertEqual(p.port, 80)
++ self.assertEqual(p.geturl(), base_url)
++
++ url = noise + base_url.encode("utf-8")
++ p = urllib.parse.urlsplit(url)
++ self.assertEqual(p.scheme, b"http")
++ self.assertEqual(p.netloc, b"User:Pass@www.python.org:080")
++ self.assertEqual(p.path, b"/doc/")
++ self.assertEqual(p.query, b"query=yes")
++ self.assertEqual(p.fragment, b"frag")
++ self.assertEqual(p.username, b"User")
++ self.assertEqual(p.password, b"Pass")
++ self.assertEqual(p.hostname, b"www.python.org")
++ self.assertEqual(p.port, 80)
++ self.assertEqual(p.geturl(), base_url.encode("utf-8"))
++
++ # Test that trailing space is preserved as some applications rely on
++ # this within query strings.
++ query_spaces_url = "https://www.python.org:88/doc/?query= "
++ p = urllib.parse.urlsplit(noise.decode("utf-8") + query_spaces_url)
++ self.assertEqual(p.scheme, "https")
++ self.assertEqual(p.netloc, "www.python.org:88")
++ self.assertEqual(p.path, "/doc/")
++ self.assertEqual(p.query, "query= ")
++ self.assertEqual(p.port, 88)
++ self.assertEqual(p.geturl(), query_spaces_url)
++
++ p = urllib.parse.urlsplit("www.pypi.org ")
++ # That "hostname" gets considered a "path" due to the
++ # trailing space and our existing logic... YUCK...
++ # and re-assembles via geturl aka unurlsplit into the original.
++ # django.core.validators.URLValidator (at least through v3.2) relies on
++ # this, for better or worse, to catch it in a ValidationError via its
++ # regular expressions.
++ # Here we test the basic round trip concept of such a trailing space.
++ self.assertEqual(urllib.parse.urlunsplit(p), "www.pypi.org ")
++
++ # with scheme as cache-key
++ url = "//www.python.org/"
++ scheme = noise.decode("utf-8") + "https" + noise.decode("utf-8")
++ for _ in range(2):
++ p = urllib.parse.urlsplit(url, scheme=scheme)
++ self.assertEqual(p.scheme, "https")
++ self.assertEqual(p.geturl(), "https://www.python.org/")
++
+ def test_attributes_bad_port(self):
+ """Check handling of invalid ports."""
+ for bytes in (False, True):
+@@ -656,7 +715,7 @@ class UrlParseTestCase(unittest.TestCase):
+ for port in ("foo", "1.5", "-1", "0x10"):
+ with self.subTest(bytes=bytes, parse=parse, port=port):
+ netloc = "www.example.net:" + port
+- url = "http://" + netloc
++ url = "http://" + netloc + "/"
+ if bytes:
+ netloc = netloc.encode("ascii")
+ url = url.encode("ascii")
+diff --git a/lib-python/3/urllib/parse.py b/lib-python/3/urllib/parse.py
+index bd26813..f5d3662 100644
+--- a/lib-python/3/urllib/parse.py
++++ b/lib-python/3/urllib/parse.py
+@@ -25,6 +25,10 @@ currently not entirely compliant with this RFC due to defacto
+ scenarios for parsing, and for backward compatibility purposes, some
+ parsing quirks from older RFCs are retained. The testcases in
+ test_urlparse.py provides a good indicator of parsing behavior.
++
++The WHATWG URL Parser spec should also be considered. We are not compliant with
++it either due to existing user code API behavior expectations (Hyrum's Law).
++It serves as a useful guide when making changes.
+ """
+
+ import re
+@@ -78,6 +82,10 @@ scheme_chars = ('abcdefghijklmnopqrstuvwxyz'
+ '0123456789'
+ '+-.')
+
++# Leading and trailing C0 control and space to be stripped per WHATWG spec.
++# == "".join([chr(i) for i in range(0, 0x20 + 1)])
++_WHATWG_C0_CONTROL_OR_SPACE = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f '
++
+ # Unsafe bytes to be removed per WHATWG spec
+ _UNSAFE_URL_BYTES_TO_REMOVE = ['\t', '\r', '\n']
+
+@@ -456,6 +464,10 @@ def urlsplit(url, scheme='', allow_fragments=True):
+ """
+
+ url, scheme, _coerce_result = _coerce_args(url, scheme)
++ # Only lstrip url as some applications rely on preserving trailing space.
++ # (https://url.spec.whatwg.org/#concept-basic-url-parser would strip both)
++ url = url.lstrip(_WHATWG_C0_CONTROL_OR_SPACE)
++ scheme = scheme.strip(_WHATWG_C0_CONTROL_OR_SPACE)
+
+ for b in _UNSAFE_URL_BYTES_TO_REMOVE:
+ url = url.replace(b, "")
diff -Nru pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-ref-cycle.patch pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-ref-cycle.patch
--- pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-ref-cycle.patch 1969-12-31 20:00:00.000000000 -0400
+++ pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-ref-cycle.patch 2024-05-01 20:39:38.000000000 -0400
@@ -0,0 +1,39 @@
+From: "Miss Islington (bot)"
+ <31488909+miss-islington@users.noreply.github.com>
+Date: Wed, 23 Aug 2023 03:10:04 -0700
+Subject: gh-108342: Break ref cycle in SSLSocket._create() exc (GH-108344)
+ (#108351)
+
+Explicitly break a reference cycle when SSLSocket._create() raises an
+exception. Clear the variable storing the exception, since the
+exception traceback contains the variables and so creates a reference
+cycle.
+
+This test leak was introduced by the test added for the fix of GH-108310.
+(cherry picked from commit 64f99350351bc46e016b2286f36ba7cd669b79e3)
+
+Co-authored-by: Victor Stinner <vstinner@python.org>
+
+Origin: cpython, https://github.com/python/cpython/commit/b8058b3da542101f4a227ef2d6a263a5d73d7973
+Bug-cpython: https://github.com/python/cpython/issues/108342
+---
+ lib-python/3/ssl.py | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/lib-python/3/ssl.py b/lib-python/3/ssl.py
+index c445003..2e6bdfd 100644
+--- a/lib-python/3/ssl.py
++++ b/lib-python/3/ssl.py
+@@ -1049,7 +1049,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
+
diff -Nru pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-ssl-pre-close-flaw.patch pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-ssl-pre-close-flaw.patch
--- pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-ssl-pre-close-flaw.patch 1969-12-31 20:00:00.000000000 -0400
+++ pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-ssl-pre-close-flaw.patch 2024-05-01 20:39:38.000000000 -0400
@@ -0,0 +1,320 @@
+From: =?utf-8?q?=C5=81ukasz_Langa?= <lukasz@langa.pl>
+Date: Tue, 22 Aug 2023 19:57:10 +0200
+Subject: gh-108310: Fix CVE-2023-40217: Check for & avoid the ssl pre-close
+ flaw (#108320)
+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] <greg@krypto.org>
+
+Origin: cpython, https://github.com/python/cpython/commit/264b1dacc67346efa0933d1e63f622676e0ed96b
+Bug-cpython: https://github.com/python/cpython/issues/108310
+---
+ lib-python/3/ssl.py | 31 +++++-
+ lib-python/3/test/test_ssl.py | 215 ++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 245 insertions(+), 1 deletion(-)
+
+diff --git a/lib-python/3/ssl.py b/lib-python/3/ssl.py
+index 69f5f63..c445003 100644
+--- a/lib-python/3/ssl.py
++++ b/lib-python/3/ssl.py
+@@ -1003,7 +1003,7 @@ class SSLSocket(socket):
+ )
+ self = cls.__new__(cls, **kwargs)
+ super(SSLSocket, self).__init__(**kwargs)
+- self.settimeout(sock.gettimeout())
++ sock_timeout = sock.gettimeout()
+ sock.detach()
+
+ self._context = context
+@@ -1022,9 +1022,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._connected = connected
+ if connected:
+ # create the SSL object
+diff --git a/lib-python/3/test/test_ssl.py b/lib-python/3/test/test_ssl.py
+index 28a4afa..f385f43 100644
+--- a/lib-python/3/test/test_ssl.py
++++ b/lib-python/3/test/test_ssl.py
+@@ -5,11 +5,14 @@ import unittest
+ import unittest.mock
+ from test import support
+ from test.support import socket_helper, warnings_helper
++import re
+ import socket
+ import select
++import struct
+ import time
+ import datetime
+ import gc
++import http.client
+ import os
+ import errno
+ import pprint
+@@ -4857,6 +4860,218 @@ class TestSSLDebug(unittest.TestCase):
+ s.connect((HOST, server.port))
+
+
++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 = socket_helper.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(f"Could not recreate conditions on {sys.platform}:"
++ f" {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 setUpModule():
+ if support.verbose:
+ plats = {
diff -Nru pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-test-reliability.patch pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-test-reliability.patch
--- pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-test-reliability.patch 1969-12-31 20:00:00.000000000 -0400
+++ pypy3-7.3.11+dfsg/debian/patches/CVE-2023-40217-test-reliability.patch 2024-05-01 20:39:38.000000000 -0400
@@ -0,0 +1,234 @@
+From: =?utf-8?q?=C5=81ukasz_Langa?= <lukasz@langa.pl>
+Date: Thu, 24 Aug 2023 12:09:11 +0200
+Subject: gh-108342: Make ssl TestPreHandshakeClose more reliable (GH-108370)
+ (#108407)
+
+* 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)
+
+Co-authored-by: Victor Stinner <vstinner@python.org>
+
+Origin: cpython, https://github.com/python/cpython/commit/d2cd0a3acba593334fdc2c42b64885de455a9d36
+Bug-cpython: https://github.com/python/cpython/issues/108342
+---
+ lib-python/3/test/test_ssl.py | 102 +++++++++++++++++++++++++++++-------------
+ 1 file changed, 71 insertions(+), 31 deletions(-)
+
+diff --git a/lib-python/3/test/test_ssl.py b/lib-python/3/test/test_ssl.py
+index f385f43..ce654f0 100644
+--- a/lib-python/3/test/test_ssl.py
++++ b/lib-python/3/test/test_ssl.py
+@@ -4870,12 +4870,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):
+@@ -4898,13 +4902,19 @@ class TestPreHandshakeClose(unittest.TestCase):
+ self.ssl_ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
+ self.listener = socket.socket()
+ self.port = socket_helper.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
+@@ -4932,8 +4942,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(f"Could not recreate conditions on {sys.platform}:"
+- f" {err=}")
++ try:
++ self.skipTest(f"Could not recreate conditions on {sys.platform}:"
++ f" {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.
+@@ -4944,7 +4959,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.
+
+@@ -4966,20 +4981,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(
+@@ -5001,8 +5027,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(
+@@ -5016,24 +5044,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)
+@@ -5048,29 +5083,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 setUpModule():
+ if support.verbose:
diff -Nru pypy3-7.3.11+dfsg/debian/patches/CVE-2023-6597-tempfile-symlink.patch pypy3-7.3.11+dfsg/debian/patches/CVE-2023-6597-tempfile-symlink.patch
--- pypy3-7.3.11+dfsg/debian/patches/CVE-2023-6597-tempfile-symlink.patch 1969-12-31 20:00:00.000000000 -0400
+++ pypy3-7.3.11+dfsg/debian/patches/CVE-2023-6597-tempfile-symlink.patch 2024-05-01 20:39:38.000000000 -0400
@@ -0,0 +1,203 @@
+From: Serhiy Storchaka <storchaka@gmail.com>
+Date: Wed, 17 Jan 2024 15:47:47 +0200
+Subject: gh-91133: tempfile.TemporaryDirectory: fix symlink bug in cleanup
+ (GH-99930) (GH-112842)
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+(cherry picked from commit 81c16cd94ec38d61aa478b9a452436dc3b1b524d)
+
+Co-authored-by: Søren Løvborg <sorenl@unity3d.com>
+
+Origin: cpython, https://github.com/python/cpython/commit/d54e22a669ae6e987199bb5d2c69bb5a46b0083b
+Bug-cpython: https://github.com/python/cpython/issues/91133
+---
+ lib-python/3/tempfile.py | 27 ++++++---
+ lib-python/3/test/test_tempfile.py | 117 ++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 134 insertions(+), 10 deletions(-)
+
+diff --git a/lib-python/3/tempfile.py b/lib-python/3/tempfile.py
+index eafce6f..59a628a 100644
+--- a/lib-python/3/tempfile.py
++++ b/lib-python/3/tempfile.py
+@@ -268,6 +268,22 @@ def _mkstemp_inner(dir, pre, suf, flags, output_type):
+ raise FileExistsError(_errno.EEXIST,
+ "No usable temporary file name found")
+
++def _dont_follow_symlinks(func, path, *args):
++ # Pass follow_symlinks=False, unless not supported on this platform.
++ if func in _os.supports_follow_symlinks:
++ func(path, *args, follow_symlinks=False)
++ elif _os.name == 'nt' or not _os.path.islink(path):
++ func(path, *args)
++
++def _resetperms(path):
++ try:
++ chflags = _os.chflags
++ except AttributeError:
++ pass
++ else:
++ _dont_follow_symlinks(chflags, path, 0)
++ _dont_follow_symlinks(_os.chmod, path, 0o700)
++
+
+ # User visible interfaces.
+
+@@ -789,17 +805,10 @@ class TemporaryDirectory(object):
+ def _rmtree(cls, name):
+ def onerror(func, path, exc_info):
+ if issubclass(exc_info[0], PermissionError):
+- def resetperms(path):
+- try:
+- _os.chflags(path, 0)
+- except AttributeError:
+- pass
+- _os.chmod(path, 0o700)
+-
+ try:
+ if path != name:
+- resetperms(_os.path.dirname(path))
+- resetperms(path)
++ _resetperms(_os.path.dirname(path))
++ _resetperms(path)
+
+ try:
+ _os.unlink(path)
+diff --git a/lib-python/3/test/test_tempfile.py b/lib-python/3/test/test_tempfile.py
+index 293a7c4..fa2025e 100644
+--- a/lib-python/3/test/test_tempfile.py
++++ b/lib-python/3/test/test_tempfile.py
+@@ -1396,6 +1396,103 @@ class TestTemporaryDirectory(BaseTestCase):
+ "were deleted")
+ d2.cleanup()
+
++ @support.skip_unless_symlink
++ def test_cleanup_with_symlink_modes(self):
++ # cleanup() should not follow symlinks when fixing mode bits (#91133)
++ with self.do_create(recurse=0) as d2:
++ file1 = os.path.join(d2, 'file1')
++ open(file1, 'wb').close()
++ dir1 = os.path.join(d2, 'dir1')
++ os.mkdir(dir1)
++ for mode in range(8):
++ mode <<= 6
++ with self.subTest(mode=format(mode, '03o')):
++ def test(target, target_is_directory):
++ d1 = self.do_create(recurse=0)
++ symlink = os.path.join(d1.name, 'symlink')
++ os.symlink(target, symlink,
++ target_is_directory=target_is_directory)
++ try:
++ os.chmod(symlink, mode, follow_symlinks=False)
++ except NotImplementedError:
++ pass
++ try:
++ os.chmod(symlink, mode)
++ except FileNotFoundError:
++ pass
++ os.chmod(d1.name, mode)
++ d1.cleanup()
++ self.assertFalse(os.path.exists(d1.name))
++
++ with self.subTest('nonexisting file'):
++ test('nonexisting', target_is_directory=False)
++ with self.subTest('nonexisting dir'):
++ test('nonexisting', target_is_directory=True)
++
++ with self.subTest('existing file'):
++ os.chmod(file1, mode)
++ old_mode = os.stat(file1).st_mode
++ test(file1, target_is_directory=False)
++ new_mode = os.stat(file1).st_mode
++ self.assertEqual(new_mode, old_mode,
++ '%03o != %03o' % (new_mode, old_mode))
++
++ with self.subTest('existing dir'):
++ os.chmod(dir1, mode)
++ old_mode = os.stat(dir1).st_mode
++ test(dir1, target_is_directory=True)
++ new_mode = os.stat(dir1).st_mode
++ self.assertEqual(new_mode, old_mode,
++ '%03o != %03o' % (new_mode, old_mode))
++
++ @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags')
++ @support.skip_unless_symlink
++ def test_cleanup_with_symlink_flags(self):
++ # cleanup() should not follow symlinks when fixing flags (#91133)
++ flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK
++ self.check_flags(flags)
++
++ with self.do_create(recurse=0) as d2:
++ file1 = os.path.join(d2, 'file1')
++ open(file1, 'wb').close()
++ dir1 = os.path.join(d2, 'dir1')
++ os.mkdir(dir1)
++ def test(target, target_is_directory):
++ d1 = self.do_create(recurse=0)
++ symlink = os.path.join(d1.name, 'symlink')
++ os.symlink(target, symlink,
++ target_is_directory=target_is_directory)
++ try:
++ os.chflags(symlink, flags, follow_symlinks=False)
++ except NotImplementedError:
++ pass
++ try:
++ os.chflags(symlink, flags)
++ except FileNotFoundError:
++ pass
++ os.chflags(d1.name, flags)
++ d1.cleanup()
++ self.assertFalse(os.path.exists(d1.name))
++
++ with self.subTest('nonexisting file'):
++ test('nonexisting', target_is_directory=False)
++ with self.subTest('nonexisting dir'):
++ test('nonexisting', target_is_directory=True)
++
++ with self.subTest('existing file'):
++ os.chflags(file1, flags)
++ old_flags = os.stat(file1).st_flags
++ test(file1, target_is_directory=False)
++ new_flags = os.stat(file1).st_flags
++ self.assertEqual(new_flags, old_flags)
++
++ with self.subTest('existing dir'):
++ os.chflags(dir1, flags)
++ old_flags = os.stat(dir1).st_flags
++ test(dir1, target_is_directory=True)
++ new_flags = os.stat(dir1).st_flags
++ self.assertEqual(new_flags, old_flags)
++
+ @support.cpython_only
+ def test_del_on_collection(self):
+ # A TemporaryDirectory is deleted when garbage collected
+@@ -1510,9 +1607,27 @@ class TestTemporaryDirectory(BaseTestCase):
+ d.cleanup()
+ self.assertFalse(os.path.exists(d.name))
+
+- @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.lchflags')
++ def check_flags(self, flags):
++ # skip the test if these flags are not supported (ex: FreeBSD 13)
++ filename = support.TESTFN
++ try:
++ open(filename, "w").close()
++ try:
++ os.chflags(filename, flags)
++ except OSError as exc:
++ # "OSError: [Errno 45] Operation not supported"
++ self.skipTest(f"chflags() doesn't support flags "
++ f"{flags:#b}: {exc}")
++ else:
++ os.chflags(filename, 0)
++ finally:
++ support.unlink(filename)
++
++ @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags')
+ def test_flags(self):
+ flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK
++ self.check_flags(flags)
++
+ d = self.do_create(recurse=3, dirs=2, files=2)
+ with d:
+ # Change files and directories flags recursively.
diff -Nru pypy3-7.3.11+dfsg/debian/patches/CVE-2024-0450-zipfile-quoted-overlap.patch pypy3-7.3.11+dfsg/debian/patches/CVE-2024-0450-zipfile-quoted-overlap.patch
--- pypy3-7.3.11+dfsg/debian/patches/CVE-2024-0450-zipfile-quoted-overlap.patch 1969-12-31 20:00:00.000000000 -0400
+++ pypy3-7.3.11+dfsg/debian/patches/CVE-2024-0450-zipfile-quoted-overlap.patch 2024-05-01 20:39:38.000000000 -0400
@@ -0,0 +1,134 @@
+From: "Miss Islington (bot)"
+ <31488909+miss-islington@users.noreply.github.com>
+Date: Wed, 17 Jan 2024 14:48:06 +0100
+Subject: gh-109858: Protect zipfile from "quoted-overlap" zipbomb (GH-110016)
+ (GH-113915)
+
+Raise BadZipFile when try to read an entry that overlaps with other entry or
+central directory.
+(cherry picked from commit 66363b9a7b9fe7c99eba3a185b74c5fdbf842eba)
+
+Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
+
+Origin: cpython, https://github.com/python/cpython/commit/a2c59992e9e8d35baba9695eb186ad6c6ff85c51
+Bug-cpython: https://github.com/python/cpython/issues/109858
+---
+ lib-python/3/test/test_zipfile.py | 60 +++++++++++++++++++++++++++++++++++++++
+ lib-python/3/zipfile.py | 12 ++++++++
+ 2 files changed, 72 insertions(+)
+
+diff --git a/lib-python/3/test/test_zipfile.py b/lib-python/3/test/test_zipfile.py
+index 605c9ef..f09c82c 100644
+--- a/lib-python/3/test/test_zipfile.py
++++ b/lib-python/3/test/test_zipfile.py
+@@ -2047,6 +2047,66 @@ class OtherTests(unittest.TestCase):
+ with zipfile.ZipFile(zip_file) as zf:
+ self.assertRaises(RuntimeError, zf.extract, 'a.txt')
+
++ @requires_zlib()
++ def test_full_overlap(self):
++ data = (
++ b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e'
++ b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00a\xed'
++ b'\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\d\x0b`P'
++ b'K\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2'
++ b'\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00'
++ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00aPK'
++ b'\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e'
++ b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00\x00'
++ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bPK\x05'
++ b'\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00\x00/\x00\x00'
++ b'\x00\x00\x00'
++ )
++ with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf:
++ self.assertEqual(zipf.namelist(), ['a', 'b'])
++ zi = zipf.getinfo('a')
++ self.assertEqual(zi.header_offset, 0)
++ self.assertEqual(zi.compress_size, 16)
++ self.assertEqual(zi.file_size, 1033)
++ zi = zipf.getinfo('b')
++ self.assertEqual(zi.header_offset, 0)
++ self.assertEqual(zi.compress_size, 16)
++ self.assertEqual(zi.file_size, 1033)
++ self.assertEqual(len(zipf.read('a')), 1033)
++ with self.assertRaisesRegex(zipfile.BadZipFile, 'File name.*differ'):
++ zipf.read('b')
++
++ @requires_zlib()
++ def test_quoted_overlap(self):
++ data = (
++ b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05Y\xfc'
++ b'8\x044\x00\x00\x00(\x04\x00\x00\x01\x00\x00\x00a\x00'
++ b'\x1f\x00\xe0\xffPK\x03\x04\x14\x00\x00\x00\x08\x00\xa0l'
++ b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00'
++ b'\x00\x00b\xed\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\'
++ b'd\x0b`PK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0'
++ b'lH\x05Y\xfc8\x044\x00\x00\x00(\x04\x00\x00\x01'
++ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
++ b'\x00aPK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0l'
++ b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00'
++ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x00\x00'
++ b'bPK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00'
++ b'\x00S\x00\x00\x00\x00\x00'
++ )
++ with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf:
++ self.assertEqual(zipf.namelist(), ['a', 'b'])
++ zi = zipf.getinfo('a')
++ self.assertEqual(zi.header_offset, 0)
++ self.assertEqual(zi.compress_size, 52)
++ self.assertEqual(zi.file_size, 1064)
++ zi = zipf.getinfo('b')
++ self.assertEqual(zi.header_offset, 36)
++ self.assertEqual(zi.compress_size, 16)
++ self.assertEqual(zi.file_size, 1033)
++ with self.assertRaisesRegex(zipfile.BadZipFile, 'Overlapped entries'):
++ zipf.read('a')
++ self.assertEqual(len(zipf.read('b')), 1033)
++
+ def tearDown(self):
+ unlink(TESTFN)
+ unlink(TESTFN2)
+diff --git a/lib-python/3/zipfile.py b/lib-python/3/zipfile.py
+index 1e942a5..95f95ee 100644
+--- a/lib-python/3/zipfile.py
++++ b/lib-python/3/zipfile.py
+@@ -338,6 +338,7 @@ class ZipInfo (object):
+ 'compress_size',
+ 'file_size',
+ '_raw_time',
++ '_end_offset',
+ )
+
+ def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
+@@ -379,6 +380,7 @@ class ZipInfo (object):
+ self.external_attr = 0 # External file attributes
+ self.compress_size = 0 # Size of the compressed file
+ self.file_size = 0 # Size of the uncompressed file
++ self._end_offset = None # Start of the next local header or central directory
+ # Other attributes are set by class ZipFile:
+ # header_offset Byte offset to the file header
+ # CRC CRC-32 of the uncompressed file
+@@ -1399,6 +1401,12 @@ class ZipFile:
+ if self.debug > 2:
+ print("total", total)
+
++ end_offset = self.start_dir
++ for zinfo in sorted(self.filelist,
++ key=lambda zinfo: zinfo.header_offset,
++ reverse=True):
++ zinfo._end_offset = end_offset
++ end_offset = zinfo.header_offset
+
+ def namelist(self):
+ """Return a list of file names in the archive."""
+@@ -1554,6 +1562,10 @@ class ZipFile:
+ 'File name in directory %r and header %r differ.'
+ % (zinfo.orig_filename, fname))
+
++ if (zinfo._end_offset is not None and
++ zef_file.tell() + zinfo.compress_size > zinfo._end_offset):
++ raise BadZipFile(f"Overlapped entries: {zinfo.orig_filename!r} (possible zip bomb)")
++
+ # check for encrypted flag & handle password
+ is_encrypted = zinfo.flag_bits & 0x1
+ if is_encrypted:
diff -Nru pypy3-7.3.11+dfsg/debian/patches/series pypy3-7.3.11+dfsg/debian/patches/series
--- pypy3-7.3.11+dfsg/debian/patches/series 2024-02-01 20:41:13.000000000 -0400
+++ pypy3-7.3.11+dfsg/debian/patches/series 2024-05-01 20:39:38.000000000 -0400
@@ -20,3 +20,9 @@
python3-sphinx
setuptools-59-editable-installs
int-jit-assert.patch
+CVE-2023-24329-strip-control-chars-urlsplit.patch
+CVE-2023-40217-ssl-pre-close-flaw.patch
+CVE-2023-40217-ref-cycle.patch
+CVE-2023-40217-test-reliability.patch
+CVE-2023-6597-tempfile-symlink.patch
+CVE-2024-0450-zipfile-quoted-overlap.patch
Reply to: