[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

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: