From c10d237b28d52fe5f17177e035a98a054407ce83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Fri, 5 Sep 2025 13:23:10 +0000 Subject: [PATCH 01/16] Inject SBOM into the installed wheels (when using the bundled ones) --- python3.11.spec | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python3.11.spec b/python3.11.spec index 838231b..0f5f334 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -249,6 +249,9 @@ BuildRequires: /usr/sbin/ifconfig %if %{with rpmwheels} BuildRequires: %{python_wheel_pkg_prefix}-setuptools-wheel BuildRequires: %{python_wheel_pkg_prefix}-pip-wheel +%else +# For %%python_wheel_inject_sbom +BuildRequires: python-rpm-macros %endif %if %{without bootstrap} @@ -1063,6 +1066,11 @@ for file in %{buildroot}%{pylibdir}/pydoc_data/topics.py $(grep --include='*.py' rm ${directory}/{__pycache__/${module}.cpython-%{pyshortver}.opt-?.pyc,${module}.py} done +%if %{without rpmwheels} +# Inject SBOM into the installed wheels (if the macro is available) +%{?python_wheel_inject_sbom:%python_wheel_inject_sbom %{buildroot}%{pylibdir}/ensurepip/_bundled/*.whl} +%endif + # ====================================================== # Checks for packaging issues # ====================================================== From 2bbb4fe674258219e08a50ad964572eb550a3fb1 Mon Sep 17 00:00:00 2001 From: Karolina Surma Date: Tue, 6 Jan 2026 17:48:59 +0100 Subject: [PATCH 02/16] Require at least the same expat version as used during the build The versioned requirement is no longer valid - this happens again now with expat 2.7.2 introducing new symbols. Make the versioned requirement future-proof - the generate version will always match at least the one present in the buildroot during the Python build. --- python3.11.spec | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/python3.11.spec b/python3.11.spec index 0f5f334..bb078d6 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 1%{?dist} +Release: 2%{?dist} License: Python-2.0.1 @@ -192,8 +192,7 @@ BuildRequires: bluez-libs-devel BuildRequires: bzip2 BuildRequires: bzip2-devel BuildRequires: desktop-file-utils -# See the runtime requirement in the -libs subpackage -BuildRequires: expat-devel >= 2.6 +BuildRequires: expat-devel BuildRequires: findutils BuildRequires: gcc-c++ @@ -500,12 +499,20 @@ Recommends: (%{pkgname}-tkinter%{?_isa} = %{version}-%{release} if tk%{?_isa}) Requires: tzdata # The requirement on libexpat is generated, but we need to version it. -# When built with expat >= 2.6, but installed with older expat, we get: +# When built with a specific expat version, but installed with an older one, +# we sometimes get: # ImportError: /usr/lib64/python3.X/lib-dynload/pyexpat.cpython-....so: -# undefined symbol: XML_SetReparseDeferralEnabled +# undefined symbol: XML_... +# The pyexpat module has build-time checks for expat version to only use the +# available symbols. However, there is no runtime protection, so when the module +# is later installed with an older expat, it may error due to undefined symbols. # This breaks many things, including python -m venv. +# We avoid this problem by requiring at least the same version of expat that +# was used during the build time. # Other subpackages (like -debug) also need this, but they all depend on -libs. -Requires: expat >= 2.6 +%global expat_version %(LANG=C rpm -q --qf '%%{version}' expat.%{_target_cpu} | sed 's/.*not installed/0/') +Requires: expat >= %{expat_version} + %description -n %{pkgname}-libs This package contains runtime libraries for use by Python: @@ -1669,6 +1676,9 @@ CheckPython optimized # ====================================================== %changelog +* Tue Jan 06 2026 Karolina Surma - 3.11.14-2 +- Require at least the same expat version as used during the build time + * Fri Oct 10 2025 Karolina Surma - 3.11.14-1 - Update to 3.11.14 From 6bb200e1be0403b0555c483bff58eec6d3531216 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Thu, 8 Jan 2026 07:47:43 +0100 Subject: [PATCH 03/16] Security fix for CVE-2025-12084 --- 00471-cve-2025-12084.patch | 140 +++++++++++++++++++++++++++++++++++++ python3.11.spec | 12 +++- 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 00471-cve-2025-12084.patch diff --git a/00471-cve-2025-12084.patch b/00471-cve-2025-12084.patch new file mode 100644 index 0000000..0796167 --- /dev/null +++ b/00471-cve-2025-12084.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Mon, 22 Dec 2025 14:48:49 +0100 +Subject: 00471: CVE-2025-12084 + +* gh-142145: Remove quadratic behavior in node ID cache clearing (GH-142146) +* gh-142754: Ensure that Element & Attr instances have the ownerDocument attribute (GH-142794) +(cherry picked from commit 1cc7551b3f9f71efbc88d96dce90f82de98b2454) +(cherry picked from commit 08d8e18ad81cd45bc4a27d6da478b51ea49486e4) +(cherry picked from commit 8d2d7bb2e754f8649a68ce4116271a4932f76907) + +Co-authored-by: Jacob Walls <38668450+jacobtylerwalls@users.noreply.github.com> +Co-authored-by: Seth Michael Larson +Co-authored-by: Petr Viktorin +Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> +Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> +Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> +Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> +Co-authored-by: Gregory P. Smith +--- + Lib/test/test_minidom.py | 33 ++++++++++++++++++- + Lib/xml/dom/minidom.py | 11 ++----- + ...-12-01-09-36-45.gh-issue-142145.tcAUhg.rst | 6 ++++ + 3 files changed, 41 insertions(+), 9 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst + +diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py +index ef38c36210..c68bd990f7 100644 +--- a/Lib/test/test_minidom.py ++++ b/Lib/test/test_minidom.py +@@ -2,6 +2,7 @@ + + import copy + import pickle ++import time + import io + from test import support + import unittest +@@ -9,7 +10,7 @@ + import pyexpat + import xml.dom.minidom + +-from xml.dom.minidom import parse, Attr, Node, Document, parseString ++from xml.dom.minidom import parse, Attr, Node, Document, Element, parseString + from xml.dom.minidom import getDOMImplementation + from xml.parsers.expat import ExpatError + +@@ -177,6 +178,36 @@ def testAppendChild(self): + self.confirm(dom.documentElement.childNodes[-1].data == "Hello") + dom.unlink() + ++ @support.requires_resource('cpu') ++ def testAppendChildNoQuadraticComplexity(self): ++ impl = getDOMImplementation() ++ ++ newdoc = impl.createDocument(None, "some_tag", None) ++ top_element = newdoc.documentElement ++ children = [newdoc.createElement(f"child-{i}") for i in range(1, 2 ** 15 + 1)] ++ element = top_element ++ ++ start = time.monotonic() ++ for child in children: ++ element.appendChild(child) ++ element = child ++ end = time.monotonic() ++ ++ # This example used to take at least 30 seconds. ++ # Conservative assertion due to the wide variety of systems and ++ # build configs timing based tests wind up run under. ++ # A --with-address-sanitizer --with-pydebug build on a rpi5 still ++ # completes this loop in <0.5 seconds. ++ self.assertLess(end - start, 4) ++ ++ def testSetAttributeNodeWithoutOwnerDocument(self): ++ # regression test for gh-142754 ++ elem = Element("test") ++ attr = Attr("id") ++ attr.value = "test-id" ++ elem.setAttributeNode(attr) ++ self.assertEqual(elem.getAttribute("id"), "test-id") ++ + def testAppendChildFragment(self): + dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() + dom.documentElement.appendChild(frag) +diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py +index ef8a159833..cada981f39 100644 +--- a/Lib/xml/dom/minidom.py ++++ b/Lib/xml/dom/minidom.py +@@ -292,13 +292,6 @@ def _append_child(self, node): + childNodes.append(node) + node.parentNode = self + +-def _in_document(node): +- # return True iff node is part of a document tree +- while node is not None: +- if node.nodeType == Node.DOCUMENT_NODE: +- return True +- node = node.parentNode +- return False + + def _write_data(writer, data): + "Writes datachars to writer." +@@ -355,6 +348,7 @@ class Attr(Node): + def __init__(self, qName, namespaceURI=EMPTY_NAMESPACE, localName=None, + prefix=None): + self.ownerElement = None ++ self.ownerDocument = None + self._name = qName + self.namespaceURI = namespaceURI + self._prefix = prefix +@@ -680,6 +674,7 @@ class Element(Node): + + def __init__(self, tagName, namespaceURI=EMPTY_NAMESPACE, prefix=None, + localName=None): ++ self.ownerDocument = None + self.parentNode = None + self.tagName = self.nodeName = tagName + self.prefix = prefix +@@ -1539,7 +1534,7 @@ def _clear_id_cache(node): + if node.nodeType == Node.DOCUMENT_NODE: + node._id_cache.clear() + node._id_search_stack = None +- elif _in_document(node): ++ elif node.ownerDocument: + node.ownerDocument._id_cache.clear() + node.ownerDocument._id_search_stack= None + +diff --git a/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst b/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst +new file mode 100644 +index 0000000000..05c7df35d1 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst +@@ -0,0 +1,6 @@ ++Remove quadratic behavior in ``xml.minidom`` node ID cache clearing. In order ++to do this without breaking existing users, we also add the *ownerDocument* ++attribute to :mod:`xml.dom.minidom` elements and attributes created by directly ++instantiating the ``Element`` or ``Attr`` class. Note that this way of creating ++nodes is not supported; creator functions like ++:py:meth:`xml.dom.Document.documentElement` should be used instead. diff --git a/python3.11.spec b/python3.11.spec index bb078d6..df7e7cf 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 2%{?dist} +Release: 3%{?dist} License: Python-2.0.1 @@ -348,6 +348,13 @@ Patch452: 00452-properly-apply-exported-cflags-for-dtrace-systemtap-builds.patch # stressed on OpenSSL 3.5. Patch462: 00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch +# 00471 # f7ffc7e947b58a4d33c7f5bb69674af20fe4875d +# CVE-2025-12084 +# +# * gh-142145: Remove quadratic behavior in node ID cache clearing (GH-142146) +# * gh-142754: Ensure that Element & Attr instances have the ownerDocument attribute (GH-142794) +Patch471: 00471-cve-2025-12084.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1676,6 +1683,9 @@ CheckPython optimized # ====================================================== %changelog +* Thu Jan 08 2026 Lumír Balhar - 3.11.14-3 +- Security fix for CVE-2025-12084 + * Tue Jan 06 2026 Karolina Surma - 3.11.14-2 - Require at least the same expat version as used during the build time From 16eea4e6c0d921dd412ff637cfaa624a0ddbf016 Mon Sep 17 00:00:00 2001 From: Karolina Surma Date: Mon, 12 Jan 2026 11:55:39 +0100 Subject: [PATCH 04/16] Extend the expat requirement to differentiate between 32 and 64 arches (cherry picked from python3.15 commit a5ca170d3f1e6e19f5df66f21482c2b10593af42) --- python3.11.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python3.11.spec b/python3.11.spec index df7e7cf..3fa0684 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -518,7 +518,7 @@ Requires: tzdata # was used during the build time. # Other subpackages (like -debug) also need this, but they all depend on -libs. %global expat_version %(LANG=C rpm -q --qf '%%{version}' expat.%{_target_cpu} | sed 's/.*not installed/0/') -Requires: expat >= %{expat_version} +Requires: expat%{?_isa} >= %{expat_version} %description -n %{pkgname}-libs From ebabf83a2d592a969c891aa1c3f4e14d9c81cb4e Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Fri, 16 Jan 2026 09:25:54 +0100 Subject: [PATCH 05/16] Security fix for CVE-2025-13836 --- 00472-cve-2025-13836.patch | 156 +++++++++++++++++++++++++++++++++++++ python3.11.spec | 17 +++- 2 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 00472-cve-2025-13836.patch diff --git a/00472-cve-2025-13836.patch b/00472-cve-2025-13836.patch new file mode 100644 index 0000000..d6e937d --- /dev/null +++ b/00472-cve-2025-13836.patch @@ -0,0 +1,156 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Serhiy Storchaka +Date: Mon, 1 Dec 2025 17:26:07 +0200 +Subject: 00472: CVE-2025-13836 + +gh-119451: Fix a potential denial of service in http.client (GH-119454) + +Reading the whole body of the HTTP response could cause OOM if +the Content-Length value is too large even if the server does not send +a large amount of data. Now the HTTP client reads large data by chunks, +therefore the amount of consumed memory is proportional to the amount +of sent data. +(cherry picked from commit 5a4c4a033a4a54481be6870aa1896fad732555b5) + +Co-authored-by: Serhiy Storchaka +--- + Lib/http/client.py | 28 ++++++-- + Lib/test/test_httplib.py | 66 +++++++++++++++++++ + ...-05-23-11-47-48.gh-issue-119451.qkJe9-.rst | 5 ++ + 3 files changed, 95 insertions(+), 4 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst + +diff --git a/Lib/http/client.py b/Lib/http/client.py +index 91ee1b470c..c977612732 100644 +--- a/Lib/http/client.py ++++ b/Lib/http/client.py +@@ -111,6 +111,11 @@ + _MAXLINE = 65536 + _MAXHEADERS = 100 + ++# Data larger than this will be read in chunks, to prevent extreme ++# overallocation. ++_MIN_READ_BUF_SIZE = 1 << 20 ++ ++ + # Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2) + # + # VCHAR = %x21-7E +@@ -635,10 +640,25 @@ def _safe_read(self, amt): + reading. If the bytes are truly not available (due to EOF), then the + IncompleteRead exception can be used to detect the problem. + """ +- data = self.fp.read(amt) +- if len(data) < amt: +- raise IncompleteRead(data, amt-len(data)) +- return data ++ cursize = min(amt, _MIN_READ_BUF_SIZE) ++ data = self.fp.read(cursize) ++ if len(data) >= amt: ++ return data ++ if len(data) < cursize: ++ raise IncompleteRead(data, amt - len(data)) ++ ++ data = io.BytesIO(data) ++ data.seek(0, 2) ++ while True: ++ # This is a geometric increase in read size (never more than ++ # doubling out the current length of data per loop iteration). ++ delta = min(cursize, amt - cursize) ++ data.write(self.fp.read(delta)) ++ if data.tell() >= amt: ++ return data.getvalue() ++ cursize += delta ++ if data.tell() < cursize: ++ raise IncompleteRead(data.getvalue(), amt - data.tell()) + + def _safe_readinto(self, b): + """Same as _safe_read, but for reading into a buffer.""" +diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py +index 8b9d49ec09..55363413b3 100644 +--- a/Lib/test/test_httplib.py ++++ b/Lib/test/test_httplib.py +@@ -1390,6 +1390,72 @@ def run_server(): + thread.join() + self.assertEqual(result, b"proxied data\n") + ++ def test_large_content_length(self): ++ serv = socket.create_server((HOST, 0)) ++ self.addCleanup(serv.close) ++ ++ def run_server(): ++ [conn, address] = serv.accept() ++ with conn: ++ while conn.recv(1024): ++ conn.sendall( ++ b"HTTP/1.1 200 Ok\r\n" ++ b"Content-Length: %d\r\n" ++ b"\r\n" % size) ++ conn.sendall(b'A' * (size//3)) ++ conn.sendall(b'B' * (size - size//3)) ++ ++ thread = threading.Thread(target=run_server) ++ thread.start() ++ self.addCleanup(thread.join, 1.0) ++ ++ conn = client.HTTPConnection(*serv.getsockname()) ++ try: ++ for w in range(15, 27): ++ size = 1 << w ++ conn.request("GET", "/") ++ with conn.getresponse() as response: ++ self.assertEqual(len(response.read()), size) ++ finally: ++ conn.close() ++ thread.join(1.0) ++ ++ def test_large_content_length_truncated(self): ++ serv = socket.create_server((HOST, 0)) ++ self.addCleanup(serv.close) ++ ++ def run_server(): ++ while True: ++ [conn, address] = serv.accept() ++ with conn: ++ conn.recv(1024) ++ if not size: ++ break ++ conn.sendall( ++ b"HTTP/1.1 200 Ok\r\n" ++ b"Content-Length: %d\r\n" ++ b"\r\n" ++ b"Text" % size) ++ ++ thread = threading.Thread(target=run_server) ++ thread.start() ++ self.addCleanup(thread.join, 1.0) ++ ++ conn = client.HTTPConnection(*serv.getsockname()) ++ try: ++ for w in range(18, 65): ++ size = 1 << w ++ conn.request("GET", "/") ++ with conn.getresponse() as response: ++ self.assertRaises(client.IncompleteRead, response.read) ++ conn.close() ++ finally: ++ conn.close() ++ size = 0 ++ conn.request("GET", "/") ++ conn.close() ++ thread.join(1.0) ++ + def test_putrequest_override_domain_validation(self): + """ + It should be possible to override the default validation +diff --git a/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst b/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst +new file mode 100644 +index 0000000000..6d6f25cd2f +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst +@@ -0,0 +1,5 @@ ++Fix a potential memory denial of service in the :mod:`http.client` module. ++When connecting to a malicious server, it could cause ++an arbitrary amount of memory to be allocated. ++This could have led to symptoms including a :exc:`MemoryError`, swapping, out ++of memory (OOM) killed processes or containers, or even system crashes. diff --git a/python3.11.spec b/python3.11.spec index 3fa0684..4693ab4 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 3%{?dist} +Release: 4%{?dist} License: Python-2.0.1 @@ -355,6 +355,18 @@ Patch462: 00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch # * gh-142754: Ensure that Element & Attr instances have the ownerDocument attribute (GH-142794) Patch471: 00471-cve-2025-12084.patch +# 00472 # 2ba215eaba508b2cdd7c3acfdf3b9a6e32872274 +# CVE-2025-13836 +# +# gh-119451: Fix a potential denial of service in http.client (GH-119454) +# +# Reading the whole body of the HTTP response could cause OOM if +# the Content-Length value is too large even if the server does not send +# a large amount of data. Now the HTTP client reads large data by chunks, +# therefore the amount of consumed memory is proportional to the amount +# of sent data. +Patch472: 00472-cve-2025-13836.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1683,6 +1695,9 @@ CheckPython optimized # ====================================================== %changelog +* Fri Jan 16 2026 Lumír Balhar - 3.11.14-4 +- Security fix for CVE-2025-13836 + * Thu Jan 08 2026 Lumír Balhar - 3.11.14-3 - Security fix for CVE-2025-12084 From 4c7421cb3c40814f20aa764be6fc07ab7c66e783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hrn=C4=8Diar?= Date: Mon, 9 Feb 2026 09:55:14 +0100 Subject: [PATCH 06/16] Security fixes for CVE-2026-0865, CVE-2025-15366 and CVE-2025-15367 --- 00473-cve-2026-0865.patch | 90 ++++++++++++++++++++++++++++++++++++++ 00474-cve-2025-15366.patch | 61 ++++++++++++++++++++++++++ 00475-cve-2025-15367.patch | 61 ++++++++++++++++++++++++++ python3.11.spec | 30 ++++++++++++- 4 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 00473-cve-2026-0865.patch create mode 100644 00474-cve-2025-15366.patch create mode 100644 00475-cve-2025-15367.patch diff --git a/00473-cve-2026-0865.patch b/00473-cve-2026-0865.patch new file mode 100644 index 0000000..f7bec8e --- /dev/null +++ b/00473-cve-2026-0865.patch @@ -0,0 +1,90 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Sat, 17 Jan 2026 11:46:21 -0600 +Subject: 00473: CVE-2026-0865 + + gh-143916: Reject control characters in wsgiref.headers.Headers (GH-143917) + +* Add 'test.support' fixture for C0 control characters +* gh-143916: Reject control characters in wsgiref.headers.Headers +--- + Lib/test/support/__init__.py | 7 +++++++ + Lib/test/test_wsgiref.py | 12 +++++++++++- + Lib/wsgiref/headers.py | 3 +++ + .../2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst | 2 ++ + 4 files changed, 23 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst + +diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py +index 79e3d1c642..9270f3f8d6 100644 +--- a/Lib/test/support/__init__.py ++++ b/Lib/test/support/__init__.py +@@ -2282,3 +2282,10 @@ def copy_python_src_ignore(path, names): + #Windows doesn't have os.uname() but it doesn't support s390x. + skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x', + 'skipped on s390x') ++ ++ ++def control_characters_c0() -> list[str]: ++ """Returns a list of C0 control characters as strings. ++ C0 control characters defined as the byte range 0x00-0x1F, and 0x7F. ++ """ ++ return [chr(c) for c in range(0x00, 0x20)] + ["\x7F"] +diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py +index 9316d0ecbc..28e3656632 100644 +--- a/Lib/test/test_wsgiref.py ++++ b/Lib/test/test_wsgiref.py +@@ -1,6 +1,6 @@ + from unittest import mock + from test import support +-from test.support import socket_helper ++from test.support import socket_helper, control_characters_c0 + from test.test_httpservers import NoLogRequestHandler + from unittest import TestCase + from wsgiref.util import setup_testing_defaults +@@ -503,6 +503,16 @@ def testExtras(self): + '\r\n' + ) + ++ def testRaisesControlCharacters(self): ++ headers = Headers() ++ for c0 in control_characters_c0(): ++ self.assertRaises(ValueError, headers.__setitem__, f"key{c0}", "val") ++ self.assertRaises(ValueError, headers.__setitem__, "key", f"val{c0}") ++ self.assertRaises(ValueError, headers.add_header, f"key{c0}", "val", param="param") ++ self.assertRaises(ValueError, headers.add_header, "key", f"val{c0}", param="param") ++ self.assertRaises(ValueError, headers.add_header, "key", "val", param=f"param{c0}") ++ ++ + class ErrorHandler(BaseCGIHandler): + """Simple handler subclass for testing BaseHandler""" + +diff --git a/Lib/wsgiref/headers.py b/Lib/wsgiref/headers.py +index fab851c5a4..fd98e85d75 100644 +--- a/Lib/wsgiref/headers.py ++++ b/Lib/wsgiref/headers.py +@@ -9,6 +9,7 @@ + # existence of which force quoting of the parameter value. + import re + tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') ++_control_chars_re = re.compile(r'[\x00-\x1F\x7F]') + + def _formatparam(param, value=None, quote=1): + """Convenience function to format and return a key=value pair. +@@ -41,6 +42,8 @@ def __init__(self, headers=None): + def _convert_string_type(self, value): + """Convert/check value type.""" + if type(value) is str: ++ if _control_chars_re.search(value): ++ raise ValueError("Control characters not allowed in headers") + return value + raise AssertionError("Header names/values must be" + " of type str (got {0})".format(repr(value))) +diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst +new file mode 100644 +index 0000000000..44bd0b2705 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst +@@ -0,0 +1,2 @@ ++Reject C0 control characters within wsgiref.headers.Headers fields, values, ++and parameters. diff --git a/00474-cve-2025-15366.patch b/00474-cve-2025-15366.patch new file mode 100644 index 0000000..0e474a4 --- /dev/null +++ b/00474-cve-2025-15366.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Tue, 20 Jan 2026 14:45:42 -0600 +Subject: 00474: CVE-2025-15366 + +gh-143921: Reject control characters in IMAP commands + +(cherry-picked from commit 6262704b134db2a4ba12e85ecfbd968534f28b45) +--- + Lib/imaplib.py | 4 +++- + Lib/test/test_imaplib.py | 6 ++++++ + .../Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst | 1 + + 3 files changed, 10 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst + +diff --git a/Lib/imaplib.py b/Lib/imaplib.py +index 20b86c35d3..bc7628d17a 100644 +--- a/Lib/imaplib.py ++++ b/Lib/imaplib.py +@@ -132,7 +132,7 @@ + # We compile these in _mode_xxx. + _Literal = br'.*{(?P\d+)}$' + _Untagged_status = br'\* (?P\d+) (?P[A-Z-]+)( (?P.*))?' +- ++_control_chars = re.compile(b'[\x00-\x1F\x7F]') + + + class IMAP4: +@@ -994,6 +994,8 @@ def _command(self, name, *args): + if arg is None: continue + if isinstance(arg, str): + arg = bytes(arg, self._encoding) ++ if _control_chars.search(arg): ++ raise ValueError("Control characters not allowed in commands") + data = data + b' ' + arg + + literal = self.literal +diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py +index 7665532d01..caf0884719 100644 +--- a/Lib/test/test_imaplib.py ++++ b/Lib/test/test_imaplib.py +@@ -510,6 +510,12 @@ def test_login(self): + self.assertEqual(data[0], b'LOGIN completed') + self.assertEqual(client.state, 'AUTH') + ++ def test_control_characters(self): ++ client, _ = self._setup(SimpleIMAPHandler) ++ for c0 in support.control_characters_c0(): ++ with self.assertRaises(ValueError): ++ client.login(f'user{c0}', 'pass') ++ + def test_logout(self): + client, _ = self._setup(SimpleIMAPHandler) + typ, data = client.login('user', 'pass') +diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst +new file mode 100644 +index 0000000000..4e13fe92bc +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst +@@ -0,0 +1 @@ ++Reject control characters in IMAP commands. diff --git a/00475-cve-2025-15367.patch b/00475-cve-2025-15367.patch new file mode 100644 index 0000000..eab4dec --- /dev/null +++ b/00475-cve-2025-15367.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson +Date: Tue, 20 Jan 2026 14:46:32 -0600 +Subject: 00475: CVE-2025-15367 + +gh-143923: Reject control characters in POP3 commands + +(cherry-picked from commit b234a2b67539f787e191d2ef19a7cbdce32874e7) +--- + Lib/poplib.py | 2 ++ + Lib/test/test_poplib.py | 8 ++++++++ + .../2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst | 1 + + 3 files changed, 11 insertions(+) + create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst + +diff --git a/Lib/poplib.py b/Lib/poplib.py +index 0f8587317c..f563030f7f 100644 +--- a/Lib/poplib.py ++++ b/Lib/poplib.py +@@ -122,6 +122,8 @@ def _putline(self, line): + def _putcmd(self, line): + if self._debugging: print('*cmd*', repr(line)) + line = bytes(line, self.encoding) ++ if re.search(b'[\x00-\x1F\x7F]', line): ++ raise ValueError('Control characters not allowed in commands') + self._putline(line) + + +diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py +index 49ba993197..b56bc3535e 100644 +--- a/Lib/test/test_poplib.py ++++ b/Lib/test/test_poplib.py +@@ -12,6 +12,7 @@ + import unittest + from unittest import TestCase, skipUnless + from test import support as test_support ++from test.support import control_characters_c0 + from test.support import hashlib_helper + from test.support import socket_helper + from test.support import threading_helper +@@ -367,6 +368,13 @@ def test_quit(self): + self.assertIsNone(self.client.sock) + self.assertIsNone(self.client.file) + ++ def test_control_characters(self): ++ for c0 in control_characters_c0(): ++ with self.assertRaises(ValueError): ++ self.client.user(f'user{c0}') ++ with self.assertRaises(ValueError): ++ self.client.pass_(f'{c0}pass') ++ + @requires_ssl + def test_stls_capa(self): + capa = self.client.capa() +diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst +new file mode 100644 +index 0000000000..3cde4df3e0 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst +@@ -0,0 +1 @@ ++Reject control characters in POP3 commands. diff --git a/python3.11.spec b/python3.11.spec index 4693ab4..f93ffc4 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 4%{?dist} +Release: 5%{?dist} License: Python-2.0.1 @@ -367,6 +367,31 @@ Patch471: 00471-cve-2025-12084.patch # of sent data. Patch472: 00472-cve-2025-13836.patch +# 00473 # 3c13163c5c0be4d073b081f19ad52c007c126d53 +# CVE-2026-0865 +# +# gh-143916: Reject control characters in wsgiref.headers.Headers (GH-143917) +# +# * Add 'test.support' fixture for C0 control characters +# * gh-143916: Reject control characters in wsgiref.headers.Headers +Patch473: 00473-cve-2026-0865.patch + +# 00474 # 837ddca0372fa87ff9cee47142200caa21e77def +# CVE-2025-15366 +# +# gh-143921: Reject control characters in IMAP commands +# +# (cherry-picked from commit 6262704b134db2a4ba12e85ecfbd968534f28b45) +Patch474: 00474-cve-2025-15366.patch + +# 00475 # 3748209a316662d4e85981ca1a7418547a1d25c6 +# CVE-2025-15367 +# +# gh-143923: Reject control characters in POP3 commands +# +# (cherry-picked from commit b234a2b67539f787e191d2ef19a7cbdce32874e7) +Patch475: 00475-cve-2025-15367.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1695,6 +1720,9 @@ CheckPython optimized # ====================================================== %changelog +* Mon Feb 09 2026 Tomáš Hrnčiar - 3.11.14-5 +- Security fixes for CVE-2026-0865, CVE-2025-15366 and CVE-2025-15367 + * Fri Jan 16 2026 Lumír Balhar - 3.11.14-4 - Security fix for CVE-2025-13836 From 46ace4ef14d7b4723ce491b146b089735f574129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hrn=C4=8Diar?= Date: Tue, 3 Mar 2026 14:32:28 +0100 Subject: [PATCH 07/16] Update to 3.11.15 --- ..._seterror-handling-ssl_error_syscall.patch | 2 +- 00471-cve-2025-12084.patch | 140 ---------------- 00472-cve-2025-13836.patch | 156 ------------------ 00473-cve-2026-0865.patch | 90 ---------- python3.11.spec | 35 +--- sources | 4 +- 6 files changed, 8 insertions(+), 419 deletions(-) delete mode 100644 00471-cve-2025-12084.patch delete mode 100644 00472-cve-2025-13836.patch delete mode 100644 00473-cve-2026-0865.patch diff --git a/00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch b/00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch index 43182ad..0016e57 100644 --- a/00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch +++ b/00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch @@ -84,7 +84,7 @@ index 0000000000..75d926ab59 +Fix the :mod:`ssl` module error handling of connection terminate by peer. +It now throws an OSError with the appropriate error code instead of an EOFError. diff --git a/Modules/_ssl.c b/Modules/_ssl.c -index 09207abde1..787c241133 100644 +index 6275d94d64..ee7b131920 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -576,7 +576,7 @@ PySSL_ChainExceptions(PySSLSocket *sslsock) { diff --git a/00471-cve-2025-12084.patch b/00471-cve-2025-12084.patch deleted file mode 100644 index 0796167..0000000 --- a/00471-cve-2025-12084.patch +++ /dev/null @@ -1,140 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: "Miss Islington (bot)" - <31488909+miss-islington@users.noreply.github.com> -Date: Mon, 22 Dec 2025 14:48:49 +0100 -Subject: 00471: CVE-2025-12084 - -* gh-142145: Remove quadratic behavior in node ID cache clearing (GH-142146) -* gh-142754: Ensure that Element & Attr instances have the ownerDocument attribute (GH-142794) -(cherry picked from commit 1cc7551b3f9f71efbc88d96dce90f82de98b2454) -(cherry picked from commit 08d8e18ad81cd45bc4a27d6da478b51ea49486e4) -(cherry picked from commit 8d2d7bb2e754f8649a68ce4116271a4932f76907) - -Co-authored-by: Jacob Walls <38668450+jacobtylerwalls@users.noreply.github.com> -Co-authored-by: Seth Michael Larson -Co-authored-by: Petr Viktorin -Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> -Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> -Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> -Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> -Co-authored-by: Gregory P. Smith ---- - Lib/test/test_minidom.py | 33 ++++++++++++++++++- - Lib/xml/dom/minidom.py | 11 ++----- - ...-12-01-09-36-45.gh-issue-142145.tcAUhg.rst | 6 ++++ - 3 files changed, 41 insertions(+), 9 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst - -diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py -index ef38c36210..c68bd990f7 100644 ---- a/Lib/test/test_minidom.py -+++ b/Lib/test/test_minidom.py -@@ -2,6 +2,7 @@ - - import copy - import pickle -+import time - import io - from test import support - import unittest -@@ -9,7 +10,7 @@ - import pyexpat - import xml.dom.minidom - --from xml.dom.minidom import parse, Attr, Node, Document, parseString -+from xml.dom.minidom import parse, Attr, Node, Document, Element, parseString - from xml.dom.minidom import getDOMImplementation - from xml.parsers.expat import ExpatError - -@@ -177,6 +178,36 @@ def testAppendChild(self): - self.confirm(dom.documentElement.childNodes[-1].data == "Hello") - dom.unlink() - -+ @support.requires_resource('cpu') -+ def testAppendChildNoQuadraticComplexity(self): -+ impl = getDOMImplementation() -+ -+ newdoc = impl.createDocument(None, "some_tag", None) -+ top_element = newdoc.documentElement -+ children = [newdoc.createElement(f"child-{i}") for i in range(1, 2 ** 15 + 1)] -+ element = top_element -+ -+ start = time.monotonic() -+ for child in children: -+ element.appendChild(child) -+ element = child -+ end = time.monotonic() -+ -+ # This example used to take at least 30 seconds. -+ # Conservative assertion due to the wide variety of systems and -+ # build configs timing based tests wind up run under. -+ # A --with-address-sanitizer --with-pydebug build on a rpi5 still -+ # completes this loop in <0.5 seconds. -+ self.assertLess(end - start, 4) -+ -+ def testSetAttributeNodeWithoutOwnerDocument(self): -+ # regression test for gh-142754 -+ elem = Element("test") -+ attr = Attr("id") -+ attr.value = "test-id" -+ elem.setAttributeNode(attr) -+ self.assertEqual(elem.getAttribute("id"), "test-id") -+ - def testAppendChildFragment(self): - dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes() - dom.documentElement.appendChild(frag) -diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py -index ef8a159833..cada981f39 100644 ---- a/Lib/xml/dom/minidom.py -+++ b/Lib/xml/dom/minidom.py -@@ -292,13 +292,6 @@ def _append_child(self, node): - childNodes.append(node) - node.parentNode = self - --def _in_document(node): -- # return True iff node is part of a document tree -- while node is not None: -- if node.nodeType == Node.DOCUMENT_NODE: -- return True -- node = node.parentNode -- return False - - def _write_data(writer, data): - "Writes datachars to writer." -@@ -355,6 +348,7 @@ class Attr(Node): - def __init__(self, qName, namespaceURI=EMPTY_NAMESPACE, localName=None, - prefix=None): - self.ownerElement = None -+ self.ownerDocument = None - self._name = qName - self.namespaceURI = namespaceURI - self._prefix = prefix -@@ -680,6 +674,7 @@ class Element(Node): - - def __init__(self, tagName, namespaceURI=EMPTY_NAMESPACE, prefix=None, - localName=None): -+ self.ownerDocument = None - self.parentNode = None - self.tagName = self.nodeName = tagName - self.prefix = prefix -@@ -1539,7 +1534,7 @@ def _clear_id_cache(node): - if node.nodeType == Node.DOCUMENT_NODE: - node._id_cache.clear() - node._id_search_stack = None -- elif _in_document(node): -+ elif node.ownerDocument: - node.ownerDocument._id_cache.clear() - node.ownerDocument._id_search_stack= None - -diff --git a/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst b/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst -new file mode 100644 -index 0000000000..05c7df35d1 ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst -@@ -0,0 +1,6 @@ -+Remove quadratic behavior in ``xml.minidom`` node ID cache clearing. In order -+to do this without breaking existing users, we also add the *ownerDocument* -+attribute to :mod:`xml.dom.minidom` elements and attributes created by directly -+instantiating the ``Element`` or ``Attr`` class. Note that this way of creating -+nodes is not supported; creator functions like -+:py:meth:`xml.dom.Document.documentElement` should be used instead. diff --git a/00472-cve-2025-13836.patch b/00472-cve-2025-13836.patch deleted file mode 100644 index d6e937d..0000000 --- a/00472-cve-2025-13836.patch +++ /dev/null @@ -1,156 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Serhiy Storchaka -Date: Mon, 1 Dec 2025 17:26:07 +0200 -Subject: 00472: CVE-2025-13836 - -gh-119451: Fix a potential denial of service in http.client (GH-119454) - -Reading the whole body of the HTTP response could cause OOM if -the Content-Length value is too large even if the server does not send -a large amount of data. Now the HTTP client reads large data by chunks, -therefore the amount of consumed memory is proportional to the amount -of sent data. -(cherry picked from commit 5a4c4a033a4a54481be6870aa1896fad732555b5) - -Co-authored-by: Serhiy Storchaka ---- - Lib/http/client.py | 28 ++++++-- - Lib/test/test_httplib.py | 66 +++++++++++++++++++ - ...-05-23-11-47-48.gh-issue-119451.qkJe9-.rst | 5 ++ - 3 files changed, 95 insertions(+), 4 deletions(-) - create mode 100644 Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst - -diff --git a/Lib/http/client.py b/Lib/http/client.py -index 91ee1b470c..c977612732 100644 ---- a/Lib/http/client.py -+++ b/Lib/http/client.py -@@ -111,6 +111,11 @@ - _MAXLINE = 65536 - _MAXHEADERS = 100 - -+# Data larger than this will be read in chunks, to prevent extreme -+# overallocation. -+_MIN_READ_BUF_SIZE = 1 << 20 -+ -+ - # Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2) - # - # VCHAR = %x21-7E -@@ -635,10 +640,25 @@ def _safe_read(self, amt): - reading. If the bytes are truly not available (due to EOF), then the - IncompleteRead exception can be used to detect the problem. - """ -- data = self.fp.read(amt) -- if len(data) < amt: -- raise IncompleteRead(data, amt-len(data)) -- return data -+ cursize = min(amt, _MIN_READ_BUF_SIZE) -+ data = self.fp.read(cursize) -+ if len(data) >= amt: -+ return data -+ if len(data) < cursize: -+ raise IncompleteRead(data, amt - len(data)) -+ -+ data = io.BytesIO(data) -+ data.seek(0, 2) -+ while True: -+ # This is a geometric increase in read size (never more than -+ # doubling out the current length of data per loop iteration). -+ delta = min(cursize, amt - cursize) -+ data.write(self.fp.read(delta)) -+ if data.tell() >= amt: -+ return data.getvalue() -+ cursize += delta -+ if data.tell() < cursize: -+ raise IncompleteRead(data.getvalue(), amt - data.tell()) - - def _safe_readinto(self, b): - """Same as _safe_read, but for reading into a buffer.""" -diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py -index 8b9d49ec09..55363413b3 100644 ---- a/Lib/test/test_httplib.py -+++ b/Lib/test/test_httplib.py -@@ -1390,6 +1390,72 @@ def run_server(): - thread.join() - self.assertEqual(result, b"proxied data\n") - -+ def test_large_content_length(self): -+ serv = socket.create_server((HOST, 0)) -+ self.addCleanup(serv.close) -+ -+ def run_server(): -+ [conn, address] = serv.accept() -+ with conn: -+ while conn.recv(1024): -+ conn.sendall( -+ b"HTTP/1.1 200 Ok\r\n" -+ b"Content-Length: %d\r\n" -+ b"\r\n" % size) -+ conn.sendall(b'A' * (size//3)) -+ conn.sendall(b'B' * (size - size//3)) -+ -+ thread = threading.Thread(target=run_server) -+ thread.start() -+ self.addCleanup(thread.join, 1.0) -+ -+ conn = client.HTTPConnection(*serv.getsockname()) -+ try: -+ for w in range(15, 27): -+ size = 1 << w -+ conn.request("GET", "/") -+ with conn.getresponse() as response: -+ self.assertEqual(len(response.read()), size) -+ finally: -+ conn.close() -+ thread.join(1.0) -+ -+ def test_large_content_length_truncated(self): -+ serv = socket.create_server((HOST, 0)) -+ self.addCleanup(serv.close) -+ -+ def run_server(): -+ while True: -+ [conn, address] = serv.accept() -+ with conn: -+ conn.recv(1024) -+ if not size: -+ break -+ conn.sendall( -+ b"HTTP/1.1 200 Ok\r\n" -+ b"Content-Length: %d\r\n" -+ b"\r\n" -+ b"Text" % size) -+ -+ thread = threading.Thread(target=run_server) -+ thread.start() -+ self.addCleanup(thread.join, 1.0) -+ -+ conn = client.HTTPConnection(*serv.getsockname()) -+ try: -+ for w in range(18, 65): -+ size = 1 << w -+ conn.request("GET", "/") -+ with conn.getresponse() as response: -+ self.assertRaises(client.IncompleteRead, response.read) -+ conn.close() -+ finally: -+ conn.close() -+ size = 0 -+ conn.request("GET", "/") -+ conn.close() -+ thread.join(1.0) -+ - def test_putrequest_override_domain_validation(self): - """ - It should be possible to override the default validation -diff --git a/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst b/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst -new file mode 100644 -index 0000000000..6d6f25cd2f ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst -@@ -0,0 +1,5 @@ -+Fix a potential memory denial of service in the :mod:`http.client` module. -+When connecting to a malicious server, it could cause -+an arbitrary amount of memory to be allocated. -+This could have led to symptoms including a :exc:`MemoryError`, swapping, out -+of memory (OOM) killed processes or containers, or even system crashes. diff --git a/00473-cve-2026-0865.patch b/00473-cve-2026-0865.patch deleted file mode 100644 index f7bec8e..0000000 --- a/00473-cve-2026-0865.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Seth Michael Larson -Date: Sat, 17 Jan 2026 11:46:21 -0600 -Subject: 00473: CVE-2026-0865 - - gh-143916: Reject control characters in wsgiref.headers.Headers (GH-143917) - -* Add 'test.support' fixture for C0 control characters -* gh-143916: Reject control characters in wsgiref.headers.Headers ---- - Lib/test/support/__init__.py | 7 +++++++ - Lib/test/test_wsgiref.py | 12 +++++++++++- - Lib/wsgiref/headers.py | 3 +++ - .../2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst | 2 ++ - 4 files changed, 23 insertions(+), 1 deletion(-) - create mode 100644 Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst - -diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 79e3d1c642..9270f3f8d6 100644 ---- a/Lib/test/support/__init__.py -+++ b/Lib/test/support/__init__.py -@@ -2282,3 +2282,10 @@ def copy_python_src_ignore(path, names): - #Windows doesn't have os.uname() but it doesn't support s390x. - skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x', - 'skipped on s390x') -+ -+ -+def control_characters_c0() -> list[str]: -+ """Returns a list of C0 control characters as strings. -+ C0 control characters defined as the byte range 0x00-0x1F, and 0x7F. -+ """ -+ return [chr(c) for c in range(0x00, 0x20)] + ["\x7F"] -diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py -index 9316d0ecbc..28e3656632 100644 ---- a/Lib/test/test_wsgiref.py -+++ b/Lib/test/test_wsgiref.py -@@ -1,6 +1,6 @@ - from unittest import mock - from test import support --from test.support import socket_helper -+from test.support import socket_helper, control_characters_c0 - from test.test_httpservers import NoLogRequestHandler - from unittest import TestCase - from wsgiref.util import setup_testing_defaults -@@ -503,6 +503,16 @@ def testExtras(self): - '\r\n' - ) - -+ def testRaisesControlCharacters(self): -+ headers = Headers() -+ for c0 in control_characters_c0(): -+ self.assertRaises(ValueError, headers.__setitem__, f"key{c0}", "val") -+ self.assertRaises(ValueError, headers.__setitem__, "key", f"val{c0}") -+ self.assertRaises(ValueError, headers.add_header, f"key{c0}", "val", param="param") -+ self.assertRaises(ValueError, headers.add_header, "key", f"val{c0}", param="param") -+ self.assertRaises(ValueError, headers.add_header, "key", "val", param=f"param{c0}") -+ -+ - class ErrorHandler(BaseCGIHandler): - """Simple handler subclass for testing BaseHandler""" - -diff --git a/Lib/wsgiref/headers.py b/Lib/wsgiref/headers.py -index fab851c5a4..fd98e85d75 100644 ---- a/Lib/wsgiref/headers.py -+++ b/Lib/wsgiref/headers.py -@@ -9,6 +9,7 @@ - # existence of which force quoting of the parameter value. - import re - tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') -+_control_chars_re = re.compile(r'[\x00-\x1F\x7F]') - - def _formatparam(param, value=None, quote=1): - """Convenience function to format and return a key=value pair. -@@ -41,6 +42,8 @@ def __init__(self, headers=None): - def _convert_string_type(self, value): - """Convert/check value type.""" - if type(value) is str: -+ if _control_chars_re.search(value): -+ raise ValueError("Control characters not allowed in headers") - return value - raise AssertionError("Header names/values must be" - " of type str (got {0})".format(repr(value))) -diff --git a/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst -new file mode 100644 -index 0000000000..44bd0b2705 ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst -@@ -0,0 +1,2 @@ -+Reject C0 control characters within wsgiref.headers.Headers fields, values, -+and parameters. diff --git a/python3.11.spec b/python3.11.spec index f93ffc4..57468d4 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -13,11 +13,11 @@ URL: https://www.python.org/ # WARNING When rebasing to a new Python version, # remember to update the python3-docs package as well -%global general_version %{pybasever}.14 +%global general_version %{pybasever}.15 #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 5%{?dist} +Release: 1%{?dist} License: Python-2.0.1 @@ -348,34 +348,6 @@ Patch452: 00452-properly-apply-exported-cflags-for-dtrace-systemtap-builds.patch # stressed on OpenSSL 3.5. Patch462: 00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch -# 00471 # f7ffc7e947b58a4d33c7f5bb69674af20fe4875d -# CVE-2025-12084 -# -# * gh-142145: Remove quadratic behavior in node ID cache clearing (GH-142146) -# * gh-142754: Ensure that Element & Attr instances have the ownerDocument attribute (GH-142794) -Patch471: 00471-cve-2025-12084.patch - -# 00472 # 2ba215eaba508b2cdd7c3acfdf3b9a6e32872274 -# CVE-2025-13836 -# -# gh-119451: Fix a potential denial of service in http.client (GH-119454) -# -# Reading the whole body of the HTTP response could cause OOM if -# the Content-Length value is too large even if the server does not send -# a large amount of data. Now the HTTP client reads large data by chunks, -# therefore the amount of consumed memory is proportional to the amount -# of sent data. -Patch472: 00472-cve-2025-13836.patch - -# 00473 # 3c13163c5c0be4d073b081f19ad52c007c126d53 -# CVE-2026-0865 -# -# gh-143916: Reject control characters in wsgiref.headers.Headers (GH-143917) -# -# * Add 'test.support' fixture for C0 control characters -# * gh-143916: Reject control characters in wsgiref.headers.Headers -Patch473: 00473-cve-2026-0865.patch - # 00474 # 837ddca0372fa87ff9cee47142200caa21e77def # CVE-2025-15366 # @@ -1720,6 +1692,9 @@ CheckPython optimized # ====================================================== %changelog +* Tue Mar 03 2026 Tomáš Hrnčiar - 3.11.15-1 +- Update to 3.11.15 + * Mon Feb 09 2026 Tomáš Hrnčiar - 3.11.14-5 - Security fixes for CVE-2026-0865, CVE-2025-15366 and CVE-2025-15367 diff --git a/sources b/sources index d418a33..c0c09ac 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (Python-3.11.14.tar.xz) = 8b5aa917fe67dbaa3c306239ed56c16cd7a3b4b701fab0b3dc0d342d60176c75440713bcab0c59a3289ac4a0f06103bd31140c492556e1937fcdbd990675f9e5 -SHA512 (Python-3.11.14.tar.xz.asc) = d0049fd6f6d06ae5d86b3587080be060d522dd52e7e56270125541d5617582ea3977b75c7e585da005694665cfc5657e39033a730187c506edc252965f4df769 +SHA512 (Python-3.11.15.tar.xz) = 1667f4839acfd23d05917a12993983b7dc3ab4438a37c72496fd040ac6981f4dea9dc95461ca701a6862beedb56c423fb83398b8b18e39e1efe12f539fced1c1 +SHA512 (Python-3.11.15.tar.xz.asc) = 673822ca037acb521bf7d3e7433f83cc2d60c8d102cfbf6695234e2a50ce12a7003040ad4017438510ebbb1ff0ba6727253fae250b07bc0e3d24b5578e80be52 From 067265083c5be1c35962b6a43dd0d68a0feeb810 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Thu, 26 Mar 2026 11:48:28 +0100 Subject: [PATCH 08/16] Security fix for CVE-2026-4519 (rhbz#2449727) --- 00478-cve-2026-4519.patch | 121 ++++++++++++++++++++++++++++++++++++++ python3.11.spec | 11 +++- 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 00478-cve-2026-4519.patch diff --git a/00478-cve-2026-4519.patch b/00478-cve-2026-4519.patch new file mode 100644 index 0000000..eb9d2ef --- /dev/null +++ b/00478-cve-2026-4519.patch @@ -0,0 +1,121 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: tomcruiseqi +Date: Wed, 25 Mar 2026 02:23:28 +0800 +Subject: 00478: CVE-2026-4519 + +Reject leading dashes in webbrowser URLs (GH-143931) (GH-146364) + +(cherry picked from commit 82a24a4442312bdcfc4c799885e8b3e00990f02b) + +Co-authored-by: Seth Michael Larson +--- + 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 9d608d63a0..0ac985f56c 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 5d72524c08..0fd0aeb3c1 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: +@@ -689,6 +702,7 @@ def _name(self, val): + + def open(self, url, new=0, autoraise=True): + sys.audit("webbrowser.open", url) ++ 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 0000000000..0f27eae99a +--- /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 --git a/python3.11.spec b/python3.11.spec index 57468d4..76fcaf6 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 1%{?dist} +Release: 2%{?dist} License: Python-2.0.1 @@ -364,6 +364,12 @@ Patch474: 00474-cve-2025-15366.patch # (cherry-picked from commit b234a2b67539f787e191d2ef19a7cbdce32874e7) Patch475: 00475-cve-2025-15367.patch +# 00478 # 40c5c88950b10eaf1c10c5afcc39887b8e23c997 +# CVE-2026-4519 +# +# Reject leading dashes in webbrowser URLs (GH-143931) (GH-146364) +Patch478: 00478-cve-2026-4519.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1692,6 +1698,9 @@ CheckPython optimized # ====================================================== %changelog +* Thu Mar 26 2026 Lumír Balhar - 3.11.15-2 +- Security fix for CVE-2026-4519 (rhbz#2449727) + * Tue Mar 03 2026 Tomáš Hrnčiar - 3.11.15-1 - Update to 3.11.15 From b6a32dca60faf21771b2e8b45fcb11e5fbaaeb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Wed, 11 Mar 2026 19:39:18 +0100 Subject: [PATCH 09/16] Only explicitly require expat >= installed version when expat < 2.7.4 See https://src.fedoraproject.org/rpms/expat/c/4da0543472 (cherry picked from python3.15 commit ce1bde3e67443b7cf5df33bf58cb2ec75cc2c8e2) --- python3.11.spec | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python3.11.spec b/python3.11.spec index 76fcaf6..c2c22c5 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -532,8 +532,12 @@ Requires: tzdata # We avoid this problem by requiring at least the same version of expat that # was used during the build time. # Other subpackages (like -debug) also need this, but they all depend on -libs. +# Since expat 2.7.4, the library has versioned symbols and this is no longer needed, +# as the generated requirement will be in the form of libexpat.so.1(LIBEXPAT_2.7.2) etc. %global expat_version %(LANG=C rpm -q --qf '%%{version}' expat.%{_target_cpu} | sed 's/.*not installed/0/') +%if v"%{expat_version}" < v"2.7.4" Requires: expat%{?_isa} >= %{expat_version} +%endif %description -n %{pkgname}-libs From 9be9ebcf5faa7cba7f19241fccd128160dd13164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Thu, 9 Apr 2026 13:16:25 +0200 Subject: [PATCH 10/16] Explicitly build with OpenSSL 3 for now https://fedoraproject.org/wiki/Changes/OpenSSL40 --- python3.11.spec | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/python3.11.spec b/python3.11.spec index c2c22c5..4bd9ce7 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 2%{?dist} +Release: 3%{?dist} License: Python-2.0.1 @@ -219,7 +219,6 @@ BuildRequires: make BuildRequires: mpdecimal-devel BuildRequires: ncurses-devel -BuildRequires: openssl-devel BuildRequires: pkgconfig BuildRequires: readline-devel BuildRequires: redhat-rpm-config >= 127 @@ -232,6 +231,10 @@ BuildRequires: tix-devel BuildRequires: tk-devel < 1:9 BuildRequires: tzdata +# Support for OpenSSL 4 only landed in Python 3.15 for now +# https://github.com/python/cpython/issues/146207 +BuildRequires: (openssl-devel < 1:4 or openssl3-devel) + %if %{with valgrind} BuildRequires: valgrind-devel %endif @@ -1702,6 +1705,9 @@ CheckPython optimized # ====================================================== %changelog +* Sat Apr 11 2026 Miro Hrončok - 3.11.15-3 +- Explicitly build with OpenSSL 3 + * Thu Mar 26 2026 Lumír Balhar - 3.11.15-2 - Security fix for CVE-2026-4519 (rhbz#2449727) From f51f0ca45b8bd9ab9f9079650f3542c0678e3a56 Mon Sep 17 00:00:00 2001 From: Charalampos Stratakis Date: Fri, 17 Apr 2026 03:41:06 +0200 Subject: [PATCH 11/16] Security fixes for CVE-2026-1502, CVE-2026-4786, CVE-2026-6100, CVE-2026-2297, CVE 2026-3644, CVE-2026-4224 Resolves: rhbz#2457941, rhbz#2458221, rhbz#2458013, rhbz#2444704, rhbz#2448188, rhbz#2448204 --- 00479-cve-2026-1502.patch | 106 +++++++++++++++++++++++++++ 00480-cve-2026-4786.patch | 63 ++++++++++++++++ 00482-cve-2026-6100.patch | 48 +++++++++++++ 00483-cve-2026-2297.patch | 33 +++++++++ 00484-cve-2026-3644.patch | 146 ++++++++++++++++++++++++++++++++++++++ 00485-cve-2026-4224.patch | 98 +++++++++++++++++++++++++ python3.11.spec | 42 ++++++++++- 7 files changed, 535 insertions(+), 1 deletion(-) create mode 100644 00479-cve-2026-1502.patch create mode 100644 00480-cve-2026-4786.patch create mode 100644 00482-cve-2026-6100.patch create mode 100644 00483-cve-2026-2297.patch create mode 100644 00484-cve-2026-3644.patch create mode 100644 00485-cve-2026-4224.patch diff --git a/00479-cve-2026-1502.patch b/00479-cve-2026-1502.patch new file mode 100644 index 0000000..b6ce808 --- /dev/null +++ b/00479-cve-2026-1502.patch @@ -0,0 +1,106 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Seth Larson +Date: Fri, 10 Apr 2026 10:21:42 -0500 +Subject: 00479: CVE-2026-1502 + +Reject CR/LF in HTTP tunnel request headers + +Co-authored-by: Illia Volochii +--- + Lib/http/client.py | 11 ++++- + Lib/test/test_httplib.py | 45 +++++++++++++++++++ + ...-03-20-09-29-42.gh-issue-146211.PQVbs7.rst | 2 + + 3 files changed, 57 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst + +diff --git a/Lib/http/client.py b/Lib/http/client.py +index c977612732..8cf783ab30 100644 +--- a/Lib/http/client.py ++++ b/Lib/http/client.py +@@ -941,12 +941,21 @@ def _wrap_ipv6(self, ip): + return ip + + def _tunnel(self): ++ if _contains_disallowed_url_pchar_re.search(self._tunnel_host): ++ raise ValueError('Tunnel host can\'t contain control characters %r' ++ % (self._tunnel_host,)) + connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( + self._wrap_ipv6(self._tunnel_host.encode("ascii")), + self._tunnel_port) + headers = [connect] + for header, value in self._tunnel_headers.items(): +- headers.append(f"{header}: {value}\r\n".encode("latin-1")) ++ header_bytes = header.encode("latin-1") ++ value_bytes = value.encode("latin-1") ++ if not _is_legal_header_name(header_bytes): ++ raise ValueError('Invalid header name %r' % (header_bytes,)) ++ if _is_illegal_header_value(value_bytes): ++ raise ValueError('Invalid header value %r' % (value_bytes,)) ++ headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes)) + headers.append(b"\r\n") + # Making a single send() call instead of one per line encourages + # the host OS to use a more optimal packet size instead of +diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py +index 55363413b3..a4ab909ca9 100644 +--- a/Lib/test/test_httplib.py ++++ b/Lib/test/test_httplib.py +@@ -371,6 +371,51 @@ def test_invalid_headers(self): + with self.assertRaisesRegex(ValueError, 'Invalid header'): + conn.putheader(name, value) + ++ def test_invalid_tunnel_headers(self): ++ cases = ( ++ ('Invalid\r\nName', 'ValidValue'), ++ ('Invalid\rName', 'ValidValue'), ++ ('Invalid\nName', 'ValidValue'), ++ ('\r\nInvalidName', 'ValidValue'), ++ ('\rInvalidName', 'ValidValue'), ++ ('\nInvalidName', 'ValidValue'), ++ (' InvalidName', 'ValidValue'), ++ ('\tInvalidName', 'ValidValue'), ++ ('Invalid:Name', 'ValidValue'), ++ (':InvalidName', 'ValidValue'), ++ ('ValidName', 'Invalid\r\nValue'), ++ ('ValidName', 'Invalid\rValue'), ++ ('ValidName', 'Invalid\nValue'), ++ ('ValidName', 'InvalidValue\r\n'), ++ ('ValidName', 'InvalidValue\r'), ++ ('ValidName', 'InvalidValue\n'), ++ ) ++ for name, value in cases: ++ with self.subTest((name, value)): ++ conn = client.HTTPConnection('example.com') ++ conn.set_tunnel('tunnel', headers={ ++ name: value ++ }) ++ conn.sock = FakeSocket('') ++ with self.assertRaisesRegex(ValueError, 'Invalid header'): ++ conn._tunnel() # Called in .connect() ++ ++ def test_invalid_tunnel_host(self): ++ cases = ( ++ 'invalid\r.host', ++ '\ninvalid.host', ++ 'invalid.host\r\n', ++ 'invalid.host\x00', ++ 'invalid host', ++ ) ++ for tunnel_host in cases: ++ with self.subTest(tunnel_host): ++ conn = client.HTTPConnection('example.com') ++ conn.set_tunnel(tunnel_host) ++ conn.sock = FakeSocket('') ++ with self.assertRaisesRegex(ValueError, 'Tunnel host can\'t contain control characters'): ++ conn._tunnel() # Called in .connect() ++ + def test_headers_debuglevel(self): + body = ( + b'HTTP/1.1 200 OK\r\n' +diff --git a/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst +new file mode 100644 +index 0000000000..4993633b8e +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst +@@ -0,0 +1,2 @@ ++Reject CR/LF characters in tunnel request headers for the ++HTTPConnection.set_tunnel() method. diff --git a/00480-cve-2026-4786.patch b/00480-cve-2026-4786.patch new file mode 100644 index 0000000..a0fcb66 --- /dev/null +++ b/00480-cve-2026-4786.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych +Date: Mon, 13 Apr 2026 22:41:51 +0100 +Subject: 00480: CVE-2026-4786 + +Fix webbrowser `%action` substitution bypass of dash-prefix check +--- + 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 0ac985f56c..d629f889db 100644 +--- a/Lib/test/test_webbrowser.py ++++ b/Lib/test/test_webbrowser.py +@@ -99,6 +99,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 0fd0aeb3c1..026bdfbd7b 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 0000000000..45cdeebe1b +--- /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 --git a/00482-cve-2026-6100.patch b/00482-cve-2026-6100.patch new file mode 100644 index 0000000..ad4c39b --- /dev/null +++ b/00482-cve-2026-6100.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych +Date: Mon, 13 Apr 2026 22:42:36 +0100 +Subject: 00482: CVE-2026-6100 + +Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor +--- + .../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 0000000000..349d1cf3ca +--- /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 798e9efc62..b08ac5e44e 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 97453a2808..51106a6a07 100644 +--- a/Modules/_lzmamodule.c ++++ b/Modules/_lzmamodule.c +@@ -1105,6 +1105,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 --git a/00483-cve-2026-2297.patch b/00483-cve-2026-2297.patch new file mode 100644 index 0000000..b71b19f --- /dev/null +++ b/00483-cve-2026-2297.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Steve Dower +Date: Wed, 4 Mar 2026 19:55:52 +0000 +Subject: 00483: CVE-2026-2297 + +Logging Bypass in Legacy .pyc File Handling +--- + 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 e53f6acf38..588da3c7ad 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 0000000000..edeb9e640c +--- /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 --git a/00484-cve-2026-3644.patch b/00484-cve-2026-3644.patch new file mode 100644 index 0000000..b01be87 --- /dev/null +++ b/00484-cve-2026-3644.patch @@ -0,0 +1,146 @@ +From 0000000000000000000000000000000000000000 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: 00484: CVE-2026-3644 + +Incomplete control character validation in http.cookies + +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 5cfa7a8072..6b36ffa9f8 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 2438c57ef4..f9a846f8fa 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 0000000000..fc2e503779 +--- /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 --git a/00485-cve-2026-4224.patch b/00485-cve-2026-4224.patch new file mode 100644 index 0000000..cb5a8b5 --- /dev/null +++ b/00485-cve-2026-4224.patch @@ -0,0 +1,98 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Stan Ulbrych +Date: Wed, 8 Apr 2026 11:27:39 +0100 +Subject: 00485: CVE-2026-4224 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Stack overflow parsing XML with deeply nested DTD content models + +Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> +--- + 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 9aa2fcedad..8afce3ffe1 100644 +--- a/Lib/test/test_pyexpat.py ++++ b/Lib/test/test_pyexpat.py +@@ -675,6 +675,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 0000000000..cb9dbadb72 +--- /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 7b76ddfabd..13c4e8e0ad 100644 +--- a/Modules/pyexpat.c ++++ b/Modules/pyexpat.c +@@ -3,6 +3,7 @@ + #endif + + #include "Python.h" ++#include "pycore_ceval.h" // _Py_EnterRecursiveCall() + #include "pycore_runtime.h" // _Py_ID() + #include + +@@ -578,6 +579,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; +@@ -589,7 +594,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); + } +@@ -597,6 +602,8 @@ conv_content_model(XML_Content * const model, + model->type, model->quant, + conv_string,model->name, children); + } ++done: ++ _Py_LeaveRecursiveCall(); + return result; + } + diff --git a/python3.11.spec b/python3.11.spec index 4bd9ce7..a9b6bcd 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 3%{?dist} +Release: 4%{?dist} License: Python-2.0.1 @@ -373,6 +373,42 @@ Patch475: 00475-cve-2025-15367.patch # Reject leading dashes in webbrowser URLs (GH-143931) (GH-146364) Patch478: 00478-cve-2026-4519.patch +# 00479 # 6fe61dd71dec5b7c9de2b1994172981667d034a1 +# CVE-2026-1502 +# +# Reject CR/LF in HTTP tunnel request headers +Patch479: 00479-cve-2026-1502.patch + +# 00480 # 9f4b1483ecfbc8c08117133c239fba544fcb42e7 +# CVE-2026-4786 +# +# Fix webbrowser `%%action` substitution bypass of dash-prefix check +Patch480: 00480-cve-2026-4786.patch + +# 00482 # 2a21454e658935990766df8c3c48af9363e8422a +# CVE-2026-6100 +# +# Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor +Patch482: 00482-cve-2026-6100.patch + +# 00483 # cdb097a23eac5a09fb063a0e91001f69ff324205 +# CVE-2026-2297 +# +# Logging Bypass in Legacy .pyc File Handling +Patch483: 00483-cve-2026-2297.patch + +# 00484 # cf0bd2f2cce15cb35558aa08de34e9d18a8089f7 +# CVE-2026-3644 +# +# Incomplete control character validation in http.cookies +Patch484: 00484-cve-2026-3644.patch + +# 00485 # 54d821ba2f5a03ccced037978fcdb0a7c4d6878f +# CVE-2026-4224 +# +# Stack overflow parsing XML with deeply nested DTD content models +Patch485: 00485-cve-2026-4224.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1705,6 +1741,10 @@ CheckPython optimized # ====================================================== %changelog +* Fri Apr 17 2026 Charalampos Stratakis - 3.11.15-4 +- Security fixes for CVE-2026-1502, CVE-2026-4786, CVE-2026-6100, CVE-2026-2297, CVE 2026-3644, CVE-2026-4224 +Resolves: rhbz#2457941, rhbz#2458221, rhbz#2458013, rhbz#2444704, rhbz#2448188, rhbz#2448204 + * Sat Apr 11 2026 Miro Hrončok - 3.11.15-3 - Explicitly build with OpenSSL 3 From fc0070dafaf1808177871ad44712aa711cd6cd62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Thu, 2 Jul 2026 10:54:53 +0200 Subject: [PATCH 12/16] Fix ssl.SSLError: [ASN1: NOT_ENOUGH_DATA] not enough data with OpenSSL 3.5.7+ --- 00489-openssl-3.5.7.patch | 75 +++++++++++++++++++++++++++++++++++++++ python3.11.spec | 17 ++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 00489-openssl-3.5.7.patch diff --git a/00489-openssl-3.5.7.patch b/00489-openssl-3.5.7.patch new file mode 100644 index 0000000..5ba5e8b --- /dev/null +++ b/00489-openssl-3.5.7.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Benjamin +Date: Fri, 24 Mar 2023 09:04:30 -0400 +Subject: 00489: Use BIO_eof to detect EOF for SSL_FILETYPE_ASN1 + +In PEM, we need to parse until error and then suppress `PEM_R_NO_START_LINE`, because PEM allows arbitrary leading and trailing data. DER, however, does not. Parsing until error and suppressing `ASN1_R_HEADER_TOO_LONG` doesn't quite work because that error also covers some cases that should be rejected. + +Instead, check `BIO_eof` early and stop the loop that way. + +This fixes https://github.com/python/cpython/issues/151504 and adds compatibility with OpenSSL 3.5.7+ + +(cherry-picked from commit acfe02f3b05436658d92add6b168538b30f357f0) +--- + Lib/test/test_ssl.py | 2 ++ + .../2022-12-20-10-55-14.gh-issue-100372.utfP65.rst | 2 ++ + Modules/_ssl.c | 10 ++++++---- + 3 files changed, 10 insertions(+), 4 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2022-12-20-10-55-14.gh-issue-100372.utfP65.rst + +diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py +index 921c41bd0d..61cde99753 100644 +--- a/Lib/test/test_ssl.py ++++ b/Lib/test/test_ssl.py +@@ -1559,6 +1559,8 @@ def test_load_verify_cadata(self): + "not enough data: cadata does not contain a certificate" + ): + ctx.load_verify_locations(cadata=b"broken") ++ with self.assertRaises(ssl.SSLError): ++ ctx.load_verify_locations(cadata=cacert_der + b"A") + + @unittest.skipIf(Py_DEBUG_WIN32, "Avoid mixing debug/release CRT on Windows") + def test_load_dh_params(self): +diff --git a/Misc/NEWS.d/next/Library/2022-12-20-10-55-14.gh-issue-100372.utfP65.rst b/Misc/NEWS.d/next/Library/2022-12-20-10-55-14.gh-issue-100372.utfP65.rst +new file mode 100644 +index 0000000000..ec37aff509 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2022-12-20-10-55-14.gh-issue-100372.utfP65.rst +@@ -0,0 +1,2 @@ ++:meth:`ssl.SSLContext.load_verify_locations` no longer incorrectly accepts ++some cases of trailing data when parsing DER. +diff --git a/Modules/_ssl.c b/Modules/_ssl.c +index ee7b131920..174e5dfce5 100644 +--- a/Modules/_ssl.c ++++ b/Modules/_ssl.c +@@ -3966,7 +3966,7 @@ _add_ca_certs(PySSLContext *self, const void *data, Py_ssize_t len, + { + BIO *biobuf = NULL; + X509_STORE *store; +- int retval = -1, err, loaded = 0; ++ int retval = -1, err, loaded = 0, was_bio_eof = 0; + + assert(filetype == SSL_FILETYPE_ASN1 || filetype == SSL_FILETYPE_PEM); + +@@ -3994,6 +3994,10 @@ _add_ca_certs(PySSLContext *self, const void *data, Py_ssize_t len, + int r; + + if (filetype == SSL_FILETYPE_ASN1) { ++ if (BIO_eof(biobuf)) { ++ was_bio_eof = 1; ++ break; ++ } + cert = d2i_X509_bio(biobuf, NULL); + } else { + cert = PEM_read_bio_X509(biobuf, NULL, +@@ -4029,9 +4033,7 @@ _add_ca_certs(PySSLContext *self, const void *data, Py_ssize_t len, + } + _setSSLError(get_state_ctx(self), msg, 0, __FILE__, __LINE__); + retval = -1; +- } else if ((filetype == SSL_FILETYPE_ASN1) && +- (ERR_GET_LIB(err) == ERR_LIB_ASN1) && +- (ERR_GET_REASON(err) == ASN1_R_HEADER_TOO_LONG)) { ++ } else if ((filetype == SSL_FILETYPE_ASN1) && was_bio_eof) { + /* EOF ASN1 file, not an error */ + ERR_clear_error(); + retval = 0; diff --git a/python3.11.spec b/python3.11.spec index a9b6bcd..08ed8b2 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 4%{?dist} +Release: 5%{?dist} License: Python-2.0.1 @@ -409,6 +409,18 @@ Patch484: 00484-cve-2026-3644.patch # Stack overflow parsing XML with deeply nested DTD content models Patch485: 00485-cve-2026-4224.patch +# 00489 # 008af720a5f6f98ed3feb8ebdbf88ab9dea4db22 +# Use BIO_eof to detect EOF for SSL_FILETYPE_ASN1 +# +# In PEM, we need to parse until error and then suppress `PEM_R_NO_START_LINE`, because PEM allows arbitrary leading and trailing data. DER, however, does not. Parsing until error and suppressing `ASN1_R_HEADER_TOO_LONG` doesn't quite work because that error also covers some cases that should be rejected. +# +# Instead, check `BIO_eof` early and stop the loop that way. +# +# This fixes https://github.com/python/cpython/issues/151504 and adds compatibility with OpenSSL 3.5.7+ +# +# (cherry-picked from commit acfe02f3b05436658d92add6b168538b30f357f0) +Patch489: 00489-openssl-3.5.7.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1741,6 +1753,9 @@ CheckPython optimized # ====================================================== %changelog +* Thu Jul 02 2026 Miro Hrončok - 3.11.15-5 +- Fix ssl.SSLError: [ASN1: NOT_ENOUGH_DATA] not enough data with OpenSSL 3.5.7+ + * Fri Apr 17 2026 Charalampos Stratakis - 3.11.15-4 - Security fixes for CVE-2026-1502, CVE-2026-4786, CVE-2026-6100, CVE-2026-2297, CVE 2026-3644, CVE-2026-4224 Resolves: rhbz#2457941, rhbz#2458221, rhbz#2458013, rhbz#2444704, rhbz#2448188, rhbz#2448204 From 55cc855c79d4a45421dca6aa52b434058fe22eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lum=C3=ADr=20Balhar?= Date: Wed, 1 Jul 2026 10:55:21 +0000 Subject: [PATCH 13/16] Run a new test to monitor changes in required symbols Co-Authored-By: Claude Sonnet 4.6 (1M context) --- plan.fmf | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plan.fmf b/plan.fmf index 028bf63..c4ea482 100644 --- a/plan.fmf +++ b/plan.fmf @@ -34,6 +34,9 @@ discover: - name: marshalparser path: /marshalparser test: "VERSION=${pybasever} SAMPLE=10 ./test_marshalparser_compatibility.sh" + - name: required_symbols + path: /required-symbols + test: "VERSION=${pybasever} ./check.sh" prepare: - name: Install dependencies @@ -51,8 +54,9 @@ prepare: - virtualenv # for virtualenv tests - glibc-all-langpacks # for locale tests - marshalparser # for testing compatibility (magic numbers) with marshalparser + - binutils # for nm (symbol inspection) - rpm # for debugging - - dnf # for upgrade + - dnf # for upgrade and downgrade - name: Update packages how: shell script: dnf upgrade -y From 79c55d4ad7b9948b5bf44f62e9f6a869680b40e6 Mon Sep 17 00:00:00 2001 From: Fedora Release Engineering Date: Thu, 16 Jul 2026 22:47:27 +0000 Subject: [PATCH 14/16] Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild --- python3.11.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python3.11.spec b/python3.11.spec index 08ed8b2..f033534 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 5%{?dist} +Release: 6%{?dist} License: Python-2.0.1 @@ -1753,6 +1753,9 @@ CheckPython optimized # ====================================================== %changelog +* Thu Jul 16 2026 Fedora Release Engineering - 3.11.15-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild + * Thu Jul 02 2026 Miro Hrončok - 3.11.15-5 - Fix ssl.SSLError: [ASN1: NOT_ENOUGH_DATA] not enough data with OpenSSL 3.5.7+ From dadebe1c22becf14fc722ccf2b3aa6bd6af16782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Thu, 30 Jul 2026 17:24:31 +0200 Subject: [PATCH 15/16] Skip UDP Lite tests if it's not supported - Fixes FTBFS on Linux kernel 7.1 and newer --- ...udp-lite-tests-if-it-s-not-supported.patch | 64 +++++++++++++++++++ python3.11.spec | 13 +++- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch diff --git a/00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch b/00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch new file mode 100644 index 0000000..5dc9a0f --- /dev/null +++ b/00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Victor Stinner +Date: Wed, 13 May 2026 17:27:56 +0200 +Subject: 00491: gh-149776: Skip UDP Lite tests if it's not supported + +Fix test_socket on Linux kernel 7.1 and newer: skip UDP Lite tests if +it's not supported. + +(cherry picked from commit 3cfc249e11a132dc69624150843779aa96c72b2b) +(cherry picked from commit 49d08674d8dba50dc29539e3c7bce21d66066b06) +--- + Lib/test/test_socket.py | 22 ++++++++++++++++++- + ...-05-13-14-53-23.gh-issue-149776.orqgsn.rst | 2 ++ + 2 files changed, 23 insertions(+), 1 deletion(-) + create mode 100644 Misc/NEWS.d/next/Tests/2026-05-13-14-53-23.gh-issue-149776.orqgsn.rst + +diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py +index 5ea8c8e62c..c96185b2e5 100644 +--- a/Lib/test/test_socket.py ++++ b/Lib/test/test_socket.py +@@ -145,6 +145,26 @@ def _have_socket_bluetooth(): + return True + + ++def _have_udp_lite(): ++ if not hasattr(socket, "IPPROTO_UDPLITE"): ++ return False ++ # Older Android versions block UDPLITE with SELinux. ++ if support.is_android and platform.android_ver().api_level < 29: ++ return False ++ ++ try: ++ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE) ++ except OSError as exc: ++ # Linux 7.1 removed UDP Lite support ++ if exc.errno == errno.EPROTONOSUPPORT: ++ return False ++ raise ++ sock.close() ++ ++ return True ++ ++ ++ + @contextlib.contextmanager + def socket_setdefaulttimeout(timeout): + old_timeout = socket.getdefaulttimeout() +@@ -169,7 +189,7 @@ def socket_setdefaulttimeout(timeout): + + HAVE_SOCKET_VSOCK = _have_socket_vsock() + +-HAVE_SOCKET_UDPLITE = hasattr(socket, "IPPROTO_UDPLITE") ++HAVE_SOCKET_UDPLITE = _have_udp_lite() + + HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth() + +diff --git a/Misc/NEWS.d/next/Tests/2026-05-13-14-53-23.gh-issue-149776.orqgsn.rst b/Misc/NEWS.d/next/Tests/2026-05-13-14-53-23.gh-issue-149776.orqgsn.rst +new file mode 100644 +index 0000000000..e86a9130ff +--- /dev/null ++++ b/Misc/NEWS.d/next/Tests/2026-05-13-14-53-23.gh-issue-149776.orqgsn.rst +@@ -0,0 +1,2 @@ ++Fix test_socket on Linux kernel 7.1 and newer: skip UDP Lite tests if it's ++not supported. Patch by Victor Stinner. diff --git a/python3.11.spec b/python3.11.spec index f033534..2faee17 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -17,7 +17,7 @@ URL: https://www.python.org/ #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 6%{?dist} +Release: 7%{?dist} License: Python-2.0.1 @@ -421,6 +421,13 @@ Patch485: 00485-cve-2026-4224.patch # (cherry-picked from commit acfe02f3b05436658d92add6b168538b30f357f0) Patch489: 00489-openssl-3.5.7.patch +# 00491 # ac14737379922303720216b61803474c84f291ef +# gh-149776: Skip UDP Lite tests if it's not supported +# +# Fix test_socket on Linux kernel 7.1 and newer: skip UDP Lite tests if +# it's not supported. +Patch491: 00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -1753,6 +1760,10 @@ CheckPython optimized # ====================================================== %changelog +* Thu Jul 30 2026 Miro Hrončok - 3.11.15-7 + - Skip UDP Lite tests if it's not supported + - Fixes FTBFS on Linux kernel 7.1 and newer + * Thu Jul 16 2026 Fedora Release Engineering - 3.11.15-6 - Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild From 278eb3fa417bf138a1cb9c618c718b6b68fe139c Mon Sep 17 00:00:00 2001 From: Karolina Surma Date: Thu, 13 Aug 2026 12:06:07 +0200 Subject: [PATCH 16/16] Update to Python 3.11.16 --- ...d-cflags-for-dtrace-systemtap-builds.patch | 6 +- ..._seterror-handling-ssl_error_syscall.patch | 10 +- 00478-cve-2026-4519.patch | 121 --------------- 00479-cve-2026-1502.patch | 106 ------------- 00480-cve-2026-4786.patch | 63 -------- 00482-cve-2026-6100.patch | 48 ------ 00483-cve-2026-2297.patch | 33 ---- 00484-cve-2026-3644.patch | 146 ------------------ 00485-cve-2026-4224.patch | 98 ------------ 00489-openssl-3.5.7.patch | 75 --------- ...udp-lite-tests-if-it-s-not-supported.patch | 64 -------- ...-test_large_content_length_truncated.patch | 23 +++ python3.11.spec | 70 ++------- sources | 4 +- 14 files changed, 42 insertions(+), 825 deletions(-) delete mode 100644 00478-cve-2026-4519.patch delete mode 100644 00479-cve-2026-1502.patch delete mode 100644 00480-cve-2026-4786.patch delete mode 100644 00482-cve-2026-6100.patch delete mode 100644 00483-cve-2026-2297.patch delete mode 100644 00484-cve-2026-3644.patch delete mode 100644 00485-cve-2026-4224.patch delete mode 100644 00489-openssl-3.5.7.patch delete mode 100644 00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch create mode 100644 00494-increase-the-timeout-of-test_large_content_length_truncated.patch diff --git a/00452-properly-apply-exported-cflags-for-dtrace-systemtap-builds.patch b/00452-properly-apply-exported-cflags-for-dtrace-systemtap-builds.patch index 1417bdb..3fea025 100644 --- a/00452-properly-apply-exported-cflags-for-dtrace-systemtap-builds.patch +++ b/00452-properly-apply-exported-cflags-for-dtrace-systemtap-builds.patch @@ -20,10 +20,10 @@ Co-authored-by: stratakis create mode 100644 Misc/NEWS.d/next/Build/2025-03-31-19-22-41.gh-issue-131865.PIJy7X.rst diff --git a/Makefile.pre.in b/Makefile.pre.in -index 81d4d50f82..0bd3447638 100644 +index ad29e1a0ca..79b07194bb 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in -@@ -1453,7 +1453,7 @@ Python/frozen.o: $(FROZEN_FILES_OUT) +@@ -1458,7 +1458,7 @@ Python/frozen.o: $(FROZEN_FILES_OUT) # an include guard, so we can't use a pipeline to transform its output. Include/pydtrace_probes.h: $(srcdir)/Include/pydtrace.d $(MKDIR_P) Include @@ -32,7 +32,7 @@ index 81d4d50f82..0bd3447638 100644 : sed in-place edit with POSIX-only tools sed 's/PYTHON_/PyDTrace_/' $@ > $@.tmp mv $@.tmp $@ -@@ -1463,7 +1463,7 @@ Python/import.o: $(srcdir)/Include/pydtrace.h +@@ -1468,7 +1468,7 @@ Python/import.o: $(srcdir)/Include/pydtrace.h Modules/gcmodule.o: $(srcdir)/Include/pydtrace.h Python/pydtrace.o: $(srcdir)/Include/pydtrace.d $(DTRACE_DEPS) diff --git a/00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch b/00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch index 0016e57..e9b2851 100644 --- a/00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch +++ b/00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch @@ -22,10 +22,10 @@ Co-authored-by: Petr Viktorin create mode 100644 Misc/NEWS.d/next/Library/2024-02-18-09-50-31.gh-issue-115627.HGchj0.rst diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py -index 0b169c37d5..921c41bd0d 100644 +index aa8ce81db6..61cde99753 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py -@@ -2633,16 +2633,18 @@ def run(self): +@@ -2635,16 +2635,18 @@ def run(self): self.write(msg.lower()) except OSError as e: # handles SSLError and socket errors @@ -53,7 +53,7 @@ index 0b169c37d5..921c41bd0d 100644 try: self.write(b"ERROR\n") except OSError: -@@ -3337,8 +3339,8 @@ def test_wrong_cert_tls13(self): +@@ -3339,8 +3341,8 @@ def test_wrong_cert_tls13(self): suppress_ragged_eofs=False) as s: s.connect((HOST, server.port)) with self.assertRaisesRegex( @@ -64,7 +64,7 @@ index 0b169c37d5..921c41bd0d 100644 ): # TLS 1.3 perform client cert exchange after handshake s.write(b'data') -@@ -4610,8 +4612,8 @@ def msg_cb(conn, direction, version, content_type, msg_type, data): +@@ -4612,8 +4614,8 @@ def msg_cb(conn, direction, version, content_type, msg_type, data): # test sometimes fails with EOF error. Test passes as long as # server aborts connection with an error. with self.assertRaisesRegex( @@ -84,7 +84,7 @@ index 0000000000..75d926ab59 +Fix the :mod:`ssl` module error handling of connection terminate by peer. +It now throws an OSError with the appropriate error code instead of an EOFError. diff --git a/Modules/_ssl.c b/Modules/_ssl.c -index 6275d94d64..ee7b131920 100644 +index 9c2e8c391d..174e5dfce5 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -576,7 +576,7 @@ PySSL_ChainExceptions(PySSLSocket *sslsock) { diff --git a/00478-cve-2026-4519.patch b/00478-cve-2026-4519.patch deleted file mode 100644 index eb9d2ef..0000000 --- a/00478-cve-2026-4519.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: tomcruiseqi -Date: Wed, 25 Mar 2026 02:23:28 +0800 -Subject: 00478: CVE-2026-4519 - -Reject leading dashes in webbrowser URLs (GH-143931) (GH-146364) - -(cherry picked from commit 82a24a4442312bdcfc4c799885e8b3e00990f02b) - -Co-authored-by: Seth Michael Larson ---- - 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 9d608d63a0..0ac985f56c 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 5d72524c08..0fd0aeb3c1 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: -@@ -689,6 +702,7 @@ def _name(self, val): - - def open(self, url, new=0, autoraise=True): - sys.audit("webbrowser.open", url) -+ 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 0000000000..0f27eae99a ---- /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 --git a/00479-cve-2026-1502.patch b/00479-cve-2026-1502.patch deleted file mode 100644 index b6ce808..0000000 --- a/00479-cve-2026-1502.patch +++ /dev/null @@ -1,106 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Seth Larson -Date: Fri, 10 Apr 2026 10:21:42 -0500 -Subject: 00479: CVE-2026-1502 - -Reject CR/LF in HTTP tunnel request headers - -Co-authored-by: Illia Volochii ---- - Lib/http/client.py | 11 ++++- - Lib/test/test_httplib.py | 45 +++++++++++++++++++ - ...-03-20-09-29-42.gh-issue-146211.PQVbs7.rst | 2 + - 3 files changed, 57 insertions(+), 1 deletion(-) - create mode 100644 Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst - -diff --git a/Lib/http/client.py b/Lib/http/client.py -index c977612732..8cf783ab30 100644 ---- a/Lib/http/client.py -+++ b/Lib/http/client.py -@@ -941,12 +941,21 @@ def _wrap_ipv6(self, ip): - return ip - - def _tunnel(self): -+ if _contains_disallowed_url_pchar_re.search(self._tunnel_host): -+ raise ValueError('Tunnel host can\'t contain control characters %r' -+ % (self._tunnel_host,)) - connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( - self._wrap_ipv6(self._tunnel_host.encode("ascii")), - self._tunnel_port) - headers = [connect] - for header, value in self._tunnel_headers.items(): -- headers.append(f"{header}: {value}\r\n".encode("latin-1")) -+ header_bytes = header.encode("latin-1") -+ value_bytes = value.encode("latin-1") -+ if not _is_legal_header_name(header_bytes): -+ raise ValueError('Invalid header name %r' % (header_bytes,)) -+ if _is_illegal_header_value(value_bytes): -+ raise ValueError('Invalid header value %r' % (value_bytes,)) -+ headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes)) - headers.append(b"\r\n") - # Making a single send() call instead of one per line encourages - # the host OS to use a more optimal packet size instead of -diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py -index 55363413b3..a4ab909ca9 100644 ---- a/Lib/test/test_httplib.py -+++ b/Lib/test/test_httplib.py -@@ -371,6 +371,51 @@ def test_invalid_headers(self): - with self.assertRaisesRegex(ValueError, 'Invalid header'): - conn.putheader(name, value) - -+ def test_invalid_tunnel_headers(self): -+ cases = ( -+ ('Invalid\r\nName', 'ValidValue'), -+ ('Invalid\rName', 'ValidValue'), -+ ('Invalid\nName', 'ValidValue'), -+ ('\r\nInvalidName', 'ValidValue'), -+ ('\rInvalidName', 'ValidValue'), -+ ('\nInvalidName', 'ValidValue'), -+ (' InvalidName', 'ValidValue'), -+ ('\tInvalidName', 'ValidValue'), -+ ('Invalid:Name', 'ValidValue'), -+ (':InvalidName', 'ValidValue'), -+ ('ValidName', 'Invalid\r\nValue'), -+ ('ValidName', 'Invalid\rValue'), -+ ('ValidName', 'Invalid\nValue'), -+ ('ValidName', 'InvalidValue\r\n'), -+ ('ValidName', 'InvalidValue\r'), -+ ('ValidName', 'InvalidValue\n'), -+ ) -+ for name, value in cases: -+ with self.subTest((name, value)): -+ conn = client.HTTPConnection('example.com') -+ conn.set_tunnel('tunnel', headers={ -+ name: value -+ }) -+ conn.sock = FakeSocket('') -+ with self.assertRaisesRegex(ValueError, 'Invalid header'): -+ conn._tunnel() # Called in .connect() -+ -+ def test_invalid_tunnel_host(self): -+ cases = ( -+ 'invalid\r.host', -+ '\ninvalid.host', -+ 'invalid.host\r\n', -+ 'invalid.host\x00', -+ 'invalid host', -+ ) -+ for tunnel_host in cases: -+ with self.subTest(tunnel_host): -+ conn = client.HTTPConnection('example.com') -+ conn.set_tunnel(tunnel_host) -+ conn.sock = FakeSocket('') -+ with self.assertRaisesRegex(ValueError, 'Tunnel host can\'t contain control characters'): -+ conn._tunnel() # Called in .connect() -+ - def test_headers_debuglevel(self): - body = ( - b'HTTP/1.1 200 OK\r\n' -diff --git a/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst -new file mode 100644 -index 0000000000..4993633b8e ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2026-03-20-09-29-42.gh-issue-146211.PQVbs7.rst -@@ -0,0 +1,2 @@ -+Reject CR/LF characters in tunnel request headers for the -+HTTPConnection.set_tunnel() method. diff --git a/00480-cve-2026-4786.patch b/00480-cve-2026-4786.patch deleted file mode 100644 index a0fcb66..0000000 --- a/00480-cve-2026-4786.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Stan Ulbrych -Date: Mon, 13 Apr 2026 22:41:51 +0100 -Subject: 00480: CVE-2026-4786 - -Fix webbrowser `%action` substitution bypass of dash-prefix check ---- - 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 0ac985f56c..d629f889db 100644 ---- a/Lib/test/test_webbrowser.py -+++ b/Lib/test/test_webbrowser.py -@@ -99,6 +99,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 0fd0aeb3c1..026bdfbd7b 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 0000000000..45cdeebe1b ---- /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 --git a/00482-cve-2026-6100.patch b/00482-cve-2026-6100.patch deleted file mode 100644 index ad4c39b..0000000 --- a/00482-cve-2026-6100.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Stan Ulbrych -Date: Mon, 13 Apr 2026 22:42:36 +0100 -Subject: 00482: CVE-2026-6100 - -Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor ---- - .../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 0000000000..349d1cf3ca ---- /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 798e9efc62..b08ac5e44e 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 97453a2808..51106a6a07 100644 ---- a/Modules/_lzmamodule.c -+++ b/Modules/_lzmamodule.c -@@ -1105,6 +1105,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 --git a/00483-cve-2026-2297.patch b/00483-cve-2026-2297.patch deleted file mode 100644 index b71b19f..0000000 --- a/00483-cve-2026-2297.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Steve Dower -Date: Wed, 4 Mar 2026 19:55:52 +0000 -Subject: 00483: CVE-2026-2297 - -Logging Bypass in Legacy .pyc File Handling ---- - 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 e53f6acf38..588da3c7ad 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 0000000000..edeb9e640c ---- /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 --git a/00484-cve-2026-3644.patch b/00484-cve-2026-3644.patch deleted file mode 100644 index b01be87..0000000 --- a/00484-cve-2026-3644.patch +++ /dev/null @@ -1,146 +0,0 @@ -From 0000000000000000000000000000000000000000 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: 00484: CVE-2026-3644 - -Incomplete control character validation in http.cookies - -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 5cfa7a8072..6b36ffa9f8 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 2438c57ef4..f9a846f8fa 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 0000000000..fc2e503779 ---- /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 --git a/00485-cve-2026-4224.patch b/00485-cve-2026-4224.patch deleted file mode 100644 index cb5a8b5..0000000 --- a/00485-cve-2026-4224.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Stan Ulbrych -Date: Wed, 8 Apr 2026 11:27:39 +0100 -Subject: 00485: CVE-2026-4224 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Stack overflow parsing XML with deeply nested DTD content models - -Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> ---- - 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 9aa2fcedad..8afce3ffe1 100644 ---- a/Lib/test/test_pyexpat.py -+++ b/Lib/test/test_pyexpat.py -@@ -675,6 +675,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 0000000000..cb9dbadb72 ---- /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 7b76ddfabd..13c4e8e0ad 100644 ---- a/Modules/pyexpat.c -+++ b/Modules/pyexpat.c -@@ -3,6 +3,7 @@ - #endif - - #include "Python.h" -+#include "pycore_ceval.h" // _Py_EnterRecursiveCall() - #include "pycore_runtime.h" // _Py_ID() - #include - -@@ -578,6 +579,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; -@@ -589,7 +594,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); - } -@@ -597,6 +602,8 @@ conv_content_model(XML_Content * const model, - model->type, model->quant, - conv_string,model->name, children); - } -+done: -+ _Py_LeaveRecursiveCall(); - return result; - } - diff --git a/00489-openssl-3.5.7.patch b/00489-openssl-3.5.7.patch deleted file mode 100644 index 5ba5e8b..0000000 --- a/00489-openssl-3.5.7.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: David Benjamin -Date: Fri, 24 Mar 2023 09:04:30 -0400 -Subject: 00489: Use BIO_eof to detect EOF for SSL_FILETYPE_ASN1 - -In PEM, we need to parse until error and then suppress `PEM_R_NO_START_LINE`, because PEM allows arbitrary leading and trailing data. DER, however, does not. Parsing until error and suppressing `ASN1_R_HEADER_TOO_LONG` doesn't quite work because that error also covers some cases that should be rejected. - -Instead, check `BIO_eof` early and stop the loop that way. - -This fixes https://github.com/python/cpython/issues/151504 and adds compatibility with OpenSSL 3.5.7+ - -(cherry-picked from commit acfe02f3b05436658d92add6b168538b30f357f0) ---- - Lib/test/test_ssl.py | 2 ++ - .../2022-12-20-10-55-14.gh-issue-100372.utfP65.rst | 2 ++ - Modules/_ssl.c | 10 ++++++---- - 3 files changed, 10 insertions(+), 4 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2022-12-20-10-55-14.gh-issue-100372.utfP65.rst - -diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py -index 921c41bd0d..61cde99753 100644 ---- a/Lib/test/test_ssl.py -+++ b/Lib/test/test_ssl.py -@@ -1559,6 +1559,8 @@ def test_load_verify_cadata(self): - "not enough data: cadata does not contain a certificate" - ): - ctx.load_verify_locations(cadata=b"broken") -+ with self.assertRaises(ssl.SSLError): -+ ctx.load_verify_locations(cadata=cacert_der + b"A") - - @unittest.skipIf(Py_DEBUG_WIN32, "Avoid mixing debug/release CRT on Windows") - def test_load_dh_params(self): -diff --git a/Misc/NEWS.d/next/Library/2022-12-20-10-55-14.gh-issue-100372.utfP65.rst b/Misc/NEWS.d/next/Library/2022-12-20-10-55-14.gh-issue-100372.utfP65.rst -new file mode 100644 -index 0000000000..ec37aff509 ---- /dev/null -+++ b/Misc/NEWS.d/next/Library/2022-12-20-10-55-14.gh-issue-100372.utfP65.rst -@@ -0,0 +1,2 @@ -+:meth:`ssl.SSLContext.load_verify_locations` no longer incorrectly accepts -+some cases of trailing data when parsing DER. -diff --git a/Modules/_ssl.c b/Modules/_ssl.c -index ee7b131920..174e5dfce5 100644 ---- a/Modules/_ssl.c -+++ b/Modules/_ssl.c -@@ -3966,7 +3966,7 @@ _add_ca_certs(PySSLContext *self, const void *data, Py_ssize_t len, - { - BIO *biobuf = NULL; - X509_STORE *store; -- int retval = -1, err, loaded = 0; -+ int retval = -1, err, loaded = 0, was_bio_eof = 0; - - assert(filetype == SSL_FILETYPE_ASN1 || filetype == SSL_FILETYPE_PEM); - -@@ -3994,6 +3994,10 @@ _add_ca_certs(PySSLContext *self, const void *data, Py_ssize_t len, - int r; - - if (filetype == SSL_FILETYPE_ASN1) { -+ if (BIO_eof(biobuf)) { -+ was_bio_eof = 1; -+ break; -+ } - cert = d2i_X509_bio(biobuf, NULL); - } else { - cert = PEM_read_bio_X509(biobuf, NULL, -@@ -4029,9 +4033,7 @@ _add_ca_certs(PySSLContext *self, const void *data, Py_ssize_t len, - } - _setSSLError(get_state_ctx(self), msg, 0, __FILE__, __LINE__); - retval = -1; -- } else if ((filetype == SSL_FILETYPE_ASN1) && -- (ERR_GET_LIB(err) == ERR_LIB_ASN1) && -- (ERR_GET_REASON(err) == ASN1_R_HEADER_TOO_LONG)) { -+ } else if ((filetype == SSL_FILETYPE_ASN1) && was_bio_eof) { - /* EOF ASN1 file, not an error */ - ERR_clear_error(); - retval = 0; diff --git a/00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch b/00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch deleted file mode 100644 index 5dc9a0f..0000000 --- a/00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Victor Stinner -Date: Wed, 13 May 2026 17:27:56 +0200 -Subject: 00491: gh-149776: Skip UDP Lite tests if it's not supported - -Fix test_socket on Linux kernel 7.1 and newer: skip UDP Lite tests if -it's not supported. - -(cherry picked from commit 3cfc249e11a132dc69624150843779aa96c72b2b) -(cherry picked from commit 49d08674d8dba50dc29539e3c7bce21d66066b06) ---- - Lib/test/test_socket.py | 22 ++++++++++++++++++- - ...-05-13-14-53-23.gh-issue-149776.orqgsn.rst | 2 ++ - 2 files changed, 23 insertions(+), 1 deletion(-) - create mode 100644 Misc/NEWS.d/next/Tests/2026-05-13-14-53-23.gh-issue-149776.orqgsn.rst - -diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py -index 5ea8c8e62c..c96185b2e5 100644 ---- a/Lib/test/test_socket.py -+++ b/Lib/test/test_socket.py -@@ -145,6 +145,26 @@ def _have_socket_bluetooth(): - return True - - -+def _have_udp_lite(): -+ if not hasattr(socket, "IPPROTO_UDPLITE"): -+ return False -+ # Older Android versions block UDPLITE with SELinux. -+ if support.is_android and platform.android_ver().api_level < 29: -+ return False -+ -+ try: -+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE) -+ except OSError as exc: -+ # Linux 7.1 removed UDP Lite support -+ if exc.errno == errno.EPROTONOSUPPORT: -+ return False -+ raise -+ sock.close() -+ -+ return True -+ -+ -+ - @contextlib.contextmanager - def socket_setdefaulttimeout(timeout): - old_timeout = socket.getdefaulttimeout() -@@ -169,7 +189,7 @@ def socket_setdefaulttimeout(timeout): - - HAVE_SOCKET_VSOCK = _have_socket_vsock() - --HAVE_SOCKET_UDPLITE = hasattr(socket, "IPPROTO_UDPLITE") -+HAVE_SOCKET_UDPLITE = _have_udp_lite() - - HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth() - -diff --git a/Misc/NEWS.d/next/Tests/2026-05-13-14-53-23.gh-issue-149776.orqgsn.rst b/Misc/NEWS.d/next/Tests/2026-05-13-14-53-23.gh-issue-149776.orqgsn.rst -new file mode 100644 -index 0000000000..e86a9130ff ---- /dev/null -+++ b/Misc/NEWS.d/next/Tests/2026-05-13-14-53-23.gh-issue-149776.orqgsn.rst -@@ -0,0 +1,2 @@ -+Fix test_socket on Linux kernel 7.1 and newer: skip UDP Lite tests if it's -+not supported. Patch by Victor Stinner. diff --git a/00494-increase-the-timeout-of-test_large_content_length_truncated.patch b/00494-increase-the-timeout-of-test_large_content_length_truncated.patch new file mode 100644 index 0000000..229b73a --- /dev/null +++ b/00494-increase-the-timeout-of-test_large_content_length_truncated.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Karolina Surma +Date: Fri, 14 Aug 2026 09:38:26 +0200 +Subject: 00494: Increase the timeout of test_large_content_length_truncated + +It has started to fail randomly when run on s390x architecture. +--- + Lib/test/test_httpservers.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py +index b0b09daab0..59434936b7 100644 +--- a/Lib/test/test_httpservers.py ++++ b/Lib/test/test_httpservers.py +@@ -899,7 +899,7 @@ def test_large_content_length(self): + self.assertEqual(res.read(), b'%d %d' % (size, size) + self.linesep) + + def test_large_content_length_truncated(self): +- with support.swap_attr(self.request_handler, 'timeout', 0.001): ++ with support.swap_attr(self.request_handler, 'timeout', support.LOOPBACK_TIMEOUT): + for w in range(18, 65): + size = 1 << w + headers = {'Content-Length' : str(size)} diff --git a/python3.11.spec b/python3.11.spec index 2faee17..a736a7b 100644 --- a/python3.11.spec +++ b/python3.11.spec @@ -13,11 +13,11 @@ URL: https://www.python.org/ # WARNING When rebasing to a new Python version, # remember to update the python3-docs package as well -%global general_version %{pybasever}.15 +%global general_version %{pybasever}.16 #global prerel ... %global upstream_version %{general_version}%{?prerel} Version: %{general_version}%{?prerel:~%{prerel}} -Release: 7%{?dist} +Release: 1%{?dist} License: Python-2.0.1 @@ -367,66 +367,11 @@ Patch474: 00474-cve-2025-15366.patch # (cherry-picked from commit b234a2b67539f787e191d2ef19a7cbdce32874e7) Patch475: 00475-cve-2025-15367.patch -# 00478 # 40c5c88950b10eaf1c10c5afcc39887b8e23c997 -# CVE-2026-4519 +# 00494 # 430aab133397ed44cc9ee621fd311e02fee317b5 +# Increase the timeout of test_large_content_length_truncated # -# Reject leading dashes in webbrowser URLs (GH-143931) (GH-146364) -Patch478: 00478-cve-2026-4519.patch - -# 00479 # 6fe61dd71dec5b7c9de2b1994172981667d034a1 -# CVE-2026-1502 -# -# Reject CR/LF in HTTP tunnel request headers -Patch479: 00479-cve-2026-1502.patch - -# 00480 # 9f4b1483ecfbc8c08117133c239fba544fcb42e7 -# CVE-2026-4786 -# -# Fix webbrowser `%%action` substitution bypass of dash-prefix check -Patch480: 00480-cve-2026-4786.patch - -# 00482 # 2a21454e658935990766df8c3c48af9363e8422a -# CVE-2026-6100 -# -# Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor -Patch482: 00482-cve-2026-6100.patch - -# 00483 # cdb097a23eac5a09fb063a0e91001f69ff324205 -# CVE-2026-2297 -# -# Logging Bypass in Legacy .pyc File Handling -Patch483: 00483-cve-2026-2297.patch - -# 00484 # cf0bd2f2cce15cb35558aa08de34e9d18a8089f7 -# CVE-2026-3644 -# -# Incomplete control character validation in http.cookies -Patch484: 00484-cve-2026-3644.patch - -# 00485 # 54d821ba2f5a03ccced037978fcdb0a7c4d6878f -# CVE-2026-4224 -# -# Stack overflow parsing XML with deeply nested DTD content models -Patch485: 00485-cve-2026-4224.patch - -# 00489 # 008af720a5f6f98ed3feb8ebdbf88ab9dea4db22 -# Use BIO_eof to detect EOF for SSL_FILETYPE_ASN1 -# -# In PEM, we need to parse until error and then suppress `PEM_R_NO_START_LINE`, because PEM allows arbitrary leading and trailing data. DER, however, does not. Parsing until error and suppressing `ASN1_R_HEADER_TOO_LONG` doesn't quite work because that error also covers some cases that should be rejected. -# -# Instead, check `BIO_eof` early and stop the loop that way. -# -# This fixes https://github.com/python/cpython/issues/151504 and adds compatibility with OpenSSL 3.5.7+ -# -# (cherry-picked from commit acfe02f3b05436658d92add6b168538b30f357f0) -Patch489: 00489-openssl-3.5.7.patch - -# 00491 # ac14737379922303720216b61803474c84f291ef -# gh-149776: Skip UDP Lite tests if it's not supported -# -# Fix test_socket on Linux kernel 7.1 and newer: skip UDP Lite tests if -# it's not supported. -Patch491: 00491-gh-149776-skip-udp-lite-tests-if-it-s-not-supported.patch +# It has started to fail randomly when run on s390x architecture. +Patch494: 00494-increase-the-timeout-of-test_large_content_length_truncated.patch # (New patches go here ^^^) # @@ -1760,6 +1705,9 @@ CheckPython optimized # ====================================================== %changelog +* Thu Aug 13 2026 Karolina Surma - 3.11.16-1 +- Update to Python 3.11.16 + * Thu Jul 30 2026 Miro Hrončok - 3.11.15-7 - Skip UDP Lite tests if it's not supported - Fixes FTBFS on Linux kernel 7.1 and newer diff --git a/sources b/sources index c0c09ac..737c40b 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (Python-3.11.15.tar.xz) = 1667f4839acfd23d05917a12993983b7dc3ab4438a37c72496fd040ac6981f4dea9dc95461ca701a6862beedb56c423fb83398b8b18e39e1efe12f539fced1c1 -SHA512 (Python-3.11.15.tar.xz.asc) = 673822ca037acb521bf7d3e7433f83cc2d60c8d102cfbf6695234e2a50ce12a7003040ad4017438510ebbb1ff0ba6727253fae250b07bc0e3d24b5578e80be52 +SHA512 (Python-3.11.16.tar.xz) = f4e168d35596c2df080663d8e8b3472f03bace987d46b49b8410f2425ba193215b9880c7d03d4653ec31c83b72209d95866fc1cb6d666799145075b092f64a48 +SHA512 (Python-3.11.16.tar.xz.asc) = b31b3205e68951478fe76f4884a124ebb4955a711b6105754d4179acb48b16a5439ec8c35383a84787cd673fa1143f2f047c6d65944909b0f1a8e3c75a0e2efe