diff --git a/docs/changelog/199.bugfix.rst b/docs/changelog/199.bugfix.rst new file mode 100644 index 0000000..bfed634 --- /dev/null +++ b/docs/changelog/199.bugfix.rst @@ -0,0 +1 @@ +Reject format widths in interpreter sysconfig paths before they allocate oversized strings. diff --git a/src/python_discovery/_py_info.py b/src/python_discovery/_py_info.py index ecf6cc6..e3d4b23 100644 --- a/src/python_discovery/_py_info.py +++ b/src/python_discovery/_py_info.py @@ -11,7 +11,7 @@ from collections import OrderedDict from itertools import product from os.path import normcase, realpath -from string import digits +from string import Formatter, digits from typing import TYPE_CHECKING, ClassVar, Final, NamedTuple, TypedDict from ._py_info_collect import PythonInfoCollector @@ -136,6 +136,9 @@ def sysconfig_path(self, key: str, config_var: dict[str, str | int | None] | Non pattern = self.sysconfig_paths.get(key) if pattern is None: return "" + if any(format_spec for _, _, format_spec, _ in Formatter().parse(pattern)): + msg = f"sysconfig path {key!r} contains format specifiers" + raise ValueError(msg) if config_var is None: config_var = self.sysconfig_vars else: diff --git a/tests/test_py_info_extra.py b/tests/test_py_info_extra.py index b7642bd..f2548b7 100644 --- a/tests/test_py_info_extra.py +++ b/tests/test_py_info_extra.py @@ -303,6 +303,14 @@ def test_install_path() -> None: assert isinstance(CURRENT.install_path("purelib"), str) +def test_install_path_rejects_format_width() -> None: + info = copy.deepcopy(CURRENT) + info.distutils_install = {} + info.sysconfig_paths["purelib"] = "{base:>1000000}/lib" + with pytest.raises(ValueError, match="format specifiers"): + info.install_path("purelib") + + def test_system_include() -> None: result = CURRENT.system_include assert isinstance(result, str)