[3.13] 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 7e0a0be409)

Co-authored-by: Joshua Swanson <22283299+joshuaswanson@users.noreply.github.com>
This commit is contained in:
Petr Viktorin
2026-04-15 12:11:10 +02:00
committed by GitHub
parent e76aa128fe
commit a5969e8f0f
3 changed files with 29 additions and 2 deletions
+6 -2
View File
@@ -600,7 +600,9 @@ class RawConfigParser(MutableMapping):
\] # ]
"""
_OPT_TMPL = r"""
(?P<option>.*?) # very permissive!
(?P<option> # very permissive!
(?:(?!{delim})\S)* # non-delimiter non-whitespace
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
\s*(?P<vi>{delim})\s* # any number of space/tab,
# followed by any of the
# allowed delimiters,
@@ -608,7 +610,9 @@ class RawConfigParser(MutableMapping):
(?P<value>.*)$ # everything up to eol
"""
_OPT_NV_TMPL = r"""
(?P<option>.*?) # very permissive!
(?P<option> # very permissive!
(?:(?!{delim})\S)* # non-delimiter non-whitespace
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
\s*(?: # any number of space/tab,
(?P<vi>{delim})\s* # optionally followed by
# any of the allowed
+20
View File
@@ -2225,6 +2225,26 @@ class SectionlessTestCase(unittest.TestCase):
self.assertEqual('2', cfg[configparser.UNNAMED_SECTION]['b'])
class ReDoSTestCase(unittest.TestCase):
"""Regression tests for quadratic regex backtracking (gh-146333)."""
def test_option_regex_does_not_backtrack(self):
# A line with many spaces between non-delimiter characters
# should be parsed in linear time, not quadratic.
parser = configparser.RawConfigParser()
content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
# This should complete almost instantly. Before the fix,
# it would take over a minute due to catastrophic backtracking.
with self.assertRaises(configparser.ParsingError):
parser.read_string(content)
def test_option_regex_no_value_does_not_backtrack(self):
parser = configparser.RawConfigParser(allow_no_value=True)
content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
parser.read_string(content)
self.assertTrue(parser.has_option("section", "x" + " " * 40000 + "y"))
class MiscTestCase(unittest.TestCase):
def test__all__(self):
support.check__all__(self, configparser, not_exported={"Error"})
@@ -0,0 +1,3 @@
Fix quadratic backtracking in :class:`configparser.RawConfigParser` option
parsing regexes (``OPTCRE`` and ``OPTCRE_NV``). A crafted configuration line
with many whitespace characters could cause excessive CPU usage.