]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-149231: tomllib: Limit the number of parts in a key (GH-149233) (GH-149815)
authorPetr Viktorin <encukou@gmail.com>
Thu, 14 May 2026 20:46:10 +0000 (22:46 +0200)
committerGitHub <noreply@github.com>
Thu, 14 May 2026 20:46:10 +0000 (22:46 +0200)
(cherry picked from commit bc7c102f3462a9f014f3ac2546acfb471b2a7eae)

Co-authored-by: Stan Ulbrych <stan@python.org>
Lib/test/test_tomllib/test_misc.py
Lib/tomllib/_parser.py
Misc/NEWS.d/next/Library/2026-05-01-16-45-31.gh-issue-149231.x2nBEE.rst [new file with mode: 0644]

index 118fde24d88521f101602fd884de306459f913a4..85526ed801554238deb2f589e2d441dca0b80bc4 100644 (file)
@@ -117,6 +117,19 @@ class TestMiscellaneous(unittest.TestCase):
                 recursive_table_toml = nest_count * "key = {" + nest_count * "}"
                 tomllib.loads(recursive_table_toml)
 
+    def test_key_recursion_limit(self):
+        nest_count = tomllib._parser.MAX_KEY_PARTS - 2
+        nested_key_toml = "a." * nest_count + "a = 1"
+        tomllib.loads(nested_key_toml)
+
+        nest_count = tomllib._parser.MAX_KEY_PARTS + 2
+        nested_key_toml = "a." * nest_count + "a = 1"
+        with self.assertRaisesRegex(
+            RecursionError,
+            r"TOML key has more than the allowed [0-9]+ parts",
+        ):
+            tomllib.loads(nested_key_toml)
+
     def test_types_import(self):
         """Test that `_types` module runs.
 
index 3ee47aa9e0afbad74f6dbb1f15571c035e09f7c0..84e70495dd0d3a2aee6c567c9054f68083114cd3 100644 (file)
@@ -4,6 +4,7 @@
 
 from __future__ import annotations
 
+import sys
 from types import MappingProxyType
 
 from ._re import (
@@ -18,10 +19,17 @@ from ._re import (
 TYPE_CHECKING = False
 if TYPE_CHECKING:
     from collections.abc import Iterable
-    from typing import IO, Any
+    from typing import IO, Any, Final
 
     from ._types import Key, ParseFloat, Pos
 
+# Pathologically excessive number of parts in a key runs into quadratic
+# behavior (e.g. in Flags.is_).
+# Even if keys aren't currently parsed using recursion, they name a
+# recursive structure, so it makes sense to limit it using getrecursionlimit()
+# and RecursionError.
+MAX_KEY_PARTS: Final = sys.getrecursionlimit()
+
 ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
 
 # Neither of these sets include quotation mark or backslash. They are
@@ -462,6 +470,10 @@ def parse_key(src: str, pos: Pos) -> tuple[Pos, Key]:
         pos = skip_chars(src, pos, TOML_WS)
         pos, key_part = parse_key_part(src, pos)
         key += (key_part,)
+        if len(key) > MAX_KEY_PARTS:
+            raise RecursionError(
+                f"TOML key has more than the allowed {MAX_KEY_PARTS} parts"
+            )
         pos = skip_chars(src, pos, TOML_WS)
 
 
diff --git a/Misc/NEWS.d/next/Library/2026-05-01-16-45-31.gh-issue-149231.x2nBEE.rst b/Misc/NEWS.d/next/Library/2026-05-01-16-45-31.gh-issue-149231.x2nBEE.rst
new file mode 100644 (file)
index 0000000..c265b54
--- /dev/null
@@ -0,0 +1 @@
+In :mod:`tomllib`, the number of parts in TOML keys is now limited.