import CS git python3.12-3.12.13-2.el8_10

This commit is contained in:
AlmaLinux RelEng Bot 2026-04-27 12:11:58 -04:00
parent 339712fca7
commit bb8e1876d4
15 changed files with 576 additions and 557 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
SOURCES/Python-3.12.12.tar.xz
SOURCES/Python-3.12.13.tar.xz

View File

@ -1 +1 @@
dbe6dc34a132b1035c121583a9f37ba87458d0f5 SOURCES/Python-3.12.12.tar.xz
ad3e9c333d91bee73f1d5f4a6fe6e88f2e74d911 SOURCES/Python-3.12.13.tar.xz

View File

@ -1,140 +0,0 @@
From 9c9dda6625a2a90d2a06c657eee021d6be19842d 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: [PATCH] [3.12] gh-142145: Remove quadratic behavior in node ID cache
clearing (GH-142146) (#142211)
* 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 <seth@python.org>
Co-authored-by: Petr Viktorin <encukou@gmail.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: 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 <greg@krypto.org>
---
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 699265ccadc7f9..ab4823c8315e57 100644
--- a/Lib/test/test_minidom.py
+++ b/Lib/test/test_minidom.py
@@ -2,13 +2,14 @@
import copy
import pickle
+import time
import io
from test import support
import unittest
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
@@ -176,6 +177,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 ef8a159833bbc0..cada981f39f3ee 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 00000000000000..05c7df35d14bef
--- /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.

View File

@ -1,159 +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:50:18 +0100
Subject: 00472: CVE-2025-13836
[3.12] gh-119451: Fix a potential denial of service in http.client (GH-119454) (#142140)
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 <storchaka@gmail.com>
---
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 fb29923d94..70451d67d4 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
@@ -639,10 +644,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 01f5a10190..e46dac0077 100644
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -1452,6 +1452,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.

View File

@ -1,90 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <seth@python.org>
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 4c42234ccc..26c0af4b13 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -2599,3 +2599,10 @@ def __iter__(self):
if self.iter_raises:
1/0
return self
+
+
+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.

View File

@ -1,110 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
<31488909+miss-islington@users.noreply.github.com>
Date: Fri, 13 Feb 2026 17:04:54 +0100
Subject: [PATCH] [3.12] gh-144125: email: verify headers are sound in
BytesGenerator
gh-144125: email: verify headers are sound in BytesGenerator
(cherry picked from commit 052e55e7d44718fe46cbba0ca995cb8fcc359413)
Co-authored-by: Seth Michael Larson <seth@python.org>
Co-authored-by: Denis Ledoux <dle@odoo.com>
Co-authored-by: Denis Ledoux <5822488+beledouxdenis@users.noreply.github.com>
Co-authored-by: Petr Viktorin <302922+encukou@users.noreply.github.com>
Co-authored-by: Bas Bloemsaat <1586868+basbloemsaat@users.noreply.github.com>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
---
Lib/email/generator.py | 12 +++++++++++-
Lib/test/test_email/test_generator.py | 4 +++-
Lib/test/test_email/test_policy.py | 6 +++++-
.../2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst | 4 ++++
4 files changed, 23 insertions(+), 3 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst
diff --git a/Lib/email/generator.py b/Lib/email/generator.py
index 47b9df8f4e6090..8cbc43ef5bc647 100644
--- a/Lib/email/generator.py
+++ b/Lib/email/generator.py
@@ -22,6 +22,7 @@
NLCRE = re.compile(r'\r\n|\r|\n')
fcre = re.compile(r'^From ', re.MULTILINE)
NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
+NEWLINE_WITHOUT_FWSP_BYTES = re.compile(br'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
class Generator:
@@ -429,7 +430,16 @@ def _write_headers(self, msg):
# This is almost the same as the string version, except for handling
# strings with 8bit bytes.
for h, v in msg.raw_items():
- self._fp.write(self.policy.fold_binary(h, v))
+ folded = self.policy.fold_binary(h, v)
+ if self.policy.verify_generated_headers:
+ linesep = self.policy.linesep.encode()
+ if not folded.endswith(linesep):
+ raise HeaderWriteError(
+ f'folded header does not end with {linesep!r}: {folded!r}')
+ if NEWLINE_WITHOUT_FWSP_BYTES.search(folded.removesuffix(linesep)):
+ raise HeaderWriteError(
+ f'folded header contains newline: {folded!r}')
+ self._fp.write(folded)
# A blank line always separates headers from body
self.write(self._NL)
diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py
index c75a842c33578e..3ca79edf6a65d9 100644
--- a/Lib/test/test_email/test_generator.py
+++ b/Lib/test/test_email/test_generator.py
@@ -313,7 +313,7 @@ def test_flatten_unicode_linesep(self):
self.assertEqual(s.getvalue(), self.typ(expected))
def test_verify_generated_headers(self):
- """gh-121650: by default the generator prevents header injection"""
+ # gh-121650: by default the generator prevents header injection
class LiteralHeader(str):
name = 'Header'
def fold(self, **kwargs):
@@ -334,6 +334,8 @@ def fold(self, **kwargs):
with self.assertRaises(email.errors.HeaderWriteError):
message.as_string()
+ with self.assertRaises(email.errors.HeaderWriteError):
+ message.as_bytes()
class TestBytesGenerator(TestGeneratorBase, TestEmailBase):
diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py
index baa35fd68e49c5..71ec0febb0fd86 100644
--- a/Lib/test/test_email/test_policy.py
+++ b/Lib/test/test_email/test_policy.py
@@ -296,7 +296,7 @@ def test_short_maxlen_error(self):
policy.fold("Subject", subject)
def test_verify_generated_headers(self):
- """Turning protection off allows header injection"""
+ # Turning protection off allows header injection
policy = email.policy.default.clone(verify_generated_headers=False)
for text in (
'Header: Value\r\nBad: Injection\r\n',
@@ -319,6 +319,10 @@ def fold(self, **kwargs):
message.as_string(),
f"{text}\nBody",
)
+ self.assertEqual(
+ message.as_bytes(),
+ f"{text}\nBody".encode(),
+ )
# XXX: Need subclassing tests.
# For adding subclassed objects, make sure the usual rules apply (subclass
diff --git a/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst b/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst
new file mode 100644
index 00000000000000..e6333e724972c5
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst
@@ -0,0 +1,4 @@
+:mod:`~email.generator.BytesGenerator` will now refuse to serialize (write) headers
+that are unsafely folded or delimited; see
+:attr:`~email.policy.Policy.verify_generated_headers`. (Contributed by Bas
+Bloemsaat and Petr Viktorin in :gh:`121650`).

View File

@ -0,0 +1,107 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Seth Larson <seth@python.org>
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 <illia.volochii@gmail.com>
---
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 70451d67d4..7db4807b30 100644
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -972,13 +972,22 @@ 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 %s\r\n" % (
self._wrap_ipv6(self._tunnel_host.encode("idna")),
self._tunnel_port,
self._http_vsn_str.encode("ascii"))
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 e46dac0077..e027d930d9 100644
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -369,6 +369,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.

View File

@ -0,0 +1,64 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Stan Ulbrych <stan@python.org>
Date: Mon, 13 Apr 2026 20:02:52 +0100
Subject: 00480: CVE-2026-4786
Fix webbrowser `%action` substitution bypass of dash-prefix check
---
Lib/test/test_webbrowser.py | 9 +++++++++
Lib/webbrowser.py | 5 +++--
.../2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst | 2 ++
3 files changed, 14 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 60f094fd6a..e900c0212b 100644
--- a/Lib/test/test_webbrowser.py
+++ b/Lib/test/test_webbrowser.py
@@ -99,6 +99,15 @@ 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 EdgeCommandTest(CommandTestMixin, unittest.TestCase):
diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py
index 0bdb644d7d..79d410bcae 100755
--- a/Lib/webbrowser.py
+++ b/Lib/webbrowser.py
@@ -268,7 +268,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:
@@ -282,7 +281,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.

View File

@ -0,0 +1,61 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Stan Ulbrych <stan@python.org>
Date: Mon, 13 Apr 2026 02:14:54 +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 +
Modules/zlibmodule.c | 1 +
4 files changed, 8 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..9502189ab1
--- /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`,
+:class:`bz2.BZ2Decompressor`, and internal :class:`!zlib._ZlibDecompressor`
+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 97bd44b4ac..a732e89d55 100644
--- a/Modules/_bz2module.c
+++ b/Modules/_bz2module.c
@@ -587,6 +587,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 7bbd6569aa..103a6ef86c 100644
--- a/Modules/_lzmamodule.c
+++ b/Modules/_lzmamodule.c
@@ -1114,6 +1114,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/Modules/zlibmodule.c b/Modules/zlibmodule.c
index f94c57e4c8..9759593b6a 100644
--- a/Modules/zlibmodule.c
+++ b/Modules/zlibmodule.c
@@ -1645,6 +1645,7 @@ decompress(ZlibDecompressor *self, uint8_t *data,
return result;
error:
+ self->zst.next_in = NULL;
Py_XDECREF(result);
return NULL;
}

View File

@ -0,0 +1,33 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Steve Dower <steve.dower@python.org>
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 9b8a8dfc5a..6e4a087a10 100644
--- a/Lib/importlib/_bootstrap_external.py
+++ b/Lib/importlib/_bootstrap_external.py
@@ -1186,7 +1186,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..dcdb44d4fa
--- /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.

View File

@ -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 <victor.stinner@gmail.com>
---
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 d0a69cbe19..63d119ad46 100644
--- a/Lib/http/cookies.py
+++ b/Lib/http/cookies.py
@@ -335,9 +335,16 @@ def update(self, values):
key = key.lower()
if key not in self._reserved:
raise CookieError("Invalid attribute %r" % (key,))
+ if _has_control_character(key, val):
+ raise CookieError("Control characters are not allowed in "
+ f"cookies {key!r} {val!r}")
data[key] = val
dict.update(self, data)
+ def __ior__(self, values):
+ self.update(values)
+ return self
+
def isReservedKey(self, K):
return K.lower() in self._reserved
@@ -363,9 +370,15 @@ def __getstate__(self):
}
def __setstate__(self, state):
- self._key = state['key']
- self._value = state['value']
- self._coded_value = state['coded_value']
+ key = state['key']
+ value = state['value']
+ coded_value = state['coded_value']
+ if _has_control_character(key, value, coded_value):
+ raise CookieError("Control characters are not allowed in cookies "
+ f"{key!r} {value!r} {coded_value!r}")
+ self._key = key
+ self._value = value
+ self._coded_value = coded_value
def output(self, attrs=None, header="Set-Cookie:"):
return "%s %s" % (header, self.OutputString(attrs))
@@ -377,13 +390,16 @@ def __repr__(self):
def js_output(self, attrs=None):
# Print javascript
+ output_string = self.OutputString(attrs)
+ if _has_control_character(output_string):
+ raise CookieError("Control characters are not allowed in cookies")
return """
<script type="text/javascript">
<!-- begin hiding
document.cookie = \"%s\";
// end hiding -->
</script>
- """ % (self.OutputString(attrs).replace('"', r'\"'))
+ """ % (output_string.replace('"', r'\"'))
def OutputString(self, attrs=None):
# Build up our result
diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py
index f196bcc48e..2478a6c630 100644
--- a/Lib/test/test_http_cookies.py
+++ b/Lib/test/test_http_cookies.py
@@ -573,6 +573,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)
@@ -587,6 +595,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.
@@ -607,6 +627,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..e53a932d12
--- /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`.

View File

@ -0,0 +1,98 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Date: Sun, 15 Mar 2026 21:46:06 +0000
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 38f951573f..37d9086f40 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'<!DOCTYPE root [\n<!ELEMENT root '
+ + b'(a, ' * N + b'a' + b')' * N
+ + b'>\n]>\n<root/>\n'
+ )
+
+ parser = expat.ParserCreate()
+ parser.ElementDeclHandler = lambda _1, _2: None
+ with support.infinite_recursion():
+ with self.assertRaises(RecursionError):
+ parser.Parse(data)
+
+
class MalformedInputTest(unittest.TestCase):
def test1(self):
xml = b"\0\r\n"
diff --git a/Misc/NEWS.d/next/Security/2026-03-14-17-31-39.gh-issue-145986.ifSSr8.rst b/Misc/NEWS.d/next/Security/2026-03-14-17-31-39.gh-issue-145986.ifSSr8.rst
new file mode 100644
index 0000000000..79536d1fef
--- /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 79492ca5c4..8673540f35 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 <ctype.h>
@@ -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;
}

View File

@ -1,18 +0,0 @@
-----BEGIN PGP SIGNATURE-----
iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmjnnr1fFIAAAAAALgAo
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx
Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6
YwXF3Q//VrreGa+P8lvp9UMjoj/YquKPwLqjzzAWf5vzHipkebdiESsB1HfGu04k
Jw+ctTnXHf/12u0W7ijv+56JtcJFqEzh8yGokWqOzc99rpCeCY9qtuwaVYtZrTNx
wepRaDAHdhP4Z2kLPDiE6pCXu2NIR5wHqHjQ8JGmprhASc07uxEhNN/gucVR2Sbr
cCfC9rHfHkdhoPpZRRbcraAaxPGL3VyBXf7HuYbHhf4GuF9EVDlFg5I0BzHCKJDd
ebPXYHvsoDgrMMqPXiX/YkGNByf3Ze6KZTNSGICy8SDzIzZgpmtOe5rzvlOXJBZZ
SVfX8SqP4Ufml+MfJrGEx30S9reYYvnyTSmttpbDznonROKPEZOuDt08+CG3yR+T
o5RdIneWmGXRf1mBrFKH9Br5tfOd+YeldfxdoQgla2fFHFVRnab1lsZFOC/HZ5z2
Q3rPfVMDYKO8yoIKqv0BUzlkn9wYphCWoPHq0Y+SGjcP+Zh5qRTMqZYIaGekhWmx
86egHHVqedMI0Q9hvgIEirupVJ1q34FZn2+3sEka9hdOie9aNHXWTmgWCGDm46qj
qC9tT/jkMzWIY2Y4RdVDMdSCb7HkBEl1eAANq511gJ+eSWAXbP1sVrQoiAQY+EkC
Yu2ceZYsl9i6zm7i/QaU/mOGB7xMZhMQLZBnZTHSzAZo/pBN7y8=
=RuLK
-----END PGP SIGNATURE-----

View File

@ -0,0 +1,18 @@
-----BEGIN PGP SIGNATURE-----
iQKTBAABCgB9FiEEcWlgX2LHUTVtBUomqCHmgOX6YwUFAmmm3hlfFIAAAAAALgAo
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDcx
Njk2MDVGNjJDNzUxMzU2RDA1NEEyNkE4MjFFNjgwRTVGQTYzMDUACgkQqCHmgOX6
YwW/NA/+MsD99kuy8nBgrK65H3nVj104QcYaau9qhQ62x5+d7P04sVs8bf0mo/AM
zec7eCTHnwoLVborEiqyd5NPRUFNJRfsIn0g4z0PIWRBaXsPztMT4suQdNw+VOTd
3+up8Um0cA2tnesiXZvT5mXY/2kjffcosP/D4OjdADEGYcUawgNeY4LQi9sTOzs6
ubI+mrtSqlAzgKM5NeVj2LdXPeLhpFz4TPk5GTlp0N9p5I1/7ZjK6eLw9p1gPZNN
xzN84JL191a9DybGJXIEJ6AcN5TxuBwXHV/KlnEK57AGj1WS5VOhKbHGoSwpRU5m
BAtCfJj0zBVqME9W/rROR5mxhta/kG71594STPhXKppyQaH+f41lseNf32So0O+w
PdCVebFJgNnCM8Sl+NMrfqiVKW17bAwk4POtbSVqALq3Y2mseDUr0ggU/ZNgeKHy
bDxPBSWAAI3kE75yZlXrAJYYiEWgc5GMZJQgFqOJfw1nDmfZL626ckyDdsJotiJO
6pZKmFqLlTPkXzw1t45z8/ZI8p/oWrMnTS/IGMcTobXcWHEBvbn9Ljt6nBLrZgO6
67vTGss5KhjvfyKwNyQGkG+KIoUJLoewFJ6l5okZB5JIALYn1f/P3bl/kJPUDNp4
eP2ao9kSYBBOwutUe1lH8tQYrNGA0C80hPykvbWDHcvF13J5k2E=
=KNz3
-----END PGP SIGNATURE-----

View File

@ -16,11 +16,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}.12
%global general_version %{pybasever}.13
#global prerel ...
%global upstream_version %{general_version}%{?prerel}
Version: %{general_version}%{?prerel:~%{prerel}}
Release: 4%{?dist}
Release: 2%{?dist}
License: Python-2.0.1
@ -392,36 +392,6 @@ Patch397: 00397-tarfile-filter.patch
# CVE-2023-52425. Future versions of Expat may be more reactive.
Patch422: 00422-fix-tests-for-xmlpullparser-with-expat-2-6-0.patch
# 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)
Patch471: 00471-CVE-2025-12084.patch
# 00472 # 2ba215eaba508b2cdd7c3acfdf3b9a6e32872274
# CVE-2025-13836
#
# [3.12] gh-119451: Fix a potential denial of service in http.client (GH-119454) (#142140)
#
# 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 # dd705786aa0c1ccfde913858598e34e1f196be2e
# 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
#
@ -438,18 +408,48 @@ Patch474: 00474-cve-2025-15366.patch
# (cherry-picked from commit b234a2b67539f787e191d2ef19a7cbdce32874e7)
Patch475: 00475-cve-2025-15367.patch
# 00476
# CVE-2026-1299
#
# gh-144125: email: verify headers are sound in BytesGenerator
Patch476: 00476-cve-2026-1299.patch
# 00478 # eb93352dc8e31f4d52546b84daad875e6ff7f29e
# CVE-2026-4519
#
# Reject leading dashes in webbrowser URLs (GH-146360)
Patch478: 00478-cve-2026-4519.patch
# 00479 # 97404b2cf62e545c2d41be7ccfed4e74da9ee665
# CVE-2026-1502
#
# Reject CR/LF in HTTP tunnel request headers
Patch479: 00479-cve-2026-1502.patch
# 00480 # 6f4eef3ba4d9818a53698e994550ee8db17a1e2e
# CVE-2026-4786
#
# Fix webbrowser `%%action` substitution bypass of dash-prefix check
Patch480: 00480-cve-2026-4786.patch
# 00482 # 69f14bc306fc62400d45565faa980b77858b9151
# CVE-2026-6100
#
# Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor
Patch482: 00482-cve-2026-6100.patch
# 00483 # 577c595137ce6ff92158ddaf2d7b7ea86437825d
# CVE-2026-2297
#
# Logging Bypass in Legacy .pyc File Handling
Patch483: 00483-cve-2026-2297.patch
# 00484 # 8b5133c1ab17a060cd134bea2a4b6e1831c47fed
# CVE-2026-3644
#
# Incomplete control character validation in http.cookies
Patch484: 00484-cve-2026-3644.patch
# 00485 # 12a5b206676927bcee131ab4f2bd6783d2f5914a
# 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.,
@ -1940,6 +1940,15 @@ fi
# ======================================================
%changelog
* Thu Apr 16 2026 Charalampos Stratakis <cstratak@redhat.com> - 3.12.13-2
- Security fixes for CVE-2026-1502, CVE-2026-4786, CVE-2026-6100, CVE-2026-2297, CVE-2026-3644, CVE-2026-4224
Resolves: RHEL-168130, RHEL-167892
* Thu Apr 16 2026 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.13-1
- Update to 3.12.13
- Security fixes for CVE-2025-6075, CVE-2025-13837, CVE-2025-15282, CVE-2025-59375, CVE-2026-0672
Related: RHEL-168130, RHEL-167892
* Fri Mar 27 2026 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.12-4
- Security fix for CVE-2026-4519
Resolves: RHEL-158029