Skip to content

Commit 85407ee

Browse files
committed
gh-146333: Fix quadratic regex backtracking in configparser option parsing
1 parent 119fce7 commit 85407ee

File tree

3 files changed

+43
-0
lines changed

3 files changed

+43
-0
lines changed

Lib/configparser.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,26 @@ def _handle_option(self, st, line, fpname):
11451145
# an option line?
11461146
st.indent_level = st.cur_indent_level
11471147

1148+
# Fast path: if no delimiter is present, skip the regex to avoid
1149+
# quadratic backtracking (gh-146333). When allow_no_value is True,
1150+
# treat the whole line as an option name with no value.
1151+
if not any(d in line.clean for d in self._delimiters):
1152+
if self._allow_no_value:
1153+
st.optname = self.optionxform(line.clean.strip())
1154+
if not st.optname:
1155+
st.errors.append(ParsingError(fpname, st.lineno, line))
1156+
return
1157+
if (self._strict and
1158+
(st.sectname, st.optname) in st.elements_added):
1159+
raise DuplicateOptionError(st.sectname, st.optname,
1160+
fpname, st.lineno)
1161+
st.elements_added.add((st.sectname, st.optname))
1162+
st.cursect[st.optname] = None
1163+
return
1164+
else:
1165+
st.errors.append(ParsingError(fpname, st.lineno, line))
1166+
return
1167+
11481168
mo = self._optcre.match(line.clean)
11491169
if not mo:
11501170
# a non-fatal parsing error occurred. set up the

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)