Skip to content

Commit e1fdb94

Browse files
authored
Limit number of parts of a key (#286)
* Limit number of parts of a key tomli has quadratic time complexity when handling parts of a key (via Flags.is_ calls in key_value_rule's loop, and maybe elsewhere). This isn't a problem unless a document has an excessive number (10_000-ish) of parts in a key. Limiting input seems like the best fix here. `sys.getrecursionlimit` and `RecursionError` is already (and in the non-mypyc version, implicitly) used for `MAX_INLINE_NESTING`. Keys aren't parsed recursively, but that's an implementation detail; `RecursionError` works nicely for names recursive structures.
1 parent c20c491 commit e1fdb94

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

src/tomli/_parser.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@
3434
# lower number than where mypyc binaries crash.
3535
MAX_INLINE_NESTING: Final = sys.getrecursionlimit()
3636

37+
# Pathologically excessive number of parts in a key runs into quadratic
38+
# behavior (e.g. in Flags.is_).
39+
# Even if keys aren't currently parsed using recursion, they name a
40+
# recursive structure, so it makes sense to limit it using getrecursionlimit()
41+
# and RecursionError.
42+
MAX_KEY_PARTS: Final = sys.getrecursionlimit()
43+
3744
ASCII_CTRL: Final = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
3845

3946
# Neither of these sets include quotation mark or backslash. They are
@@ -475,6 +482,10 @@ def parse_key(src: str, pos: Pos) -> tuple[Pos, Key]:
475482
pos = skip_chars(src, pos, TOML_WS)
476483
pos, key_part = parse_key_part(src, pos)
477484
key += (key_part,)
485+
if len(key) > MAX_KEY_PARTS:
486+
raise RecursionError(
487+
f"TOML key has more than the allowed {MAX_KEY_PARTS} parts"
488+
)
478489
pos = skip_chars(src, pos, TOML_WS)
479490

480491

tests/test_misc.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,23 @@ def test_inline_table_recursion_limit(self):
130130
):
131131
tomllib.loads(recursive_table_toml)
132132

133+
def test_key_recursion_limit(self):
134+
nest_count = 310
135+
nested_key_toml = "a." * nest_count + "a = 1"
136+
tomllib.loads(nested_key_toml)
137+
138+
nest_count = sys.getrecursionlimit() - 2
139+
nested_key_toml = "a." * nest_count + "a = 1"
140+
tomllib.loads(nested_key_toml)
141+
142+
nest_count = sys.getrecursionlimit() + 2
143+
nested_key_toml = "a." * nest_count + "a = 1"
144+
with self.assertRaisesRegex(
145+
RecursionError,
146+
r"TOML key has more than the allowed [0-9]+ parts",
147+
):
148+
tomllib.loads(nested_key_toml)
149+
133150
def test_types_import(self):
134151
"""Test that `_types` module runs.
135152

0 commit comments

Comments
 (0)