summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/python
diff options
context:
space:
mode:
authorPraveen Kumar <praveen.kumar@windriver.com>2025-11-21 16:56:42 +0530
committerSteve Sakoman <steve@sakoman.com>2025-12-01 06:50:49 -0800
commitc6234dce6342e9ba582f643e53ac348b5cdb0dab (patch)
tree7e4e64665bc0a9cb58a6b2dfd1d22dce32760de8 /meta/recipes-devtools/python
parent6639c7b29502bed5ce1bfb0abcfd4dc09b3e1da6 (diff)
downloadpoky-c6234dce6342e9ba582f643e53ac348b5cdb0dab.tar.gz
python3: fix CVE-2025-6075
If the value passed to os.path.expandvars() is user-controlled a performance degradation is possible when expanding environment variables. Reference: https://nvd.nist.gov/vuln/detail/CVE-2025-6075 Upstream-patch: https://github.com/python/cpython/commit/892747b4cf0f95ba8beb51c0d0658bfaa381ebca (From OE-Core rev: 9a7f33d85355ffbe382aa175c04c64541e77b441) Signed-off-by: Praveen Kumar <praveen.kumar@windriver.com> Signed-off-by: Steve Sakoman <steve@sakoman.com>
Diffstat (limited to 'meta/recipes-devtools/python')
-rw-r--r--meta/recipes-devtools/python/python3/CVE-2025-6075.patch364
-rw-r--r--meta/recipes-devtools/python/python3_3.10.19.bb1
2 files changed, 365 insertions, 0 deletions
diff --git a/meta/recipes-devtools/python/python3/CVE-2025-6075.patch b/meta/recipes-devtools/python/python3/CVE-2025-6075.patch
new file mode 100644
index 0000000000..eab5a882a0
--- /dev/null
+++ b/meta/recipes-devtools/python/python3/CVE-2025-6075.patch
@@ -0,0 +1,364 @@
1From 892747b4cf0f95ba8beb51c0d0658bfaa381ebca Mon Sep 17 00:00:00 2001
2From: Ɓukasz Langa <lukasz@langa.pl>
3Date: Fri, 31 Oct 2025 17:51:32 +0100
4Subject: [PATCH] gh-136065: Fix quadratic complexity in os.path.expandvars()
5 (GH-134952) (GH-140851)
6
7(cherry picked from commit f029e8db626ddc6e3a3beea4eff511a71aaceb5c)
8
9Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
10
11CVE: CVE-2025-6075
12
13Upstream-Status: Backport [https://github.com/python/cpython/commit/892747b4cf0f95ba8beb51c0d0658bfaa381ebca]
14
15Signed-off-by: Praveen Kumar <praveen.kumar@windriver.com>
16---
17 Lib/ntpath.py | 126 ++++++------------
18 Lib/posixpath.py | 43 +++---
19 Lib/test/test_genericpath.py | 14 ++
20 Lib/test/test_ntpath.py | 20 ++-
21 ...-05-30-22-33-27.gh-issue-136065.bu337o.rst | 1 +
22 5 files changed, 93 insertions(+), 111 deletions(-)
23 create mode 100644 Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst
24
25diff --git a/Lib/ntpath.py b/Lib/ntpath.py
26index 9b0cca4..bd2b4e2 100644
27--- a/Lib/ntpath.py
28+++ b/Lib/ntpath.py
29@@ -374,17 +374,23 @@ def expanduser(path):
30 # XXX With COMMAND.COM you can use any characters in a variable name,
31 # XXX except '^|<>='.
32
33+_varpattern = r"'[^']*'?|%(%|[^%]*%?)|\$(\$|[-\w]+|\{[^}]*\}?)"
34+_varsub = None
35+_varsubb = None
36+
37 def expandvars(path):
38 """Expand shell variables of the forms $var, ${var} and %var%.
39
40 Unknown variables are left unchanged."""
41 path = os.fspath(path)
42+ global _varsub, _varsubb
43 if isinstance(path, bytes):
44 if b'$' not in path and b'%' not in path:
45 return path
46- import string
47- varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
48- quote = b'\''
49+ if not _varsubb:
50+ import re
51+ _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub
52+ sub = _varsubb
53 percent = b'%'
54 brace = b'{'
55 rbrace = b'}'
56@@ -393,94 +399,44 @@ def expandvars(path):
57 else:
58 if '$' not in path and '%' not in path:
59 return path
60- import string
61- varchars = string.ascii_letters + string.digits + '_-'
62- quote = '\''
63+ if not _varsub:
64+ import re
65+ _varsub = re.compile(_varpattern, re.ASCII).sub
66+ sub = _varsub
67 percent = '%'
68 brace = '{'
69 rbrace = '}'
70 dollar = '$'
71 environ = os.environ
72- res = path[:0]
73- index = 0
74- pathlen = len(path)
75- while index < pathlen:
76- c = path[index:index+1]
77- if c == quote: # no expansion within single quotes
78- path = path[index + 1:]
79- pathlen = len(path)
80- try:
81- index = path.index(c)
82- res += c + path[:index + 1]
83- except ValueError:
84- res += c + path
85- index = pathlen - 1
86- elif c == percent: # variable or '%'
87- if path[index + 1:index + 2] == percent:
88- res += c
89- index += 1
90- else:
91- path = path[index+1:]
92- pathlen = len(path)
93- try:
94- index = path.index(percent)
95- except ValueError:
96- res += percent + path
97- index = pathlen - 1
98- else:
99- var = path[:index]
100- try:
101- if environ is None:
102- value = os.fsencode(os.environ[os.fsdecode(var)])
103- else:
104- value = environ[var]
105- except KeyError:
106- value = percent + var + percent
107- res += value
108- elif c == dollar: # variable or '$$'
109- if path[index + 1:index + 2] == dollar:
110- res += c
111- index += 1
112- elif path[index + 1:index + 2] == brace:
113- path = path[index+2:]
114- pathlen = len(path)
115- try:
116- index = path.index(rbrace)
117- except ValueError:
118- res += dollar + brace + path
119- index = pathlen - 1
120- else:
121- var = path[:index]
122- try:
123- if environ is None:
124- value = os.fsencode(os.environ[os.fsdecode(var)])
125- else:
126- value = environ[var]
127- except KeyError:
128- value = dollar + brace + var + rbrace
129- res += value
130- else:
131- var = path[:0]
132- index += 1
133- c = path[index:index + 1]
134- while c and c in varchars:
135- var += c
136- index += 1
137- c = path[index:index + 1]
138- try:
139- if environ is None:
140- value = os.fsencode(os.environ[os.fsdecode(var)])
141- else:
142- value = environ[var]
143- except KeyError:
144- value = dollar + var
145- res += value
146- if c:
147- index -= 1
148+
149+ def repl(m):
150+ lastindex = m.lastindex
151+ if lastindex is None:
152+ return m[0]
153+ name = m[lastindex]
154+ if lastindex == 1:
155+ if name == percent:
156+ return name
157+ if not name.endswith(percent):
158+ return m[0]
159+ name = name[:-1]
160 else:
161- res += c
162- index += 1
163- return res
164+ if name == dollar:
165+ return name
166+ if name.startswith(brace):
167+ if not name.endswith(rbrace):
168+ return m[0]
169+ name = name[1:-1]
170+
171+ try:
172+ if environ is None:
173+ return os.fsencode(os.environ[os.fsdecode(name)])
174+ else:
175+ return environ[name]
176+ except KeyError:
177+ return m[0]
178+
179+ return sub(repl, path)
180
181
182 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
183diff --git a/Lib/posixpath.py b/Lib/posixpath.py
184index b8dd563..75020ee 100644
185--- a/Lib/posixpath.py
186+++ b/Lib/posixpath.py
187@@ -279,42 +279,41 @@ def expanduser(path):
188 # This expands the forms $variable and ${variable} only.
189 # Non-existent variables are left unchanged.
190
191-_varprog = None
192-_varprogb = None
193+_varpattern = r'\$(\w+|\{[^}]*\}?)'
194+_varsub = None
195+_varsubb = None
196
197 def expandvars(path):
198 """Expand shell variables of form $var and ${var}. Unknown variables
199 are left unchanged."""
200 path = os.fspath(path)
201- global _varprog, _varprogb
202+ global _varsub, _varsubb
203 if isinstance(path, bytes):
204 if b'$' not in path:
205 return path
206- if not _varprogb:
207+ if not _varsubb:
208 import re
209- _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII)
210- search = _varprogb.search
211+ _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub
212+ sub = _varsubb
213 start = b'{'
214 end = b'}'
215 environ = getattr(os, 'environb', None)
216 else:
217 if '$' not in path:
218 return path
219- if not _varprog:
220+ if not _varsub:
221 import re
222- _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII)
223- search = _varprog.search
224+ _varsub = re.compile(_varpattern, re.ASCII).sub
225+ sub = _varsub
226 start = '{'
227 end = '}'
228 environ = os.environ
229- i = 0
230- while True:
231- m = search(path, i)
232- if not m:
233- break
234- i, j = m.span(0)
235- name = m.group(1)
236- if name.startswith(start) and name.endswith(end):
237+
238+ def repl(m):
239+ name = m[1]
240+ if name.startswith(start):
241+ if not name.endswith(end):
242+ return m[0]
243 name = name[1:-1]
244 try:
245 if environ is None:
246@@ -322,13 +321,11 @@ def expandvars(path):
247 else:
248 value = environ[name]
249 except KeyError:
250- i = j
251+ return m[0]
252 else:
253- tail = path[j:]
254- path = path[:i] + value
255- i = len(path)
256- path += tail
257- return path
258+ return value
259+
260+ return sub(repl, path)
261
262
263 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
264diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py
265index 1ff7f75..b0a1326 100644
266--- a/Lib/test/test_genericpath.py
267+++ b/Lib/test/test_genericpath.py
268@@ -7,6 +7,7 @@ import os
269 import sys
270 import unittest
271 import warnings
272+from test import support
273 from test.support import os_helper
274 from test.support import warnings_helper
275 from test.support.script_helper import assert_python_ok
276@@ -430,6 +431,19 @@ class CommonTest(GenericTest):
277 os.fsencode('$bar%s bar' % nonascii))
278 check(b'$spam}bar', os.fsencode('%s}bar' % nonascii))
279
280+ @support.requires_resource('cpu')
281+ def test_expandvars_large(self):
282+ expandvars = self.pathmodule.expandvars
283+ with os_helper.EnvironmentVarGuard() as env:
284+ env.clear()
285+ env["A"] = "B"
286+ n = 100_000
287+ self.assertEqual(expandvars('$A'*n), 'B'*n)
288+ self.assertEqual(expandvars('${A}'*n), 'B'*n)
289+ self.assertEqual(expandvars('$A!'*n), 'B!'*n)
290+ self.assertEqual(expandvars('${A}A'*n), 'BA'*n)
291+ self.assertEqual(expandvars('${'*10*n), '${'*10*n)
292+
293 def test_abspath(self):
294 self.assertIn("foo", self.pathmodule.abspath("foo"))
295 with warnings.catch_warnings():
296diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py
297index f790f77..161e57d 100644
298--- a/Lib/test/test_ntpath.py
299+++ b/Lib/test/test_ntpath.py
300@@ -5,8 +5,8 @@ import sys
301 import unittest
302 import warnings
303 from ntpath import ALLOW_MISSING
304+from test import support
305 from test.support import os_helper
306-from test.support import TestFailed
307 from test.support.os_helper import FakePath
308 from test import test_genericpath
309 from tempfile import TemporaryFile
310@@ -56,7 +56,7 @@ def tester(fn, wantResult):
311 fn = fn.replace("\\", "\\\\")
312 gotResult = eval(fn)
313 if wantResult != gotResult and _norm(wantResult) != _norm(gotResult):
314- raise TestFailed("%s should return: %s but returned: %s" \
315+ raise support.TestFailed("%s should return: %s but returned: %s" \
316 %(str(fn), str(wantResult), str(gotResult)))
317
318 # then with bytes
319@@ -72,7 +72,7 @@ def tester(fn, wantResult):
320 warnings.simplefilter("ignore", DeprecationWarning)
321 gotResult = eval(fn)
322 if _norm(wantResult) != _norm(gotResult):
323- raise TestFailed("%s should return: %s but returned: %s" \
324+ raise support.TestFailed("%s should return: %s but returned: %s" \
325 %(str(fn), str(wantResult), repr(gotResult)))
326
327
328@@ -689,6 +689,19 @@ class TestNtpath(NtpathTestCase):
329 check('%spam%bar', '%sbar' % nonascii)
330 check('%{}%bar'.format(nonascii), 'ham%sbar' % nonascii)
331
332+ @support.requires_resource('cpu')
333+ def test_expandvars_large(self):
334+ expandvars = ntpath.expandvars
335+ with os_helper.EnvironmentVarGuard() as env:
336+ env.clear()
337+ env["A"] = "B"
338+ n = 100_000
339+ self.assertEqual(expandvars('%A%'*n), 'B'*n)
340+ self.assertEqual(expandvars('%A%A'*n), 'BA'*n)
341+ self.assertEqual(expandvars("''"*n + '%%'), "''"*n + '%')
342+ self.assertEqual(expandvars("%%"*n), "%"*n)
343+ self.assertEqual(expandvars("$$"*n), "$"*n)
344+
345 def test_expanduser(self):
346 tester('ntpath.expanduser("test")', 'test')
347
348@@ -923,6 +936,7 @@ class TestNtpath(NtpathTestCase):
349 self.assertIsInstance(b_final_path, bytes)
350 self.assertGreater(len(b_final_path), 0)
351
352+
353 class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
354 pathmodule = ntpath
355 attributes = ['relpath']
356diff --git a/Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst b/Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst
357new file mode 100644
358index 0000000..1d152bb
359--- /dev/null
360+++ b/Misc/NEWS.d/next/Security/2025-05-30-22-33-27.gh-issue-136065.bu337o.rst
361@@ -0,0 +1 @@
362+Fix quadratic complexity in :func:`os.path.expandvars`.
363--
3642.40.0
diff --git a/meta/recipes-devtools/python/python3_3.10.19.bb b/meta/recipes-devtools/python/python3_3.10.19.bb
index 8680c13893..6f23d258c1 100644
--- a/meta/recipes-devtools/python/python3_3.10.19.bb
+++ b/meta/recipes-devtools/python/python3_3.10.19.bb
@@ -37,6 +37,7 @@ SRC_URI = "http://www.python.org/ftp/python/${PV}/Python-${PV}.tar.xz \
37 file://0001-Avoid-shebang-overflow-on-python-config.py.patch \ 37 file://0001-Avoid-shebang-overflow-on-python-config.py.patch \
38 file://0001-test_storlines-skip-due-to-load-variability.patch \ 38 file://0001-test_storlines-skip-due-to-load-variability.patch \
39 file://0001-gh-107811-tarfile-treat-overflow-in-UID-GID-as-failu.patch \ 39 file://0001-gh-107811-tarfile-treat-overflow-in-UID-GID-as-failu.patch \
40 file://CVE-2025-6075.patch \
40 " 41 "
41 42
42SRC_URI:append:class-native = " \ 43SRC_URI:append:class-native = " \