Skip to content

Commit 4b5d7b1

Browse files
[3.10] gh-146333: Fix quadratic regex backtracking in configparser option parsing (GH-146399) (GH-148559)
Use negative lookahead in option regex to prevent backtracking, and to avoid changing logic outside the regexes (since people could use the regex directly). (cherry picked from commit 7e0a0be) Co-authored-by: Joshua Swanson <22283299+joshuaswanson@users.noreply.github.com> (cherry picked from commit a5969e8)
1 parent cf23b91 commit 4b5d7b1

3 files changed

Lines changed: 29 additions & 2 deletions

File tree

Lib/configparser.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,15 +568,19 @@ class RawConfigParser(MutableMapping):
568568
\] # ]
569569
"""
570570
_OPT_TMPL = r"""
571-
(?P<option>.*?) # very permissive!
571+
(?P<option> # very permissive!
572+
(?:(?!{delim})\S)* # non-delimiter non-whitespace
573+
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
572574
\s*(?P<vi>{delim})\s* # any number of space/tab,
573575
# followed by any of the
574576
# allowed delimiters,
575577
# followed by any space/tab
576578
(?P<value>.*)$ # everything up to eol
577579
"""
578580
_OPT_NV_TMPL = r"""
579-
(?P<option>.*?) # very permissive!
581+
(?P<option> # very permissive!
582+
(?:(?!{delim})\S)* # non-delimiter non-whitespace
583+
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
580584
\s*(?: # any number of space/tab,
581585
(?P<vi>{delim})\s* # optionally followed by
582586
# any of the allowed

Lib/test/test_configparser.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,6 +2133,26 @@ def test_instance_assignment(self):
21332133
self.assertEqual(cfg['two'].getlen('one'), 5)
21342134

21352135

2136+
class ReDoSTestCase(unittest.TestCase):
2137+
"""Regression tests for quadratic regex backtracking (gh-146333)."""
2138+
2139+
def test_option_regex_does_not_backtrack(self):
2140+
# A line with many spaces between non-delimiter characters
2141+
# should be parsed in linear time, not quadratic.
2142+
parser = configparser.RawConfigParser()
2143+
content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
2144+
# This should complete almost instantly. Before the fix,
2145+
# it would take over a minute due to catastrophic backtracking.
2146+
with self.assertRaises(configparser.ParsingError):
2147+
parser.read_string(content)
2148+
2149+
def test_option_regex_no_value_does_not_backtrack(self):
2150+
parser = configparser.RawConfigParser(allow_no_value=True)
2151+
content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
2152+
parser.read_string(content)
2153+
self.assertTrue(parser.has_option("section", "x" + " " * 40000 + "y"))
2154+
2155+
21362156
class MiscTestCase(unittest.TestCase):
21372157
def test__all__(self):
21382158
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)