7
0
forked from pool/python311

7 Commits

Author SHA256 Message Date
162a9695a4 Fix CVE-2025-13836, CVE-2025-12084, and CVE-2025-13837.
- Add CVE-2025-13836-http-resp-cont-len.patch (bsc#1254400,
  CVE-2025-13836) to prevent reading an HTTP response from
  a server, if no read amount is specified, with using
  Content-Length per default as the length.
- Add CVE-2025-12084-minidom-quad-search.patch prevent quadratic
  behavior in node ID cache clearing (CVE-2025-12084,
  bsc#1254997).
- Add CVE-2025-13837-plistlib-mailicious-length.patch protect
  against OOM when loading malicious content (CVE-2025-13837,
  bsc#1254401).
2025-12-19 23:09:22 +01:00
48953809e4 Add CVE-2025-6075-expandvars-perf-degrad.patch
Avoid simple quadratic complexity vulnerabilities of os.path.expandvars()
  (CVE-2025-6075, bsc#1252974).
Readjusted patches:
  - CVE-2023-52425-libexpat-2.6.0-backport.patch
  - CVE-2023-52425-remove-reparse_deferral-tests.patch
  - fix_configure_rst.patch
  - skip_if_buildbot-extend.patch
2025-11-20 23:16:35 +01:00
06d1a72674 Mark the upgrade to 3.11.14 as fixing CVE-2025-8291, bsc#1251305. 2025-11-12 00:49:37 +01:00
c61cd14450 Accepting request 1311759 from devel:languages:python:Factory
OBS-URL: https://build.opensuse.org/request/show/1311759
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python311?expand=0&rev=60
2025-10-17 15:25:27 +00:00
c3b20ea06a - Update to 3.11.14:
- Security
    - gh-139700: Check consistency of the zip64 end of central
      directory record. Support records with “zip64 extensible data”
      if there are no bytes prepended to the ZIP file.
    - gh-139400: xml.parsers.expat: Make sure that parent Expat
      parsers are only garbage-collected once they are no longer
      referenced by subparsers created by
      ExternalEntityParserCreate(). Patch by Sebastian Pipping.
    - gh-135661: Fix parsing start and end tags in
      html.parser.HTMLParser according to the HTML5 standard.
      * Whitespaces no longer accepted between </ and the tag name. E.g.
        </ script> does not end the script section.
      * Vertical tabulation (\v) and non-ASCII whitespaces no longer
        recognized as whitespaces. The only whitespaces are \t\n\r\f and
        space.
      * Null character (U+0000) no longer ends the tag name.
      * Attributes and slashes after the tag name in end tags are now
        ignored, instead of terminating after the first > in quoted
        attribute value. E.g. </script/foo=">"/>.
      * Multiple slashes and whitespaces between the last attribute and
        closing > are now ignored in both start and end tags. E.g. <a
        foo=bar/ //>.
      * Multiple = between attribute name and value are no longer
        collapsed. E.g. <a foo==bar> produces attribute “foo” with value
        “=bar”.
    - gh-135661: Fix CDATA section parsing in html.parser.HTMLParser
      according to the HTML5 standard: ] ]> and ]] > no longer end the
      CDATA section. Add private method _set_support_cdata() which can
      be used to specify how to parse <[CDATA[ — as a CDATA section in

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python311?expand=0&rev=199
2025-10-16 16:27:30 +00:00
4fd1005be3 Accepting request 1308253 from devel:languages:python:Factory
- Add gh139257-Support-docutils-0.22.patch to fix build with latest
  docutils (>=0.22) gh#python/cpython#139257

OBS-URL: https://build.opensuse.org/request/show/1308253
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python311?expand=0&rev=59
2025-10-02 17:19:31 +00:00
09df53c2ab - Add gh139257-Support-docutils-0.22.patch to fix build with latest
docutils (>=0.22) gh#python/cpython#139257

OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:Factory/python311?expand=0&rev=197
2025-09-30 16:22:12 +00:00
17 changed files with 966 additions and 453 deletions

View File

@@ -6,10 +6,10 @@
Lib/test/test_xml_etree.py | 12 ------------ Lib/test/test_xml_etree.py | 12 ------------
5 files changed, 37 insertions(+), 44 deletions(-) 5 files changed, 37 insertions(+), 44 deletions(-)
Index: Python-3.11.12/Lib/test/support/__init__.py Index: Python-3.11.14/Lib/test/support/__init__.py
=================================================================== ===================================================================
--- Python-3.11.12.orig/Lib/test/support/__init__.py 2025-04-11 10:52:43.191010503 +0200 --- Python-3.11.14.orig/Lib/test/support/__init__.py 2025-11-15 19:15:08.449938538 +0100
+++ Python-3.11.12/Lib/test/support/__init__.py 2025-04-11 10:52:44.802161741 +0200 +++ Python-3.11.14/Lib/test/support/__init__.py 2025-11-15 19:15:12.859120260 +0100
@@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
import functools import functools
import os import os
@@ -27,7 +27,7 @@ Index: Python-3.11.12/Lib/test/support/__init__.py
] ]
@@ -2244,6 +2245,17 @@ @@ -2279,6 +2280,17 @@
} }
return ignored return ignored
@@ -46,10 +46,10 @@ Index: Python-3.11.12/Lib/test/support/__init__.py
+fails_with_expat_2_6_0 = (unittest.expectedFailure +fails_with_expat_2_6_0 = (unittest.expectedFailure
+ if is_expat_2_6_0 + if is_expat_2_6_0
+ else lambda test: test) + else lambda test: test)
Index: Python-3.11.12/Lib/test/test_minidom.py Index: Python-3.11.14/Lib/test/test_minidom.py
=================================================================== ===================================================================
--- Python-3.11.12.orig/Lib/test/test_minidom.py 2025-04-11 10:52:21.907086938 +0200 --- Python-3.11.14.orig/Lib/test/test_minidom.py 2025-11-15 19:14:53.915952608 +0100
+++ Python-3.11.12/Lib/test/test_minidom.py 2025-04-11 10:52:44.802522893 +0200 +++ Python-3.11.14/Lib/test/test_minidom.py 2025-11-15 19:15:12.859877278 +0100
@@ -6,7 +6,6 @@ @@ -6,7 +6,6 @@
from test import support from test import support
import unittest import unittest
@@ -94,10 +94,10 @@ Index: Python-3.11.12/Lib/test/test_minidom.py
parseString('<element xmlns:abc="http:abc.com/de f g/hi/j k"><abc:foo /></element>') parseString('<element xmlns:abc="http:abc.com/de f g/hi/j k"><abc:foo /></element>')
def testDocRemoveChild(self): def testDocRemoveChild(self):
Index: Python-3.11.12/Lib/test/test_pyexpat.py Index: Python-3.11.14/Lib/test/test_pyexpat.py
=================================================================== ===================================================================
--- Python-3.11.12.orig/Lib/test/test_pyexpat.py 2025-04-11 10:52:22.076696906 +0200 --- Python-3.11.14.orig/Lib/test/test_pyexpat.py 2025-11-15 19:14:53.915952608 +0100
+++ Python-3.11.12/Lib/test/test_pyexpat.py 2025-04-11 10:52:44.803228085 +0200 +++ Python-3.11.14/Lib/test/test_pyexpat.py 2025-11-15 19:15:12.860334045 +0100
@@ -14,8 +14,7 @@ @@ -14,8 +14,7 @@
from xml.parsers import expat from xml.parsers import expat
from xml.parsers.expat import errors from xml.parsers.expat import errors
@@ -108,7 +108,7 @@ Index: Python-3.11.12/Lib/test/test_pyexpat.py
class SetAttributeTest(unittest.TestCase): class SetAttributeTest(unittest.TestCase):
def setUp(self): def setUp(self):
@@ -770,9 +769,8 @@ @@ -806,9 +805,8 @@
self.assertIs(parser.GetReparseDeferralEnabled(), enabled) self.assertIs(parser.GetReparseDeferralEnabled(), enabled)
def test_reparse_deferral_enabled(self): def test_reparse_deferral_enabled(self):
@@ -120,7 +120,7 @@ Index: Python-3.11.12/Lib/test/test_pyexpat.py
started = [] started = []
@@ -801,9 +799,9 @@ @@ -837,9 +835,9 @@
parser = expat.ParserCreate() parser = expat.ParserCreate()
parser.StartElementHandler = start_element parser.StartElementHandler = start_element
@@ -132,10 +132,10 @@ Index: Python-3.11.12/Lib/test/test_pyexpat.py
for chunk in (b'<doc', b'/>'): for chunk in (b'<doc', b'/>'):
parser.Parse(chunk, False) parser.Parse(chunk, False)
Index: Python-3.11.12/Lib/test/test_sax.py Index: Python-3.11.14/Lib/test/test_sax.py
=================================================================== ===================================================================
--- Python-3.11.12.orig/Lib/test/test_sax.py 2025-04-11 10:52:22.111440337 +0200 --- Python-3.11.14.orig/Lib/test/test_sax.py 2025-11-15 19:14:53.915952608 +0100
+++ Python-3.11.12/Lib/test/test_sax.py 2025-04-11 10:52:44.803567098 +0200 +++ Python-3.11.14/Lib/test/test_sax.py 2025-11-15 19:15:12.860746114 +0100
@@ -19,13 +19,11 @@ @@ -19,13 +19,11 @@
from io import BytesIO, StringIO from io import BytesIO, StringIO
import codecs import codecs
@@ -187,10 +187,10 @@ Index: Python-3.11.12/Lib/test/test_sax.py
self.assertFalse(parser._parser.GetReparseDeferralEnabled()) self.assertFalse(parser._parser.GetReparseDeferralEnabled())
Index: Python-3.11.12/Lib/test/test_xml_etree.py Index: Python-3.11.14/Lib/test/test_xml_etree.py
=================================================================== ===================================================================
--- Python-3.11.12.orig/Lib/test/test_xml_etree.py 2025-04-11 10:52:22.425637912 +0200 --- Python-3.11.14.orig/Lib/test/test_xml_etree.py 2025-11-15 19:14:53.915952608 +0100
+++ Python-3.11.12/Lib/test/test_xml_etree.py 2025-04-11 10:52:44.804234785 +0200 +++ Python-3.11.14/Lib/test/test_xml_etree.py 2025-11-15 19:15:12.861491049 +0100
@@ -13,7 +13,6 @@ @@ -13,7 +13,6 @@
import operator import operator
import os import os

View File

@@ -4,9 +4,11 @@
Lib/test/test_xml_etree.py | 2 ++ Lib/test/test_xml_etree.py | 2 ++
3 files changed, 6 insertions(+) 3 files changed, 6 insertions(+)
--- a/Lib/test/test_pyexpat.py Index: Python-3.11.14/Lib/test/test_pyexpat.py
+++ b/Lib/test/test_pyexpat.py ===================================================================
@@ -768,6 +768,7 @@ class ReparseDeferralTest(unittest.TestC --- Python-3.11.14.orig/Lib/test/test_pyexpat.py 2025-11-15 19:15:12.860334045 +0100
+++ Python-3.11.14/Lib/test/test_pyexpat.py 2025-11-15 19:15:15.541090355 +0100
@@ -804,6 +804,7 @@
parser.SetReparseDeferralEnabled(True) parser.SetReparseDeferralEnabled(True)
self.assertIs(parser.GetReparseDeferralEnabled(), enabled) self.assertIs(parser.GetReparseDeferralEnabled(), enabled)
@@ -14,7 +16,7 @@
def test_reparse_deferral_enabled(self): def test_reparse_deferral_enabled(self):
if not is_expat_2_6_0: if not is_expat_2_6_0:
self.skipTest("Linked libexpat doesn't support reparse deferral") self.skipTest("Linked libexpat doesn't support reparse deferral")
@@ -791,6 +792,7 @@ class ReparseDeferralTest(unittest.TestC @@ -827,6 +828,7 @@
self.assertEqual(started, ['doc']) self.assertEqual(started, ['doc'])
@@ -22,9 +24,11 @@
def test_reparse_deferral_disabled(self): def test_reparse_deferral_disabled(self):
started = [] started = []
--- a/Lib/test/test_sax.py Index: Python-3.11.14/Lib/test/test_sax.py
+++ b/Lib/test/test_sax.py ===================================================================
@@ -1213,6 +1213,7 @@ class ExpatReaderTest(XmlTestBase): --- Python-3.11.14.orig/Lib/test/test_sax.py 2025-11-15 19:15:12.860746114 +0100
+++ Python-3.11.14/Lib/test/test_sax.py 2025-11-15 19:15:15.541608234 +0100
@@ -1213,6 +1213,7 @@
self.assertEqual(result.getvalue(), start + b"<doc>text</doc>") self.assertEqual(result.getvalue(), start + b"<doc>text</doc>")
@@ -32,7 +36,7 @@
def test_flush_reparse_deferral_enabled(self): def test_flush_reparse_deferral_enabled(self):
if not is_expat_2_6_0: if not is_expat_2_6_0:
self.skipTest("Linked libexpat doesn't support reparse deferral") self.skipTest("Linked libexpat doesn't support reparse deferral")
@@ -1238,6 +1239,7 @@ class ExpatReaderTest(XmlTestBase): @@ -1238,6 +1239,7 @@
self.assertEqual(result.getvalue(), start + b"<doc></doc>") self.assertEqual(result.getvalue(), start + b"<doc></doc>")
@@ -40,9 +44,11 @@
def test_flush_reparse_deferral_disabled(self): def test_flush_reparse_deferral_disabled(self):
if not is_expat_2_6_0: if not is_expat_2_6_0:
self.skipTest("Linked libexpat doesn't support reparse deferral") self.skipTest("Linked libexpat doesn't support reparse deferral")
--- a/Lib/test/test_xml_etree.py Index: Python-3.11.14/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py ===================================================================
@@ -1620,6 +1620,7 @@ class XMLPullParserTest(unittest.TestCas --- Python-3.11.14.orig/Lib/test/test_xml_etree.py 2025-11-15 19:15:12.861491049 +0100
+++ Python-3.11.14/Lib/test/test_xml_etree.py 2025-11-15 19:15:15.542327817 +0100
@@ -1620,6 +1620,7 @@
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
ET.XMLPullParser(events=('start', 'end', 'bogus')) ET.XMLPullParser(events=('start', 'end', 'bogus'))
@@ -50,7 +56,7 @@
def test_flush_reparse_deferral_enabled(self): def test_flush_reparse_deferral_enabled(self):
parser = ET.XMLPullParser(events=('start', 'end')) parser = ET.XMLPullParser(events=('start', 'end'))
@@ -1641,6 +1642,7 @@ class XMLPullParserTest(unittest.TestCas @@ -1641,6 +1642,7 @@
self.assert_event_tags(parser, [('end', 'doc')]) self.assert_event_tags(parser, [('end', 'doc')])

View File

@@ -0,0 +1,93 @@
From b95c10349956d95e258553def0fcc52ea3ef8f82 Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <seth@python.org>
Date: Wed, 3 Dec 2025 01:16:37 -0600
Subject: [PATCH] gh-142145: Remove quadratic behavior in node ID cache
clearing (GH-142146)
* Remove quadratic behavior in node ID cache clearing
Co-authored-by: Jacob Walls <38668450+jacobtylerwalls@users.noreply.github.com>
* Add news fragment
---------
(cherry picked from commit 08d8e18ad81cd45bc4a27d6da478b51ea49486e4)
Co-authored-by: Seth Michael Larson <seth@python.org>
Co-authored-by: Jacob Walls <38668450+jacobtylerwalls@users.noreply.github.com>
---
Lib/test/test_minidom.py | 18 ++++++++++
Lib/xml/dom/minidom.py | 9 -----
Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst | 1
3 files changed, 20 insertions(+), 8 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst
Index: Python-3.11.14/Lib/test/test_minidom.py
===================================================================
--- Python-3.11.14.orig/Lib/test/test_minidom.py 2025-12-19 22:55:59.547417036 +0100
+++ Python-3.11.14/Lib/test/test_minidom.py 2025-12-19 22:56:07.607956864 +0100
@@ -2,6 +2,7 @@
import copy
import pickle
+import time
import io
from test import support
import unittest
@@ -176,6 +177,23 @@
self.confirm(dom.documentElement.childNodes[-1].data == "Hello")
dom.unlink()
+ 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.time()
+ for child in children:
+ element.appendChild(child)
+ element = child
+ end = time.time()
+
+ # This example used to take at least 30 seconds.
+ self.assertLess(end - start, 1)
+
def testAppendChildFragment(self):
dom, orig, c1, c2, c3, frag = self._create_fragment_test_nodes()
dom.documentElement.appendChild(frag)
Index: Python-3.11.14/Lib/xml/dom/minidom.py
===================================================================
--- Python-3.11.14.orig/Lib/xml/dom/minidom.py 2025-10-09 18:16:55.000000000 +0200
+++ Python-3.11.14/Lib/xml/dom/minidom.py 2025-12-19 22:56:07.608359083 +0100
@@ -292,13 +292,6 @@
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."
@@ -1539,7 +1532,7 @@
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
Index: Python-3.11.14/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.11.14/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst 2025-12-19 22:56:07.608664851 +0100
@@ -0,0 +1 @@
+Remove quadratic behavior in ``xml.minidom`` node ID cache clearing.

View File

@@ -0,0 +1,155 @@
From 4f2bc24b750a82d3b439f174e7717fc09820bfeb Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Mon, 1 Dec 2025 17:26:07 +0200
Subject: [PATCH] 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 91ee1b470cfd47..c977612732afbc 100644
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -111,6 +111,11 @@
_MAXLINE = 65536
_MAXHEADERS = 100
+# Data larger than this will be read in chunks, to prevent extreme
+# overallocation.
+_MIN_READ_BUF_SIZE = 1 << 20
+
+
# Header name/value ABNF (http://tools.ietf.org/html/rfc7230#section-3.2)
#
# VCHAR = %x21-7E
@@ -635,10 +640,25 @@ def _safe_read(self, amt):
reading. If the bytes are truly not available (due to EOF), then the
IncompleteRead exception can be used to detect the problem.
"""
- data = self.fp.read(amt)
- if len(data) < amt:
- raise IncompleteRead(data, amt-len(data))
- return data
+ cursize = min(amt, _MIN_READ_BUF_SIZE)
+ data = self.fp.read(cursize)
+ if len(data) >= amt:
+ return data
+ if len(data) < cursize:
+ raise IncompleteRead(data, amt - len(data))
+
+ data = io.BytesIO(data)
+ data.seek(0, 2)
+ while True:
+ # This is a geometric increase in read size (never more than
+ # doubling out the current length of data per loop iteration).
+ delta = min(cursize, amt - cursize)
+ data.write(self.fp.read(delta))
+ if data.tell() >= amt:
+ return data.getvalue()
+ cursize += delta
+ if data.tell() < cursize:
+ raise IncompleteRead(data.getvalue(), amt - data.tell())
def _safe_readinto(self, b):
"""Same as _safe_read, but for reading into a buffer."""
diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
index 8b9d49ec094813..55363413b3b140 100644
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -1390,6 +1390,72 @@ def run_server():
thread.join()
self.assertEqual(result, b"proxied data\n")
+ def test_large_content_length(self):
+ serv = socket.create_server((HOST, 0))
+ self.addCleanup(serv.close)
+
+ def run_server():
+ [conn, address] = serv.accept()
+ with conn:
+ while conn.recv(1024):
+ conn.sendall(
+ b"HTTP/1.1 200 Ok\r\n"
+ b"Content-Length: %d\r\n"
+ b"\r\n" % size)
+ conn.sendall(b'A' * (size//3))
+ conn.sendall(b'B' * (size - size//3))
+
+ thread = threading.Thread(target=run_server)
+ thread.start()
+ self.addCleanup(thread.join, 1.0)
+
+ conn = client.HTTPConnection(*serv.getsockname())
+ try:
+ for w in range(15, 27):
+ size = 1 << w
+ conn.request("GET", "/")
+ with conn.getresponse() as response:
+ self.assertEqual(len(response.read()), size)
+ finally:
+ conn.close()
+ thread.join(1.0)
+
+ def test_large_content_length_truncated(self):
+ serv = socket.create_server((HOST, 0))
+ self.addCleanup(serv.close)
+
+ def run_server():
+ while True:
+ [conn, address] = serv.accept()
+ with conn:
+ conn.recv(1024)
+ if not size:
+ break
+ conn.sendall(
+ b"HTTP/1.1 200 Ok\r\n"
+ b"Content-Length: %d\r\n"
+ b"\r\n"
+ b"Text" % size)
+
+ thread = threading.Thread(target=run_server)
+ thread.start()
+ self.addCleanup(thread.join, 1.0)
+
+ conn = client.HTTPConnection(*serv.getsockname())
+ try:
+ for w in range(18, 65):
+ size = 1 << w
+ conn.request("GET", "/")
+ with conn.getresponse() as response:
+ self.assertRaises(client.IncompleteRead, response.read)
+ conn.close()
+ finally:
+ conn.close()
+ size = 0
+ conn.request("GET", "/")
+ conn.close()
+ thread.join(1.0)
+
def test_putrequest_override_domain_validation(self):
"""
It should be possible to override the default validation
diff --git a/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst b/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst
new file mode 100644
index 00000000000000..6d6f25cd2f8bf7
--- /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

@@ -0,0 +1,160 @@
From aa9edbb11a2bf7805fd5046cdd5c2d3864aa39f2 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Mon, 1 Dec 2025 17:28:15 +0200
Subject: [PATCH] [3.11] gh-119342: Fix a potential denial of service in
plistlib (GH-119343)
Reading a specially prepared small Plist file could cause OOM because file's
read(n) preallocates a bytes object for reading the specified amount of
data. Now plistlib reads large data by chunks, therefore the upper limit of
consumed memory is proportional to the size of the input file.
(cherry picked from commit 694922cf40aa3a28f898b5f5ee08b71b4922df70)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
---
Lib/plistlib.py | 31 ++++++++++------
Lib/test/test_plistlib.py | 37 +++++++++++++++++--
...-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst | 5 +++
3 files changed, 59 insertions(+), 14 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst
diff --git a/Lib/plistlib.py b/Lib/plistlib.py
index 53e718f063b3ec..63fefbd5f6d499 100644
--- a/Lib/plistlib.py
+++ b/Lib/plistlib.py
@@ -73,6 +73,9 @@
PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)
globals().update(PlistFormat.__members__)
+# Data larger than this will be read in chunks, to prevent extreme
+# overallocation.
+_MIN_READ_BUF_SIZE = 1 << 20
class UID:
def __init__(self, data):
@@ -499,12 +502,24 @@ def _get_size(self, tokenL):
return tokenL
+ def _read(self, size):
+ cursize = min(size, _MIN_READ_BUF_SIZE)
+ data = self._fp.read(cursize)
+ while True:
+ if len(data) != cursize:
+ raise InvalidFileException
+ if cursize == size:
+ return data
+ delta = min(cursize, size - cursize)
+ data += self._fp.read(delta)
+ cursize += delta
+
def _read_ints(self, n, size):
- data = self._fp.read(size * n)
+ data = self._read(size * n)
if size in _BINARY_FORMAT:
return struct.unpack(f'>{n}{_BINARY_FORMAT[size]}', data)
else:
- if not size or len(data) != size * n:
+ if not size:
raise InvalidFileException()
return tuple(int.from_bytes(data[i: i + size], 'big')
for i in range(0, size * n, size))
@@ -561,22 +576,16 @@ def _read_object(self, ref):
elif tokenH == 0x40: # data
s = self._get_size(tokenL)
- result = self._fp.read(s)
- if len(result) != s:
- raise InvalidFileException()
+ result = self._read(s)
elif tokenH == 0x50: # ascii string
s = self._get_size(tokenL)
- data = self._fp.read(s)
- if len(data) != s:
- raise InvalidFileException()
+ data = self._read(s)
result = data.decode('ascii')
elif tokenH == 0x60: # unicode string
s = self._get_size(tokenL) * 2
- data = self._fp.read(s)
- if len(data) != s:
- raise InvalidFileException()
+ data = self._read(s)
result = data.decode('utf-16be')
elif tokenH == 0x80: # UID
diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py
index 95b7a649774dca..2bc64afdbe932f 100644
--- a/Lib/test/test_plistlib.py
+++ b/Lib/test/test_plistlib.py
@@ -841,8 +841,7 @@ def test_xml_plist_with_entity_decl(self):
class TestBinaryPlistlib(unittest.TestCase):
- @staticmethod
- def decode(*objects, offset_size=1, ref_size=1):
+ def build(self, *objects, offset_size=1, ref_size=1):
data = [b'bplist00']
offset = 8
offsets = []
@@ -854,7 +853,11 @@ def decode(*objects, offset_size=1, ref_size=1):
len(objects), 0, offset)
data.extend(offsets)
data.append(tail)
- return plistlib.loads(b''.join(data), fmt=plistlib.FMT_BINARY)
+ return b''.join(data)
+
+ def decode(self, *objects, offset_size=1, ref_size=1):
+ data = self.build(*objects, offset_size=offset_size, ref_size=ref_size)
+ return plistlib.loads(data, fmt=plistlib.FMT_BINARY)
def test_nonstandard_refs_size(self):
# Issue #21538: Refs and offsets are 24-bit integers
@@ -963,6 +966,34 @@ def test_invalid_binary(self):
with self.assertRaises(plistlib.InvalidFileException):
plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY)
+ def test_truncated_large_data(self):
+ self.addCleanup(os_helper.unlink, os_helper.TESTFN)
+ def check(data):
+ with open(os_helper.TESTFN, 'wb') as f:
+ f.write(data)
+ # buffered file
+ with open(os_helper.TESTFN, 'rb') as f:
+ with self.assertRaises(plistlib.InvalidFileException):
+ plistlib.load(f, fmt=plistlib.FMT_BINARY)
+ # unbuffered file
+ with open(os_helper.TESTFN, 'rb', buffering=0) as f:
+ with self.assertRaises(plistlib.InvalidFileException):
+ plistlib.load(f, fmt=plistlib.FMT_BINARY)
+ for w in range(20, 64):
+ s = 1 << w
+ # data
+ check(self.build(b'\x4f\x13' + s.to_bytes(8, 'big')))
+ # ascii string
+ check(self.build(b'\x5f\x13' + s.to_bytes(8, 'big')))
+ # unicode string
+ check(self.build(b'\x6f\x13' + s.to_bytes(8, 'big')))
+ # array
+ check(self.build(b'\xaf\x13' + s.to_bytes(8, 'big')))
+ # dict
+ check(self.build(b'\xdf\x13' + s.to_bytes(8, 'big')))
+ # number of objects
+ check(b'bplist00' + struct.pack('>6xBBQQQ', 1, 1, s, 0, 8))
+
class TestKeyedArchive(unittest.TestCase):
def test_keyed_archive_data(self):
diff --git a/Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst b/Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst
new file mode 100644
index 00000000000000..04fd8faca4cf7e
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst
@@ -0,0 +1,5 @@
+Fix a potential memory denial of service in the :mod:`plistlib` module.
+When reading a Plist file received from untrusted source, 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,190 +0,0 @@
From 9043edabc7e2f0dd655146e0a4571e2a0b2906af Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Fri, 13 Jun 2025 19:57:48 +0300
Subject: [PATCH] gh-135462: Fix quadratic complexity in processing special
input in HTMLParser (GH-135464)
End-of-file errors are now handled according to the HTML5 specs --
comments and declarations are automatically closed, tags are ignored.
(cherry picked from commit 6eb6c5dbfb528bd07d77b60fd71fd05d81d45c41)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
---
Lib/html/parser.py | 41 +++++---
Lib/test/test_htmlparser.py | 51 +++++++---
Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst | 4
3 files changed, 74 insertions(+), 22 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst
Index: Python-3.11.13/Lib/html/parser.py
===================================================================
--- Python-3.11.13.orig/Lib/html/parser.py 2025-07-02 18:12:07.084569398 +0200
+++ Python-3.11.13/Lib/html/parser.py 2025-07-02 18:12:12.582519793 +0200
@@ -25,6 +25,7 @@
charref = re.compile('&#(?:[0-9]+|[xX][0-9a-fA-F]+)[^0-9a-fA-F]')
starttagopen = re.compile('<[a-zA-Z]')
+endtagopen = re.compile('</[a-zA-Z]')
piclose = re.compile('>')
commentclose = re.compile(r'--\s*>')
# Note:
@@ -176,7 +177,7 @@
k = self.parse_pi(i)
elif startswith("<!", i):
k = self.parse_html_declaration(i)
- elif (i + 1) < n:
+ elif (i + 1) < n or end:
self.handle_data("<")
k = i + 1
else:
@@ -184,17 +185,35 @@
if k < 0:
if not end:
break
- k = rawdata.find('>', i + 1)
- if k < 0:
- k = rawdata.find('<', i + 1)
- if k < 0:
- k = i + 1
+ if starttagopen.match(rawdata, i): # < + letter
+ pass
+ elif startswith("</", i):
+ if i + 2 == n:
+ self.handle_data("</")
+ elif endtagopen.match(rawdata, i): # </ + letter
+ pass
+ else:
+ # bogus comment
+ self.handle_comment(rawdata[i+2:])
+ elif startswith("<!--", i):
+ j = n
+ for suffix in ("--!", "--", "-"):
+ if rawdata.endswith(suffix, i+4):
+ j -= len(suffix)
+ break
+ self.handle_comment(rawdata[i+4:j])
+ elif startswith("<![CDATA[", i):
+ self.unknown_decl(rawdata[i+3:])
+ elif rawdata[i:i+9].lower() == '<!doctype':
+ self.handle_decl(rawdata[i+2:])
+ elif startswith("<!", i):
+ # bogus comment
+ self.handle_comment(rawdata[i+2:])
+ elif startswith("<?", i):
+ self.handle_pi(rawdata[i+2:])
else:
- k += 1
- if self.convert_charrefs and not self.cdata_elem:
- self.handle_data(unescape(rawdata[i:k]))
- else:
- self.handle_data(rawdata[i:k])
+ raise AssertionError("we should not get here!")
+ k = n
i = self.updatepos(i, k)
elif startswith("&#", i):
match = charref.match(rawdata, i)
Index: Python-3.11.13/Lib/test/test_htmlparser.py
===================================================================
--- Python-3.11.13.orig/Lib/test/test_htmlparser.py 2025-07-02 18:12:08.523658593 +0200
+++ Python-3.11.13/Lib/test/test_htmlparser.py 2025-07-02 18:13:32.674943007 +0200
@@ -4,6 +4,8 @@
import pprint
import unittest
+from test import support
+
class EventCollector(html.parser.HTMLParser):
@@ -391,28 +393,34 @@
('data', '<'),
('starttag', 'bc<', [('a', None)]),
('endtag', 'html'),
- ('data', '\n<img src="URL>'),
- ('comment', '/img'),
- ('endtag', 'html<')])
+ ('data', '\n')])
def test_starttag_junk_chars(self):
+ self._run_check("<", [('data', '<')])
+ self._run_check("<>", [('data', '<>')])
+ self._run_check("< >", [('data', '< >')])
+ self._run_check("< ", [('data', '< ')])
self._run_check("</>", [])
+ self._run_check("<$>", [('data', '<$>')])
self._run_check("</$>", [('comment', '$')])
self._run_check("</", [('data', '</')])
- self._run_check("</a", [('data', '</a')])
+ self._run_check("</a", [])
+ self._run_check("</ a>", [('endtag', 'a')])
+ self._run_check("</ a", [('comment', ' a')])
self._run_check("<a<a>", [('starttag', 'a<a', [])])
self._run_check("</a<a>", [('endtag', 'a<a')])
- self._run_check("<!", [('data', '<!')])
- self._run_check("<a", [('data', '<a')])
- self._run_check("<a foo='bar'", [('data', "<a foo='bar'")])
- self._run_check("<a foo='bar", [('data', "<a foo='bar")])
- self._run_check("<a foo='>'", [('data', "<a foo='>'")])
- self._run_check("<a foo='>", [('data', "<a foo='>")])
+ self._run_check("<!", [('comment', '')])
+ self._run_check("<a", [])
+ self._run_check("<a foo='bar'", [])
+ self._run_check("<a foo='bar", [])
+ self._run_check("<a foo='>'", [])
+ self._run_check("<a foo='>", [])
self._run_check("<a$>", [('starttag', 'a$', [])])
self._run_check("<a$b>", [('starttag', 'a$b', [])])
self._run_check("<a$b/>", [('startendtag', 'a$b', [])])
self._run_check("<a$b >", [('starttag', 'a$b', [])])
self._run_check("<a$b />", [('startendtag', 'a$b', [])])
+ self._run_check("</a$b>", [('endtag', 'a$b')])
def test_slashes_in_starttag(self):
self._run_check('<a foo="var"/>', [('startendtag', 'a', [('foo', 'var')])])
@@ -549,8 +557,9 @@
('comment', ' -- close enough --'),
('comment', ''),
('comment', '<-- this was an empty comment'),
- ('comment', '!! another bogus comment !!!'),
+ ('comment', '!! another bogus comment !!!')
]
+
self._run_check(html, expected)
def test_broken_condcoms(self):
@@ -598,6 +607,26 @@
('endtag', 'a'), ('data', ' bar & baz')]
)
+ @support.requires_resource('cpu')
+ def test_eof_no_quadratic_complexity(self):
+ # Each of these examples used to take about an hour.
+ # Now they take a fraction of a second.
+ def check(source):
+ parser = html.parser.HTMLParser()
+ parser.feed(source)
+ parser.close()
+ n = 120_000
+ check("<a " * n)
+ check("<a a=" * n)
+ check("</a " * 14 * n)
+ check("</a a=" * 11 * n)
+ check("<!--" * 4 * n)
+ check("<!" * 60 * n)
+ check("<?" * 19 * n)
+ check("</$" * 15 * n)
+ check("<![CDATA[" * 9 * n)
+ check("<!doctype" * 35 * n)
+
class AttributesTestCase(TestCaseBase):
Index: Python-3.11.13/Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.11.13/Misc/NEWS.d/next/Security/2025-06-13-15-55-22.gh-issue-135462.KBeJpc.rst 2025-07-02 18:12:12.583386736 +0200
@@ -0,0 +1,4 @@
+Fix quadratic complexity in processing specially crafted input in
+:class:`html.parser.HTMLParser`. End-of-file errors are now handled according
+to the HTML5 specs -- comments and declarations are automatically closed,
+tags are ignored.

View File

@@ -0,0 +1,359 @@
From e717839989908ecea9c1c8f3bd17ec9fb1ac8963 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Fri, 31 Oct 2025 15:49:51 +0200
Subject: [PATCH] [3.11] gh-136065: Fix quadratic complexity in
os.path.expandvars() (GH-134952) (cherry picked from commit
f029e8db626ddc6e3a3beea4eff511a71aaceb5c)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
---
Lib/ntpath.py | 126 +++-------
Lib/posixpath.py | 43 +--
Lib/test/test_genericpath.py | 14 +
Lib/test/test_ntpath.py | 22 +
Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst | 1
5 files changed, 94 insertions(+), 112 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst
Index: Python-3.11.14/Lib/ntpath.py
===================================================================
--- Python-3.11.14.orig/Lib/ntpath.py 2025-11-15 19:14:27.424009612 +0100
+++ Python-3.11.14/Lib/ntpath.py 2025-11-15 19:14:41.389069009 +0100
@@ -378,17 +378,23 @@
# XXX With COMMAND.COM you can use any characters in a variable name,
# XXX except '^|<>='.
+_varpattern = r"'[^']*'?|%(%|[^%]*%?)|\$(\$|[-\w]+|\{[^}]*\}?)"
+_varsub = None
+_varsubb = None
+
def expandvars(path):
"""Expand shell variables of the forms $var, ${var} and %var%.
Unknown variables are left unchanged."""
path = os.fspath(path)
+ global _varsub, _varsubb
if isinstance(path, bytes):
if b'$' not in path and b'%' not in path:
return path
- import string
- varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
- quote = b'\''
+ if not _varsubb:
+ import re
+ _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub
+ sub = _varsubb
percent = b'%'
brace = b'{'
rbrace = b'}'
@@ -397,94 +403,44 @@
else:
if '$' not in path and '%' not in path:
return path
- import string
- varchars = string.ascii_letters + string.digits + '_-'
- quote = '\''
+ if not _varsub:
+ import re
+ _varsub = re.compile(_varpattern, re.ASCII).sub
+ sub = _varsub
percent = '%'
brace = '{'
rbrace = '}'
dollar = '$'
environ = os.environ
- res = path[:0]
- index = 0
- pathlen = len(path)
- while index < pathlen:
- c = path[index:index+1]
- if c == quote: # no expansion within single quotes
- path = path[index + 1:]
- pathlen = len(path)
- try:
- index = path.index(c)
- res += c + path[:index + 1]
- except ValueError:
- res += c + path
- index = pathlen - 1
- elif c == percent: # variable or '%'
- if path[index + 1:index + 2] == percent:
- res += c
- index += 1
- else:
- path = path[index+1:]
- pathlen = len(path)
- try:
- index = path.index(percent)
- except ValueError:
- res += percent + path
- index = pathlen - 1
- else:
- var = path[:index]
- try:
- if environ is None:
- value = os.fsencode(os.environ[os.fsdecode(var)])
- else:
- value = environ[var]
- except KeyError:
- value = percent + var + percent
- res += value
- elif c == dollar: # variable or '$$'
- if path[index + 1:index + 2] == dollar:
- res += c
- index += 1
- elif path[index + 1:index + 2] == brace:
- path = path[index+2:]
- pathlen = len(path)
- try:
- index = path.index(rbrace)
- except ValueError:
- res += dollar + brace + path
- index = pathlen - 1
- else:
- var = path[:index]
- try:
- if environ is None:
- value = os.fsencode(os.environ[os.fsdecode(var)])
- else:
- value = environ[var]
- except KeyError:
- value = dollar + brace + var + rbrace
- res += value
- else:
- var = path[:0]
- index += 1
- c = path[index:index + 1]
- while c and c in varchars:
- var += c
- index += 1
- c = path[index:index + 1]
- try:
- if environ is None:
- value = os.fsencode(os.environ[os.fsdecode(var)])
- else:
- value = environ[var]
- except KeyError:
- value = dollar + var
- res += value
- if c:
- index -= 1
+
+ def repl(m):
+ lastindex = m.lastindex
+ if lastindex is None:
+ return m[0]
+ name = m[lastindex]
+ if lastindex == 1:
+ if name == percent:
+ return name
+ if not name.endswith(percent):
+ return m[0]
+ name = name[:-1]
else:
- res += c
- index += 1
- return res
+ if name == dollar:
+ return name
+ if name.startswith(brace):
+ if not name.endswith(rbrace):
+ return m[0]
+ name = name[1:-1]
+
+ try:
+ if environ is None:
+ return os.fsencode(os.environ[os.fsdecode(name)])
+ else:
+ return environ[name]
+ except KeyError:
+ return m[0]
+
+ return sub(repl, path)
# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
Index: Python-3.11.14/Lib/posixpath.py
===================================================================
--- Python-3.11.14.orig/Lib/posixpath.py 2025-11-15 19:14:27.465471369 +0100
+++ Python-3.11.14/Lib/posixpath.py 2025-11-15 19:14:41.389334199 +0100
@@ -287,42 +287,41 @@
# This expands the forms $variable and ${variable} only.
# Non-existent variables are left unchanged.
-_varprog = None
-_varprogb = None
+_varpattern = r'\$(\w+|\{[^}]*\}?)'
+_varsub = None
+_varsubb = None
def expandvars(path):
"""Expand shell variables of form $var and ${var}. Unknown variables
are left unchanged."""
path = os.fspath(path)
- global _varprog, _varprogb
+ global _varsub, _varsubb
if isinstance(path, bytes):
if b'$' not in path:
return path
- if not _varprogb:
+ if not _varsubb:
import re
- _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII)
- search = _varprogb.search
+ _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub
+ sub = _varsubb
start = b'{'
end = b'}'
environ = getattr(os, 'environb', None)
else:
if '$' not in path:
return path
- if not _varprog:
+ if not _varsub:
import re
- _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII)
- search = _varprog.search
+ _varsub = re.compile(_varpattern, re.ASCII).sub
+ sub = _varsub
start = '{'
end = '}'
environ = os.environ
- i = 0
- while True:
- m = search(path, i)
- if not m:
- break
- i, j = m.span(0)
- name = m.group(1)
- if name.startswith(start) and name.endswith(end):
+
+ def repl(m):
+ name = m[1]
+ if name.startswith(start):
+ if not name.endswith(end):
+ return m[0]
name = name[1:-1]
try:
if environ is None:
@@ -330,13 +329,11 @@
else:
value = environ[name]
except KeyError:
- i = j
+ return m[0]
else:
- tail = path[j:]
- path = path[:i] + value
- i = len(path)
- path += tail
- return path
+ return value
+
+ return sub(repl, path)
# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
Index: Python-3.11.14/Lib/test/test_genericpath.py
===================================================================
--- Python-3.11.14.orig/Lib/test/test_genericpath.py 2025-11-15 19:14:28.470950071 +0100
+++ Python-3.11.14/Lib/test/test_genericpath.py 2025-11-15 19:14:41.389533528 +0100
@@ -7,6 +7,7 @@
import sys
import unittest
import warnings
+from test import support
from test.support import is_emscripten
from test.support import os_helper
from test.support import warnings_helper
@@ -434,6 +435,19 @@
os.fsencode('$bar%s bar' % nonascii))
check(b'$spam}bar', os.fsencode('%s}bar' % nonascii))
+ @support.requires_resource('cpu')
+ def test_expandvars_large(self):
+ expandvars = self.pathmodule.expandvars
+ with os_helper.EnvironmentVarGuard() as env:
+ env.clear()
+ env["A"] = "B"
+ n = 100_000
+ self.assertEqual(expandvars('$A'*n), 'B'*n)
+ self.assertEqual(expandvars('${A}'*n), 'B'*n)
+ self.assertEqual(expandvars('$A!'*n), 'B!'*n)
+ self.assertEqual(expandvars('${A}A'*n), 'BA'*n)
+ self.assertEqual(expandvars('${'*10*n), '${'*10*n)
+
def test_abspath(self):
self.assertIn("foo", self.pathmodule.abspath("foo"))
with warnings.catch_warnings():
Index: Python-3.11.14/Lib/test/test_ntpath.py
===================================================================
--- Python-3.11.14.orig/Lib/test/test_ntpath.py 2025-11-15 19:14:29.042372971 +0100
+++ Python-3.11.14/Lib/test/test_ntpath.py 2025-11-15 19:14:41.389799697 +0100
@@ -6,8 +6,8 @@
import unittest
import warnings
from ntpath import ALLOW_MISSING
-from test.support import os_helper
-from test.support import TestFailed, is_emscripten
+from test import support
+from test.support import os_helper, is_emscripten
from test.support.os_helper import FakePath
from test import test_genericpath
from tempfile import TemporaryFile
@@ -57,7 +57,7 @@
fn = fn.replace("\\", "\\\\")
gotResult = eval(fn)
if wantResult != gotResult and _norm(wantResult) != _norm(gotResult):
- raise TestFailed("%s should return: %s but returned: %s" \
+ raise support.TestFailed("%s should return: %s but returned: %s" \
%(str(fn), str(wantResult), str(gotResult)))
# then with bytes
@@ -73,7 +73,7 @@
warnings.simplefilter("ignore", DeprecationWarning)
gotResult = eval(fn)
if _norm(wantResult) != _norm(gotResult):
- raise TestFailed("%s should return: %s but returned: %s" \
+ raise support.TestFailed("%s should return: %s but returned: %s" \
%(str(fn), str(wantResult), repr(gotResult)))
@@ -820,6 +820,19 @@
check('%spam%bar', '%sbar' % nonascii)
check('%{}%bar'.format(nonascii), 'ham%sbar' % nonascii)
+ @support.requires_resource('cpu')
+ def test_expandvars_large(self):
+ expandvars = ntpath.expandvars
+ with os_helper.EnvironmentVarGuard() as env:
+ env.clear()
+ env["A"] = "B"
+ n = 100_000
+ self.assertEqual(expandvars('%A%'*n), 'B'*n)
+ self.assertEqual(expandvars('%A%A'*n), 'BA'*n)
+ self.assertEqual(expandvars("''"*n + '%%'), "''"*n + '%')
+ self.assertEqual(expandvars("%%"*n), "%"*n)
+ self.assertEqual(expandvars("$$"*n), "$"*n)
+
def test_expanduser(self):
tester('ntpath.expanduser("test")', 'test')
@@ -1090,6 +1103,7 @@
self.assertIsInstance(b_final_path, bytes)
self.assertGreater(len(b_final_path), 0)
+
class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
pathmodule = ntpath
attributes = ['relpath']
Index: Python-3.11.14/Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.11.14/Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst 2025-11-15 19:14:41.390091148 +0100
@@ -0,0 +1 @@
+Fix quadratic complexity in :func:`os.path.expandvars`.

View File

@@ -1,212 +0,0 @@
From cb3519590c62f9b1abf7f31b92ec37d4b725ce15 Mon Sep 17 00:00:00 2001
From: Alexander Urieles <aeurielesn@users.noreply.github.com>
Date: Mon, 28 Jul 2025 17:37:26 +0200
Subject: [PATCH] gh-130577: tarfile now validates archives to ensure member
offsets are non-negative (GH-137027) (cherry picked from commit
7040aa54f14676938970e10c5f74ea93cd56aa38)
Co-authored-by: Alexander Urieles <aeurielesn@users.noreply.github.com>
Co-authored-by: Gregory P. Smith <greg@krypto.org>
---
Lib/tarfile.py | 3
Lib/test/test_tarfile.py | 156 ++++++++++
Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst | 3
3 files changed, 162 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst
Index: Python-3.11.13/Lib/tarfile.py
===================================================================
--- Python-3.11.13.orig/Lib/tarfile.py 2025-08-01 22:21:29.158050900 +0200
+++ Python-3.11.13/Lib/tarfile.py 2025-08-01 22:21:33.121079687 +0200
@@ -1613,6 +1613,9 @@
"""Round up a byte count by BLOCKSIZE and return it,
e.g. _block(834) => 1024.
"""
+ # Only non-negative offsets are allowed
+ if count < 0:
+ raise InvalidHeaderError("invalid offset")
blocks, remainder = divmod(count, BLOCKSIZE)
if remainder:
blocks += 1
Index: Python-3.11.13/Lib/test/test_tarfile.py
===================================================================
--- Python-3.11.13.orig/Lib/test/test_tarfile.py 2025-08-01 22:21:30.644301786 +0200
+++ Python-3.11.13/Lib/test/test_tarfile.py 2025-08-01 22:21:33.121718600 +0200
@@ -50,6 +50,7 @@
xzname = os.path.join(TEMPDIR, "testtar.tar.xz")
tmpname = os.path.join(TEMPDIR, "tmp.tar")
dotlessname = os.path.join(TEMPDIR, "testtar")
+SPACE = b" "
sha256_regtype = (
"e09e4bc8b3c9d9177e77256353b36c159f5f040531bbd4b024a8f9b9196c71ce"
@@ -4386,6 +4387,161 @@
ar.extractall(self.testdir, filter='fully_trusted')
+class OffsetValidationTests(unittest.TestCase):
+ tarname = tmpname
+ invalid_posix_header = (
+ # name: 100 bytes
+ tarfile.NUL * tarfile.LENGTH_NAME
+ # mode, space, null terminator: 8 bytes
+ + b"000755" + SPACE + tarfile.NUL
+ # uid, space, null terminator: 8 bytes
+ + b"000001" + SPACE + tarfile.NUL
+ # gid, space, null terminator: 8 bytes
+ + b"000001" + SPACE + tarfile.NUL
+ # size, space: 12 bytes
+ + b"\xff" * 11 + SPACE
+ # mtime, space: 12 bytes
+ + tarfile.NUL * 11 + SPACE
+ # chksum: 8 bytes
+ + b"0011407" + tarfile.NUL
+ # type: 1 byte
+ + tarfile.REGTYPE
+ # linkname: 100 bytes
+ + tarfile.NUL * tarfile.LENGTH_LINK
+ # magic: 6 bytes, version: 2 bytes
+ + tarfile.POSIX_MAGIC
+ # uname: 32 bytes
+ + tarfile.NUL * 32
+ # gname: 32 bytes
+ + tarfile.NUL * 32
+ # devmajor, space, null terminator: 8 bytes
+ + tarfile.NUL * 6 + SPACE + tarfile.NUL
+ # devminor, space, null terminator: 8 bytes
+ + tarfile.NUL * 6 + SPACE + tarfile.NUL
+ # prefix: 155 bytes
+ + tarfile.NUL * tarfile.LENGTH_PREFIX
+ # padding: 12 bytes
+ + tarfile.NUL * 12
+ )
+ invalid_gnu_header = (
+ # name: 100 bytes
+ tarfile.NUL * tarfile.LENGTH_NAME
+ # mode, null terminator: 8 bytes
+ + b"0000755" + tarfile.NUL
+ # uid, null terminator: 8 bytes
+ + b"0000001" + tarfile.NUL
+ # gid, space, null terminator: 8 bytes
+ + b"0000001" + tarfile.NUL
+ # size, space: 12 bytes
+ + b"\xff" * 11 + SPACE
+ # mtime, space: 12 bytes
+ + tarfile.NUL * 11 + SPACE
+ # chksum: 8 bytes
+ + b"0011327" + tarfile.NUL
+ # type: 1 byte
+ + tarfile.REGTYPE
+ # linkname: 100 bytes
+ + tarfile.NUL * tarfile.LENGTH_LINK
+ # magic: 8 bytes
+ + tarfile.GNU_MAGIC
+ # uname: 32 bytes
+ + tarfile.NUL * 32
+ # gname: 32 bytes
+ + tarfile.NUL * 32
+ # devmajor, null terminator: 8 bytes
+ + tarfile.NUL * 8
+ # devminor, null terminator: 8 bytes
+ + tarfile.NUL * 8
+ # padding: 167 bytes
+ + tarfile.NUL * 167
+ )
+ invalid_v7_header = (
+ # name: 100 bytes
+ tarfile.NUL * tarfile.LENGTH_NAME
+ # mode, space, null terminator: 8 bytes
+ + b"000755" + SPACE + tarfile.NUL
+ # uid, space, null terminator: 8 bytes
+ + b"000001" + SPACE + tarfile.NUL
+ # gid, space, null terminator: 8 bytes
+ + b"000001" + SPACE + tarfile.NUL
+ # size, space: 12 bytes
+ + b"\xff" * 11 + SPACE
+ # mtime, space: 12 bytes
+ + tarfile.NUL * 11 + SPACE
+ # chksum: 8 bytes
+ + b"0010070" + tarfile.NUL
+ # type: 1 byte
+ + tarfile.REGTYPE
+ # linkname: 100 bytes
+ + tarfile.NUL * tarfile.LENGTH_LINK
+ # padding: 255 bytes
+ + tarfile.NUL * 255
+ )
+ valid_gnu_header = tarfile.TarInfo("filename").tobuf(tarfile.GNU_FORMAT)
+ data_block = b"\xff" * tarfile.BLOCKSIZE
+
+ def _write_buffer(self, buffer):
+ with open(self.tarname, "wb") as f:
+ f.write(buffer)
+
+ def _get_members(self, ignore_zeros=None):
+ with open(self.tarname, "rb") as f:
+ with tarfile.open(
+ mode="r", fileobj=f, ignore_zeros=ignore_zeros
+ ) as tar:
+ return tar.getmembers()
+
+ def _assert_raises_read_error_exception(self):
+ with self.assertRaisesRegex(
+ tarfile.ReadError, "file could not be opened successfully"
+ ):
+ self._get_members()
+
+ def test_invalid_offset_header_validations(self):
+ for tar_format, invalid_header in (
+ ("posix", self.invalid_posix_header),
+ ("gnu", self.invalid_gnu_header),
+ ("v7", self.invalid_v7_header),
+ ):
+ with self.subTest(format=tar_format):
+ self._write_buffer(invalid_header)
+ self._assert_raises_read_error_exception()
+
+ def test_early_stop_at_invalid_offset_header(self):
+ buffer = self.valid_gnu_header + self.invalid_gnu_header + self.valid_gnu_header
+ self._write_buffer(buffer)
+ members = self._get_members()
+ self.assertEqual(len(members), 1)
+ self.assertEqual(members[0].name, "filename")
+ self.assertEqual(members[0].offset, 0)
+
+ def test_ignore_invalid_archive(self):
+ # 3 invalid headers with their respective data
+ buffer = (self.invalid_gnu_header + self.data_block) * 3
+ self._write_buffer(buffer)
+ members = self._get_members(ignore_zeros=True)
+ self.assertEqual(len(members), 0)
+
+ def test_ignore_invalid_offset_headers(self):
+ for first_block, second_block, expected_offset in (
+ (
+ (self.valid_gnu_header),
+ (self.invalid_gnu_header + self.data_block),
+ 0,
+ ),
+ (
+ (self.invalid_gnu_header + self.data_block),
+ (self.valid_gnu_header),
+ 1024,
+ ),
+ ):
+ self._write_buffer(first_block + second_block)
+ members = self._get_members(ignore_zeros=True)
+ self.assertEqual(len(members), 1)
+ self.assertEqual(members[0].name, "filename")
+ self.assertEqual(members[0].offset, expected_offset)
+
+
def setUpModule():
os_helper.unlink(TEMPDIR)
os.makedirs(TEMPDIR)
Index: Python-3.11.13/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.11.13/Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst 2025-08-01 22:21:33.122108946 +0200
@@ -0,0 +1,3 @@
+:mod:`tarfile` now validates archives to ensure member offsets are
+non-negative. (Contributed by Alexander Enrique Urieles Nieto in
+:gh:`130577`.)

Binary file not shown.

View File

@@ -1 +0,0 @@
{"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIICzjCCAlSgAwIBAgIUfnOGm4U1QCsCXWiDvPy5Tgni2HUwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNjAzMTkyNzM1WhcNMjUwNjAzMTkzNzM1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpTsf/wrCdu4Domf4WOtO4CLkj51wmj4iesYv5N6DYhghPjqQFwGYI9gFc/WX6QMIWh5YHU2NGxrmM7KfbAYzz6OCAXMwggFvMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUkQ2/O4Fivj1bTq7NTQczm1RdtYAwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wIgYDVR0RAQH/BBgwFoEUcGFibG9nc2FsQHB5dGhvbi5vcmcwKQYKKwYBBAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMCsGCisGAQQBg78wAQgEHQwbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGLBgorBgEEAdZ5AgQCBH0EewB5AHcA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGXN0NwGwAABAMASDBGAiEA6pD8PjS+5z2SQre/NS/wOdFSjVMsxvtfF6A1jg+1T3YCIQDC44S/Z3c0dNddM7EkE+A3j7Vft3hqRUoFkNe4U6g5qTAKBggqhkjOPQQDAwNoADBlAjA6lBI2r3KCZFc+2affpH3S3Xj3gMOKh8Lr5Z7TgkGp3Q6QsnExGmJJ0leXhqH6rQkCMQDfxk/6DyhbO7KTrIUfmrbZoa7dV75cresJS69Xk67XN57qsqY52DZj9o4fbUIw4ro="}, "tlogEntries": [{"logIndex": "228953871", "logId": {"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.1"}, "integratedTime": "1748978856", "inclusionPromise": {"signedEntryTimestamp": "MEUCIQC9nXmfcRqyOL2Zmw1zI7+kulTbmDE3Yfzew81mXJGU4QIgF8Uhdg2uzttSA6erOuEchX68PCyJ0cVFHE0XJX2+ZfE="}, "inclusionProof": {"logIndex": "107049609", "rootHash": "Ilofw5POqC/C3zqfrdMQP1DyhNW+UfB9fHdjrbK6qaM=", "treeSize": "107049610", "hashes": ["AcD1iyjU7nuIPqAq29ynz7PEdq6zPXglj6e2tkH+/do=", "1BNDCN01B3dbUo/TfLaQgKIYTvPyrkcrHKd69GxuF2E=", "t59A0CV2pHM2S9AgZgcEA6FbXhgNZGo0jMRIXHiqsJ0=", "bCrkgWpJ8MBic+mIfCRsKi+5XAMqgM8Lc6G0LLfzZ7M=", "4iwdOrGkcqdN0qqZUx/gv8a8qpLMqVj8aXRVmhQ558c=", "mAX/zvx1jR0ujLtDApsQpHyxmoDGidClHMOn0BX1aQA=", "u5LKLBPTYgXZg0fBi6/8LuEeNy3EBAxJF0AkkB4Co6E=", "SPUVncwJRVX/n/RICCYqLpAzraqx7S0eMdXRr1RLRgg=", "uEJFtwcGQJMd9kjQhkXb7gl2WD3WMElCc15uDFvFGxs=", "VdOKzpQhJlpXgijzXANf/hNlje1G/N1kUuVnKNskkso=", "mta5fH/gFwxJ/0fT8yGpn3sFCY0G1RY555Iflm0LInM=", "7v8qPHNDLerpduaMx06eb/MwgoQwczTn/cYGKX/9wZ4="], "checkpoint": {"envelope": "rekor.sigstore.dev - 1193050959916656506\n107049610\nIlofw5POqC/C3zqfrdMQP1DyhNW+UfB9fHdjrbK6qaM=\n\n\u2014 rekor.sigstore.dev wNI9ajBGAiEAjtzTnsnrGx0G3Dg99s89cPUh6EA+cxkicQ9j4qYU60wCIQCKcAL4kdakbq2JrBVgk7bRNf3FoJRrEI6SCjv16f7Crg==\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4ZmI1ZjlmYmM3NjA5ZmE4MjJjYjMxNTQ5ODg0NTc1ZGI3ZmQ5NjU3Y2JmZmI4OTUxMGI1ZDc5NzU5NjNhODNhIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUM5Q1JZRjNSWGUzdDNxQlBJd2UrR3pMMTJCOXVLTjIrRFpWa2JjZW1FTS93SWdPMDFKaVhnbUJxZEN5RVhoM05JUEt5QlRBb2hpcjZHTkhZdXhiSUxKNDlRPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTjZha05EUVd4VFowRjNTVUpCWjBsVlptNVBSMjAwVlRGUlEzTkRXRmRwUkhaUWVUVlVaMjVwTWtoVmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFZkMDVxUVhwTlZHdDVUbnBOTVZkb1kwNU5hbFYzVG1wQmVrMVVhM3BPZWsweFYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZ3VkhObUwzZHlRMlIxTkVSdmJXWTBWMDkwVHpSRFRHdHFOVEYzYldvMGFXVnpXWFlLTlU0MlJGbG9aMmhRYW5GUlJuZEhXVWs1WjBaakwxZFlObEZOU1Zkb05WbElWVEpPUjNoeWJVMDNTMlppUVZsNmVqWlBRMEZZVFhkblowWjJUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZyVVRJdkNrODBSbWwyYWpGaVZIRTNUbFJSWTNwdE1WSmtkRmxCZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBsbldVUldVakJTUVZGSUwwSkNaM2RHYjBWVlkwZEdhV0pIT1c1ak1rWnpVVWhDTldSSGFIWmlhVFYyWTIxamQwdFJXVXRMZDFsQ1FrRkhSQXAyZWtGQ1FWRlJZbUZJVWpCalNFMDJUSGs1YUZreVRuWmtWelV3WTNrMWJtSXlPVzVpUjFWMVdUSTVkRTFEYzBkRGFYTkhRVkZSUW1jM09IZEJVV2RGQ2toUmQySmhTRkl3WTBoTk5reDVPV2haTWs1MlpGYzFNR041Tlc1aU1qbHVZa2RWZFZreU9YUk5TVWRNUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpJTUVVS1pYZENOVUZJWTBFelZEQjNZWE5pU0VWVVNtcEhValJqYlZkak0wRnhTa3RZY21wbFVFc3pMMmcwY0hsblF6aHdOMjgwUVVGQlIxaE9NRTUzUjNkQlFRcENRVTFCVTBSQ1IwRnBSVUUyY0VRNFVHcFRLelY2TWxOUmNtVXZUbE12ZDA5a1JsTnFWazF6ZUhaMFprWTJRVEZxWnlzeFZETlpRMGxSUkVNME5GTXZDbG96WXpCa1RtUmtUVGRGYTBVclFUTnFOMVptZEROb2NWSlZiMFpyVG1VMFZUWm5OWEZVUVV0Q1oyZHhhR3RxVDFCUlVVUkJkMDV2UVVSQ2JFRnFRVFlLYkVKSk1uSXpTME5hUm1Nck1tRm1abkJJTTFNeldHb3paMDFQUzJnNFRISTFXamRVWjJ0SGNETlJObEZ6YmtWNFIyMUtTakJzWlZob2NVZzJjbEZyUXdwTlVVUm1lR3N2TmtSNWFHSlBOMHRVY2tsVlptMXlZbHB2WVRka1ZqYzFZM0psYzBwVE5qbFlhelkzV0U0MU4zRnpjVmsxTWtSYWFqbHZOR1ppVlVsM0NqUnliejBLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In19fX0="}], "timestampVerificationData": {}}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "j7X5+8dgn6giyzFUmIRXXbf9llfL/7iVELXXl1ljqDo="}, "signature": "MEUCIQC9CRYF3RXe3t3qBPIwe+GzL12B9uKN2+DZVkbcemEM/wIgO01JiXgmBqdCyEXh3NIPKyBTAohir6GNHYuxbILJ49Q="}}

BIN
Python-3.11.14.tar.xz LFS Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -3,9 +3,11 @@
Misc/NEWS | 2 +- Misc/NEWS | 2 +-
2 files changed, 1 insertion(+), 4 deletions(-) 2 files changed, 1 insertion(+), 4 deletions(-)
--- a/Doc/using/configure.rst Index: Python-3.11.14/Doc/using/configure.rst
+++ b/Doc/using/configure.rst ===================================================================
@@ -43,7 +43,6 @@ General Options --- Python-3.11.14.orig/Doc/using/configure.rst 2025-11-15 19:14:54.096952433 +0100
+++ Python-3.11.14/Doc/using/configure.rst 2025-11-15 19:15:04.439920979 +0100
@@ -43,7 +43,6 @@
See :data:`sys.int_info.bits_per_digit <sys.int_info>`. See :data:`sys.int_info.bits_per_digit <sys.int_info>`.
@@ -13,7 +15,7 @@
.. option:: --with-cxx-main=COMPILER .. option:: --with-cxx-main=COMPILER
Compile the Python ``main()`` function and link Python executable with C++ Compile the Python ``main()`` function and link Python executable with C++
@@ -529,13 +528,11 @@ macOS Options @@ -529,13 +528,11 @@
See ``Mac/README.rst``. See ``Mac/README.rst``.
@@ -27,9 +29,11 @@
.. option:: --enable-framework=INSTALLDIR .. option:: --enable-framework=INSTALLDIR
Create a Python.framework rather than a traditional Unix install. Optional Create a Python.framework rather than a traditional Unix install. Optional
--- a/Misc/NEWS Index: Python-3.11.14/Misc/NEWS
+++ b/Misc/NEWS ===================================================================
@@ -9911,7 +9911,7 @@ C API --- Python-3.11.14.orig/Misc/NEWS 2025-11-15 19:14:54.096952433 +0100
+++ Python-3.11.14/Misc/NEWS 2025-11-15 19:15:04.445942414 +0100
@@ -9987,7 +9987,7 @@
- bpo-40939: Removed documentation for the removed ``PyParser_*`` C API. - bpo-40939: Removed documentation for the removed ``PyParser_*`` C API.
- bpo-43795: The list in :ref:`limited-api-list` now shows the public name - bpo-43795: The list in :ref:`limited-api-list` now shows the public name

View File

@@ -0,0 +1,36 @@
From 19b61747df3d62c822285c488753d6fbdf91e3ac Mon Sep 17 00:00:00 2001
From: Daniel Garcia Moreno <daniel.garcia@suse.com>
Date: Tue, 23 Sep 2025 10:20:16 +0200
Subject: [PATCH 1/2] gh-139257: Support docutils >= 0.22
---
Doc/tools/extensions/pyspecific.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
Index: Python-3.11.13/Doc/tools/extensions/pyspecific.py
===================================================================
--- Python-3.11.13.orig/Doc/tools/extensions/pyspecific.py 2025-06-03 20:38:25.000000000 +0200
+++ Python-3.11.13/Doc/tools/extensions/pyspecific.py 2025-09-30 18:20:10.096471679 +0200
@@ -48,11 +48,21 @@
SOURCE_URI = 'https://github.com/python/cpython/tree/3.11/%s'
# monkey-patch reST parser to disable alphabetic and roman enumerated lists
+def _disable_alphabetic_and_roman(text):
+ try:
+ # docutils >= 0.22
+ from docutils.parsers.rst.states import InvalidRomanNumeralError
+ raise InvalidRomanNumeralError(text)
+ except ImportError:
+ # docutils < 0.22
+ return None
+
+
from docutils.parsers.rst.states import Body
Body.enum.converters['loweralpha'] = \
Body.enum.converters['upperalpha'] = \
Body.enum.converters['lowerroman'] = \
- Body.enum.converters['upperroman'] = lambda x: None
+ Body.enum.converters['upperroman'] = _disable_alphabetic_and_roman
# monkey-patch the productionlist directive to allow hyphens in group names
# https://github.com/sphinx-doc/sphinx/issues/11854

View File

@@ -1,3 +1,95 @@
-------------------------------------------------------------------
Thu Dec 18 10:33:44 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2025-13836-http-resp-cont-len.patch (bsc#1254400,
CVE-2025-13836) to prevent reading an HTTP response from
a server, if no read amount is specified, with using
Content-Length per default as the length.
- Add CVE-2025-12084-minidom-quad-search.patch prevent quadratic
behavior in node ID cache clearing (CVE-2025-12084,
bsc#1254997).
- Add CVE-2025-13837-plistlib-mailicious-length.patch protect
against OOM when loading malicious content (CVE-2025-13837,
bsc#1254401).
-------------------------------------------------------------------
Thu Nov 13 17:13:03 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
- Add CVE-2025-6075-expandvars-perf-degrad.patch avoid simple
quadratic complexity vulnerabilities of os.path.expandvars()
(CVE-2025-6075, bsc#1252974).
- Readjusted patches:
- CVE-2023-52425-libexpat-2.6.0-backport.patch
- CVE-2023-52425-remove-reparse_deferral-tests.patch
- fix_configure_rst.patch
- skip_if_buildbot-extend.patch
-------------------------------------------------------------------
Wed Oct 15 08:52:35 UTC 2025 - Daniel Garcia <daniel.garcia@suse.com>
- Update to 3.11.14:
- Security
- gh-139700: Check consistency of the zip64 end of central
directory record. Support records with “zip64 extensible data”
if there are no bytes prepended to the ZIP file
(CVE-2025-8291, bsc#1251305).
- gh-139400: xml.parsers.expat: Make sure that parent Expat
parsers are only garbage-collected once they are no longer
referenced by subparsers created by
ExternalEntityParserCreate(). Patch by Sebastian Pipping.
- gh-135661: Fix parsing start and end tags in
html.parser.HTMLParser according to the HTML5 standard.
* Whitespaces no longer accepted between </ and the tag name. E.g.
</ script> does not end the script section.
* Vertical tabulation (\v) and non-ASCII whitespaces no longer
recognized as whitespaces. The only whitespaces are \t\n\r\f and
space.
* Null character (U+0000) no longer ends the tag name.
* Attributes and slashes after the tag name in end tags are now
ignored, instead of terminating after the first > in quoted
attribute value. E.g. </script/foo=">"/>.
* Multiple slashes and whitespaces between the last attribute and
closing > are now ignored in both start and end tags. E.g. <a
foo=bar/ //>.
* Multiple = between attribute name and value are no longer
collapsed. E.g. <a foo==bar> produces attribute “foo” with value
“=bar”.
- gh-135661: Fix CDATA section parsing in html.parser.HTMLParser
according to the HTML5 standard: ] ]> and ]] > no longer end the
CDATA section. Add private method _set_support_cdata() which can
be used to specify how to parse <[CDATA[ — as a CDATA section in
foreign content (SVG or MathML) or as a bogus comment in the
HTML namespace.
- gh-102555: Fix comment parsing in html.parser.HTMLParser
according to the HTML5 standard. --!> now ends the comment. -- >
no longer ends the comment. Support abnormally ended empty
comments <--> and <--->.
- gh-135462: Fix quadratic complexity in processing specially
crafted input in html.parser.HTMLParser. End-of-file errors are
now handled according to the HTML5 specs comments and
declarations are automatically closed, tags are ignored.
- gh-118350: Fix support of escapable raw text mode (elements
“textarea” and “title”) in html.parser.HTMLParser.
- gh-86155: html.parser.HTMLParser.close() no longer loses data
when the <script> tag is not closed. Patch by Waylan Limberg.
- Library
- gh-139312: Upgrade bundled libexpat to 2.7.3
- gh-138998: Update bundled libexpat to 2.7.2
- gh-130577: tarfile now validates archives to ensure member
offsets are non-negative. (Contributed by Alexander Enrique
Urieles Nieto in gh-130577.)
- gh-135374: Update the bundled copy of setuptools to 79.0.1.
- Drop upstreamed patches:
- CVE-2025-8194-tarfile-no-neg-offsets.patch
- CVE-2025-6069-quad-complex-HTMLParser.patch
-------------------------------------------------------------------
Mon Sep 29 06:52:07 UTC 2025 - Daniel Garcia <daniel.garcia@suse.com>
- Add gh139257-Support-docutils-0.22.patch to fix build with latest
docutils (>=0.22) gh#python/cpython#139257
------------------------------------------------------------------- -------------------------------------------------------------------
Fri Sep 19 14:38:03 UTC 2025 - Dominique Leuenberger <dimstar@opensuse.org> Fri Sep 19 14:38:03 UTC 2025 - Dominique Leuenberger <dimstar@opensuse.org>

View File

@@ -107,7 +107,7 @@
# _md5.cpython-38m-x86_64-linux-gnu.so # _md5.cpython-38m-x86_64-linux-gnu.so
%define dynlib() %{sitedir}/lib-dynload/%{1}.cpython-%{abi_tag}-%{archname}-%{_os}%{?_gnu}%{?armsuffix}.so %define dynlib() %{sitedir}/lib-dynload/%{1}.cpython-%{abi_tag}-%{archname}-%{_os}%{?_gnu}%{?armsuffix}.so
Name: %{python_pkg_name}%{psuffix} Name: %{python_pkg_name}%{psuffix}
Version: 3.11.13 Version: 3.11.14
Release: 0 Release: 0
Summary: Python 3 Interpreter Summary: Python 3 Interpreter
License: Python-2.0 License: Python-2.0
@@ -186,12 +186,20 @@ Patch19: bso1227999-reproducible-builds.patch
Patch22: gh120226-fix-sendfile-test-kernel-610.patch Patch22: gh120226-fix-sendfile-test-kernel-610.patch
# PATCH-FIX-UPSTREAM Add platform triplets for 64-bit LoongArch gh#python/cpython#30939 glaubitz@suse.com # PATCH-FIX-UPSTREAM Add platform triplets for 64-bit LoongArch gh#python/cpython#30939 glaubitz@suse.com
Patch24: add-loongarch64-support.patch Patch24: add-loongarch64-support.patch
# PATCH-FIX-UPSTREAM CVE-2025-6069-quad-complex-HTMLParser.patch bsc#1244705 mcepl@suse.com # PATCH-FIX-OPENSUSE gh139257-Support-docutils-0.22.patch gh#python/cpython#139257 daniel.garcia@suse.com
# avoid quadratic complexity when processing malformed inputs with HTMLParser Patch25: gh139257-Support-docutils-0.22.patch
Patch25: CVE-2025-6069-quad-complex-HTMLParser.patch # PATCH-FIX-UPSTREAM CVE-2025-6075-expandvars-perf-degrad.patch bsc#1252974 mcepl@suse.com
# PATCH-FIX-UPSTREAM CVE-2025-8194-tarfile-no-neg-offsets.patch bsc#1247249 mcepl@suse.com # Avoid potential quadratic complexity vulnerabilities in path modules
# tarfile now validates archives to ensure member offsets are non-negative Patch26: CVE-2025-6075-expandvars-perf-degrad.patch
Patch26: CVE-2025-8194-tarfile-no-neg-offsets.patch # PATCH-FIX-UPSTREAM CVE-2025-13836-http-resp-cont-len.patch bsc#1254400 mcepl@suse.com
# Avoid loading possibly compromised length of HTTP response
Patch27: CVE-2025-13836-http-resp-cont-len.patch
# PATCH-FIX-UPSTREAM CVE-2025-12084-minidom-quad-search.patch bsc#1254997 mcepl@suse.com
# prevent quadratic behavior in node ID cache clearing
Patch28: CVE-2025-12084-minidom-quad-search.patch
# PATCH-FIX-UPSTREAM CVE-2025-13837-plistlib-mailicious-length.patch bsc#1254401 mcepl@suse.com
# protect against OOM when loading malicious content
Patch29: CVE-2025-13837-plistlib-mailicious-length.patch
BuildRequires: autoconf-archive BuildRequires: autoconf-archive
BuildRequires: automake BuildRequires: automake
BuildRequires: crypto-policies-scripts BuildRequires: crypto-policies-scripts

View File

@@ -2,9 +2,11 @@
Lib/test/support/__init__.py | 2 +- Lib/test/support/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-) 1 file changed, 1 insertion(+), 1 deletion(-)
--- a/Lib/test/support/__init__.py Index: Python-3.11.14/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py ===================================================================
@@ -384,7 +384,7 @@ def skip_if_buildbot(reason=None): --- Python-3.11.14.orig/Lib/test/support/__init__.py 2025-11-15 19:14:54.049952478 +0100
+++ Python-3.11.14/Lib/test/support/__init__.py 2025-11-15 19:15:08.449938538 +0100
@@ -394,7 +394,7 @@
if not reason: if not reason:
reason = 'not suitable for buildbots' reason = 'not suitable for buildbots'
try: try: