Version in base suite: 3.11.2-6+deb12u7 Base version: python3.11_3.11.2-6+deb12u7 Target version: python3.11_3.11.2-6+deb12u8 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/python3.11/python3.11_3.11.2-6+deb12u7.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/python3.11/python3.11_3.11.2-6+deb12u8.dsc changelog | 20 ++ patches/CVE-2025-13462.patch | 140 ++++++++++++++++++++ patches/CVE-2026-2297.patch | 45 ++++++ patches/CVE-2026-3644.patch | 150 +++++++++++++++++++++ patches/CVE-2026-4224.patch | 121 +++++++++++++++++ patches/CVE-2026-4519-1.patch | 121 +++++++++++++++++ patches/CVE-2026-4519-2.patch | 154 ++++++++++++++++++++++ patches/CVE-2026-4519-3.patch | 66 +++++++++ patches/CVE-2026-6100.patch | 53 +++++++ patches/series | 9 + patches/skip-xml-tests-expat-CVE-2023-52425.patch | 48 ++++++ 11 files changed, 927 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp91073ejg/python3.11_3.11.2-6+deb12u7.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp91073ejg/python3.11_3.11.2-6+deb12u8.dsc: no acceptable signature found 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 01:58:00.000000000 +0000 +++ python3.11-3.11.2/debian/changelog 2026-05-12 05:17:27.000000000 +0000 @@ -1,3 +1,23 @@ +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-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 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 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/CVE-2025-13462.patch 2026-05-12 05:03:12.000000000 +0000 @@ -0,0 +1,140 @@ +From 9a23b753552afa28e3a2f4d8863572fc66479406 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +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 +Co-authored-by: Eashwar Ranganathan +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 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/CVE-2026-2297.patch 2026-05-12 05:03:12.000000000 +0000 @@ -0,0 +1,45 @@ +From 69ddd9bb2cc4bd69b1565647c18659c6a789ccd9 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +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 + +* Fix docs reference + +--------- + +Co-authored-by: Steve Dower +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 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/CVE-2026-3644.patch 2026-05-12 05:03:43.000000000 +0000 @@ -0,0 +1,150 @@ +From 37b75af7264fcffb95135618bef0937dd0ae61b8 Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> +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 <89152624+StanFromIreland@users.noreply.github.com> +Co-authored-by: Victor Stinner +Co-authored-by: Victor Stinner +--- + 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 """ + +- """ % (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 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/CVE-2026-4224.patch 2026-05-12 05:03:12.000000000 +0000 @@ -0,0 +1,121 @@ +From 642865ddf4b232da1f3b1f7abcfa3254c4bfe785 Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych +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 <89152624+StanFromIreland@users.noreply.github.com> +Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> + +* Update Misc/NEWS.d/next/Security/2026-03-14-17-31-39.gh-issue-145986.ifSSr8.rst + +--------- + +Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> +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'\n]>\n\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 `_. +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 + + #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 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/CVE-2026-4519-1.patch 2026-05-12 05:03:12.000000000 +0000 @@ -0,0 +1,121 @@ +From ceac1efc66516ac387eef2c9a0ce671895b44f03 Mon Sep 17 00:00:00 2001 +From: tomcruiseqi +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 +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 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/CVE-2026-4519-2.patch 2026-05-12 05:03:12.000000000 +0000 @@ -0,0 +1,154 @@ +From 96fc5048605863c7b6fd6289643feb0e97edd96c Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +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 +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 ++ ++ ++@unittest.skipUnless(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 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/CVE-2026-4519-3.patch 2026-05-12 05:03:12.000000000 +0000 @@ -0,0 +1,66 @@ +From f4654824ae0850ac87227fb270f9057477946769 Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych +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-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 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/CVE-2026-6100.patch 2026-05-12 05:17:27.000000000 +0000 @@ -0,0 +1,53 @@ +From e20c6c9667c99ecaab96e1a2b3767082841ffc8b Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych +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 10:42:43.000000000 +0000 +++ python3.11-3.11.2/debian/patches/series 2026-05-12 05:17:27.000000000 +0000 @@ -72,3 +72,12 @@ 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-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 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/skip-xml-tests-expat-CVE-2023-52425.patch 2026-05-12 05:17:27.000000000 +0000 @@ -0,0 +1,48 @@ +From: Arnaud Rebillout +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, "\n", chunk_size)