diff options
Diffstat (limited to 'meta/recipes-devtools/python/python3')
| -rw-r--r-- | meta/recipes-devtools/python/python3/CVE-2025-6075.patch | 364 |
1 files changed, 364 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 @@ | |||
| 1 | From 892747b4cf0f95ba8beb51c0d0658bfaa381ebca Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Ćukasz Langa <lukasz@langa.pl> | ||
| 3 | Date: Fri, 31 Oct 2025 17:51:32 +0100 | ||
| 4 | Subject: [PATCH] gh-136065: Fix quadratic complexity in os.path.expandvars() | ||
| 5 | (GH-134952) (GH-140851) | ||
| 6 | |||
| 7 | (cherry picked from commit f029e8db626ddc6e3a3beea4eff511a71aaceb5c) | ||
| 8 | |||
| 9 | Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> | ||
| 10 | |||
| 11 | CVE: CVE-2025-6075 | ||
| 12 | |||
| 13 | Upstream-Status: Backport [https://github.com/python/cpython/commit/892747b4cf0f95ba8beb51c0d0658bfaa381ebca] | ||
| 14 | |||
| 15 | Signed-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 | |||
| 25 | diff --git a/Lib/ntpath.py b/Lib/ntpath.py | ||
| 26 | index 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. | ||
| 183 | diff --git a/Lib/posixpath.py b/Lib/posixpath.py | ||
| 184 | index 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. | ||
| 264 | diff --git a/Lib/test/test_genericpath.py b/Lib/test/test_genericpath.py | ||
| 265 | index 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(): | ||
| 296 | diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py | ||
| 297 | index 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'] | ||
| 356 | diff --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 | ||
| 357 | new file mode 100644 | ||
| 358 | index 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 | -- | ||
| 364 | 2.40.0 | ||
