Skip to content

Commit 7e0a0be

Browse files
gh-146333: Fix quadratic regex backtracking in configparser option parsing (GH-146399)
Use negative lookahead in option regex to prevent backtracking, and to avoid changing logic outside the regexes (since people could use the regex directly).
1 parent feee573 commit 7e0a0be

File tree

3 files changed

+29
-2
lines changed

3 files changed

+29
-2
lines changed

Lib/configparser.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,15 +613,19 @@ class RawConfigParser(MutableMapping):
613613
\] # ]
614614
"""
615615
_OPT_TMPL = r"""
616-
(?P<option>.*?) # very permissive!
616+
(?P<option> # very permissive!
617+
(?:(?!{delim})\S)* # non-delimiter non-whitespace
618+
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
617619
\s*(?P<vi>{delim})\s* # any number of space/tab,
618620
# followed by any of the
619621
# allowed delimiters,
620622
# followed by any space/tab
621623
(?P<value>.*)$ # everything up to eol
622624
"""
623625
_OPT_NV_TMPL = r"""
624-
(?P<option>.*?) # very permissive!
626+
(?P<option> # very permissive!
627+
(?:(?!{delim})\S)* # non-delimiter non-whitespace
628+
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
625629
\s*(?: # any number of space/tab,
626630
(?P<vi>{delim})\s* # optionally followed by
627631
# any of the allowed

Lib/test/test_configparser.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2270,6 +2270,26 @@ def test_section_bracket_in_key(self):
22702270
output.close()
22712271

22722272

2273+
class ReDoSTestCase(unittest.TestCase):
2274+
"""Regression tests for quadratic regex backtracking (gh-146333)."""
2275+
2276+
def test_option_regex_does_not_backtrack(self):
2277+
# A line with many spaces between non-delimiter characters
2278+
# should be parsed in linear time, not quadratic.
2279+
parser = configparser.RawConfigParser()
2280+
content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
2281+
# This should complete almost instantly. Before the fix,
2282+
# it would take over a minute due to catastrophic backtracking.
2283+
with self.assertRaises(configparser.ParsingError):
2284+
parser.read_string(content)
2285+
2286+
def test_option_regex_no_value_does_not_backtrack(self):
2287+
parser = configparser.RawConfigParser(allow_no_value=True)
2288+
content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
2289+
parser.read_string(content)
2290+
self.assertTrue(parser.has_option("section", "x" + " " * 40000 + "y"))
2291+
2292+
22732293
class MiscTestCase(unittest.TestCase):
22742294
def test__all__(self):
22752295
support.check__all__(self, configparser, not_exported={"Error"})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix quadratic backtracking in :class:`configparser.RawConfigParser` option
2+
parsing regexes (``OPTCRE`` and ``OPTCRE_NV``). A crafted configuration line
3+
with many whitespace characters could cause excessive CPU usage.

0 commit comments

Comments
 (0)