From b1eefc95c207adf8622e102e3f5715277ccdcbd6 Mon Sep 17 00:00:00 2001 From: lzq11122 Date: Tue, 11 Nov 2025 14:00:33 +0800 Subject: [PATCH] Add patch to fix CVE-2025-6075 --- 1022-add-patch-to-fix-CVE-2025-6075.patch | 330 ++++++++++++++++++++++ python3.spec | 7 +- 2 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 1022-add-patch-to-fix-CVE-2025-6075.patch diff --git a/1022-add-patch-to-fix-CVE-2025-6075.patch b/1022-add-patch-to-fix-CVE-2025-6075.patch new file mode 100644 index 0000000..cf4224a --- /dev/null +++ b/1022-add-patch-to-fix-CVE-2025-6075.patch @@ -0,0 +1,330 @@ +From ede58caea7eb7abfe6b636ae8eacc9b183e99dcf Mon Sep 17 00:00:00 2001 +From: lzq11122 +Date: Tue, 11 Nov 2025 13:34:09 +0800 +Subject: [PATCH 1/1] add patch to fix CVE-2025-6075 + +--- + Lib/ntpath.py | 123 ++++++------------ + Lib/posixpath.py | 41 +++--- + Lib/test/test_genericpath.py | 13 ++ + Lib/test/test_ntpath.py | 16 ++- + ...-05-09-20-22-54.gh-issue-133767.kN2i3Q.rst | 1 + + 5 files changed, 84 insertions(+), 110 deletions(-) + +diff --git a/Lib/ntpath.py b/Lib/ntpath.py +index ebc55eb..a285883 100644 +--- a/Lib/ntpath.py ++++ b/Lib/ntpath.py +@@ -378,17 +378,23 @@ def expanduser(path): + # 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,41 @@ def expandvars(path): + 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. +diff --git a/Lib/posixpath.py b/Lib/posixpath.py +index ce71a47..f8056b6 100644 +--- a/Lib/posixpath.py ++++ b/Lib/posixpath.py +@@ -287,42 +287,40 @@ def expanduser(path): + # 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 +328,10 @@ def expandvars(path): + 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. +diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py +index 4f311c2..0c9253a 100644 +--- a/Lib/test/test_genericpath.py ++++ b/Lib/test/test_genericpath.py +@@ -7,6 +7,7 @@ import os + 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 +@@ -433,6 +434,18 @@ class CommonTest(GenericTest): + check(os.fsencode('$bar%s bar' % nonascii), + 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")) +diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py +index 7d0c0a0..4a1c83f 100644 +--- a/Lib/test/test_ntpath.py ++++ b/Lib/test/test_ntpath.py +@@ -1,7 +1,6 @@ + import ntpath + import os + import string +-import subprocess + import sys + import unittest + import warnings +@@ -9,7 +8,8 @@ from ntpath import ALLOW_MISSING + from test.support import os_helper + from test.support import TestFailed, is_emscripten + from test.support.os_helper import FakePath +-from test import test_genericpath ++from test.support import TestFailed, FakePath, os_helper ++from test import test_genericpath,support + from tempfile import TemporaryFile + + +@@ -819,6 +819,18 @@ class TestNtpath(NtpathTestCase): + check('%{}% bar'.format(nonascii), 'ham%s bar' % nonascii) + 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') +diff --git a/Misc/NEWS.d/next/Security/2025-05-09-20-22-54.gh-issue-133767.kN2i3Q.rst b/Misc/NEWS.d/next/Security/2025-05-09-20-22-54.gh-issue-133767.kN2i3Q.rst +index 39d2f1e..b196e78 100644 +--- a/Misc/NEWS.d/next/Security/2025-05-09-20-22-54.gh-issue-133767.kN2i3Q.rst ++++ b/Misc/NEWS.d/next/Security/2025-05-09-20-22-54.gh-issue-133767.kN2i3Q.rst +@@ -1,2 +1,3 @@ + Fix use-after-free in the "unicode-escape" decoder with a non-"strict" error ++Fix quadratic complexity in :func:`os.path.expandvars`. + handler. +-- +2.43.5 + diff --git a/python3.spec b/python3.spec index bb9cd2a..0f0d091 100644 --- a/python3.spec +++ b/python3.spec @@ -1,4 +1,4 @@ -%define anolis_release 10 +%define anolis_release 11 %global pybasever 3.11 # pybasever without the dot: @@ -273,6 +273,8 @@ Patch1019: CVE-2023-27043.patch # https://github.com/python/cpython/commit/b7431133441a92670132600e5af78b64dd25539b.patch Patch1020: CVE-2024-6232.patch Patch1021: 1021-bugfix-for-CVE-2025-8291.patch +# https://github.com/python/cpython/commit/8b8e68d3dc95f454f58fdd8aac10848facb1491d +Patch1022: 1022-add-patch-to-fix-CVE-2025-6075.patch # ========================================== # Descriptions, and metadata for subpackages @@ -1545,6 +1547,9 @@ CheckPython optimized # ====================================================== %changelog +* Tue Nov 11 2025 lzq11122 - 3.11-6.11 +- Add patch to fix CVE-2025-6075 + * Thu Oct 16 2025 tomcruiseqi - 3.11.6-10 - Fix CVE-2025-8291 -- Gitee