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

Bug#1070218: marked as done (bookworm-pu: package pypy3/7.3.11+dfsg-2+deb12u2)



Your message dated Sat, 29 Jun 2024 10:46:19 +0000
with message-id <E1sNVb1-002bgF-KU@coccia.debian.org>
and subject line Released with 12.6
has caused the Debian Bug report #1070218,
regarding bookworm-pu: package pypy3/7.3.11+dfsg-2+deb12u2
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1070218: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1070218
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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

--- End Message ---
--- Begin Message ---
Version: 12.6

The upload requested in this bug has been released as part of 12.6.

--- End Message ---

Reply to: