Your message dated Sat, 11 Jul 2026 10:42:31 +0000
with message-id <[email protected]>
and subject line Released in 12.15
has caused the Debian Bug report #1136382,
regarding bookworm-pu: package python3.11/3.11.2-6+deb12u8
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 [email protected]
immediately.)


-- 
1136382: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1136382
Debian Bug Tracking System
Contact [email protected] with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:python3.11
User: [email protected]
Usertags: pu

Dear Release team,

this is a follow-up update to the +deb12u7 update already accepted in
bookwork-proposed-updates a few weeks ago. It fixes low severity CVEs,
which have been backported to the upstream 3.11 branch for the most
part. It will bring the bookworm package in-line with the trixie package
+deb13u2 (currently in trixie-p-u) in terms of CVE fixes.

The debdiff also includes a patch to fix the autopkgtest.

[ CVE details ]

The following patches:

* CVE-2025-13462
* CVE-2026-2297
* CVE-2026-4224
* CVE-2026-4519
* CVE-2026-6100

have been cherry-picked from the upstream 3.11 branch.

CVE-2026-3644 is not yet merged upstream, but it will be included in the
next 3.11 point release, according to:
https://github.com/python/cpython/pull/146026#issuecomment-4418137974

CVE-2026-6019 wasn't merged upstream because the automatic backport
failed, and upstream didn't bother (very low severity), however it turns
out that automatic backport will pass after CVE-2026-3644 is applied, so
it's possible that it will also be backported upstream, cf.
https://github.com/python/cpython/pull/148848#issuecomment-4299364914
(and other comments below).

For this reason I believe it's Ok to be a bit ahead of upstream for
these 2 CVE fixes. Note that they both come with unit tests.

[ Autopkgtest fix ]

The debdiff also contains a fix so that the autopkgtest succeeds again.
In short, autopkgtest has been failing for almost a year, since the
upload of expat/2.5.0-1+deb12u2. It makes it difficult to detect
regressions.

I have found a long discussion upstream on the matter, and from my
reading, upstream recommends downstream who patched expat to skip the
failing tests. This is what I did here. I elaborated a bit more in the
message of the patch itself.

[ Other info ]

The debusine workflow looks good:
https://debusine.debian.net/debian/developers/work-request/688225/

I checked the failures in reverse autopkgtests and they are unrelated.

Git commits are available at:
https://salsa.debian.org/arnaudr/python3.11/-/tree/wip/deb12u8?ref_type=heads

IMPORTANT! The debdiff attached is relative to the version
3.11.2-6+deb12u7 that is already in bookworm-p-u.

Thanks,

Arnaud
diff -Nru python3.11-3.11.2/debian/changelog python3.11-3.11.2/debian/changelog
--- python3.11-3.11.2/debian/changelog  2026-04-08 08:58:00.000000000 +0700
+++ python3.11-3.11.2/debian/changelog  2026-05-12 12:17:27.000000000 +0700
@@ -1,3 +1,25 @@
+python3.11 (3.11.2-6+deb12u8) bookworm; urgency=medium
+
+  * Non-maintainer upload.
+  * Apply upstream patches for the following CVEs:
+    - CVE-2025-13462: Incorrect parsing of TarInfo header when GNU long name
+      and type AREGTYPE are combined
+    - CVE-2026-2297: SourcelessFileLoader does not use io.open_code()
+    - CVE-2026-3644: Reject control characters in more places in
+      http.cookies.Morsel (follow-up of patch for CVE-2026-0672)
+    - CVE-2026-4224: pyexpat.c: Unbounded C recursion in conv_content_model
+      causes crash
+    - CVE-2026-4519: Reject leading dashes in webbrowser.open()
+    - CVE-2026-6019: SimpleCookie.js_output is vulnerable to HTML injection
+      (Closes: #1135116)
+    - CVE-2026-6100: Possible UAF in {LZMA,BZ2}Decompressor
+  * Add patch to skip some failing XML tests. Failure is due to the fact that
+    we build / tests against expat/2.5.0-1+deb12u2, which was patched for
+    CVE-2023-52425, and that broke some tests. See the patch itself for more
+    details.
+
+ -- Arnaud Rebillout <[email protected]>  Tue, 12 May 2026 12:17:27 +0700
+
 python3.11 (3.11.2-6+deb12u7) bookworm; urgency=medium
 
   * Non-maintainer upload.
diff -Nru python3.11-3.11.2/debian/patches/CVE-2025-13462.patch 
python3.11-3.11.2/debian/patches/CVE-2025-13462.patch
--- python3.11-3.11.2/debian/patches/CVE-2025-13462.patch       1970-01-01 
08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/CVE-2025-13462.patch       2026-05-12 
12:03:12.000000000 +0700
@@ -0,0 +1,140 @@
+From 9a23b753552afa28e3a2f4d8863572fc66479406 Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <[email protected]>
+Date: Thu, 30 Apr 2026 23:18:47 +0200
+Subject: [PATCH] [3.11] gh-141707: Skip TarInfo DIRTYPE normalization during
+ GNU long name handling (#145815)
+
+gh-141707: Skip TarInfo DIRTYPE normalization during GNU long name handling
+(cherry picked from commit 42d754e34c06e57ad6b8e7f92f32af679912d8ab)
+
+Co-authored-by: Seth Michael Larson <[email protected]>
+Co-authored-by: Eashwar Ranganathan <[email protected]>
+Origin: upstream, 
https://github.com/python/cpython/commit/9a23b753552afa28e3a2f4d8863572fc66479406
+---
+ Lib/tarfile.py                                | 29 ++++++++++++++++---
+ Lib/test/test_tarfile.py                      | 19 ++++++++++++
+ Misc/ACKS                                     |  1 +
+ ...-11-18-06-35-53.gh-issue-141707.DBmQIy.rst |  2 ++
+ 4 files changed, 47 insertions(+), 4 deletions(-)
+ create mode 100644 
Misc/NEWS.d/next/Library/2025-11-18-06-35-53.gh-issue-141707.DBmQIy.rst
+
+diff --git a/Lib/tarfile.py b/Lib/tarfile.py
+index c04c576ea22d2d..e2d9f9e6c61b31 100755
+--- a/Lib/tarfile.py
++++ b/Lib/tarfile.py
+@@ -1058,6 +1058,20 @@ def _create_pax_generic_header(cls, pax_headers, type, 
encoding):
+     @classmethod
+     def frombuf(cls, buf, encoding, errors):
+         """Construct a TarInfo object from a 512 byte bytes object.
++
++        To support the old v7 tar format AREGTYPE headers are
++        transformed to DIRTYPE headers if their name ends in '/'.
++        """
++        return cls._frombuf(buf, encoding, errors)
++
++    @classmethod
++    def _frombuf(cls, buf, encoding, errors, *, dircheck=True):
++        """Construct a TarInfo object from a 512 byte bytes object.
++
++        If ``dircheck`` is set to ``True`` then ``AREGTYPE`` headers will
++        be normalized to ``DIRTYPE`` if the name ends in a trailing slash.
++        ``dircheck`` must be set to ``False`` if this function is called
++        on a follow-up header such as ``GNUTYPE_LONGNAME``.
+         """
+         if len(buf) == 0:
+             raise EmptyHeaderError("empty header")
+@@ -1088,7 +1102,7 @@ def frombuf(cls, buf, encoding, errors):
+ 
+         # Old V7 tar format represents a directory as a regular
+         # file with a trailing slash.
+-        if obj.type == AREGTYPE and obj.name.endswith("/"):
++        if dircheck and obj.type == AREGTYPE and obj.name.endswith("/"):
+             obj.type = DIRTYPE
+ 
+         # The old GNU sparse format occupies some of the unused
+@@ -1123,8 +1137,15 @@ def fromtarfile(cls, tarfile):
+         """Return the next TarInfo object from TarFile object
+            tarfile.
+         """
++        return cls._fromtarfile(tarfile)
++
++    @classmethod
++    def _fromtarfile(cls, tarfile, *, dircheck=True):
++        """
++        See dircheck documentation in _frombuf().
++        """
+         buf = tarfile.fileobj.read(BLOCKSIZE)
+-        obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
++        obj = cls._frombuf(buf, tarfile.encoding, tarfile.errors, 
dircheck=dircheck)
+         obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
+         return obj._proc_member(tarfile)
+ 
+@@ -1182,7 +1203,7 @@ def _proc_gnulong(self, tarfile):
+ 
+         # Fetch the next header and process it.
+         try:
+-            next = self.fromtarfile(tarfile)
++            next = self._fromtarfile(tarfile, dircheck=False)
+         except HeaderError as e:
+             raise SubsequentHeaderError(str(e)) from None
+ 
+@@ -1317,7 +1338,7 @@ def _proc_pax(self, tarfile):
+ 
+         # Fetch the next header.
+         try:
+-            next = self.fromtarfile(tarfile)
++            next = self._fromtarfile(tarfile, dircheck=False)
+         except HeaderError as e:
+             raise SubsequentHeaderError(str(e)) from None
+ 
+diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
+index 366aac781df1e7..11066c005629c2 100644
+--- a/Lib/test/test_tarfile.py
++++ b/Lib/test/test_tarfile.py
+@@ -1054,6 +1054,25 @@ def test_longname_directory(self):
+                 self.assertIsNotNone(tar.getmember(longdir))
+                 self.assertIsNotNone(tar.getmember(longdir.removesuffix('/')))
+ 
++    def test_longname_file_not_directory(self):
++        # Test reading a longname file and ensure it is not handled as a 
directory
++        # Issue #141707
++        buf = io.BytesIO()
++        with tarfile.open(mode='w', fileobj=buf, format=self.format) as tar:
++            ti = tarfile.TarInfo()
++            ti.type = tarfile.AREGTYPE
++            ti.name = ('a' * 99) + '/' + ('b' * 3)
++            tar.addfile(ti)
++
++            expected = {t.name: t.type for t in tar.getmembers()}
++
++        buf.seek(0)
++        with tarfile.open(mode='r', fileobj=buf) as tar:
++            actual = {t.name: t.type for t in tar.getmembers()}
++
++        self.assertEqual(expected, actual)
++
++
+ class GNUReadTest(LongnameTest, ReadTest, unittest.TestCase):
+ 
+     subdir = "gnu"
+diff --git a/Misc/ACKS b/Misc/ACKS
+index 89474408a6bbd4..1c0f5d7f782fd3 100644
+--- a/Misc/ACKS
++++ b/Misc/ACKS
+@@ -1448,6 +1448,7 @@ Dhushyanth Ramasamy
+ Ashwin Ramaswami
+ Jeff Ramnani
+ Bayard Randel
++Eashwar Ranganathan
+ Varpu Rantala
+ Brodie Rao
+ Rémi Rampin
+diff --git 
a/Misc/NEWS.d/next/Library/2025-11-18-06-35-53.gh-issue-141707.DBmQIy.rst 
b/Misc/NEWS.d/next/Library/2025-11-18-06-35-53.gh-issue-141707.DBmQIy.rst
+new file mode 100644
+index 00000000000000..1f5b8ed90b8a90
+--- /dev/null
++++ b/Misc/NEWS.d/next/Library/2025-11-18-06-35-53.gh-issue-141707.DBmQIy.rst
+@@ -0,0 +1,2 @@
++Don't change :class:`tarfile.TarInfo` type from ``AREGTYPE`` to ``DIRTYPE`` 
when parsing
++GNU long name or link headers.
diff -Nru python3.11-3.11.2/debian/patches/CVE-2026-2297.patch 
python3.11-3.11.2/debian/patches/CVE-2026-2297.patch
--- python3.11-3.11.2/debian/patches/CVE-2026-2297.patch        1970-01-01 
08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/CVE-2026-2297.patch        2026-05-12 
12:03:12.000000000 +0700
@@ -0,0 +1,45 @@
+From 69ddd9bb2cc4bd69b1565647c18659c6a789ccd9 Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <[email protected]>
+Date: Thu, 30 Apr 2026 23:18:42 +0200
+Subject: [PATCH] [3.11] gh-145506: Fixes CVE-2026-2297 by ensuring
+ SourcelessFileLoader uses io.open_code (GH-145507) (#145515)
+
+* gh-145506: Fixes CVE-2026-2297 by ensuring SourcelessFileLoader uses 
io.open_code (GH-145507)
+(cherry picked from commit a51b1b512de1d56b3714b65628a2eae2b07e535e)
+
+Co-authored-by: Steve Dower <[email protected]>
+
+* Fix docs reference
+
+---------
+
+Co-authored-by: Steve Dower <[email protected]>
+Origin: upstream, 
https://github.com/python/cpython/commit/69ddd9bb2cc4bd69b1565647c18659c6a789ccd9
+---
+ Lib/importlib/_bootstrap_external.py                            | 2 +-
+ .../Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst     | 2 ++
+ 2 files changed, 3 insertions(+), 1 deletion(-)
+ create mode 100644 
Misc/NEWS.d/next/Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst
+
+diff --git a/Lib/importlib/_bootstrap_external.py 
b/Lib/importlib/_bootstrap_external.py
+index e53f6acf38fc64..588da3c7ad1517 100644
+--- a/Lib/importlib/_bootstrap_external.py
++++ b/Lib/importlib/_bootstrap_external.py
+@@ -1126,7 +1126,7 @@ def get_filename(self, fullname):
+ 
+     def get_data(self, path):
+         """Return the data from path as raw bytes."""
+-        if isinstance(self, (SourceLoader, ExtensionFileLoader)):
++        if isinstance(self, (SourceLoader, SourcelessFileLoader, 
ExtensionFileLoader)):
+             with _io.open_code(str(path)) as file:
+                 return file.read()
+         else:
+diff --git 
a/Misc/NEWS.d/next/Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst 
b/Misc/NEWS.d/next/Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst
+new file mode 100644
+index 00000000000000..edeb9e640c2732
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2026-03-04-18-59-17.gh-issue-145506.6hwvEh.rst
+@@ -0,0 +1,2 @@
++Fixes CVE-2026-2297 by ensuring that ``SourcelessFileLoader`` uses
++:func:`io.open_code` when opening ``.pyc`` files.
diff -Nru python3.11-3.11.2/debian/patches/CVE-2026-3644.patch 
python3.11-3.11.2/debian/patches/CVE-2026-3644.patch
--- python3.11-3.11.2/debian/patches/CVE-2026-3644.patch        1970-01-01 
08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/CVE-2026-3644.patch        2026-05-12 
12:03:43.000000000 +0700
@@ -0,0 +1,150 @@
+From 37b75af7264fcffb95135618bef0937dd0ae61b8 Mon Sep 17 00:00:00 2001
+From: Stan Ulbrych <[email protected]>
+Date: Mon, 16 Mar 2026 13:43:43 +0000
+Subject: [PATCH] gh-145599, CVE 2026-3644: Reject control characters in
+ `http.cookies.Morsel.update()` (GH-145600)
+
+Reject control characters in `http.cookies.Morsel.update()` and 
`http.cookies.BaseCookie.js_output`.
+(cherry picked from commit 57e88c1cf95e1481b94ae57abe1010469d47a6b4)
+
+Co-authored-by: Stan Ulbrych 
<[email protected]>
+Co-authored-by: Victor Stinner <[email protected]>
+Co-authored-by: Victor Stinner <[email protected]>
+---
+ Lib/http/cookies.py                           | 24 ++++++++++--
+ Lib/test/test_http_cookies.py                 | 38 +++++++++++++++++++
+ ...-03-06-17-03-38.gh-issue-145599.kchwZV.rst |  4 ++
+ 3 files changed, 62 insertions(+), 4 deletions(-)
+ create mode 100644 
Misc/NEWS.d/next/Security/2026-03-06-17-03-38.gh-issue-145599.kchwZV.rst
+
+diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py
+index 5cfa7a8072c7f7..6b36ffa9f89bb1 100644
+--- a/Lib/http/cookies.py
++++ b/Lib/http/cookies.py
+@@ -335,9 +335,16 @@ def update(self, values):
+             key = key.lower()
+             if key not in self._reserved:
+                 raise CookieError("Invalid attribute %r" % (key,))
++            if _has_control_character(key, val):
++                raise CookieError("Control characters are not allowed in "
++                                  f"cookies {key!r} {val!r}")
+             data[key] = val
+         dict.update(self, data)
+ 
++    def __ior__(self, values):
++        self.update(values)
++        return self
++
+     def isReservedKey(self, K):
+         return K.lower() in self._reserved
+ 
+@@ -363,9 +370,15 @@ def __getstate__(self):
+         }
+ 
+     def __setstate__(self, state):
+-        self._key = state['key']
+-        self._value = state['value']
+-        self._coded_value = state['coded_value']
++        key = state['key']
++        value = state['value']
++        coded_value = state['coded_value']
++        if _has_control_character(key, value, coded_value):
++            raise CookieError("Control characters are not allowed in cookies "
++                              f"{key!r} {value!r} {coded_value!r}")
++        self._key = key
++        self._value = value
++        self._coded_value = coded_value
+ 
+     def output(self, attrs=None, header="Set-Cookie:"):
+         return "%s %s" % (header, self.OutputString(attrs))
+@@ -377,13 +390,16 @@ def __repr__(self):
+ 
+     def js_output(self, attrs=None):
+         # Print javascript
++        output_string = self.OutputString(attrs)
++        if _has_control_character(output_string):
++            raise CookieError("Control characters are not allowed in cookies")
+         return """
+         <script type="text/javascript">
+         <!-- begin hiding
+         document.cookie = \"%s\";
+         // end hiding -->
+         </script>
+-        """ % (self.OutputString(attrs).replace('"', r'\"'))
++        """ % (output_string.replace('"', r'\"'))
+ 
+     def OutputString(self, attrs=None):
+         # Build up our result
+diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py
+index 2438c57ef40458..f9a846f8faa91b 100644
+--- a/Lib/test/test_http_cookies.py
++++ b/Lib/test/test_http_cookies.py
+@@ -527,6 +527,14 @@ def test_control_characters(self):
+             with self.assertRaises(cookies.CookieError):
+                 morsel["path"] = c0
+ 
++            # .__setstate__()
++            with self.assertRaises(cookies.CookieError):
++                morsel.__setstate__({'key': c0, 'value': 'val', 
'coded_value': 'coded'})
++            with self.assertRaises(cookies.CookieError):
++                morsel.__setstate__({'key': 'key', 'value': c0, 
'coded_value': 'coded'})
++            with self.assertRaises(cookies.CookieError):
++                morsel.__setstate__({'key': 'key', 'value': 'val', 
'coded_value': c0})
++
+             # .setdefault()
+             with self.assertRaises(cookies.CookieError):
+                 morsel.setdefault("path", c0)
+@@ -541,6 +549,18 @@ def test_control_characters(self):
+             with self.assertRaises(cookies.CookieError):
+                 morsel.set("path", "val", c0)
+ 
++            # .update()
++            with self.assertRaises(cookies.CookieError):
++                morsel.update({"path": c0})
++            with self.assertRaises(cookies.CookieError):
++                morsel.update({c0: "val"})
++
++            # .__ior__()
++            with self.assertRaises(cookies.CookieError):
++                morsel |= {"path": c0}
++            with self.assertRaises(cookies.CookieError):
++                morsel |= {c0: "val"}
++
+     def test_control_characters_output(self):
+         # Tests that even if the internals of Morsel are modified
+         # that a call to .output() has control character safeguards.
+@@ -561,6 +581,24 @@ def test_control_characters_output(self):
+             with self.assertRaises(cookies.CookieError):
+                 cookie.output()
+ 
++        # Tests that .js_output() also has control character safeguards.
++        for c0 in support.control_characters_c0():
++            morsel = cookies.Morsel()
++            morsel.set("key", "value", "coded-value")
++            morsel._key = c0  # Override private variable.
++            cookie = cookies.SimpleCookie()
++            cookie["cookie"] = morsel
++            with self.assertRaises(cookies.CookieError):
++                cookie.js_output()
++
++            morsel = cookies.Morsel()
++            morsel.set("key", "value", "coded-value")
++            morsel._coded_value = c0  # Override private variable.
++            cookie = cookies.SimpleCookie()
++            cookie["cookie"] = morsel
++            with self.assertRaises(cookies.CookieError):
++                cookie.js_output()
++
+ 
+ def load_tests(loader, tests, pattern):
+     tests.addTest(doctest.DocTestSuite(cookies))
+diff --git 
a/Misc/NEWS.d/next/Security/2026-03-06-17-03-38.gh-issue-145599.kchwZV.rst 
b/Misc/NEWS.d/next/Security/2026-03-06-17-03-38.gh-issue-145599.kchwZV.rst
+new file mode 100644
+index 00000000000000..e53a932d12fcdc
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2026-03-06-17-03-38.gh-issue-145599.kchwZV.rst
+@@ -0,0 +1,4 @@
++Reject control characters in :class:`http.cookies.Morsel`
++:meth:`~http.cookies.Morsel.update` and
++:meth:`~http.cookies.BaseCookie.js_output`.
++This addresses :cve:`2026-3644`.
diff -Nru python3.11-3.11.2/debian/patches/CVE-2026-4224.patch 
python3.11-3.11.2/debian/patches/CVE-2026-4224.patch
--- python3.11-3.11.2/debian/patches/CVE-2026-4224.patch        1970-01-01 
08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/CVE-2026-4224.patch        2026-05-12 
12:03:12.000000000 +0700
@@ -0,0 +1,121 @@
+From 642865ddf4b232da1f3b1f7abcfa3254c4bfe785 Mon Sep 17 00:00:00 2001
+From: Stan Ulbrych <[email protected]>
+Date: Wed, 8 Apr 2026 11:27:39 +0100
+Subject: [PATCH] [3.11] gh-145986: Avoid unbound C recursion in
+ `conv_content_model` in `pyexpat.c` (CVE 2026-4224) (GH-145987) (#146000)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+* [3.11] gh-145986: Avoid unbound C recursion in `conv_content_model` in 
`pyexpat.c` (CVE 2026-4224) (GH-145987)
+
+Fix C stack overflow (CVE-2026-4224) when an Expat parser
+with a registered `ElementDeclHandler` parses inline DTD
+containing deeply nested content model.
+
+---------
+(cherry picked from commit eb0e8be3a7e11b87d198a2c3af1ed0eccf532768)
+(cherry picked from commit e5caf45faac)
+
+Co-authored-by: Stan Ulbrych 
<[email protected]>
+Co-authored-by: Bénédikt Tran <[email protected]>
+
+* Update 
Misc/NEWS.d/next/Security/2026-03-14-17-31-39.gh-issue-145986.ifSSr8.rst
+
+---------
+
+Co-authored-by: Bénédikt Tran <[email protected]>
+Origin: backport, 
https://github.com/python/cpython/commit/642865ddf4b232da1f3b1f7abcfa3254c4bfe785
+---
+ Lib/test/test_pyexpat.py                       | 18 ++++++++++++++++++
+ ...6-03-14-17-31-39.gh-issue-145986.ifSSr8.rst |  4 ++++
+ Modules/pyexpat.c                              |  9 ++++++++-
+ 3 files changed, 30 insertions(+), 1 deletion(-)
+ create mode 100644 
Misc/NEWS.d/next/Security/2026-03-14-17-31-39.gh-issue-145986.ifSSr8.rst
+
+diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py
+index 9aa2fcedadf637..8afce3ffe134f5 100644
+--- a/Lib/test/test_pyexpat.py
++++ b/Lib/test/test_pyexpat.py
+@@ -8,6 +8,7 @@ import sys
+ import sysconfig
+ import unittest
+ import traceback
++from test import support
+ 
+ from xml.parsers import expat
+ from xml.parsers.expat import errors
+@@ -647,6 +648,24 @@ def test_change_size_2(self):
+         parser.Parse(xml2, True)
+         self.assertEqual(self.n, 4)
+ 
++class ElementDeclHandlerTest(unittest.TestCase):
++    def test_deeply_nested_content_model(self):
++        # This should raise a RecursionError and not crash.
++        # See https://github.com/python/cpython/issues/145986.
++        N = 500_000
++        data = (
++            b'<!DOCTYPE root [\n<!ELEMENT root '
++            + b'(a, ' * N + b'a' + b')' * N
++            + b'>\n]>\n<root/>\n'
++        )
++
++        parser = expat.ParserCreate()
++        parser.ElementDeclHandler = lambda _1, _2: None
++        with support.infinite_recursion():
++            with self.assertRaises(RecursionError):
++                parser.Parse(data)
++
++
+ class MalformedInputTest(unittest.TestCase):
+     def test1(self):
+         xml = b"\0\r\n"
+diff --git 
a/Misc/NEWS.d/next/Security/2026-03-14-17-31-39.gh-issue-145986.ifSSr8.rst 
b/Misc/NEWS.d/next/Security/2026-03-14-17-31-39.gh-issue-145986.ifSSr8.rst
+new file mode 100644
+index 00000000000000..cb9dbadb72d976
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2026-03-14-17-31-39.gh-issue-145986.ifSSr8.rst
+@@ -0,0 +1,4 @@
++:mod:`xml.parsers.expat`: Fixed a crash caused by unbounded C recursion when
++converting deeply nested XML content models with
++:meth:`~xml.parsers.expat.xmlparser.ElementDeclHandler`.
++This addresses `CVE-2026-4224 
<https://www.cve.org/CVERecord?id=CVE-2026-4224>`_.
+diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c
+index 7b76ddfabd9476..13c4e8e0adcb5c 100644
+--- a/Modules/pyexpat.c
++++ b/Modules/pyexpat.c
+@@ -1,4 +1,5 @@
+ #include "Python.h"
++#include "pycore_ceval.h"           // _Py_EnterRecursiveCall()
+ #include <ctype.h>
+ 
+ #include "structmember.h"         // PyMemberDef
+@@ -517,6 +518,10 @@ static PyObject *
+ conv_content_model(XML_Content * const model,
+                    PyObject *(*conv_string)(const XML_Char *))
+ {
++    if (_Py_EnterRecursiveCall(" in conv_content_model")) {
++        return NULL;
++    }
++
+     PyObject *result = NULL;
+     PyObject *children = PyTuple_New(model->numchildren);
+     int i;
+@@ -528,7 +533,7 @@ conv_content_model(XML_Content * const model,
+                                                  conv_string);
+             if (child == NULL) {
+                 Py_XDECREF(children);
+-                return NULL;
++                goto done;
+             }
+             PyTuple_SET_ITEM(children, i, child);
+         }
+@@ -536,6 +541,8 @@ conv_content_model(XML_Content * const model,
+                                model->type, model->quant,
+                                conv_string,model->name, children);
+     }
++done:
++    _Py_LeaveRecursiveCall();
+     return result;
+ }
+ 
diff -Nru python3.11-3.11.2/debian/patches/CVE-2026-4519-1.patch 
python3.11-3.11.2/debian/patches/CVE-2026-4519-1.patch
--- python3.11-3.11.2/debian/patches/CVE-2026-4519-1.patch      1970-01-01 
08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/CVE-2026-4519-1.patch      2026-05-12 
12:03:12.000000000 +0700
@@ -0,0 +1,121 @@
+From ceac1efc66516ac387eef2c9a0ce671895b44f03 Mon Sep 17 00:00:00 2001
+From: tomcruiseqi <[email protected]>
+Date: Wed, 25 Mar 2026 02:23:28 +0800
+Subject: [PATCH] [3.11] gh-143930: Reject leading dashes in webbrowser URLs
+ (GH-143931) (GH-146364)
+
+(cherry picked from commit 82a24a4442312bdcfc4c799885e8b3e00990f02b)
+
+Co-authored-by: Seth Michael Larson <[email protected]>
+Origin: backport, 
https://github.com/python/cpython/commit/ceac1efc66516ac387eef2c9a0ce671895b44f03
+---
+ Lib/test/test_webbrowser.py                        |  5 +++++
+ Lib/webbrowser.py                                  | 14 ++++++++++++++
+ .../2026-01-16-12-04-49.gh-issue-143930.zYC5x3.rst |  1 +
+ 3 files changed, 20 insertions(+)
+ create mode 100644 
Misc/NEWS.d/next/Security/2026-01-16-12-04-49.gh-issue-143930.zYC5x3.rst
+
+diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
+index 9d608d63a01ed3..0ac985f56c840e 100644
+--- a/Lib/test/test_webbrowser.py
++++ b/Lib/test/test_webbrowser.py
+@@ -59,6 +59,11 @@ def test_open(self):
+                    options=[],
+                    arguments=[URL])
+ 
++    def test_reject_dash_prefixes(self):
++        browser = self.browser_class(name=CMD_NAME)
++        with self.assertRaises(ValueError):
++            browser.open(f"--key=val {URL}")
++
+ 
+ class BackgroundBrowserCommandTest(CommandTestMixin, unittest.TestCase):
+ 
+diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
+index 5d72524c087677..0fd0aeb3c1b6ef 100755
+--- a/Lib/webbrowser.py
++++ b/Lib/webbrowser.py
+@@ -155,6 +155,12 @@ def open_new(self, url):
+     def open_new_tab(self, url):
+         return self.open(url, 2)
+ 
++    @staticmethod
++    def _check_url(url):
++        """Ensures that the URL is safe to pass to subprocesses as a 
parameter"""
++        if url and url.lstrip().startswith("-"):
++            raise ValueError(f"Invalid URL: {url}")
++
+ 
+ class GenericBrowser(BaseBrowser):
+     """Class for all browsers started with a command
+@@ -172,6 +178,7 @@ def __init__(self, name):
+ 
+     def open(self, url, new=0, autoraise=True):
+         sys.audit("webbrowser.open", url)
++        self._check_url(url)
+         cmdline = [self.name] + [arg.replace("%s", url)
+                                  for arg in self.args]
+         try:
+@@ -192,6 +199,7 @@ def open(self, url, new=0, autoraise=True):
+         cmdline = [self.name] + [arg.replace("%s", url)
+                                  for arg in self.args]
+         sys.audit("webbrowser.open", url)
++        self._check_url(url)
+         try:
+             if sys.platform[:3] == 'win':
+                 p = subprocess.Popen(cmdline)
+@@ -257,6 +265,7 @@ def _invoke(self, args, remote, autoraise, url=None):
+ 
+     def open(self, url, new=0, autoraise=True):
+         sys.audit("webbrowser.open", url)
++        self._check_url(url)
+         if new == 0:
+             action = self.remote_action
+         elif new == 1:
+@@ -358,6 +367,7 @@ class Konqueror(BaseBrowser):
+ 
+     def open(self, url, new=0, autoraise=True):
+         sys.audit("webbrowser.open", url)
++        self._check_url(url)
+         # XXX Currently I know no way to prevent KFM from opening a new win.
+         if new == 2:
+             action = "newTab"
+@@ -442,6 +452,7 @@ def _remote(self, action):
+ 
+     def open(self, url, new=0, autoraise=True):
+         sys.audit("webbrowser.open", url)
++        self._check_url(url)
+         if new:
+             ok = self._remote("LOADNEW " + url)
+         else:
+@@ -605,6 +616,7 @@ def register_standard_browsers():
+     class WindowsDefault(BaseBrowser):
+         def open(self, url, new=0, autoraise=True):
+             sys.audit("webbrowser.open", url)
++            self._check_url(url)
+             try:
+                 os.startfile(url)
+             except OSError:
+@@ -637,6 +649,7 @@ def __init__(self, name):
+ 
+         def open(self, url, new=0, autoraise=True):
+             sys.audit("webbrowser.open", url)
++            self._check_url(url)
+             assert "'" not in url
+             # hack for local urls
+             if not ':' in url:
+@@ -688,6 +701,7 @@ def _name(self, val):
+             self.name = val
+ 
+         def open(self, url, new=0, autoraise=True):
++            self._check_url(url)
+             if self.name == 'default':
+                 script = 'open location "%s"' % url.replace('"', '%22') # 
opens in default browser
+             else:
+diff --git 
a/Misc/NEWS.d/next/Security/2026-01-16-12-04-49.gh-issue-143930.zYC5x3.rst 
b/Misc/NEWS.d/next/Security/2026-01-16-12-04-49.gh-issue-143930.zYC5x3.rst
+new file mode 100644
+index 00000000000000..0f27eae99a0dfd
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2026-01-16-12-04-49.gh-issue-143930.zYC5x3.rst
+@@ -0,0 +1 @@
++Reject leading dashes in URLs passed to :func:`webbrowser.open`
diff -Nru python3.11-3.11.2/debian/patches/CVE-2026-4519-2.patch 
python3.11-3.11.2/debian/patches/CVE-2026-4519-2.patch
--- python3.11-3.11.2/debian/patches/CVE-2026-4519-2.patch      1970-01-01 
08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/CVE-2026-4519-2.patch      2026-05-12 
12:03:12.000000000 +0700
@@ -0,0 +1,154 @@
+From 96fc5048605863c7b6fd6289643feb0e97edd96c Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <[email protected]>
+Date: Sat, 4 Apr 2026 00:53:49 +0200
+Subject: [PATCH] [3.11] gh-143930: Tweak the exception message and increase
+ test coverage (GH-146476) (GH-148045) (GH-148051) (GH-148052)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+(cherry picked from commit cc023511238ad93ecc8796157c6f9139a2bb2932)
+(cherry picked from commit 89bfb8e5ed3c7caa241028f1a4eac5f6275a46a4)
+(cherry picked from commit 3681d47a440865aead912a054d4599087b4270dd)
+
+Co-authored-by: Łukasz Langa <[email protected]>
+Origin: upstream, 
https://github.com/python/cpython/commit/96fc5048605863c7b6fd6289643feb0e97edd96c
+---
+ Lib/test/test_webbrowser.py                   | 81 +++++++++++++++++--
+ Lib/webbrowser.py                             |  2 +-
+ ...-01-16-12-04-49.gh-issue-143930.zYC5x3.rst |  2 +-
+ 3 files changed, 77 insertions(+), 8 deletions(-)
+
+diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
+index 0ac985f56c840e..522219bc8c55b3 100644
+--- a/Lib/test/test_webbrowser.py
++++ b/Lib/test/test_webbrowser.py
+@@ -1,6 +1,7 @@
++import io
++import os
+ import webbrowser
+ import unittest
+-import os
+ import sys
+ import subprocess
+ from unittest import mock
+@@ -49,6 +50,14 @@ def _test(self, meth, *, args=[URL], kw={}, options, 
arguments):
+             popen_args.pop(popen_args.index(option))
+         self.assertEqual(popen_args, arguments)
+ 
++    def test_reject_dash_prefixes(self):
++        browser = self.browser_class(name=CMD_NAME)
++        with self.assertRaisesRegex(
++            ValueError,
++            r"^Invalid URL \(leading dash disallowed\): '--key=val http.*'$"
++        ):
++            browser.open(f"--key=val {URL}")
++
+ 
+ class GenericBrowserCommandTest(CommandTestMixin, unittest.TestCase):
+ 
+@@ -59,11 +68,6 @@ def test_open(self):
+                    options=[],
+                    arguments=[URL])
+ 
+-    def test_reject_dash_prefixes(self):
+-        browser = self.browser_class(name=CMD_NAME)
+-        with self.assertRaises(ValueError):
+-            browser.open(f"--key=val {URL}")
+-
+ 
+ class BackgroundBrowserCommandTest(CommandTestMixin, unittest.TestCase):
+ 
+@@ -224,6 +228,71 @@ def test_open_new_tab(self):
+                    arguments=['openURL({},new-tab)'.format(URL)])
+ 
+ 
++class MockPopenPipe:
++    def __init__(self, cmd, mode):
++        self.cmd = cmd
++        self.mode = mode
++        self.pipe = io.StringIO()
++        self._closed = False
++
++    def write(self, buf):
++        self.pipe.write(buf)
++
++    def close(self):
++        self._closed = True
++        return None
++
++
[email protected](sys.platform == "darwin", "macOS specific test")
++class MacOSXOSAScriptTest(unittest.TestCase):
++    def setUp(self):
++        # Ensure that 'BROWSER' is not set to 'open' or something else.
++        # See: https://github.com/python/cpython/issues/131254.
++        env = self.enterContext(os_helper.EnvironmentVarGuard())
++        env.unset("BROWSER")
++
++        support.patch(self, os, "popen", self.mock_popen)
++        self.browser = webbrowser.MacOSXOSAScript("default")
++
++    def mock_popen(self, cmd, mode):
++        self.popen_pipe = MockPopenPipe(cmd, mode)
++        return self.popen_pipe
++
++    def test_default(self):
++        browser = webbrowser.get()
++        assert isinstance(browser, webbrowser.MacOSXOSAScript)
++        self.assertEqual(browser.name, "default")
++
++    def test_default_open(self):
++        url = "https://python.org";
++        self.browser.open(url)
++        self.assertTrue(self.popen_pipe._closed)
++        self.assertEqual(self.popen_pipe.cmd, "osascript")
++        script = self.popen_pipe.pipe.getvalue()
++        self.assertEqual(script.strip(), f'open location "{url}"')
++
++    def test_url_quote(self):
++        self.browser.open('https://python.org/"quote";')
++        script = self.popen_pipe.pipe.getvalue()
++        self.assertEqual(
++            script.strip(), 'open location "https://python.org/%22quote%22";'
++        )
++
++    def test_explicit_browser(self):
++        browser = webbrowser.MacOSXOSAScript("safari")
++        browser.open("https://python.org";)
++        script = self.popen_pipe.pipe.getvalue()
++        self.assertIn('tell application "safari"', script)
++        self.assertIn('open location "https://python.org";', script)
++
++    def test_reject_dash_prefixes(self):
++        with self.assertRaisesRegex(
++            ValueError,
++            r"^Invalid URL \(leading dash disallowed\): '--key=val http.*'$"
++        ):
++            self.browser.open(f"--key=val {URL}")
++
++
+ class BrowserRegistrationTest(unittest.TestCase):
+ 
+     def setUp(self):
+diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
+index 0fd0aeb3c1b6ef..52ad205bd1b881 100755
+--- a/Lib/webbrowser.py
++++ b/Lib/webbrowser.py
+@@ -159,7 +159,7 @@ def open_new_tab(self, url):
+     def _check_url(url):
+         """Ensures that the URL is safe to pass to subprocesses as a 
parameter"""
+         if url and url.lstrip().startswith("-"):
+-            raise ValueError(f"Invalid URL: {url}")
++            raise ValueError(f"Invalid URL (leading dash disallowed): 
{url!r}")
+ 
+ 
+ class GenericBrowser(BaseBrowser):
+diff --git 
a/Misc/NEWS.d/next/Security/2026-01-16-12-04-49.gh-issue-143930.zYC5x3.rst 
b/Misc/NEWS.d/next/Security/2026-01-16-12-04-49.gh-issue-143930.zYC5x3.rst
+index 0f27eae99a0dfd..c561023c3c2d7a 100644
+--- a/Misc/NEWS.d/next/Security/2026-01-16-12-04-49.gh-issue-143930.zYC5x3.rst
++++ b/Misc/NEWS.d/next/Security/2026-01-16-12-04-49.gh-issue-143930.zYC5x3.rst
+@@ -1 +1 @@
+-Reject leading dashes in URLs passed to :func:`webbrowser.open`
++Reject leading dashes in URLs passed to :func:`webbrowser.open`.
diff -Nru python3.11-3.11.2/debian/patches/CVE-2026-4519-3.patch 
python3.11-3.11.2/debian/patches/CVE-2026-4519-3.patch
--- python3.11-3.11.2/debian/patches/CVE-2026-4519-3.patch      1970-01-01 
08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/CVE-2026-4519-3.patch      2026-05-12 
12:03:12.000000000 +0700
@@ -0,0 +1,66 @@
+From f4654824ae0850ac87227fb270f9057477946769 Mon Sep 17 00:00:00 2001
+From: Stan Ulbrych <[email protected]>
+Date: Mon, 13 Apr 2026 22:41:51 +0100
+Subject: [PATCH] [3.11] gh-148169: Fix webbrowser `%action` substitution
+ bypass of dash-prefix check (GH-148170) (#148520)
+
+(cherry picked from commit d22922c8a7958353689dc4763dd72da2dea03fff)
+
+Origin: upstream, 
https://github.com/python/cpython/commit/f4654824ae0850ac87227fb270f9057477946769
+---
+ Lib/test/test_webbrowser.py                               | 8 ++++++++
+ Lib/webbrowser.py                                         | 5 +++--
+ .../2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst        | 2 ++
+ 3 files changed, 13 insertions(+), 2 deletions(-)
+ create mode 100644 
Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst
+
+diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py
+index 74eca81c707ead..f10f54e4d4ec32 100644
+--- a/Lib/test/test_webbrowser.py
++++ b/Lib/test/test_webbrowser.py
+@@ -103,6 +103,14 @@ def test_open_new_tab(self):
+                    options=[],
+                    arguments=[URL])
+ 
++    def test_reject_action_dash_prefixes(self):
++        browser = self.browser_class(name=CMD_NAME)
++        with self.assertRaises(ValueError):
++            browser.open('%action--incognito')
++        # new=1: action is "--new-window", so "%action" itself expands to
++        # a dash-prefixed flag even with no dash in the original URL.
++        with self.assertRaises(ValueError):
++            browser.open('%action', new=1)
+ 
+ class MozillaCommandTest(CommandTestMixin, unittest.TestCase):
+ 
+diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
+index ce6ec9a27d7d8b..9819a4ecf6d43e 100755
+--- a/Lib/webbrowser.py
++++ b/Lib/webbrowser.py
+@@ -265,7 +265,6 @@ def _invoke(self, args, remote, autoraise, url=None):
+ 
+     def open(self, url, new=0, autoraise=True):
+         sys.audit("webbrowser.open", url)
+-        self._check_url(url)
+         if new == 0:
+             action = self.remote_action
+         elif new == 1:
+@@ -279,7 +278,9 @@ def open(self, url, new=0, autoraise=True):
+             raise Error("Bad 'new' parameter to open(); " +
+                         "expected 0, 1, or 2, got %s" % new)
+ 
+-        args = [arg.replace("%s", url).replace("%action", action)
++        self._check_url(url.replace("%action", action))
++
++        args = [arg.replace("%action", action).replace("%s", url)
+                 for arg in self.remote_args]
+         args = [arg for arg in args if arg]
+         success = self._invoke(args, True, autoraise, url)
+diff --git 
a/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst 
b/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst
+new file mode 100644
+index 00000000000000..45cdeebe1b6d64
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst
+@@ -0,0 +1,2 @@
++A bypass in :mod:`webbrowser` allowed URLs prefixed with ``%action`` to pass
++the dash-prefix safety check.
diff -Nru python3.11-3.11.2/debian/patches/CVE-2026-6019.patch 
python3.11-3.11.2/debian/patches/CVE-2026-6019.patch
--- python3.11-3.11.2/debian/patches/CVE-2026-6019.patch        1970-01-01 
08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/CVE-2026-6019.patch        2026-05-12 
12:03:43.000000000 +0700
@@ -0,0 +1,133 @@
+From 3c59b8b53fc75c7f9578d16fb8201ceb43e8f76c Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <[email protected]>
+Date: Thu, 23 Apr 2026 15:05:17 +0200
+Subject: [PATCH] [3.13] gh-90309: Base64-encode cookie values embedded in JS
+ (GH-148888)
+
+(cherry picked from commit 76b3923d688c0efc580658476c5f525ec8735104)
+
+Co-authored-by: Seth Larson <[email protected]>
+Origin: upstream, 
https://github.com/python/cpython/commit/3c59b8b53fc75c7f9578d16fb8201ceb43e8f76c
+---
+ Lib/http/cookies.py                           |  8 +++--
+ Lib/test/test_http_cookies.py                 | 29 ++++++++++++-------
+ ...6-04-21-13-46-30.gh-issue-90309.srvj9q.rst |  3 ++
+ 3 files changed, 27 insertions(+), 13 deletions(-)
+ create mode 100644 
Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst
+
+diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py
+index 63d119ad46c084..aebc2a163e4487 100644
+--- a/Lib/http/cookies.py
++++ b/Lib/http/cookies.py
+@@ -389,17 +389,21 @@ def __repr__(self):
+         return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
+ 
+     def js_output(self, attrs=None):
++        import base64
+         # Print javascript
+         output_string = self.OutputString(attrs)
+         if _has_control_character(output_string):
+             raise CookieError("Control characters are not allowed in cookies")
++        # Base64-encode value to avoid template
++        # injection in cookie values.
++        output_encoded = 
base64.b64encode(output_string.encode('utf-8')).decode("ascii")
+         return """
+         <script type="text/javascript">
+         <!-- begin hiding
+-        document.cookie = \"%s\";
++        document.cookie = atob(\"%s\");
+         // end hiding -->
+         </script>
+-        """ % (output_string.replace('"', r'\"'))
++        """ % (output_encoded,)
+ 
+     def OutputString(self, attrs=None):
+         # Build up our result
+diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py
+index 7edb026324ebdc..88914123d51303 100644
+--- a/Lib/test/test_http_cookies.py
++++ b/Lib/test/test_http_cookies.py
+@@ -1,5 +1,5 @@
+ # Simple test suite for http/cookies.py
+-
++import base64
+ import copy
+ import unittest
+ import doctest
+@@ -106,17 +106,19 @@ def test_load(self):
+ 
+         self.assertEqual(C.output(['path']),
+             'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
+-        self.assertEqual(C.js_output(), r"""
++        cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; 
Path=/acme; Version=1').decode('ascii')
++        self.assertEqual(C.js_output(), fr"""
+         <script type="text/javascript">
+         <!-- begin hiding
+-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
++        document.cookie = atob("{cookie_encoded}");
+         // end hiding -->
+         </script>
+         """)
+-        self.assertEqual(C.js_output(['path']), r"""
++        cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; 
Path=/acme').decode('ascii')
++        self.assertEqual(C.js_output(['path']), fr"""
+         <script type="text/javascript">
+         <!-- begin hiding
+-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
++        document.cookie = atob("{cookie_encoded}");
+         // end hiding -->
+         </script>
+         """)
+@@ -213,17 +215,19 @@ def test_quoted_meta(self):
+ 
+         self.assertEqual(C.output(['path']),
+                          'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
+-        self.assertEqual(C.js_output(), r"""
++        expected_encoded_cookie = 
base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme; 
Version=1').decode('ascii')
++        self.assertEqual(C.js_output(), fr"""
+         <script type="text/javascript">
+         <!-- begin hiding
+-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
++        document.cookie = atob("{expected_encoded_cookie}");
+         // end hiding -->
+         </script>
+         """)
+-        self.assertEqual(C.js_output(['path']), r"""
++        expected_encoded_cookie = 
base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme').decode('ascii')
++        self.assertEqual(C.js_output(['path']), fr"""
+         <script type="text/javascript">
+         <!-- begin hiding
+-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
++        document.cookie = atob("{expected_encoded_cookie}");
+         // end hiding -->
+         </script>
+         """)
+@@ -314,13 +318,16 @@ def test_setter(self):
+             self.assertEqual(
+                 M.output(),
+                 "Set-Cookie: %s=%s; Path=/foo" % (i, "%s_coded_val" % i))
++            expected_encoded_cookie = base64.b64encode(
++                ("%s=%s; Path=/foo" % (i, "%s_coded_val" % i)).encode("ascii")
++            ).decode('ascii')
+             expected_js_output = """
+         <script type="text/javascript">
+         <!-- begin hiding
+-        document.cookie = "%s=%s; Path=/foo";
++        document.cookie = atob("%s");
+         // end hiding -->
+         </script>
+-        """ % (i, "%s_coded_val" % i)
++        """ % (expected_encoded_cookie,)
+             self.assertEqual(M.js_output(), expected_js_output)
+         for i in ["foo bar", "foo@bar"]:
+             # Try some illegal characters
+diff --git 
a/Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst 
b/Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst
+new file mode 100644
+index 00000000000000..d7d376737e4ad1
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst
+@@ -0,0 +1,3 @@
++Base64-encode values when embedding cookies to JavaScript using the
++:meth:`http.cookies.BaseCookie.js_output` method to avoid injection
++and escaping.
diff -Nru python3.11-3.11.2/debian/patches/CVE-2026-6100.patch 
python3.11-3.11.2/debian/patches/CVE-2026-6100.patch
--- python3.11-3.11.2/debian/patches/CVE-2026-6100.patch        1970-01-01 
08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/CVE-2026-6100.patch        2026-05-12 
12:17:27.000000000 +0700
@@ -0,0 +1,53 @@
+From e20c6c9667c99ecaab96e1a2b3767082841ffc8b Mon Sep 17 00:00:00 2001
+From: Stan Ulbrych <[email protected]>
+Date: Mon, 13 Apr 2026 22:42:36 +0100
+Subject: [PATCH] [3.11] gh-148395: Fix a possible UAF in
+ `{LZMA,BZ2}Decompressor` (GH-148396) (#148504)
+
+Fix dangling input pointer after `MemoryError` in 
_lzma/_bz2/_ZlibDecompressor.decompress
+
+(cherry picked from commit 8fc66aef6d7b3ae58f43f5c66f9366cc8cbbfcd2)
+
+Origin: upstream, 
https://github.com/python/cpython/commit/e20c6c9667c99ecaab96e1a2b3767082841ffc8b
+---
+ .../Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst  | 5 +++++
+ Modules/_bz2module.c                                         | 1 +
+ Modules/_lzmamodule.c                                        | 1 +
+ 3 files changed, 7 insertions(+)
+ create mode 100644 
Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst
+
+diff --git 
a/Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst 
b/Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst
+new file mode 100644
+index 00000000000000..349d1cf3cacdf4
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2026-04-10-16-28-21.gh-issue-148395.kfzm0G.rst
+@@ -0,0 +1,5 @@
++Fix a dangling input pointer in :class:`lzma.LZMADecompressor`,
++and :class:`bz2.BZ2Decompressor`
++when memory allocation fails with :exc:`MemoryError`, which could let a
++subsequent :meth:`!decompress` call read or write through a stale pointer to
++the already-released caller buffer.
+diff --git a/Modules/_bz2module.c b/Modules/_bz2module.c
+index 798e9efc628f05..b08ac5e44e52f4 100644
+--- a/Modules/_bz2module.c
++++ b/Modules/_bz2module.c
+@@ -595,6 +595,7 @@ decompress(BZ2Decompressor *d, char *data, size_t len, 
Py_ssize_t max_length)
+     return result;
+ 
+ error:
++    bzs->next_in = NULL;
+     Py_XDECREF(result);
+     return NULL;
+ }
+diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c
+index 97453a28088131..51106a6a075b30 100644
+--- a/Modules/_lzmamodule.c
++++ b/Modules/_lzmamodule.c
+@@ -1103,6 +1103,7 @@ decompress(Decompressor *d, uint8_t *data, size_t len, 
Py_ssize_t max_length)
+     return result;
+ 
+ error:
++    lzs->next_in = NULL;
+     Py_XDECREF(result);
+     return NULL;
+ }
diff -Nru python3.11-3.11.2/debian/patches/series 
python3.11-3.11.2/debian/patches/series
--- python3.11-3.11.2/debian/patches/series     2026-04-07 17:42:43.000000000 
+0700
+++ python3.11-3.11.2/debian/patches/series     2026-05-12 12:17:27.000000000 
+0700
@@ -72,3 +72,13 @@
 CVE-2025-15282.patch
 CVE-2025-11468.patch
 CVE-2026-1299.patch
+CVE-2026-4224.patch
+CVE-2026-4519-1.patch
+CVE-2026-4519-2.patch
+CVE-2026-4519-3.patch
+CVE-2026-3644.patch
+CVE-2026-6019.patch
+CVE-2026-6100.patch
+CVE-2026-2297.patch
+CVE-2025-13462.patch
+skip-xml-tests-expat-CVE-2023-52425.patch
diff -Nru 
python3.11-3.11.2/debian/patches/skip-xml-tests-expat-CVE-2023-52425.patch 
python3.11-3.11.2/debian/patches/skip-xml-tests-expat-CVE-2023-52425.patch
--- python3.11-3.11.2/debian/patches/skip-xml-tests-expat-CVE-2023-52425.patch  
1970-01-01 08:00:00.000000000 +0800
+++ python3.11-3.11.2/debian/patches/skip-xml-tests-expat-CVE-2023-52425.patch  
2026-05-12 12:17:27.000000000 +0700
@@ -0,0 +1,48 @@
+From: Arnaud Rebillout <[email protected]>
+Date: Mon, 11 May 2026 22:27:54 +0700
+Origin: vendor
+Forwarded: not-needed
+Subject: Skip xml tests that fail with expat patched for CVE-2023-52425
+
+These tests fail due to the fact that we build against a version of libexpat
+that is < 2.6, and patched for CVE-2023-52425. Upstream checks expat version
+and adjust unit tests accordingly, but assumes vanilla expat, and can't support
+patched expat.
+
+Upstream recommendation is to disable those tests downstream, cf.
+* https://github.com/python/cpython/issues/125067#issuecomment-2460866998
+* https://github.com/python/cpython/issues/125067#issuecomment-2464312388
+
+However it's worth reading the whole discussion. It's not 100% clear if the
+failing tests are just noise. Maybe by skipping those tests we hide a real 
issue.
+
+For Debian, at this point I believe we'd better skip the tests and have the
+autopkgtests back in the green: it is the best way to detect regressions going
+forward. The autopkgtests are in the red in ci.debian.net for almost a year
+(since the upload of expat/2.5.0-1+deb12u2), this is not helpful, and we're
+unlikely to catch any regression.
+
+One last reference: an example of downstream patch from SUSE, who patched these
+failing tests away:
+https://build.opensuse.org/projects/openSUSE:Leap:15.6:Update/packages/python311/files/CVE-2023-52425-libexpat-2.6.0-backport.patch?expand=1
+---
+--- a/Lib/test/test_xml_etree.py
++++ b/Lib/test/test_xml_etree.py
+@@ -13,6 +13,7 @@ import itertools
+ import operator
+ import os
+ import pickle
++import pyexpat
+ import sys
+ import textwrap
+ import types
+@@ -1419,6 +1419,9 @@ class XMLPullParserTest(unittest.TestCas
+     def test_simple_xml(self):
+         for chunk_size in (None, 1, 5):
+             with self.subTest(chunk_size=chunk_size):
++                if chunk_size in [1, 5]:
++                    self.skipTest(
++                        f"Fail with patched version of Expat 
{pyexpat.version_info}")
+                 parser = ET.XMLPullParser()
+                 self.assert_event_tags(parser, [])
+                 self._feed(parser, "<!-- comment -->\n", chunk_size)

--- End Message ---
--- Begin Message ---
Version: 12.15

This update was released as part of 12.15.

--- End Message ---

Reply via email to