File tree 3 files changed +30
-13
lines changed
3 files changed +30
-13
lines changed Original file line number Diff line number Diff line change 6
6
7
7
<!-- Include any especially major or disruptive changes here -->
8
8
9
+ This release is a milestone: it fixes Black's first CVE security vulnerability. If you
10
+ run Black on untrusted input, or if you habitually put thousands of leading tab
11
+ characters in your docstrings, you are strongly encouraged to upgrade immediately to fix
12
+ [ CVE-2024 -21503] ( https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-21503 ) .
13
+
14
+ This release also fixes a bug in Black's AST safety check that allowed Black to make
15
+ incorrect changes to certain f-strings that are valid in Python 3.12 and higher.
16
+
9
17
### Stable style
10
18
11
19
<!-- Changes that affect Black's stable style -->
36
44
37
45
### Performance
38
46
39
- <!-- Changes that improve Black's performance. -->
47
+ - Fix catastrophic performance on docstrings that contain large numbers of leading tab
48
+ characters. This fixes
49
+ [ CVE-2024 -21503] ( https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-21503 ) .
50
+ (#4278 )
40
51
41
52
### Output
42
53
Original file line number Diff line number Diff line change 14
14
STRING_PREFIX_RE : Final = re .compile (
15
15
r"^([" + STRING_PREFIX_CHARS + r"]*)(.*)$" , re .DOTALL
16
16
)
17
- FIRST_NON_WHITESPACE_RE : Final = re .compile (r"\s*\t+\s*(\S)" )
18
17
UNICODE_ESCAPE_RE : Final = re .compile (
19
18
r"(?P<backslashes>\\+)(?P<body>"
20
19
r"(u(?P<u>[a-fA-F0-9]{4}))" # Character with 16-bit hex value xxxx
@@ -51,18 +50,13 @@ def lines_with_leading_tabs_expanded(s: str) -> List[str]:
51
50
"""
52
51
lines = []
53
52
for line in s .splitlines ():
54
- # Find the index of the first non-whitespace character after a string of
55
- # whitespace that includes at least one tab
56
- match = FIRST_NON_WHITESPACE_RE .match (line )
57
- if match :
58
- first_non_whitespace_idx = match .start (1 )
59
-
60
- lines .append (
61
- line [:first_non_whitespace_idx ].expandtabs ()
62
- + line [first_non_whitespace_idx :]
63
- )
64
- else :
53
+ stripped_line = line .lstrip ()
54
+ if not stripped_line or stripped_line == line :
65
55
lines .append (line )
56
+ else :
57
+ prefix_length = len (line ) - len (stripped_line )
58
+ prefix = line [:prefix_length ].expandtabs ()
59
+ lines .append (prefix + stripped_line )
66
60
if s .endswith ("\n " ):
67
61
lines .append ("" )
68
62
return lines
Original file line number Diff line number Diff line change 48
48
from black .output import color_diff , diff
49
49
from black .parsing import ASTSafetyError
50
50
from black .report import Report
51
+ from black .strings import lines_with_leading_tabs_expanded
51
52
52
53
# Import other test classes
53
54
from tests .util import (
@@ -2041,6 +2042,17 @@ def test_line_ranges_in_pyproject_toml(self) -> None:
2041
2042
b"Cannot use line-ranges in the pyproject.toml file." in result .stderr_bytes
2042
2043
)
2043
2044
2045
+ def test_lines_with_leading_tabs_expanded (self ) -> None :
2046
+ # See CVE-2024-21503. Mostly test that this completes in a reasonable
2047
+ # time.
2048
+ payload = "\t " * 10_000
2049
+ assert lines_with_leading_tabs_expanded (payload ) == [payload ]
2050
+
2051
+ tab = " " * 8
2052
+ assert lines_with_leading_tabs_expanded ("\t x" ) == [f"{ tab } x" ]
2053
+ assert lines_with_leading_tabs_expanded ("\t \t x" ) == [f"{ tab } { tab } x" ]
2054
+ assert lines_with_leading_tabs_expanded ("\t x\n y" ) == [f"{ tab } x" , " y" ]
2055
+
2044
2056
2045
2057
class TestCaching :
2046
2058
def test_get_cache_dir (
You can’t perform that action at this time.
0 commit comments