3 Commits

Author SHA256 Message Date
380660d7b6 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:52:27 +01:00
793408c576 fix: complete unfinished elimination of the patch 53 2025-12-12 12:06:00 +01:00
a7506e8af6 Update to 3.9.25
Security
    - gh-137836: Add support of the “plaintext” element, RAWTEXT
      elements “xmp”, “iframe”, “noembed” and “noframes”, and
      optionally RAWTEXT element “noscript” in
      html.parser.HTMLParser.
    - gh-136063: email.message: ensure linear complexity for
      legacy HTTP parameters parsing. Patch by Bénédikt Tran.
    - gh-136065: Fix quadratic complexity in
      os.path.expandvars() (CVE-2025-6075, bsc#1252974).
Library
    - gh-98793: Fix argument typechecks in
      _overlapped.WSAConnect() and
      _overlapped.Overlapped.WSASendTo() functions. bpo-44817:
      Ignore WinError 53 (ERROR_BAD_NETPATH), 65
      (ERROR_NETWORK_ACCESS_DENIED) and 161 (ERROR_BAD_PATHNAME)
      when using ntpath.realpath().
Core and Builtins
    - gh-120384: Fix an array out of bounds crash in
      list_ass_subscript, which could be invoked via some
      specificly tailored input: including concurrent
      modification of a list object, where one thread assigns
      a slice and another clears it.
    - gh-120298: Fix use-after free in list_richcompare_impl
      which can be invoked via some specificly tailored evil
      input.
2025-12-11 22:48:48 +01:00
12 changed files with 532 additions and 450 deletions

View File

@@ -4,9 +4,11 @@
Lib/test/test_xml_etree.py | 7 +++++++ Lib/test/test_xml_etree.py | 7 +++++++
3 files changed, 14 insertions(+) 3 files changed, 14 insertions(+)
--- a/Lib/test/test_pyexpat.py Index: Python-3.9.25/Lib/test/test_pyexpat.py
+++ b/Lib/test/test_pyexpat.py ===================================================================
@@ -766,6 +766,10 @@ class ReparseDeferralTest(unittest.TestC --- Python-3.9.25.orig/Lib/test/test_pyexpat.py 2025-12-11 22:43:38.646411669 +0100
+++ Python-3.9.25/Lib/test/test_pyexpat.py 2025-12-11 22:43:57.288891858 +0100
@@ -802,6 +802,10 @@
self.assertEqual(started, ['doc']) self.assertEqual(started, ['doc'])
def test_reparse_deferral_disabled(self): def test_reparse_deferral_disabled(self):
@@ -17,9 +19,11 @@
started = [] started = []
def start_element(name, _): def start_element(name, _):
--- a/Lib/test/test_sax.py Index: Python-3.9.25/Lib/test/test_sax.py
+++ b/Lib/test/test_sax.py ===================================================================
@@ -1236,6 +1236,9 @@ class ExpatReaderTest(XmlTestBase): --- Python-3.9.25.orig/Lib/test/test_sax.py 2025-12-11 22:43:38.675498657 +0100
+++ Python-3.9.25/Lib/test/test_sax.py 2025-12-11 22:43:57.289349463 +0100
@@ -1236,6 +1236,9 @@
self.assertEqual(result.getvalue(), start + b"<doc></doc>") self.assertEqual(result.getvalue(), start + b"<doc></doc>")
@@ -29,9 +33,11 @@
def test_flush_reparse_deferral_disabled(self): def test_flush_reparse_deferral_disabled(self):
result = BytesIO() result = BytesIO()
xmlgen = XMLGenerator(result) xmlgen = XMLGenerator(result)
--- a/Lib/test/test_xml_etree.py Index: Python-3.9.25/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py ===================================================================
@@ -1416,9 +1416,13 @@ class XMLPullParserTest(unittest.TestCas --- Python-3.9.25.orig/Lib/test/test_xml_etree.py 2025-12-11 22:43:38.988627336 +0100
+++ Python-3.9.25/Lib/test/test_xml_etree.py 2025-12-11 22:43:57.289604596 +0100
@@ -1416,9 +1416,13 @@
self.assert_event_tags(parser, [('end', 'root')]) self.assert_event_tags(parser, [('end', 'root')])
self.assertIsNone(parser.close()) self.assertIsNone(parser.close())
@@ -45,7 +51,7 @@
def test_simple_xml_chunk_5(self): def test_simple_xml_chunk_5(self):
self.test_simple_xml(chunk_size=5, flush=True) self.test_simple_xml(chunk_size=5, flush=True)
@@ -1643,6 +1647,9 @@ class XMLPullParserTest(unittest.TestCas @@ -1643,6 +1647,9 @@
self.assert_event_tags(parser, [('end', 'doc')]) self.assert_event_tags(parser, [('end', 'doc')])

View File

@@ -44,10 +44,10 @@ Subject: [PATCH] PEP-644: Require OpenSSL 1.1.1 or newer
15 files changed, 77 insertions(+), 873 deletions(-) 15 files changed, 77 insertions(+), 873 deletions(-)
create mode 100644 Misc/NEWS.d/next/Build/2021-03-30-14-19-39.bpo-43669.lWMUYx.rst create mode 100644 Misc/NEWS.d/next/Build/2021-03-30-14-19-39.bpo-43669.lWMUYx.rst
Index: Python-3.9.24/Doc/using/unix.rst Index: Python-3.9.25/Doc/using/unix.rst
=================================================================== ===================================================================
--- Python-3.9.24.orig/Doc/using/unix.rst 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Doc/using/unix.rst 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Doc/using/unix.rst 2025-11-14 00:55:05.521462804 +0100 +++ Python-3.9.25/Doc/using/unix.rst 2025-12-19 23:25:05.628832227 +0100
@@ -113,6 +113,7 @@ @@ -113,6 +113,7 @@
| | embedding the interpreter. | | | embedding the interpreter. |
+-----------------------------------------------+------------------------------------------+ +-----------------------------------------------+------------------------------------------+
@@ -56,10 +56,10 @@ Index: Python-3.9.24/Doc/using/unix.rst
Miscellaneous Miscellaneous
============= =============
Index: Python-3.9.24/Lib/ssl.py Index: Python-3.9.25/Lib/ssl.py
=================================================================== ===================================================================
--- Python-3.9.24.orig/Lib/ssl.py 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Lib/ssl.py 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Lib/ssl.py 2025-11-14 00:55:05.522031528 +0100 +++ Python-3.9.25/Lib/ssl.py 2025-12-19 23:25:05.629281589 +0100
@@ -912,15 +912,12 @@ @@ -912,15 +912,12 @@
"""Return the currently selected NPN protocol as a string, or ``None`` """Return the currently selected NPN protocol as a string, or ``None``
if a next protocol was not negotiated or if NPN is not supported by one if a next protocol was not negotiated or if NPN is not supported by one
@@ -89,10 +89,10 @@ Index: Python-3.9.24/Lib/ssl.py
@_sslcopydoc @_sslcopydoc
def selected_alpn_protocol(self): def selected_alpn_protocol(self):
Index: Python-3.9.24/Lib/test/test_ssl.py Index: Python-3.9.25/Lib/test/test_ssl.py
=================================================================== ===================================================================
--- Python-3.9.24.orig/Lib/test/test_ssl.py 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Lib/test/test_ssl.py 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Lib/test/test_ssl.py 2025-11-14 00:55:05.522484943 +0100 +++ Python-3.9.25/Lib/test/test_ssl.py 2025-12-19 23:25:05.630031412 +0100
@@ -39,7 +39,6 @@ @@ -39,7 +39,6 @@
PROTOCOLS = sorted(ssl._PROTOCOL_NAMES) PROTOCOLS = sorted(ssl._PROTOCOL_NAMES)
HOST = socket_helper.HOST HOST = socket_helper.HOST
@@ -332,16 +332,16 @@ Index: Python-3.9.24/Lib/test/test_ssl.py
self.assertFalse(stats['session_reused']) self.assertFalse(stats['session_reused'])
sess_stat = server_context.session_stats() sess_stat = server_context.session_stats()
self.assertEqual(sess_stat['accept'], 1) self.assertEqual(sess_stat['accept'], 1)
Index: Python-3.9.24/Misc/NEWS.d/next/Build/2021-03-30-14-19-39.bpo-43669.lWMUYx.rst Index: Python-3.9.25/Misc/NEWS.d/next/Build/2021-03-30-14-19-39.bpo-43669.lWMUYx.rst
=================================================================== ===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000 --- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.9.24/Misc/NEWS.d/next/Build/2021-03-30-14-19-39.bpo-43669.lWMUYx.rst 2025-11-14 00:55:05.523862509 +0100 +++ Python-3.9.25/Misc/NEWS.d/next/Build/2021-03-30-14-19-39.bpo-43669.lWMUYx.rst 2025-12-19 23:25:05.630820206 +0100
@@ -0,0 +1 @@ @@ -0,0 +1 @@
+Implement :pep:`644`. Python now requires OpenSSL 1.1.1 or newer. +Implement :pep:`644`. Python now requires OpenSSL 1.1.1 or newer.
Index: Python-3.9.24/Modules/Setup Index: Python-3.9.25/Modules/Setup
=================================================================== ===================================================================
--- Python-3.9.24.orig/Modules/Setup 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Modules/Setup 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Modules/Setup 2025-11-14 00:55:05.524260958 +0100 +++ Python-3.9.25/Modules/Setup 2025-12-19 23:25:05.631164735 +0100
@@ -210,11 +210,23 @@ @@ -210,11 +210,23 @@
#_socket socketmodule.c #_socket socketmodule.c
@@ -371,10 +371,10 @@ Index: Python-3.9.24/Modules/Setup
# The crypt module is now disabled by default because it breaks builds # The crypt module is now disabled by default because it breaks builds
# on many systems (where -lcrypt is needed), e.g. Linux (I believe). # on many systems (where -lcrypt is needed), e.g. Linux (I believe).
Index: Python-3.9.24/Modules/_hashopenssl.c Index: Python-3.9.25/Modules/_hashopenssl.c
=================================================================== ===================================================================
--- Python-3.9.24.orig/Modules/_hashopenssl.c 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Modules/_hashopenssl.c 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Modules/_hashopenssl.c 2025-11-14 00:55:05.524484942 +0100 +++ Python-3.9.25/Modules/_hashopenssl.c 2025-12-19 23:25:05.631562345 +0100
@@ -43,51 +43,12 @@ @@ -43,51 +43,12 @@
# error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL" # error "OPENSSL_THREADS is not defined, Python requires thread-safe OpenSSL"
#endif #endif
@@ -557,10 +557,10 @@ Index: Python-3.9.24/Modules/_hashopenssl.c
- return m; - return m;
+ return PyModuleDef_Init(&_hashlibmodule); + return PyModuleDef_Init(&_hashlibmodule);
} }
Index: Python-3.9.24/Modules/_ssl.c Index: Python-3.9.25/Modules/_ssl.c
=================================================================== ===================================================================
--- Python-3.9.24.orig/Modules/_ssl.c 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Modules/_ssl.c 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Modules/_ssl.c 2025-11-14 00:55:05.525585095 +0100 +++ Python-3.9.25/Modules/_ssl.c 2025-12-19 23:25:05.632257411 +0100
@@ -29,9 +29,9 @@ @@ -29,9 +29,9 @@
#define _PySSL_FIX_ERRNO #define _PySSL_FIX_ERRNO
@@ -1552,10 +1552,10 @@ Index: Python-3.9.24/Modules/_ssl.c
#if defined(SSL2_VERSION) && !defined(OPENSSL_NO_SSL2) #if defined(SSL2_VERSION) && !defined(OPENSSL_NO_SSL2)
addbool(m, "HAS_SSLv2", 1); addbool(m, "HAS_SSLv2", 1);
Index: Python-3.9.24/Modules/_ssl/debughelpers.c Index: Python-3.9.25/Modules/_ssl/debughelpers.c
=================================================================== ===================================================================
--- Python-3.9.24.orig/Modules/_ssl/debughelpers.c 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Modules/_ssl/debughelpers.c 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Modules/_ssl/debughelpers.c 2025-11-14 00:55:05.526651095 +0100 +++ Python-3.9.25/Modules/_ssl/debughelpers.c 2025-12-19 23:25:05.633150689 +0100
@@ -114,8 +114,6 @@ @@ -114,8 +114,6 @@
return 0; return 0;
} }
@@ -1571,10 +1571,10 @@ Index: Python-3.9.24/Modules/_ssl/debughelpers.c
} }
- -
-#endif -#endif
Index: Python-3.9.24/Modules/clinic/_hashopenssl.c.h Index: Python-3.9.25/Modules/clinic/_hashopenssl.c.h
=================================================================== ===================================================================
--- Python-3.9.24.orig/Modules/clinic/_hashopenssl.c.h 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Modules/clinic/_hashopenssl.c.h 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Modules/clinic/_hashopenssl.c.h 2025-11-14 00:55:05.527005334 +0100 +++ Python-3.9.25/Modules/clinic/_hashopenssl.c.h 2025-12-19 23:25:05.633452755 +0100
@@ -965,7 +965,7 @@ @@ -965,7 +965,7 @@
return return_value; return return_value;
} }
@@ -1619,10 +1619,10 @@ Index: Python-3.9.24/Modules/clinic/_hashopenssl.c.h
- #define _HASHLIB_GET_FIPS_MODE_METHODDEF - #define _HASHLIB_GET_FIPS_MODE_METHODDEF
-#endif /* !defined(_HASHLIB_GET_FIPS_MODE_METHODDEF) */ -#endif /* !defined(_HASHLIB_GET_FIPS_MODE_METHODDEF) */
/*[clinic end generated code: output=b6b280e46bf0b139 input=a9049054013a1b77]*/ /*[clinic end generated code: output=b6b280e46bf0b139 input=a9049054013a1b77]*/
Index: Python-3.9.24/Modules/clinic/_ssl.c.h Index: Python-3.9.25/Modules/clinic/_ssl.c.h
=================================================================== ===================================================================
--- Python-3.9.24.orig/Modules/clinic/_ssl.c.h 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Modules/clinic/_ssl.c.h 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Modules/clinic/_ssl.c.h 2025-11-14 00:55:05.527465105 +0100 +++ Python-3.9.25/Modules/clinic/_ssl.c.h 2025-12-19 23:25:05.633884238 +0100
@@ -139,29 +139,6 @@ @@ -139,29 +139,6 @@
return _ssl__SSLSocket_version_impl(self); return _ssl__SSLSocket_version_impl(self);
} }
@@ -1757,11 +1757,11 @@ Index: Python-3.9.24/Modules/clinic/_ssl.c.h
#ifndef _SSL_RAND_EGD_METHODDEF #ifndef _SSL_RAND_EGD_METHODDEF
#define _SSL_RAND_EGD_METHODDEF #define _SSL_RAND_EGD_METHODDEF
#endif /* !defined(_SSL_RAND_EGD_METHODDEF) */ #endif /* !defined(_SSL_RAND_EGD_METHODDEF) */
Index: Python-3.9.24/Tools/ssl/multissltests.py Index: Python-3.9.25/Tools/ssl/multissltests.py
=================================================================== ===================================================================
--- Python-3.9.24.orig/Tools/ssl/multissltests.py 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/Tools/ssl/multissltests.py 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/Tools/ssl/multissltests.py 2025-11-14 00:55:05.527928158 +0100 +++ Python-3.9.25/Tools/ssl/multissltests.py 2025-12-19 23:25:05.634262920 +0100
@@ -43,8 +43,6 @@ @@ -44,8 +44,6 @@
log = logging.getLogger("multissl") log = logging.getLogger("multissl")
OPENSSL_OLD_VERSIONS = [ OPENSSL_OLD_VERSIONS = [
@@ -1770,7 +1770,7 @@ Index: Python-3.9.24/Tools/ssl/multissltests.py
] ]
OPENSSL_RECENT_VERSIONS = [ OPENSSL_RECENT_VERSIONS = [
@@ -53,11 +51,9 @@ @@ -54,11 +52,9 @@
] ]
LIBRESSL_OLD_VERSIONS = [ LIBRESSL_OLD_VERSIONS = [
@@ -1782,10 +1782,10 @@ Index: Python-3.9.24/Tools/ssl/multissltests.py
] ]
# store files in ../multissl # store files in ../multissl
Index: Python-3.9.24/configure Index: Python-3.9.25/configure
=================================================================== ===================================================================
--- Python-3.9.24.orig/configure 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/configure 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/configure 2025-11-14 00:55:05.530484938 +0100 +++ Python-3.9.25/configure 2025-12-19 23:25:05.636084606 +0100
@@ -88,6 +88,13 @@ @@ -88,6 +88,13 @@
# splitting by setting IFS to empty value.) # splitting by setting IFS to empty value.)
IFS=" "" $as_nl" IFS=" "" $as_nl"
@@ -1813,10 +1813,10 @@ Index: Python-3.9.24/configure
echo "" >&6 echo "" >&6
fi fi
- -
Index: Python-3.9.24/configure.ac Index: Python-3.9.25/configure.ac
=================================================================== ===================================================================
--- Python-3.9.24.orig/configure.ac 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/configure.ac 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/configure.ac 2025-11-14 00:55:05.531555268 +0100 +++ Python-3.9.25/configure.ac 2025-12-19 23:25:05.637165408 +0100
@@ -5756,42 +5756,6 @@ @@ -5756,42 +5756,6 @@
# Check for usable OpenSSL # Check for usable OpenSSL
AX_CHECK_OPENSSL([have_openssl=yes],[have_openssl=no]) AX_CHECK_OPENSSL([have_openssl=yes],[have_openssl=no])
@@ -1860,10 +1860,10 @@ Index: Python-3.9.24/configure.ac
# ssl module default cipher suite string # ssl module default cipher suite string
AH_TEMPLATE(PY_SSL_DEFAULT_CIPHERS, AH_TEMPLATE(PY_SSL_DEFAULT_CIPHERS,
[Default cipher suites list for ssl module. [Default cipher suites list for ssl module.
Index: Python-3.9.24/pyconfig.h.in Index: Python-3.9.25/pyconfig.h.in
=================================================================== ===================================================================
--- Python-3.9.24.orig/pyconfig.h.in 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/pyconfig.h.in 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/pyconfig.h.in 2025-11-14 00:55:05.532315919 +0100 +++ Python-3.9.25/pyconfig.h.in 2025-12-19 23:25:05.638051492 +0100
@@ -1351,9 +1351,6 @@ @@ -1351,9 +1351,6 @@
/* Define to 1 if you have the `writev' function. */ /* Define to 1 if you have the `writev' function. */
#undef HAVE_WRITEV #undef HAVE_WRITEV
@@ -1874,10 +1874,10 @@ Index: Python-3.9.24/pyconfig.h.in
/* Define if the zlib library has inflateCopy */ /* Define if the zlib library has inflateCopy */
#undef HAVE_ZLIB_COPY #undef HAVE_ZLIB_COPY
Index: Python-3.9.24/setup.py Index: Python-3.9.25/setup.py
=================================================================== ===================================================================
--- Python-3.9.24.orig/setup.py 2025-11-14 00:54:58.674489238 +0100 --- Python-3.9.25.orig/setup.py 2025-12-19 23:25:00.457106547 +0100
+++ Python-3.9.24/setup.py 2025-11-14 00:55:05.532484937 +0100 +++ Python-3.9.25/setup.py 2025-12-19 23:25:05.638647942 +0100
@@ -539,10 +539,7 @@ @@ -539,10 +539,7 @@
for l in (self.missing, self.failed, self.failed_on_import)): for l in (self.missing, self.failed, self.failed_on_import)):
print() print()

View File

@@ -0,0 +1,93 @@
From f4eb9ab014545b521fb261b80adfa6d138e7e092 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.9.25/Lib/test/test_minidom.py
===================================================================
--- Python-3.9.25.orig/Lib/test/test_minidom.py 2025-12-19 23:24:47.176384491 +0100
+++ Python-3.9.25/Lib/test/test_minidom.py 2025-12-19 23:27:27.634483015 +0100
@@ -2,6 +2,7 @@
import copy
import pickle
+import time
import io
from test import support
import unittest
@@ -162,6 +163,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.9.25/Lib/xml/dom/minidom.py
===================================================================
--- Python-3.9.25.orig/Lib/xml/dom/minidom.py 2025-10-31 19:40:52.000000000 +0100
+++ Python-3.9.25/Lib/xml/dom/minidom.py 2025-12-19 23:27:27.635128214 +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."
@@ -1537,7 +1530,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.9.25/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.9.25/Misc/NEWS.d/next/Security/2025-12-01-09-36-45.gh-issue-142145.tcAUhg.rst 2025-12-19 23:27:27.635403950 +0100
@@ -0,0 +1 @@
+Remove quadratic behavior in ``xml.minidom`` node ID cache clearing.

View File

@@ -0,0 +1,158 @@
From b3a7998115e195c40e00cfa662bcaa899d937c05 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 | 32 +++-
Lib/test/test_httplib.py | 66 ++++++++++
Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst | 5
3 files changed, 95 insertions(+), 8 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst
Index: Python-3.9.25/Lib/http/client.py
===================================================================
--- Python-3.9.25.orig/Lib/http/client.py 2025-10-31 19:40:52.000000000 +0100
+++ Python-3.9.25/Lib/http/client.py 2025-12-19 23:26:40.448421016 +0100
@@ -113,6 +113,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
@@ -621,14 +626,25 @@
reading. If the bytes are truly not available (due to EOF), then the
IncompleteRead exception can be used to detect the problem.
"""
- s = []
- while amt > 0:
- chunk = self.fp.read(min(amt, MAXAMOUNT))
- if not chunk:
- raise IncompleteRead(b''.join(s), amt)
- s.append(chunk)
- amt -= len(chunk)
- return b"".join(s)
+ 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."""
Index: Python-3.9.25/Lib/test/test_httplib.py
===================================================================
--- Python-3.9.25.orig/Lib/test/test_httplib.py 2025-10-31 19:40:52.000000000 +0100
+++ Python-3.9.25/Lib/test/test_httplib.py 2025-12-19 23:25:14.713217193 +0100
@@ -1196,6 +1196,72 @@
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
Index: Python-3.9.25/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.9.25/Misc/NEWS.d/next/Security/2024-05-23-11-47-48.gh-issue-119451.qkJe9-.rst 2025-12-19 23:25:14.713527850 +0100
@@ -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,159 @@
From e99059d800b741504ef18693803927a0dc062be4 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.10] 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 +++++++++-
Misc/NEWS.d/next/Security/2024-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
Index: Python-3.9.25/Lib/plistlib.py
===================================================================
--- Python-3.9.25.orig/Lib/plistlib.py 2025-10-31 19:40:52.000000000 +0100
+++ Python-3.9.25/Lib/plistlib.py 2025-12-19 23:27:31.617856557 +0100
@@ -64,6 +64,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):
@@ -490,12 +493,24 @@
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))
@@ -552,22 +567,16 @@
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
Index: Python-3.9.25/Lib/test/test_plistlib.py
===================================================================
--- Python-3.9.25.orig/Lib/test/test_plistlib.py 2025-10-31 19:40:52.000000000 +0100
+++ Python-3.9.25/Lib/test/test_plistlib.py 2025-12-19 23:27:31.618131664 +0100
@@ -837,8 +837,7 @@
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 = []
@@ -850,7 +849,11 @@
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
@@ -958,6 +961,34 @@
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):
Index: Python-3.9.25/Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ Python-3.9.25/Misc/NEWS.d/next/Security/2024-05-21-22-11-31.gh-issue-119342.BTFj4Z.rst 2025-12-19 23:27:31.618365285 +0100
@@ -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,385 +0,0 @@
From 8b8e68d3dc95f454f58fdd8aac10848facb1491d Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Fri, 31 Oct 2025 15:49:51 +0200
Subject: [PATCH 1/2] [3.9] 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 | 19 +
Lib/test/test_ntpath.py | 23 +
Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst | 1
5 files changed, 96 insertions(+), 116 deletions(-)
create mode 100644 Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst
Index: Python-3.9.24/Lib/ntpath.py
===================================================================
--- Python-3.9.24.orig/Lib/ntpath.py 2025-11-21 12:52:18.350673347 +0100
+++ Python-3.9.24/Lib/ntpath.py 2025-11-21 12:52:34.076133325 +0100
@@ -335,17 +335,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'}'
@@ -354,94 +360,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.9.24/Lib/posixpath.py
===================================================================
--- Python-3.9.24.orig/Lib/posixpath.py 2025-11-21 12:52:18.388628236 +0100
+++ Python-3.9.24/Lib/posixpath.py 2025-11-21 12:52:34.076301225 +0100
@@ -275,42 +275,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:
@@ -318,13 +317,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.9.24/Lib/test/test_genericpath.py
===================================================================
--- Python-3.9.24.orig/Lib/test/test_genericpath.py 2025-11-21 12:52:19.232406542 +0100
+++ Python-3.9.24/Lib/test/test_genericpath.py 2025-11-21 12:52:34.077309462 +0100
@@ -9,7 +9,7 @@
import warnings
from test import support
from test.support.script_helper import assert_python_ok
-from test.support import FakePath
+from test.support import FakePath, EnvironmentVarGuard
def create_file(filename, data=b'foo'):
@@ -374,7 +374,7 @@
def test_expandvars(self):
expandvars = self.pathmodule.expandvars
- with support.EnvironmentVarGuard() as env:
+ with EnvironmentVarGuard() as env:
env.clear()
env["foo"] = "bar"
env["{foo"] = "baz1"
@@ -408,7 +408,7 @@
expandvars = self.pathmodule.expandvars
def check(value, expected):
self.assertEqual(expandvars(value), expected)
- with support.EnvironmentVarGuard() as env:
+ with EnvironmentVarGuard() as env:
env.clear()
nonascii = support.FS_NONASCII
env['spam'] = nonascii
@@ -429,6 +429,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 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.9.24/Lib/test/test_ntpath.py
===================================================================
--- Python-3.9.24.orig/Lib/test/test_ntpath.py 2025-11-21 12:52:19.665352116 +0100
+++ Python-3.9.24/Lib/test/test_ntpath.py 2025-11-21 12:52:34.077441463 +0100
@@ -1,11 +1,10 @@
import ntpath
import os
-import subprocess
import sys
import unittest
import warnings
from ntpath import ALLOW_MISSING
-from test.support import TestFailed, FakePath
+from test.support import TestFailed, FakePath, EnvironmentVarGuard
from test import support, test_genericpath
from tempfile import TemporaryFile
@@ -642,7 +641,7 @@
ntpath.realpath("file.txt", **kwargs))
def test_expandvars(self):
- with support.EnvironmentVarGuard() as env:
+ with EnvironmentVarGuard() as env:
env.clear()
env["foo"] = "bar"
env["{foo"] = "baz1"
@@ -671,7 +670,7 @@
def test_expandvars_nonascii(self):
def check(value, expected):
tester('ntpath.expandvars(%r)' % value, expected)
- with support.EnvironmentVarGuard() as env:
+ with EnvironmentVarGuard() as env:
env.clear()
nonascii = support.FS_NONASCII
env['spam'] = nonascii
@@ -687,10 +686,23 @@
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 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')
- with support.EnvironmentVarGuard() as env:
+ with EnvironmentVarGuard() as env:
env.clear()
tester('ntpath.expanduser("~test")', '~test')
@@ -908,6 +920,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.9.24/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.9.24/Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst 2025-11-21 12:52:34.076771610 +0100
@@ -0,0 +1 @@
+Fix quadratic complexity in :func:`os.path.expandvars`.

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
Python-3.9.25.tar.xz LFS Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,49 @@
-------------------------------------------------------------------
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 Dec 11 21:44:35 UTC 2025 - Matej Cepl <mcepl@cepl.eu>
* Update to 3.9.25:
- Security
- gh-137836: Add support of the “plaintext” element, RAWTEXT
elements “xmp”, “iframe”, “noembed” and “noframes”, and
optionally RAWTEXT element “noscript” in
html.parser.HTMLParser.
- gh-136063: email.message: ensure linear complexity for
legacy HTTP parameters parsing. Patch by Bénédikt Tran.
- gh-136065: Fix quadratic complexity in
os.path.expandvars() (CVE-2025-6075, bsc#1252974).
- Library
- gh-98793: Fix argument typechecks in
_overlapped.WSAConnect() and
_overlapped.Overlapped.WSASendTo() functions. bpo-44817:
Ignore WinError 53 (ERROR_BAD_NETPATH), 65
(ERROR_NETWORK_ACCESS_DENIED) and 161 (ERROR_BAD_PATHNAME)
when using ntpath.realpath().
- Core and Builtins
- gh-120384: Fix an array out of bounds crash in
list_ass_subscript, which could be invoked via some
specificly tailored input: including concurrent
modification of a list object, where one thread assigns
a slice and another clears it.
- gh-120298: Fix use-after free in list_richcompare_impl
which can be invoked via some specificly tailored evil
input.
- Remove upstreamed patches:
-CVE-2025-6075-expandvars-perf-degrad.patch
------------------------------------------------------------------- -------------------------------------------------------------------
Thu Nov 13 17:13:03 UTC 2025 - Matej Cepl <mcepl@cepl.eu> Thu Nov 13 17:13:03 UTC 2025 - Matej Cepl <mcepl@cepl.eu>

View File

@@ -99,7 +99,7 @@
%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
%bcond_without profileopt %bcond_without profileopt
Name: %{python_pkg_name}%{psuffix} Name: %{python_pkg_name}%{psuffix}
Version: 3.9.24 Version: 3.9.25
Release: 0 Release: 0
Summary: Python 3 Interpreter Summary: Python 3 Interpreter
License: Python-2.0 License: Python-2.0
@@ -194,9 +194,15 @@ Patch50: gh120226-fix-sendfile-test-kernel-610.patch
Patch51: sphinx-802.patch Patch51: sphinx-802.patch
# PATCH-FIX-OPENSUSE gh139257-Support-docutils-0.22.patch gh#python/cpython#139257 daniel.garcia@suse.com # PATCH-FIX-OPENSUSE gh139257-Support-docutils-0.22.patch gh#python/cpython#139257 daniel.garcia@suse.com
Patch52: gh139257-Support-docutils-0.22.patch Patch52: gh139257-Support-docutils-0.22.patch
# PATCH-FIX-UPSTREAM CVE-2025-6075-expandvars-perf-degrad.patch bsc#1252974 mcepl@suse.com # PATCH-FIX-UPSTREAM CVE-2025-13836-http-resp-cont-len.patch bsc#1254400 mcepl@suse.com
# Avoid potential quadratic complexity vulnerabilities in path modules # Avoid loading possibly compromised length of HTTP response
Patch53: CVE-2025-6075-expandvars-perf-degrad.patch Patch53: 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
Patch54: 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
Patch55: CVE-2025-13837-plistlib-mailicious-length.patch
BuildRequires: autoconf-archive BuildRequires: autoconf-archive
BuildRequires: automake BuildRequires: automake
BuildRequires: fdupes BuildRequires: fdupes
@@ -469,7 +475,6 @@ other applications.
%patch -p1 -P 50 %patch -p1 -P 50
%patch -p1 -P 51 %patch -p1 -P 51
%patch -p1 -P 52 %patch -p1 -P 52
%patch -p1 -P 53
# drop Autoconf version requirement # drop Autoconf version requirement
sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac