import UBI python3.12-3.12.13-2.el9_8
This commit is contained in:
parent
6a330e606b
commit
080b272fc1
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
|||||||
SOURCES/Python-3.12.12.tar.xz
|
SOURCES/Python-3.12.13.tar.xz
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
dbe6dc34a132b1035c121583a9f37ba87458d0f5 SOURCES/Python-3.12.12.tar.xz
|
ad3e9c333d91bee73f1d5f4a6fe6e88f2e74d911 SOURCES/Python-3.12.13.tar.xz
|
||||||
|
|||||||
@ -84,7 +84,7 @@ index 0000000000..75d926ab59
|
|||||||
+Fix the :mod:`ssl` module error handling of connection terminate by peer.
|
+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.
|
+It now throws an OSError with the appropriate error code instead of an EOFError.
|
||||||
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
|
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
|
||||||
index 0b8cf0b6df..42a4c95890 100644
|
index aae4dc323d..27dd7bbe11 100644
|
||||||
--- a/Modules/_ssl.c
|
--- a/Modules/_ssl.c
|
||||||
+++ b/Modules/_ssl.c
|
+++ b/Modules/_ssl.c
|
||||||
@@ -573,7 +573,7 @@ PySSL_ChainExceptions(PySSLSocket *sslsock) {
|
@@ -573,7 +573,7 @@ PySSL_ChainExceptions(PySSLSocket *sslsock) {
|
||||||
|
|||||||
@ -1,139 +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 <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 699265ccad..ab4823c831 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 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.
|
|
||||||
@ -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.
|
|
||||||
@ -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.
|
|
||||||
@ -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`).
|
|
||||||
107
SOURCES/00479-cve-2026-1502.patch
Normal file
107
SOURCES/00479-cve-2026-1502.patch
Normal 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.
|
||||||
33
SOURCES/00483-cve-2026-2297.patch
Normal file
33
SOURCES/00483-cve-2026-2297.patch
Normal 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.
|
||||||
146
SOURCES/00484-cve-2026-3644.patch
Normal file
146
SOURCES/00484-cve-2026-3644.patch
Normal 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`.
|
||||||
98
SOURCES/00485-cve-2026-4224.patch
Normal file
98
SOURCES/00485-cve-2026-4224.patch
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
@ -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-----
|
|
||||||
18
SOURCES/Python-3.12.13.tar.xz.asc
Normal file
18
SOURCES/Python-3.12.13.tar.xz.asc
Normal 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-----
|
||||||
@ -16,11 +16,11 @@ URL: https://www.python.org/
|
|||||||
|
|
||||||
# WARNING When rebasing to a new Python version,
|
# WARNING When rebasing to a new Python version,
|
||||||
# remember to update the python3-docs package as well
|
# remember to update the python3-docs package as well
|
||||||
%global general_version %{pybasever}.12
|
%global general_version %{pybasever}.13
|
||||||
#global prerel ...
|
#global prerel ...
|
||||||
%global upstream_version %{general_version}%{?prerel}
|
%global upstream_version %{general_version}%{?prerel}
|
||||||
Version: %{general_version}%{?prerel:~%{prerel}}
|
Version: %{general_version}%{?prerel:~%{prerel}}
|
||||||
Release: 4%{?dist}.3
|
Release: 2%{?dist}
|
||||||
License: Python-2.0.1
|
License: Python-2.0.1
|
||||||
|
|
||||||
|
|
||||||
@ -402,36 +402,6 @@ Patch422: 00422-fix-tests-for-xmlpullparser-with-expat-2-6-0.patch
|
|||||||
# stressed on OpenSSL 3.5.
|
# stressed on OpenSSL 3.5.
|
||||||
Patch462: 00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch
|
Patch462: 00462-fix-pyssl_seterror-handling-ssl_error_syscall.patch
|
||||||
|
|
||||||
# 00471 # 37c05f26d11e8e24f2a760167015a267996b1d69
|
|
||||||
# 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
|
# 00474 # 837ddca0372fa87ff9cee47142200caa21e77def
|
||||||
# CVE-2025-15366
|
# CVE-2025-15366
|
||||||
#
|
#
|
||||||
@ -448,18 +418,18 @@ Patch474: 00474-cve-2025-15366.patch
|
|||||||
# (cherry-picked from commit b234a2b67539f787e191d2ef19a7cbdce32874e7)
|
# (cherry-picked from commit b234a2b67539f787e191d2ef19a7cbdce32874e7)
|
||||||
Patch475: 00475-cve-2025-15367.patch
|
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
|
# 00478 # eb93352dc8e31f4d52546b84daad875e6ff7f29e
|
||||||
# CVE-2026-4519
|
# CVE-2026-4519
|
||||||
#
|
#
|
||||||
# Reject leading dashes in webbrowser URLs (GH-146360)
|
# Reject leading dashes in webbrowser URLs (GH-146360)
|
||||||
Patch478: 00478-cve-2026-4519.patch
|
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
|
# 00480 # 6f4eef3ba4d9818a53698e994550ee8db17a1e2e
|
||||||
# CVE-2026-4786
|
# CVE-2026-4786
|
||||||
#
|
#
|
||||||
@ -472,6 +442,24 @@ Patch480: 00480-cve-2026-4786.patch
|
|||||||
# Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor
|
# Fix a possible UAF in {LZMA,BZ2,_Zlib}Decompressor
|
||||||
Patch482: 00482-cve-2026-6100.patch
|
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 ^^^)
|
# (New patches go here ^^^)
|
||||||
#
|
#
|
||||||
# When adding new patches to "python" and "python3" in Fedora, EL, etc.,
|
# When adding new patches to "python" and "python3" in Fedora, EL, etc.,
|
||||||
@ -1787,17 +1775,28 @@ CheckPython optimized
|
|||||||
# ======================================================
|
# ======================================================
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* Thu Apr 16 2026 Charalampos Stratakis <cstratak@redhat.com> - 3.12.12-4.3
|
* Thu Apr 16 2026 Charalampos Stratakis <cstratak@redhat.com> - 3.12.13-2
|
||||||
- Security fixes for CVE-2026-4786, CVE-2026-6100
|
- Security fixes for CVE-2026-1502, CVE-2026-4786, CVE-2026-6100, CVE-2026-2297, CVE-2026-3644, CVE-2026-4224
|
||||||
Resolves: RHEL-168156, RHEL-167914
|
Resolves: RHEL-168159, RHEL-167917
|
||||||
|
|
||||||
* Fri Mar 27 2026 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.12-4.2
|
* 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-168159, RHEL-167917
|
||||||
|
|
||||||
|
* Fri Mar 27 2026 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.12-7
|
||||||
- Security fix for CVE-2026-4519
|
- Security fix for CVE-2026-4519
|
||||||
Resolves: RHEL-158051
|
Resolves: RHEL-158126
|
||||||
|
|
||||||
* Fri Feb 27 2026 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.12-4.1
|
* Mon Mar 09 2026 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.12-6
|
||||||
|
- Rebuilding previous fixes for different build target
|
||||||
|
Related: RHEL-143112, RHEL-143172, RHEL-144895
|
||||||
|
|
||||||
|
* Fri Feb 27 2026 Tomáš Hrnčiar <thrnciar@redhat.com> - 3.12.12-5
|
||||||
- Security fixes for CVE-2026-0865, CVE-2025-15366, CVE-2025-15367 and CVE-2026-1299
|
- Security fixes for CVE-2026-0865, CVE-2025-15366, CVE-2025-15367 and CVE-2026-1299
|
||||||
Resolves: RHEL-143106 RHEL-143168 RHEL-144891
|
Resolves: RHEL-143112
|
||||||
|
Resolves: RHEL-143172
|
||||||
|
Resolves: RHEL-144895
|
||||||
|
|
||||||
* Mon Jan 19 2026 Lumír Balhar <lbalhar@redhat.com> - 3.12.12-4
|
* Mon Jan 19 2026 Lumír Balhar <lbalhar@redhat.com> - 3.12.12-4
|
||||||
- Release bump to correct the build for wrong release
|
- Release bump to correct the build for wrong release
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user