diff --git a/00001-rpath.patch b/00001-rpath.patch index 778c0771b1fafa6db83a6c66069285171a6a0c05..f19b00d77ef12b63e1706bba12765ec348fc9f26 100644 --- a/00001-rpath.patch +++ b/00001-rpath.patch @@ -1,15 +1,5 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: David Malcolm -Date: Wed, 13 Jan 2010 21:25:18 +0000 -Subject: [PATCH] 00001: Fixup distutils/unixccompiler.py to remove standard - library path from rpath Was Patch0 in ivazquez' python3000 specfile - ---- - Lib/distutils/unixccompiler.py | 9 +++++++++ - 1 file changed, 9 insertions(+) - diff --git a/Lib/distutils/unixccompiler.py b/Lib/distutils/unixccompiler.py -index d00c48981e..0283a28c19 100644 +index f0792de..4d83793 100644 --- a/Lib/distutils/unixccompiler.py +++ b/Lib/distutils/unixccompiler.py @@ -82,6 +82,15 @@ class UnixCCompiler(CCompiler): @@ -28,3 +18,6 @@ index d00c48981e..0283a28c19 100644 def preprocess(self, source, output_file=None, macros=None, include_dirs=None, extra_preargs=None, extra_postargs=None): fixed_args = self._fix_compile_args(None, macros, include_dirs) +-- +1.8.3.1 + diff --git a/00251-change-user-install-location.patch b/00251-change-user-install-location.patch index 53096ec807fccfab6459042ae18f3067bc2062c8..ac6901d62e51bc7a3498e4d0ce598a6585a616b3 100644 --- a/00251-change-user-install-location.patch +++ b/00251-change-user-install-location.patch @@ -1,70 +1,42 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= +From: Lumir Balhar Date: Mon, 15 Feb 2021 12:19:27 +0100 Subject: [PATCH] 00251: Change user install location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Set values of base and platbase in sysconfig from /usr -to /usr/local when RPM build is not detected -to make pip and similar tools install into separate location. +Change the values of sysconfig's "posix_prefix" install scheme to /usr/local +when RPM build or venv/virtualenv is not detected, +to make pip, sysconfig and distutils install into an isolated location. -Set values of prefix and exec_prefix in distutils install command -to /usr/local if executable is /usr/bin/python* and RPM build -is not detected to make distutils and pypa/distutils install into separate location. +The original values are saved as an additional "rpm_prefix" install scheme. + +The site module adds the /usr/local paths to sys.path when site packages are +enabled and RPM build is not detected. Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe -Downstream only. -We've tried to rework in Fedora 36/Python 3.10 to follow https://bugs.python.org/issue43976 -but we have identified serious problems with that approach, -see https://bugzilla.redhat.com/2026979 or https://bugzilla.redhat.com/2097183 +Rewrote in Fedora 36+ to patch sysconfig instead of distutils, +see https://discuss.python.org/t/pep-632-deprecate-distutils-module/5134/104 -pypa/distutils integration: https://github.com/pypa/distutils/pull/70 +Downstream only for now, waiting for https://bugs.python.org/issue43976 Co-authored-by: Petr Viktorin Co-authored-by: Miro Hrončok Co-authored-by: Michal Cyprian Co-authored-by: Lumír Balhar --- - Lib/distutils/command/install.py | 8 ++++-- - Lib/site.py | 9 +++++- - Lib/sysconfig.py | 49 +++++++++++++++++++++++++++++++- - Lib/test/test_sysconfig.py | 17 +++++++++-- - 4 files changed, 77 insertions(+), 6 deletions(-) + Lib/site.py | 9 ++++++++- + Lib/sysconfig.py | 25 +++++++++++++++++++++++++ + Lib/test/test_sysconfig.py | 4 +++- + 3 files changed, 36 insertions(+), 2 deletions(-) -diff --git a/Lib/distutils/command/install.py b/Lib/distutils/command/install.py -index 01d5331a63..79f70f0de4 100644 ---- a/Lib/distutils/command/install.py -+++ b/Lib/distutils/command/install.py -@@ -159,6 +159,8 @@ class install(Command): - - negative_opt = {'no-compile' : 'compile'} - -+ # Allow Fedora to add components to the prefix -+ _prefix_addition = getattr(sysconfig, '_prefix_addition', '') - - def initialize_options(self): - """Initializes options.""" -@@ -441,8 +443,10 @@ def finalize_unix(self): - raise DistutilsOptionError( - "must not supply exec-prefix without prefix") - -- self.prefix = os.path.normpath(sys.prefix) -- self.exec_prefix = os.path.normpath(sys.exec_prefix) -+ self.prefix = ( -+ os.path.normpath(sys.prefix) + self._prefix_addition) -+ self.exec_prefix = ( -+ os.path.normpath(sys.exec_prefix) + self._prefix_addition) - - else: - if self.exec_prefix is None: diff --git a/Lib/site.py b/Lib/site.py -index 69670d9d7f..104cb93899 100644 +index 939893eb5e..d1316c3355 100644 --- a/Lib/site.py +++ b/Lib/site.py -@@ -377,8 +377,15 @@ def getsitepackages(prefixes=None): +@@ -380,8 +380,15 @@ def getsitepackages(prefixes=None): return sitepackages def addsitepackages(known_paths, prefixes=None): @@ -82,118 +54,55 @@ index 69670d9d7f..104cb93899 100644 if os.path.isdir(sitedir): addsitedir(sitedir, known_paths) diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py -index ebe3711827..55af57b335 100644 +index daf9f00006..40e4edf0ae 100644 --- a/Lib/sysconfig.py +++ b/Lib/sysconfig.py -@@ -103,6 +103,11 @@ - else: - _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv'] +@@ -58,6 +58,31 @@ + }, + } -+# For a brief period of time in the Fedora 36 life cycle, -+# this installation scheme existed and was documented in the release notes. -+# For backwards compatibility, we keep it here (at least on 3.10 and 3.11). ++# backup the original posix_prefix as rpm_prefix ++# RPM packages use it and we need to be able to read it even when changed +_INSTALL_SCHEMES['rpm_prefix'] = _INSTALL_SCHEMES['posix_prefix'] ++# Virtualenv >= 20.10.0 favors the "venv" scheme over the defaults when creating virtual environments. ++# See: https://github.com/pypa/virtualenv/commit/8da79db86d8a5c74d03667a40e64ff832076445e ++# See: https://bugs.python.org/issue45413 ++# "venv" should be the same as the unpatched posix_prefix for us, ++# so new virtual environments aren't created with paths like venv/local/bin/python. ++_INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_prefix'] + - - # NOTE: site.py has copy of this function. - # Sync it when modify this function. -@@ -162,6 +167,19 @@ def joinuser(*args): - }, - } - -+# This is used by distutils.command.install in the stdlib -+# as well as pypa/distutils (e.g. bundled in setuptools). -+# The self.prefix value is set to sys.prefix + /local/ -+# if neither RPM build nor virtual environment is -+# detected to make distutils install packages -+# into the separate location. -+# https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe +if (not (hasattr(sys, 'real_prefix') or + sys.prefix != sys.base_prefix) and + 'RPM_BUILD_ROOT' not in os.environ): -+ _prefix_addition = '/local' -+ -+ - _SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include', - 'scripts', 'data') - -@@ -258,11 +276,40 @@ def _extend_dict(target_dict, other_dict): - target_dict[key] = value - ++ _INSTALL_SCHEMES['posix_prefix'] = { ++ 'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}', ++ 'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}', ++ 'purelib': '{base}/local/lib/python{py_version_short}/site-packages', ++ 'platlib': '{platbase}/local/{platlibdir}/python{py_version_short}/site-packages', ++ 'include': ++ '{installed_base}/include/python{py_version_short}{abiflags}', ++ 'platinclude': ++ '{installed_platbase}/include/python{py_version_short}{abiflags}', ++ 'scripts': '{base}/local/bin', ++ 'data': '{base}/local', ++ } -+_CONFIG_VARS_LOCAL = None -+ -+ -+def _config_vars_local(): -+ # This function returns the config vars with prefixes amended to /usr/local -+ # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe -+ global _CONFIG_VARS_LOCAL -+ if _CONFIG_VARS_LOCAL is None: -+ _CONFIG_VARS_LOCAL = dict(get_config_vars()) -+ _CONFIG_VARS_LOCAL['base'] = '/usr/local' -+ _CONFIG_VARS_LOCAL['platbase'] = '/usr/local' -+ return _CONFIG_VARS_LOCAL -+ -+ - def _expand_vars(scheme, vars): - res = {} - if vars is None: - vars = {} -- _extend_dict(vars, get_config_vars()) -+ -+ # when we are not in a virtual environment or an RPM build -+ # we change '/usr' to '/usr/local' -+ # to avoid surprises, we explicitly check for the /usr/ prefix -+ # Python virtual environments have different prefixes -+ # we only do this for posix_prefix, not to mangle the venv scheme -+ # posix_prefix is used by sudo pip install -+ # we only change the defaults here, so explicit --prefix will take precedence -+ # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe -+ if (scheme == 'posix_prefix' and -+ _PREFIX == '/usr' and -+ 'RPM_BUILD_ROOT' not in os.environ): -+ _extend_dict(vars, _config_vars_local()) -+ else: -+ _extend_dict(vars, get_config_vars()) -+ - if os.name == 'nt': - # On Windows we want to substitute 'lib' for schemes rather - # than the native value (without modifying vars, in case it + # NOTE: site.py has copy of this function. + # Sync it when modify this function. diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py -index d96371d242..72b028435f 100644 +index 9408657c91..fd49b2bcce 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py -@@ -111,8 +111,19 @@ def test_get_path(self): - for scheme in _INSTALL_SCHEMES: - for name in _INSTALL_SCHEMES[scheme]: - expected = _INSTALL_SCHEMES[scheme][name].format(**config_vars) -+ tested = get_path(name, scheme) -+ # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe -+ if tested.startswith('/usr/local'): -+ # /usr/local should only be used in posix_prefix -+ self.assertEqual(scheme, 'posix_prefix') -+ # Fedora CI runs tests for venv and virtualenv that check for other prefixes -+ self.assertEqual(sys.prefix, '/usr') -+ # When building the RPM of Python, %check runs this with RPM_BUILD_ROOT set -+ # Fedora CI runs this with RPM_BUILD_ROOT unset -+ self.assertNotIn('RPM_BUILD_ROOT', os.environ) -+ tested = tested.replace('/usr/local', '/usr') - self.assertEqual( -- os.path.normpath(get_path(name, scheme)), -+ os.path.normpath(tested), - os.path.normpath(expected), - ) - -@@ -336,7 +347,7 @@ def test_get_config_h_filename(self): +@@ -263,7 +263,7 @@ def test_get_config_h_filename(self): self.assertTrue(os.path.isfile(config_h), config_h) def test_get_scheme_names(self): -- wanted = ['nt', 'posix_home', 'posix_prefix', 'posix_venv', 'nt_venv', 'venv'] -+ wanted = ['nt', 'posix_home', 'posix_prefix', 'posix_venv', 'nt_venv', 'venv', 'rpm_prefix'] +- wanted = ['nt', 'posix_home', 'posix_prefix'] ++ wanted = ['nt', 'posix_home', 'posix_prefix', 'rpm_prefix', 'venv'] if HAS_USER_BASE: wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) -@@ -348,6 +359,8 @@ def test_symlink(self): # Issue 7880 +@@ -274,6 +274,8 @@ def test_symlink(self): # Issue 7880 cmd = "-c", "import sysconfig; print(sysconfig.get_platform())" self.assertEqual(py.call_real(*cmd), py.call_link(*cmd)) diff --git a/Python-3.11.1.tar.xz b/Python-3.10.2.tar.xz similarity index 65% rename from Python-3.11.1.tar.xz rename to Python-3.10.2.tar.xz index 9932def2b06f70c7121bf90939e979324122ee46..c35c1fc12a86bbc9b3c7242a28489bacf2d52675 100644 Binary files a/Python-3.11.1.tar.xz and b/Python-3.10.2.tar.xz differ diff --git a/backport-0001-CVE-2020-10735.patch b/backport-0001-CVE-2020-10735.patch new file mode 100644 index 0000000000000000000000000000000000000000..1769f6a1152683cfd230cb8b4f62a00c31a857e4 --- /dev/null +++ b/backport-0001-CVE-2020-10735.patch @@ -0,0 +1,1395 @@ +From 8f0fa4bd10aba723aff988720cd26b93be99bc12 Mon Sep 17 00:00:00 2001 +From: "Gregory P. Smith" +Date: Fri, 2 Sep 2022 09:51:49 -0700 +Subject: [PATCH] [3.10] gh-95778: CVE-2020-10735: Prevent DoS by very large + int() (#96501) + +Integer to and from text conversions via CPython's bignum `int` type is not safe against denial of service attacks due to malicious input. Very large input strings with hundred thousands of digits can consume several CPU seconds. + +This PR comes fresh from a pile of work done in our private PSRT security response team repo. + +This backports https://github.com/python/cpython/pull/96499 aka 511ca9452033ef95bc7d7fc404b8161068226002 + +Signed-off-by: Christian Heimes [Red Hat] +Tons-of-polishing-up-by: Gregory P. Smith [Google] +Reviews via the private PSRT repo via many others (see the NEWS entry in the PR). + + +* Issue: gh-95778 + + +I wrote up [a one pager for the release managers](https://docs.google.com/document/d/1KjuF_aXlzPUxTK4BMgezGJ2Pn7uevfX7g0_mvgHlL7Y/edit#). +--- + Doc/data/python3.10.abi | 5 +- + Doc/library/functions.rst | 8 + + Doc/library/json.rst | 11 ++ + Doc/library/stdtypes.rst | 166 ++++++++++++++++++ + Doc/library/sys.rst | 57 ++++-- + Doc/library/test.rst | 10 ++ + Doc/using/cmdline.rst | 14 +- + Doc/whatsnew/3.10.rst | 16 ++ + Include/internal/pycore_initconfig.h | 2 + + Include/internal/pycore_interp.h | 2 + + Include/internal/pycore_long.h | 35 ++++ + Lib/idlelib/idle_test/test_sidebar.py | 4 +- + Lib/test/support/__init__.py | 11 ++ + Lib/test/test_ast.py | 8 + + Lib/test/test_cmd_line.py | 33 ++++ + Lib/test/test_compile.py | 13 ++ + Lib/test/test_decimal.py | 18 ++ + Lib/test/test_int.py | 114 ++++++++++++ + Lib/test/test_json/test_decode.py | 9 + + Lib/test/test_sys.py | 10 +- + Lib/test/test_xmlrpc.py | 10 ++ + ...08-07-16-53-38.gh-issue-95778.ch010gps.rst | 14 ++ + Objects/longobject.c | 46 ++++- + Parser/pegen.c | 23 +++ + Python/clinic/sysmodule.c.h | 55 +++++- + Python/initconfig.c | 60 +++++++ + Python/sysmodule.c | 46 ++++- + 27 files changed, 779 insertions(+), 21 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst + +diff --git a/Doc/data/python3.10.abi b/Doc/data/python3.10.abi +index b7886ae..16fbb39 100644 +--- a/Doc/data/python3.10.abi ++++ b/Doc/data/python3.10.abi +@@ -4851,7 +4851,7 @@ + + + +- ++ + + + +@@ -5002,6 +5002,9 @@ + + + ++ ++ ++ + + + +diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst +index 9a9c87e..b237f7b 100644 +--- a/Doc/library/functions.rst ++++ b/Doc/library/functions.rst +@@ -891,6 +891,14 @@ are always available. They are listed here in alphabetical order. + .. versionchanged:: 3.8 + Falls back to :meth:`__index__` if :meth:`__int__` is not defined. + ++ .. versionchanged:: 3.10.7 ++ :class:`int` string inputs and string representations can be limited to ++ help avoid denial of service attacks. A :exc:`ValueError` is raised when ++ the limit is exceeded while converting a string *x* to an :class:`int` or ++ when converting an :class:`int` into a string would exceed the limit. ++ See the :ref:`integer string conversion length limitation ++ ` documentation. ++ + + .. function:: isinstance(object, classinfo) + +diff --git a/Doc/library/json.rst b/Doc/library/json.rst +index 1810e04..471f632 100644 +--- a/Doc/library/json.rst ++++ b/Doc/library/json.rst +@@ -18,6 +18,11 @@ is a lightweight data interchange format inspired by + `JavaScript `_ object literal syntax + (although it is not a strict subset of JavaScript [#rfc-errata]_ ). + ++.. warning:: ++ Be cautious when parsing JSON data from untrusted sources. A malicious ++ JSON string may cause the decoder to consume considerable CPU and memory ++ resources. Limiting the size of data to be parsed is recommended. ++ + :mod:`json` exposes an API familiar to users of the standard library + :mod:`marshal` and :mod:`pickle` modules. + +@@ -255,6 +260,12 @@ Basic Usage + be used to use another datatype or parser for JSON integers + (e.g. :class:`float`). + ++ .. versionchanged:: 3.10.7 ++ The default *parse_int* of :func:`int` now limits the maximum length of ++ the integer string via the interpreter's :ref:`integer string ++ conversion length limitation ` to help avoid denial ++ of service attacks. ++ + *parse_constant*, if specified, will be called with one of the following + strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. + This can be used to raise an exception if invalid JSON numbers +diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst +index 8fa252b..2723f58 100644 +--- a/Doc/library/stdtypes.rst ++++ b/Doc/library/stdtypes.rst +@@ -584,6 +584,13 @@ class`. float also has the following additional methods. + :exc:`OverflowError` on infinities and a :exc:`ValueError` on + NaNs. + ++ .. note:: ++ ++ The values returned by ``as_integer_ratio()`` can be huge. Attempts ++ to render such integers into decimal strings may bump into the ++ :ref:`integer string conversion length limitation ++ `. ++ + .. method:: float.is_integer() + + Return ``True`` if the float instance is finite with integral +@@ -5368,6 +5375,165 @@ types, where they are relevant. Some of these are not reported by the + [] + + ++.. _int_max_str_digits: ++ ++Integer string conversion length limitation ++=========================================== ++ ++CPython has a global limit for converting between :class:`int` and :class:`str` ++to mitigate denial of service attacks. This limit *only* applies to decimal or ++other non-power-of-two number bases. Hexadecimal, octal, and binary conversions ++are unlimited. The limit can be configured. ++ ++The :class:`int` type in CPython is an abitrary length number stored in binary ++form (commonly known as a "bignum"). There exists no algorithm that can convert ++a string to a binary integer or a binary integer to a string in linear time, ++*unless* the base is a power of 2. Even the best known algorithms for base 10 ++have sub-quadratic complexity. Converting a large value such as ``int('1' * ++500_000)`` can take over a second on a fast CPU. ++ ++Limiting conversion size offers a practical way to avoid `CVE-2020-10735 ++`_. ++ ++The limit is applied to the number of digit characters in the input or output ++string when a non-linear conversion algorithm would be involved. Underscores ++and the sign are not counted towards the limit. ++ ++When an operation would exceed the limit, a :exc:`ValueError` is raised: ++ ++.. doctest:: ++ ++ >>> import sys ++ >>> sys.set_int_max_str_digits(4300) # Illustrative, this is the default. ++ >>> _ = int('2' * 5432) ++ Traceback (most recent call last): ++ ... ++ ValueError: Exceeds the limit (4300) for integer string conversion: value has 5432 digits. ++ >>> i = int('2' * 4300) ++ >>> len(str(i)) ++ 4300 ++ >>> i_squared = i*i ++ >>> len(str(i_squared)) ++ Traceback (most recent call last): ++ ... ++ ValueError: Exceeds the limit (4300) for integer string conversion: value has 8599 digits. ++ >>> len(hex(i_squared)) ++ 7144 ++ >>> assert int(hex(i_squared), base=16) == i*i # Hexadecimal is unlimited. ++ ++The default limit is 4300 digits as provided in ++:data:`sys.int_info.default_max_str_digits `. ++The lowest limit that can be configured is 640 digits as provided in ++:data:`sys.int_info.str_digits_check_threshold `. ++ ++Verification: ++ ++.. doctest:: ++ ++ >>> import sys ++ >>> assert sys.int_info.default_max_str_digits == 4300, sys.int_info ++ >>> assert sys.int_info.str_digits_check_threshold == 640, sys.int_info ++ >>> msg = int('578966293710682886880994035146873798396722250538762761564' ++ ... '9252925514383915483333812743580549779436104706260696366600' ++ ... '571186405732').to_bytes(53, 'big') ++ ... ++ ++.. versionadded:: 3.10.7 ++ ++Affected APIs ++------------- ++ ++The limition only applies to potentially slow conversions between :class:`int` ++and :class:`str` or :class:`bytes`: ++ ++* ``int(string)`` with default base 10. ++* ``int(string, base)`` for all bases that are not a power of 2. ++* ``str(integer)``. ++* ``repr(integer)`` ++* any other string conversion to base 10, for example ``f"{integer}"``, ++ ``"{}".format(integer)``, or ``b"%d" % integer``. ++ ++The limitations do not apply to functions with a linear algorithm: ++ ++* ``int(string, base)`` with base 2, 4, 8, 16, or 32. ++* :func:`int.from_bytes` and :func:`int.to_bytes`. ++* :func:`hex`, :func:`oct`, :func:`bin`. ++* :ref:`formatspec` for hex, octal, and binary numbers. ++* :class:`str` to :class:`float`. ++* :class:`str` to :class:`decimal.Decimal`. ++ ++Configuring the limit ++--------------------- ++ ++Before Python starts up you can use an environment variable or an interpreter ++command line flag to configure the limit: ++ ++* :envvar:`PYTHONINTMAXSTRDIGITS`, e.g. ++ ``PYTHONINTMAXSTRDIGITS=640 python3`` to set the limit to 640 or ++ ``PYTHONINTMAXSTRDIGITS=0 python3`` to disable the limitation. ++* :option:`-X int_max_str_digits <-X>`, e.g. ++ ``python3 -X int_max_str_digits=640`` ++* :data:`sys.flags.int_max_str_digits` contains the value of ++ :envvar:`PYTHONINTMAXSTRDIGITS` or :option:`-X int_max_str_digits <-X>`. ++ If both the env var and the ``-X`` option are set, the ``-X`` option takes ++ precedence. A value of *-1* indicates that both were unset, thus a value of ++ :data:`sys.int_info.default_max_str_digits` was used during initilization. ++ ++From code, you can inspect the current limit and set a new one using these ++:mod:`sys` APIs: ++ ++* :func:`sys.get_int_max_str_digits` and :func:`sys.set_int_max_str_digits` are ++ a getter and setter for the interpreter-wide limit. Subinterpreters have ++ their own limit. ++ ++Information about the default and minimum can be found in :attr:`sys.int_info`: ++ ++* :data:`sys.int_info.default_max_str_digits ` is the compiled-in ++ default limit. ++* :data:`sys.int_info.str_digits_check_threshold ` is the lowest ++ accepted value for the limit (other than 0 which disables it). ++ ++.. versionadded:: 3.10.7 ++ ++.. caution:: ++ ++ Setting a low limit *can* lead to problems. While rare, code exists that ++ contains integer constants in decimal in their source that exceed the ++ minimum threshold. A consequence of setting the limit is that Python source ++ code containing decimal integer literals longer than the limit will ++ encounter an error during parsing, usually at startup time or import time or ++ even at installation time - anytime an up to date ``.pyc`` does not already ++ exist for the code. A workaround for source that contains such large ++ constants is to convert them to ``0x`` hexadecimal form as it has no limit. ++ ++ Test your application thoroughly if you use a low limit. Ensure your tests ++ run with the limit set early via the environment or flag so that it applies ++ during startup and even during any installation step that may invoke Python ++ to precompile ``.py`` sources to ``.pyc`` files. ++ ++Recommended configuration ++------------------------- ++ ++The default :data:`sys.int_info.default_max_str_digits` is expected to be ++reasonable for most applications. If your application requires a different ++limit, set it from your main entry point using Python version agnostic code as ++these APIs were added in security patch releases in versions before 3.11. ++ ++Example:: ++ ++ >>> import sys ++ >>> if hasattr(sys, "set_int_max_str_digits"): ++ ... upper_bound = 68000 ++ ... lower_bound = 4004 ++ ... current_limit = sys.get_int_max_str_digits() ++ ... if current_limit == 0 or current_limit > upper_bound: ++ ... sys.set_int_max_str_digits(upper_bound) ++ ... elif current_limit < lower_bound: ++ ... sys.set_int_max_str_digits(lower_bound) ++ ++If you need to disable it entirely, set it to ``0``. ++ ++ + .. rubric:: Footnotes + + .. [1] Additional information on these special methods may be found in the Python +diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst +index 29cb54b..d0b35ab 100644 +--- a/Doc/library/sys.rst ++++ b/Doc/library/sys.rst +@@ -462,9 +462,9 @@ always available. + The :term:`named tuple` *flags* exposes the status of command line + flags. The attributes are read only. + +- ============================= ================================================================ ++ ============================= ============================================================================================================== + attribute flag +- ============================= ================================================================ ++ ============================= ============================================================================================================== + :const:`debug` :option:`-d` + :const:`inspect` :option:`-i` + :const:`interactive` :option:`-i` +@@ -480,7 +480,8 @@ always available. + :const:`hash_randomization` :option:`-R` + :const:`dev_mode` :option:`-X dev <-X>` (:ref:`Python Development Mode `) + :const:`utf8_mode` :option:`-X utf8 <-X>` +- ============================= ================================================================ ++ :const:`int_max_str_digits` :option:`-X int_max_str_digits <-X>` (:ref:`integer string conversion length limitation `) ++ ============================= ============================================================================================================== + + .. versionchanged:: 3.2 + Added ``quiet`` attribute for the new :option:`-q` flag. +@@ -499,6 +500,9 @@ always available. + Mode ` and the ``utf8_mode`` attribute for the new :option:`-X` + ``utf8`` flag. + ++ .. versionchanged:: 3.10.7 ++ Added the ``int_max_str_digits`` attribute. ++ + + .. data:: float_info + +@@ -679,6 +683,13 @@ always available. + + .. versionadded:: 3.6 + ++.. function:: get_int_max_str_digits() ++ ++ Returns the current value for the :ref:`integer string conversion length ++ limitation `. See also :func:`set_int_max_str_digits`. ++ ++ .. versionadded:: 3.10.7 ++ + .. function:: getrefcount(object) + + Return the reference count of the *object*. The count returned is generally one +@@ -952,19 +963,31 @@ always available. + + .. tabularcolumns:: |l|L| + +- +-------------------------+----------------------------------------------+ +- | Attribute | Explanation | +- +=========================+==============================================+ +- | :const:`bits_per_digit` | number of bits held in each digit. Python | +- | | integers are stored internally in base | +- | | ``2**int_info.bits_per_digit`` | +- +-------------------------+----------------------------------------------+ +- | :const:`sizeof_digit` | size in bytes of the C type used to | +- | | represent a digit | +- +-------------------------+----------------------------------------------+ ++ +----------------------------------------+-----------------------------------------------+ ++ | Attribute | Explanation | ++ +========================================+===============================================+ ++ | :const:`bits_per_digit` | number of bits held in each digit. Python | ++ | | integers are stored internally in base | ++ | | ``2**int_info.bits_per_digit`` | ++ +----------------------------------------+-----------------------------------------------+ ++ | :const:`sizeof_digit` | size in bytes of the C type used to | ++ | | represent a digit | ++ +----------------------------------------+-----------------------------------------------+ ++ | :const:`default_max_str_digits` | default value for | ++ | | :func:`sys.get_int_max_str_digits` when it | ++ | | is not otherwise explicitly configured. | ++ +----------------------------------------+-----------------------------------------------+ ++ | :const:`str_digits_check_threshold` | minimum non-zero value for | ++ | | :func:`sys.set_int_max_str_digits`, | ++ | | :envvar:`PYTHONINTMAXSTRDIGITS`, or | ++ | | :option:`-X int_max_str_digits <-X>`. | ++ +----------------------------------------+-----------------------------------------------+ + + .. versionadded:: 3.1 + ++ .. versionchanged:: 3.10.7 ++ Added ``default_max_str_digits`` and ``str_digits_check_threshold``. ++ + + .. data:: __interactivehook__ + +@@ -1256,6 +1279,14 @@ always available. + + .. availability:: Unix. + ++.. function:: set_int_max_str_digits(n) ++ ++ Set the :ref:`integer string conversion length limitation ++ ` used by this interpreter. See also ++ :func:`get_int_max_str_digits`. ++ ++ .. versionadded:: 3.10.7 ++ + .. function:: setprofile(profilefunc) + + .. index:: +diff --git a/Doc/library/test.rst b/Doc/library/test.rst +index a8dc354..fff48bb 100644 +--- a/Doc/library/test.rst ++++ b/Doc/library/test.rst +@@ -935,6 +935,16 @@ The :mod:`test.support` module defines the following functions: + .. versionadded:: 3.10 + + ++.. function:: adjust_int_max_str_digits(max_digits) ++ ++ This function returns a context manager that will change the global ++ :func:`sys.set_int_max_str_digits` setting for the duration of the ++ context to allow execution of test code that needs a different limit ++ on the number of digits when converting between an integer and string. ++ ++ .. versionadded:: 3.10.7 ++ ++ + The :mod:`test.support` module defines the following classes: + + +diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst +index c4f65eb..1a0eb25 100644 +--- a/Doc/using/cmdline.rst ++++ b/Doc/using/cmdline.rst +@@ -457,6 +457,9 @@ Miscellaneous options + stored in a traceback of a trace. Use ``-X tracemalloc=NFRAME`` to start + tracing with a traceback limit of *NFRAME* frames. See the + :func:`tracemalloc.start` for more information. ++ * ``-X int_max_str_digits`` configures the :ref:`integer string conversion ++ length limitation `. See also ++ :envvar:`PYTHONINTMAXSTRDIGITS`. + * ``-X importtime`` to show how long each import takes. It shows module + name, cumulative time (including nested imports) and self time (excluding + nested imports). Note that its output may be broken in multi-threaded +@@ -506,6 +509,9 @@ Miscellaneous options + .. versionadded:: 3.10 + The ``-X warn_default_encoding`` option. + ++ .. versionadded:: 3.10.7 ++ The ``-X int_max_str_digits`` option. ++ + .. deprecated-removed:: 3.9 3.10 + The ``-X oldparser`` option. + +@@ -519,7 +525,6 @@ Options you shouldn't use + + .. _Jython: http://www.jython.org/ + +- + .. _using-on-envvars: + + Environment variables +@@ -676,6 +681,13 @@ conflict. + + .. versionadded:: 3.2.3 + ++.. envvar:: PYTHONINTMAXSTRDIGITS ++ ++ If this variable is set to an integer, it is used to configure the ++ interpreter's global :ref:`integer string conversion length limitation ++ `. ++ ++ .. versionadded:: 3.10.7 + + .. envvar:: PYTHONIOENCODING + +diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst +index c07523c..dd4da45 100644 +--- a/Doc/whatsnew/3.10.rst ++++ b/Doc/whatsnew/3.10.rst +@@ -2303,3 +2303,19 @@ Removed + + * The ``PyThreadState.use_tracing`` member has been removed to optimize Python. + (Contributed by Mark Shannon in :issue:`43760`.) ++ ++ ++Notable security feature in 3.10.7 ++================================== ++ ++Converting between :class:`int` and :class:`str` in bases other than 2 ++(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) ++now raises a :exc:`ValueError` if the number of digits in string form is ++above a limit to avoid potential denial of service attacks due to the ++algorithmic complexity. This is a mitigation for `CVE-2020-10735 ++`_. ++This limit can be configured or disabled by environment variable, command ++line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion ++length limitation ` documentation. The default limit ++is 4300 digits in string form. ++ +diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h +index 4b009e8..21c71bf 100644 +--- a/Include/internal/pycore_initconfig.h ++++ b/Include/internal/pycore_initconfig.h +@@ -165,6 +165,8 @@ extern PyStatus _PyConfig_SetPyArgv( + PyAPI_FUNC(PyObject*) _PyConfig_AsDict(const PyConfig *config); + PyAPI_FUNC(int) _PyConfig_FromDict(PyConfig *config, PyObject *dict); + ++extern int _Py_global_config_int_max_str_digits; ++ + + /* --- Function used for testing ---------------------------------- */ + +diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h +index 4307b61..a564b12 100644 +--- a/Include/internal/pycore_interp.h ++++ b/Include/internal/pycore_interp.h +@@ -305,6 +305,8 @@ struct _is { + + struct ast_state ast; + struct type_cache type_cache; ++ ++ int int_max_str_digits; + }; + + extern void _PyInterpreterState_ClearModules(PyInterpreterState *interp); +diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h +index 2bea3a5..ee9a592 100644 +--- a/Include/internal/pycore_long.h ++++ b/Include/internal/pycore_long.h +@@ -11,6 +11,41 @@ extern "C" { + #include "pycore_interp.h" // PyInterpreterState.small_ints + #include "pycore_pystate.h" // _PyThreadState_GET() + ++/* ++ * Default int base conversion size limitation: Denial of Service prevention. ++ * ++ * Chosen such that this isn't wildly slow on modern hardware and so that ++ * everyone's existing deployed numpy test suite passes before ++ * https://github.com/numpy/numpy/issues/22098 is widely available. ++ * ++ * $ python -m timeit -s 's = * "1"*4300' 'int(s)' ++ * 2000 loops, best of 5: 125 usec per loop ++ * $ python -m timeit -s 's = * "1"*4300; v = int(s)' 'str(v)' ++ * 1000 loops, best of 5: 311 usec per loop ++ * (zen2 cloud VM) ++ * ++ * 4300 decimal digits fits a ~14284 bit number. ++ */ ++#define _PY_LONG_DEFAULT_MAX_STR_DIGITS 4300 ++/* ++ * Threshold for max digits check. For performance reasons int() and ++ * int.__str__() don't checks values that are smaller than this ++ * threshold. Acts as a guaranteed minimum size limit for bignums that ++ * applications can expect from CPython. ++ * ++ * % python -m timeit -s 's = "1"*640; v = int(s)' 'str(int(s))' ++ * 20000 loops, best of 5: 12 usec per loop ++ * ++ * "640 digits should be enough for anyone." - gps ++ * fits a ~2126 bit decimal number. ++ */ ++#define _PY_LONG_MAX_STR_DIGITS_THRESHOLD 640 ++ ++#if ((_PY_LONG_DEFAULT_MAX_STR_DIGITS != 0) && \ ++ (_PY_LONG_DEFAULT_MAX_STR_DIGITS < _PY_LONG_MAX_STR_DIGITS_THRESHOLD)) ++# error "_PY_LONG_DEFAULT_MAX_STR_DIGITS smaller than threshold." ++#endif ++ + // Don't call this function but _PyLong_GetZero() and _PyLong_GetOne() + static inline PyObject* __PyLong_GetSmallInt_internal(int value) + { +diff --git a/Lib/idlelib/idle_test/test_sidebar.py b/Lib/idlelib/idle_test/test_sidebar.py +index 53ac3eb..2c50134 100644 +--- a/Lib/idlelib/idle_test/test_sidebar.py ++++ b/Lib/idlelib/idle_test/test_sidebar.py +@@ -6,6 +6,7 @@ from itertools import chain + import unittest + import unittest.mock + from test.support import requires, swap_attr ++from test import support + import tkinter as tk + from idlelib.idle_test.tkinter_testing_utils import run_in_tk_mainloop + +@@ -615,7 +616,8 @@ class ShellSidebarTest(unittest.TestCase): + + @run_in_tk_mainloop() + def test_very_long_wrapped_line(self): +- with swap_attr(self.shell, 'squeezer', None): ++ with support.adjust_int_max_str_digits(11_111), \ ++ swap_attr(self.shell, 'squeezer', None): + self.do_input('x = ' + '1'*10_000 + '\n') + yield + self.assertEqual(self.get_sidebar_lines(), ['>>>']) +diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py +index 44c5bd5..c6000be 100644 +--- a/Lib/test/support/__init__.py ++++ b/Lib/test/support/__init__.py +@@ -2066,3 +2066,14 @@ def clear_ignored_deprecations(*tokens: object) -> None: + if warnings.filters != new_filters: + warnings.filters[:] = new_filters + warnings._filters_mutated() ++ ++ ++@contextlib.contextmanager ++def adjust_int_max_str_digits(max_digits): ++ """Temporarily change the integer string conversion length limit.""" ++ current = sys.get_int_max_str_digits() ++ try: ++ sys.set_int_max_str_digits(max_digits) ++ yield ++ finally: ++ sys.set_int_max_str_digits(current) +diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py +index 39fc7e9..af3335a 100644 +--- a/Lib/test/test_ast.py ++++ b/Lib/test/test_ast.py +@@ -999,6 +999,14 @@ Module( + self.assertRaises(ValueError, ast.literal_eval, '+True') + self.assertRaises(ValueError, ast.literal_eval, '2+3') + ++ def test_literal_eval_str_int_limit(self): ++ with support.adjust_int_max_str_digits(4000): ++ ast.literal_eval('3'*4000) # no error ++ with self.assertRaises(SyntaxError) as err_ctx: ++ ast.literal_eval('3'*4001) ++ self.assertIn('Exceeds the limit ', str(err_ctx.exception)) ++ self.assertIn(' Consider hexadecimal ', str(err_ctx.exception)) ++ + def test_literal_eval_complex(self): + # Issue #4907 + self.assertEqual(ast.literal_eval('6j'), 6j) +diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py +index d93e98f..a88ca60 100644 +--- a/Lib/test/test_cmd_line.py ++++ b/Lib/test/test_cmd_line.py +@@ -814,6 +814,39 @@ class CmdLineTest(unittest.TestCase): + self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr) + self.assertNotEqual(proc.returncode, 0) + ++ def test_int_max_str_digits(self): ++ code = "import sys; print(sys.flags.int_max_str_digits, sys.get_int_max_str_digits())" ++ ++ assert_python_failure('-X', 'int_max_str_digits', '-c', code) ++ assert_python_failure('-X', 'int_max_str_digits=foo', '-c', code) ++ assert_python_failure('-X', 'int_max_str_digits=100', '-c', code) ++ ++ assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='foo') ++ assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='100') ++ ++ def res2int(res): ++ out = res.out.strip().decode("utf-8") ++ return tuple(int(i) for i in out.split()) ++ ++ res = assert_python_ok('-c', code) ++ self.assertEqual(res2int(res), (-1, sys.get_int_max_str_digits())) ++ res = assert_python_ok('-X', 'int_max_str_digits=0', '-c', code) ++ self.assertEqual(res2int(res), (0, 0)) ++ res = assert_python_ok('-X', 'int_max_str_digits=4000', '-c', code) ++ self.assertEqual(res2int(res), (4000, 4000)) ++ res = assert_python_ok('-X', 'int_max_str_digits=100000', '-c', code) ++ self.assertEqual(res2int(res), (100000, 100000)) ++ ++ res = assert_python_ok('-c', code, PYTHONINTMAXSTRDIGITS='0') ++ self.assertEqual(res2int(res), (0, 0)) ++ res = assert_python_ok('-c', code, PYTHONINTMAXSTRDIGITS='4000') ++ self.assertEqual(res2int(res), (4000, 4000)) ++ res = assert_python_ok( ++ '-X', 'int_max_str_digits=6000', '-c', code, ++ PYTHONINTMAXSTRDIGITS='4000' ++ ) ++ self.assertEqual(res2int(res), (6000, 6000)) ++ + + @unittest.skipIf(interpreter_requires_environment(), + 'Cannot run -I tests when PYTHON env vars are required.') +diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py +index 5f80a58..3162683 100644 +--- a/Lib/test/test_compile.py ++++ b/Lib/test/test_compile.py +@@ -191,6 +191,19 @@ if 1: + self.assertEqual(eval("0o777"), 511) + self.assertEqual(eval("-0o0000010"), -8) + ++ def test_int_literals_too_long(self): ++ n = 3000 ++ source = f"a = 1\nb = 2\nc = {'3'*n}\nd = 4" ++ with support.adjust_int_max_str_digits(n): ++ compile(source, "", "exec") # no errors. ++ with support.adjust_int_max_str_digits(n-1): ++ with self.assertRaises(SyntaxError) as err_ctx: ++ compile(source, "", "exec") ++ exc = err_ctx.exception ++ self.assertEqual(exc.lineno, 3) ++ self.assertIn('Exceeds the limit ', str(exc)) ++ self.assertIn(' Consider hexadecimal ', str(exc)) ++ + def test_unary_minus(self): + # Verify treatment of unary minus on negative numbers SF bug #660455 + if sys.maxsize == 2147483647: +diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py +index b6173a5..9d77cc3 100644 +--- a/Lib/test/test_decimal.py ++++ b/Lib/test/test_decimal.py +@@ -2474,6 +2474,15 @@ class CUsabilityTest(UsabilityTest): + class PyUsabilityTest(UsabilityTest): + decimal = P + ++ def setUp(self): ++ super().setUp() ++ self._previous_int_limit = sys.get_int_max_str_digits() ++ sys.set_int_max_str_digits(7000) ++ ++ def tearDown(self): ++ sys.set_int_max_str_digits(self._previous_int_limit) ++ super().tearDown() ++ + class PythonAPItests(unittest.TestCase): + + def test_abc(self): +@@ -4533,6 +4542,15 @@ class CCoverage(Coverage): + class PyCoverage(Coverage): + decimal = P + ++ def setUp(self): ++ super().setUp() ++ self._previous_int_limit = sys.get_int_max_str_digits() ++ sys.set_int_max_str_digits(7000) ++ ++ def tearDown(self): ++ sys.set_int_max_str_digits(self._previous_int_limit) ++ super().tearDown() ++ + class PyFunctionality(unittest.TestCase): + """Extra functionality in decimal.py""" + +diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py +index d6be64e..2f678ed 100644 +--- a/Lib/test/test_int.py ++++ b/Lib/test/test_int.py +@@ -568,5 +568,119 @@ class IntTestCases(unittest.TestCase): + self.assertEqual(int('1_2_3_4_5_6_7', 32), 1144132807) + + ++class IntStrDigitLimitsTests(unittest.TestCase): ++ ++ int_class = int # Override this in subclasses to reuse the suite. ++ ++ def setUp(self): ++ super().setUp() ++ self._previous_limit = sys.get_int_max_str_digits() ++ sys.set_int_max_str_digits(2048) ++ ++ def tearDown(self): ++ sys.set_int_max_str_digits(self._previous_limit) ++ super().tearDown() ++ ++ def test_disabled_limit(self): ++ self.assertGreater(sys.get_int_max_str_digits(), 0) ++ self.assertLess(sys.get_int_max_str_digits(), 20_000) ++ with support.adjust_int_max_str_digits(0): ++ self.assertEqual(sys.get_int_max_str_digits(), 0) ++ i = self.int_class('1' * 20_000) ++ str(i) ++ self.assertGreater(sys.get_int_max_str_digits(), 0) ++ ++ def test_max_str_digits_edge_cases(self): ++ """Ignore the +/- sign and space padding.""" ++ int_class = self.int_class ++ maxdigits = sys.get_int_max_str_digits() ++ ++ int_class('1' * maxdigits) ++ int_class(' ' + '1' * maxdigits) ++ int_class('1' * maxdigits + ' ') ++ int_class('+' + '1' * maxdigits) ++ int_class('-' + '1' * maxdigits) ++ self.assertEqual(len(str(10 ** (maxdigits - 1))), maxdigits) ++ ++ def check(self, i, base=None): ++ with self.assertRaises(ValueError): ++ if base is None: ++ self.int_class(i) ++ else: ++ self.int_class(i, base) ++ ++ def test_max_str_digits(self): ++ maxdigits = sys.get_int_max_str_digits() ++ ++ self.check('1' * (maxdigits + 1)) ++ self.check(' ' + '1' * (maxdigits + 1)) ++ self.check('1' * (maxdigits + 1) + ' ') ++ self.check('+' + '1' * (maxdigits + 1)) ++ self.check('-' + '1' * (maxdigits + 1)) ++ self.check('1' * (maxdigits + 1)) ++ ++ i = 10 ** maxdigits ++ with self.assertRaises(ValueError): ++ str(i) ++ ++ def test_power_of_two_bases_unlimited(self): ++ """The limit does not apply to power of 2 bases.""" ++ maxdigits = sys.get_int_max_str_digits() ++ ++ for base in (2, 4, 8, 16, 32): ++ with self.subTest(base=base): ++ self.int_class('1' * (maxdigits + 1), base) ++ assert maxdigits < 100_000 ++ self.int_class('1' * 100_000, base) ++ ++ def test_underscores_ignored(self): ++ maxdigits = sys.get_int_max_str_digits() ++ ++ triples = maxdigits // 3 ++ s = '111' * triples ++ s_ = '1_11' * triples ++ self.int_class(s) # succeeds ++ self.int_class(s_) # succeeds ++ self.check(f'{s}111') ++ self.check(f'{s_}_111') ++ ++ def test_sign_not_counted(self): ++ int_class = self.int_class ++ max_digits = sys.get_int_max_str_digits() ++ s = '5' * max_digits ++ i = int_class(s) ++ pos_i = int_class(f'+{s}') ++ assert i == pos_i ++ neg_i = int_class(f'-{s}') ++ assert -pos_i == neg_i ++ str(pos_i) ++ str(neg_i) ++ ++ def _other_base_helper(self, base): ++ int_class = self.int_class ++ max_digits = sys.get_int_max_str_digits() ++ s = '2' * max_digits ++ i = int_class(s, base) ++ if base > 10: ++ with self.assertRaises(ValueError): ++ str(i) ++ elif base < 10: ++ str(i) ++ with self.assertRaises(ValueError) as err: ++ int_class(f'{s}1', base) ++ ++ def test_int_from_other_bases(self): ++ base = 3 ++ with self.subTest(base=base): ++ self._other_base_helper(base) ++ base = 36 ++ with self.subTest(base=base): ++ self._other_base_helper(base) ++ ++ ++class IntSubclassStrDigitLimitsTests(IntStrDigitLimitsTests): ++ int_class = IntSubclass ++ ++ + if __name__ == "__main__": + unittest.main() +diff --git a/Lib/test/test_json/test_decode.py b/Lib/test/test_json/test_decode.py +index fdb9e62..124045b 100644 +--- a/Lib/test/test_json/test_decode.py ++++ b/Lib/test/test_json/test_decode.py +@@ -2,6 +2,7 @@ import decimal + from io import StringIO + from collections import OrderedDict + from test.test_json import PyTest, CTest ++from test import support + + + class TestDecode: +@@ -95,5 +96,13 @@ class TestDecode: + d = self.json.JSONDecoder() + self.assertRaises(ValueError, d.raw_decode, 'a'*42, -50000) + ++ def test_limit_int(self): ++ maxdigits = 5000 ++ with support.adjust_int_max_str_digits(maxdigits): ++ self.loads('1' * maxdigits) ++ with self.assertRaises(ValueError): ++ self.loads('1' * (maxdigits + 1)) ++ ++ + class TestPyDecode(TestDecode, PyTest): pass + class TestCDecode(TestDecode, CTest): pass +diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py +index 35da72c..bdac8b6 100644 +--- a/Lib/test/test_sys.py ++++ b/Lib/test/test_sys.py +@@ -483,11 +483,17 @@ class SysModuleTest(unittest.TestCase): + self.assertIsInstance(sys.executable, str) + self.assertEqual(len(sys.float_info), 11) + self.assertEqual(sys.float_info.radix, 2) +- self.assertEqual(len(sys.int_info), 2) ++ self.assertEqual(len(sys.int_info), 4) + self.assertTrue(sys.int_info.bits_per_digit % 5 == 0) + self.assertTrue(sys.int_info.sizeof_digit >= 1) ++ self.assertGreaterEqual(sys.int_info.default_max_str_digits, 500) ++ self.assertGreaterEqual(sys.int_info.str_digits_check_threshold, 100) ++ self.assertGreater(sys.int_info.default_max_str_digits, ++ sys.int_info.str_digits_check_threshold) + self.assertEqual(type(sys.int_info.bits_per_digit), int) + self.assertEqual(type(sys.int_info.sizeof_digit), int) ++ self.assertIsInstance(sys.int_info.default_max_str_digits, int) ++ self.assertIsInstance(sys.int_info.str_digits_check_threshold, int) + self.assertIsInstance(sys.hexversion, int) + + self.assertEqual(len(sys.hash_info), 9) +@@ -592,7 +598,7 @@ class SysModuleTest(unittest.TestCase): + "dont_write_bytecode", "no_user_site", "no_site", + "ignore_environment", "verbose", "bytes_warning", "quiet", + "hash_randomization", "isolated", "dev_mode", "utf8_mode", +- "warn_default_encoding") ++ "warn_default_encoding", "int_max_str_digits") + for attr in attrs: + self.assertTrue(hasattr(sys.flags, attr), attr) + attr_type = bool if attr == "dev_mode" else int +diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py +index 1f06f5f..e38bffe 100644 +--- a/Lib/test/test_xmlrpc.py ++++ b/Lib/test/test_xmlrpc.py +@@ -287,6 +287,16 @@ class XMLRPCTestCase(unittest.TestCase): + check('9876543210.0123456789', + decimal.Decimal('9876543210.0123456789')) + ++ def test_limit_int(self): ++ check = self.check_loads ++ maxdigits = 5000 ++ with support.adjust_int_max_str_digits(maxdigits): ++ s = '1' * (maxdigits + 1) ++ with self.assertRaises(ValueError): ++ check(f'{s}', None) ++ with self.assertRaises(ValueError): ++ check(f'{s}', None) ++ + def test_get_host_info(self): + # see bug #3613, this raised a TypeError + transp = xmlrpc.client.Transport() +diff --git a/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst b/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst +new file mode 100644 +index 0000000..ea3b85d +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst +@@ -0,0 +1,14 @@ ++Converting between :class:`int` and :class:`str` in bases other than 2 ++(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) now ++raises a :exc:`ValueError` if the number of digits in string form is above a ++limit to avoid potential denial of service attacks due to the algorithmic ++complexity. This is a mitigation for `CVE-2020-10735 ++`_. ++ ++This new limit can be configured or disabled by environment variable, command ++line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion length ++limitation ` documentation. The default limit is 4300 ++digits in string form. ++ ++Patch by Gregory P. Smith [Google] and Christian Heimes [Red Hat] with feedback ++from Victor Stinner, Thomas Wouters, Steve Dower, and Ned Deily. +diff --git a/Objects/longobject.c b/Objects/longobject.c +index 685bd56..780ea81 100644 +--- a/Objects/longobject.c ++++ b/Objects/longobject.c +@@ -4,6 +4,7 @@ + + #include "Python.h" + #include "pycore_bitutils.h" // _Py_popcount32() ++#include "pycore_initconfig.h" // _Py_global_config_int_max_str_digits + #include "pycore_interp.h" // _PY_NSMALLPOSINTS + #include "pycore_long.h" // __PyLong_GetSmallInt_internal() + #include "pycore_object.h" // _PyObject_InitVar() +@@ -35,6 +36,8 @@ _Py_IDENTIFIER(big); + #define IS_SMALL_INT(ival) (-NSMALLNEGINTS <= (ival) && (ival) < NSMALLPOSINTS) + #define IS_SMALL_UINT(ival) ((ival) < NSMALLPOSINTS) + ++#define _MAX_STR_DIGITS_ERROR_FMT "Exceeds the limit (%d) for integer string conversion: value has %zd digits" ++ + static PyObject * + get_small_int(sdigit ival) + { +@@ -1660,6 +1663,17 @@ long_to_decimal_string_internal(PyObject *aa, + tenpow *= 10; + strlen++; + } ++ if (strlen > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) { ++ PyInterpreterState *interp = _PyInterpreterState_GET(); ++ int max_str_digits = interp->int_max_str_digits; ++ Py_ssize_t strlen_nosign = strlen - negative; ++ if ((max_str_digits > 0) && (strlen_nosign > max_str_digits)) { ++ Py_DECREF(scratch); ++ PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT, ++ max_str_digits, strlen_nosign); ++ return -1; ++ } ++ } + if (writer) { + if (_PyUnicodeWriter_Prepare(writer, strlen, '9') == -1) { + Py_DECREF(scratch); +@@ -2173,6 +2187,7 @@ PyLong_FromString(const char *str, char **pend, int base) + + start = str; + if ((base & (base - 1)) == 0) { ++ /* binary bases are not limited by int_max_str_digits */ + int res = long_from_binary_base(&str, base, &z); + if (res < 0) { + /* Syntax error. */ +@@ -2324,6 +2339,17 @@ digit beyond the first. + goto onError; + } + ++ /* Limit the size to avoid excessive computation attacks. */ ++ if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) { ++ PyInterpreterState *interp = _PyInterpreterState_GET(); ++ int max_str_digits = interp->int_max_str_digits; ++ if ((max_str_digits > 0) && (digits > max_str_digits)) { ++ PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT, ++ max_str_digits, digits); ++ return NULL; ++ } ++ } ++ + /* Create an int object that can contain the largest possible + * integer with this base and length. Note that there's no + * need to initialize z->ob_digit -- no slot is read up before +@@ -4944,6 +4970,7 @@ long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase) + } + return PyLong_FromLong(0L); + } ++ /* default base and limit, forward to standard implementation */ + if (obase == NULL) + return PyNumber_Long(x); + +@@ -5674,6 +5701,8 @@ internal representation of integers. The attributes are read only."); + static PyStructSequence_Field int_info_fields[] = { + {"bits_per_digit", "size of a digit in bits"}, + {"sizeof_digit", "size in bytes of the C type used to represent a digit"}, ++ {"default_max_str_digits", "maximum string conversion digits limitation"}, ++ {"str_digits_check_threshold", "minimum positive value for int_max_str_digits"}, + {NULL, NULL} + }; + +@@ -5681,7 +5710,7 @@ static PyStructSequence_Desc int_info_desc = { + "sys.int_info", /* name */ + int_info__doc__, /* doc */ + int_info_fields, /* fields */ +- 2 /* number of fields */ ++ 4 /* number of fields */ + }; + + PyObject * +@@ -5696,6 +5725,17 @@ PyLong_GetInfo(void) + PyLong_FromLong(PyLong_SHIFT)); + PyStructSequence_SET_ITEM(int_info, field++, + PyLong_FromLong(sizeof(digit))); ++ /* ++ * The following two fields were added after investigating uses of ++ * sys.int_info in the wild: Exceedingly rarely used. The ONLY use found was ++ * numba using sys.int_info.bits_per_digit as attribute access rather than ++ * sequence unpacking. Cython and sympy also refer to sys.int_info but only ++ * as info for debugging. No concern about adding these in a backport. ++ */ ++ PyStructSequence_SET_ITEM(int_info, field++, ++ PyLong_FromLong(_PY_LONG_DEFAULT_MAX_STR_DIGITS)); ++ PyStructSequence_SET_ITEM(int_info, field++, ++ PyLong_FromLong(_PY_LONG_MAX_STR_DIGITS_THRESHOLD)); + if (PyErr_Occurred()) { + Py_CLEAR(int_info); + return NULL; +@@ -5720,6 +5760,10 @@ _PyLong_Init(PyInterpreterState *interp) + + interp->small_ints[i] = v; + } ++ interp->int_max_str_digits = _Py_global_config_int_max_str_digits; ++ if (interp->int_max_str_digits == -1) { ++ interp->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS; ++ } + return 0; + } + +diff --git a/Parser/pegen.c b/Parser/pegen.c +index e507415..71940be 100644 +--- a/Parser/pegen.c ++++ b/Parser/pegen.c +@@ -1,5 +1,6 @@ + #include + #include "pycore_ast.h" // _PyAST_Validate(), ++#include "pycore_pystate.h" // _PyThreadState_GET() + #include + #include "tokenizer.h" + +@@ -1118,6 +1119,28 @@ _PyPegen_number_token(Parser *p) + + if (c == NULL) { + p->error_indicator = 1; ++ PyThreadState *tstate = _PyThreadState_GET(); ++ // The only way a ValueError should happen in _this_ code is via ++ // PyLong_FromString hitting a length limit. ++ if (tstate->curexc_type == PyExc_ValueError && ++ tstate->curexc_value != NULL) { ++ PyObject *type, *value, *tb; ++ // This acts as PyErr_Clear() as we're replacing curexc. ++ PyErr_Fetch(&type, &value, &tb); ++ Py_XDECREF(tb); ++ Py_DECREF(type); ++ /* Intentionally omitting columns to avoid a wall of 1000s of '^'s ++ * on the error message. Nobody is going to overlook their huge ++ * numeric literal once given the line. */ ++ RAISE_ERROR_KNOWN_LOCATION( ++ p, PyExc_SyntaxError, ++ t->lineno, -1 /* col_offset */, ++ t->end_lineno, -1 /* end_col_offset */, ++ "%S - Consider hexadecimal for huge integer literals " ++ "to avoid decimal conversion limits.", ++ value); ++ Py_DECREF(value); ++ } + return NULL; + } + +diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h +index 04c8481..2a6ad89 100644 +--- a/Python/clinic/sysmodule.c.h ++++ b/Python/clinic/sysmodule.c.h +@@ -647,6 +647,59 @@ exit: + + #endif /* defined(USE_MALLOPT) */ + ++PyDoc_STRVAR(sys_get_int_max_str_digits__doc__, ++"get_int_max_str_digits($module, /)\n" ++"--\n" ++"\n" ++"Set the maximum string digits limit for non-binary int<->str conversions."); ++ ++#define SYS_GET_INT_MAX_STR_DIGITS_METHODDEF \ ++ {"get_int_max_str_digits", (PyCFunction)sys_get_int_max_str_digits, METH_NOARGS, sys_get_int_max_str_digits__doc__}, ++ ++static PyObject * ++sys_get_int_max_str_digits_impl(PyObject *module); ++ ++static PyObject * ++sys_get_int_max_str_digits(PyObject *module, PyObject *Py_UNUSED(ignored)) ++{ ++ return sys_get_int_max_str_digits_impl(module); ++} ++ ++PyDoc_STRVAR(sys_set_int_max_str_digits__doc__, ++"set_int_max_str_digits($module, /, maxdigits)\n" ++"--\n" ++"\n" ++"Set the maximum string digits limit for non-binary int<->str conversions."); ++ ++#define SYS_SET_INT_MAX_STR_DIGITS_METHODDEF \ ++ {"set_int_max_str_digits", (PyCFunction)(void(*)(void))sys_set_int_max_str_digits, METH_FASTCALL|METH_KEYWORDS, sys_set_int_max_str_digits__doc__}, ++ ++static PyObject * ++sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits); ++ ++static PyObject * ++sys_set_int_max_str_digits(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) ++{ ++ PyObject *return_value = NULL; ++ static const char * const _keywords[] = {"maxdigits", NULL}; ++ static _PyArg_Parser _parser = {NULL, _keywords, "set_int_max_str_digits", 0}; ++ PyObject *argsbuf[1]; ++ int maxdigits; ++ ++ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); ++ if (!args) { ++ goto exit; ++ } ++ maxdigits = _PyLong_AsInt(args[0]); ++ if (maxdigits == -1 && PyErr_Occurred()) { ++ goto exit; ++ } ++ return_value = sys_set_int_max_str_digits_impl(module, maxdigits); ++ ++exit: ++ return return_value; ++} ++ + PyDoc_STRVAR(sys_getrefcount__doc__, + "getrefcount($module, object, /)\n" + "--\n" +@@ -983,4 +1036,4 @@ sys__deactivate_opcache(PyObject *module, PyObject *Py_UNUSED(ignored)) + #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF + #define SYS_GETANDROIDAPILEVEL_METHODDEF + #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ +-/*[clinic end generated code: output=68c62b9ca317a0c8 input=a9049054013a1b77]*/ ++/*[clinic end generated code: output=6230a1e3a4415744 input=a9049054013a1b77]*/ +diff --git a/Python/initconfig.c b/Python/initconfig.c +index b298611..87b9210 100644 +--- a/Python/initconfig.c ++++ b/Python/initconfig.c +@@ -3,6 +3,7 @@ + #include "pycore_getopt.h" // _PyOS_GetOpt() + #include "pycore_initconfig.h" // _PyStatus_OK() + #include "pycore_interp.h" // _PyInterpreterState.runtime ++#include "pycore_long.h" // _PY_LONG_MAX_STR_DIGITS_THRESHOLD + #include "pycore_pathconfig.h" // _Py_path_config + #include "pycore_pyerrors.h" // _PyErr_Fetch() + #include "pycore_pylifecycle.h" // _Py_PreInitializeFromConfig() +@@ -95,6 +96,9 @@ static const char usage_3[] = "\ + -X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the\n\ + given directory instead of to the code tree\n\ + -X warn_default_encoding: enable opt-in EncodingWarning for 'encoding=None'\n\ ++ -X int_max_str_digits=number: limit the size of int<->str conversions.\n\ ++ This helps avoid denial of service attacks when parsing untrusted data.\n\ ++ The default is sys.int_info.default_max_str_digits. 0 disables.\n\ + \n\ + --check-hash-based-pycs always|default|never:\n\ + control how Python invalidates hash-based .pyc files\n\ +@@ -121,6 +125,10 @@ static const char usage_6[] = + " to seed the hashes of str and bytes objects. It can also be set to an\n" + " integer in the range [0,4294967295] to get hash values with a\n" + " predictable seed.\n" ++"PYTHONINTMAXSTRDIGITS: limits the maximum digit characters in an int value\n" ++" when converting from a string and when converting an int back to a str.\n" ++" A value of 0 disables the limit. Conversions to or from bases 2, 4, 8,\n" ++" 16, and 32 are never limited.\n" + "PYTHONMALLOC: set the Python memory allocators and/or install debug hooks\n" + " on Python memory allocators. Use PYTHONMALLOC=debug to install debug\n" + " hooks.\n" +@@ -722,6 +730,10 @@ _PyConfig_InitCompatConfig(PyConfig *config) + #endif + } + ++/* Excluded from public struct PyConfig for backporting reasons. */ ++/* default to unconfigured, _PyLong_InitTypes() does the rest */ ++int _Py_global_config_int_max_str_digits = -1; ++ + + static void + config_init_defaults(PyConfig *config) +@@ -1758,6 +1770,48 @@ config_init_tracemalloc(PyConfig *config) + return _PyStatus_OK(); + } + ++static PyStatus ++config_init_int_max_str_digits(PyConfig *config) ++{ ++ int maxdigits; ++ int valid = 0; ++ ++ const char *env = config_get_env(config, "PYTHONINTMAXSTRDIGITS"); ++ if (env) { ++ if (!_Py_str_to_int(env, &maxdigits)) { ++ valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)); ++ } ++ if (!valid) { ++#define STRINGIFY(VAL) _STRINGIFY(VAL) ++#define _STRINGIFY(VAL) #VAL ++ return _PyStatus_ERR( ++ "PYTHONINTMAXSTRDIGITS: invalid limit; must be >= " ++ STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD) ++ " or 0 for unlimited."); ++ } ++ _Py_global_config_int_max_str_digits = maxdigits; ++ } ++ ++ const wchar_t *xoption = config_get_xoption(config, L"int_max_str_digits"); ++ if (xoption) { ++ const wchar_t *sep = wcschr(xoption, L'='); ++ if (sep) { ++ if (!config_wstr_to_int(sep + 1, &maxdigits)) { ++ valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)); ++ } ++ } ++ if (!valid) { ++ return _PyStatus_ERR( ++ "-X int_max_str_digits: invalid limit; must be >= " ++ STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD) ++ " or 0 for unlimited."); ++#undef _STRINGIFY ++#undef STRINGIFY ++ } ++ _Py_global_config_int_max_str_digits = maxdigits; ++ } ++ return _PyStatus_OK(); ++} + + static PyStatus + config_init_pycache_prefix(PyConfig *config) +@@ -1809,6 +1863,12 @@ config_read_complex_options(PyConfig *config) + return status; + } + } ++ if (_Py_global_config_int_max_str_digits < 0) { ++ status = config_init_int_max_str_digits(config); ++ if (_PyStatus_EXCEPTION(status)) { ++ return status; ++ } ++ } + + if (config->pycache_prefix == NULL) { + status = config_init_pycache_prefix(config); +diff --git a/Python/sysmodule.c b/Python/sysmodule.c +index ac49f78..1d5a06a 100644 +--- a/Python/sysmodule.c ++++ b/Python/sysmodule.c +@@ -17,6 +17,7 @@ Data members: + #include "Python.h" + #include "pycore_ceval.h" // _Py_RecursionLimitLowerWaterMark() + #include "pycore_initconfig.h" // _PyStatus_EXCEPTION() ++#include "pycore_long.h" // _PY_LONG_MAX_STR_DIGITS_THRESHOLD + #include "pycore_object.h" // _PyObject_IS_GC() + #include "pycore_pathconfig.h" // _PyPathConfig_ComputeSysPath0() + #include "pycore_pyerrors.h" // _PyErr_Fetch() +@@ -1652,6 +1653,45 @@ sys_mdebug_impl(PyObject *module, int flag) + } + #endif /* USE_MALLOPT */ + ++ ++/*[clinic input] ++sys.get_int_max_str_digits ++ ++Set the maximum string digits limit for non-binary int<->str conversions. ++[clinic start generated code]*/ ++ ++static PyObject * ++sys_get_int_max_str_digits_impl(PyObject *module) ++/*[clinic end generated code: output=0042f5e8ae0e8631 input=8dab13e2023e60d5]*/ ++{ ++ PyInterpreterState *interp = _PyInterpreterState_GET(); ++ return PyLong_FromSsize_t(interp->int_max_str_digits); ++} ++ ++/*[clinic input] ++sys.set_int_max_str_digits ++ ++ maxdigits: int ++ ++Set the maximum string digits limit for non-binary int<->str conversions. ++[clinic start generated code]*/ ++ ++static PyObject * ++sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits) ++/*[clinic end generated code: output=734d4c2511f2a56d input=d7e3f325db6910c5]*/ ++{ ++ PyThreadState *tstate = _PyThreadState_GET(); ++ if ((!maxdigits) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)) { ++ tstate->interp->int_max_str_digits = maxdigits; ++ Py_RETURN_NONE; ++ } else { ++ PyErr_Format( ++ PyExc_ValueError, "maxdigits must be 0 or larger than %d", ++ _PY_LONG_MAX_STR_DIGITS_THRESHOLD); ++ return NULL; ++ } ++} ++ + size_t + _PySys_GetSizeOf(PyObject *o) + { +@@ -2027,6 +2067,8 @@ static PyMethodDef sys_methods[] = { + SYS_GETANDROIDAPILEVEL_METHODDEF + SYS_UNRAISABLEHOOK_METHODDEF + SYS__DEACTIVATE_OPCACHE_METHODDEF ++ SYS_GET_INT_MAX_STR_DIGITS_METHODDEF ++ SYS_SET_INT_MAX_STR_DIGITS_METHODDEF + {NULL, NULL} /* sentinel */ + }; + +@@ -2516,6 +2558,7 @@ static PyStructSequence_Field flags_fields[] = { + {"dev_mode", "-X dev"}, + {"utf8_mode", "-X utf8"}, + {"warn_default_encoding", "-X warn_default_encoding"}, ++ {"int_max_str_digits", "-X int_max_str_digits"}, + {0} + }; + +@@ -2523,7 +2566,7 @@ static PyStructSequence_Desc flags_desc = { + "sys.flags", /* name */ + flags__doc__, /* doc */ + flags_fields, /* fields */ +- 16 ++ 17 + }; + + static int +@@ -2563,6 +2606,7 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags) + SetFlagObj(PyBool_FromLong(config->dev_mode)); + SetFlag(preconfig->utf8_mode); + SetFlag(config->warn_default_encoding); ++ SetFlag(_Py_global_config_int_max_str_digits); + #undef SetFlagObj + #undef SetFlag + return 0; +-- +2.36.1 + diff --git a/backport-0002-CVE-2020-10735.patch b/backport-0002-CVE-2020-10735.patch new file mode 100644 index 0000000000000000000000000000000000000000..89716a11518b158a59daac06af1a3516b5901e5e --- /dev/null +++ b/backport-0002-CVE-2020-10735.patch @@ -0,0 +1,36 @@ +From 3a56a938f375f17f39baeb4b1529793dd0eb79b8 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Sun, 4 Sep 2022 00:12:34 -0700 +Subject: [PATCH] gh-95778: remove unneeded doc note on float.as_integer_ratio + (GH-96553) + +Per mdickinson@'s comment on the main branch PR. +(cherry picked from commit 69bb83c2bf254f92491d527ccec1ff41897add56) + +Co-authored-by: Gregory P. Smith +--- + Doc/library/stdtypes.rst | 7 ------- + 1 file changed, 7 deletions(-) + +diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst +index bc365bb..c82a6ad 100644 +--- a/Doc/library/stdtypes.rst ++++ b/Doc/library/stdtypes.rst +@@ -584,13 +584,6 @@ class`. float also has the following additional methods. + :exc:`OverflowError` on infinities and a :exc:`ValueError` on + NaNs. + +- .. note:: +- +- The values returned by ``as_integer_ratio()`` can be huge. Attempts +- to render such integers into decimal strings may bump into the +- :ref:`integer string conversion length limitation +- `. +- + .. method:: float.is_integer() + + Return ``True`` if the float instance is finite with integral +-- +1.8.3.1 + diff --git a/backport-0003-CVE-2020-10735.patch b/backport-0003-CVE-2020-10735.patch new file mode 100644 index 0000000000000000000000000000000000000000..1e0d788557c8b94fbba54e88ecf7e2dc9e0ded6a --- /dev/null +++ b/backport-0003-CVE-2020-10735.patch @@ -0,0 +1,226 @@ +From eace09e63ed7978dbdfeb1ae537fac505e6b5b0e Mon Sep 17 00:00:00 2001 +From: "Gregory P. Smith" +Date: Sun, 4 Sep 2022 09:54:56 -0700 +Subject: [PATCH] [3.10] gh-95778: Correctly pre-check for int-to-str + conversion (GH-96537) (#96563) + +Converting a large enough `int` to a decimal string raises `ValueError` as expected. However, the raise comes _after_ the quadratic-time base-conversion algorithm has run to completion. For effective DOS prevention, we need some kind of check before entering the quadratic-time loop. Oops! =) + +The quick fix: essentially we catch _most_ values that exceed the threshold up front. Those that slip through will still be on the small side (read: sufficiently fast), and will get caught by the existing check so that the limit remains exact. + +The justification for the current check. The C code check is: +```c +max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10 +``` + +In GitHub markdown math-speak, writing $M$ for `max_str_digits`, $L$ for `PyLong_SHIFT` and $s$ for `size_a`, that check is: +$$\left\lfloor\frac{M}{3L}\right\rfloor \le \left\lfloor\frac{s - 11}{10}\right\rfloor$$ + +From this it follows that +$$\frac{M}{3L} < \frac{s-1}{10}$$ +hence that +$$\frac{L(s-1)}{M} > \frac{10}{3} > \log_2(10).$$ +So +$$2^{L(s-1)} > 10^M.$$ +But our input integer $a$ satisfies $|a| \ge 2^{L(s-1)}$, so $|a|$ is larger than $10^M$. This shows that we don't accidentally capture anything _below_ the intended limit in the check. + + +* Issue: gh-95778 + + +Co-authored-by: Gregory P. Smith [Google LLC] +(cherry picked from commit b126196838bbaf5f4d35120e0e6bcde435b0b480) + +Co-authored-by: Mark Dickinson +--- + Include/internal/pycore_long.h | 4 +- + Lib/test/test_int.py | 82 ++++++++++++++++++++++ + ...2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst | 2 +- + Objects/longobject.c | 26 +++++-- + 4 files changed, 107 insertions(+), 7 deletions(-) + +diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h +index ee9a592..90069f8 100644 +--- a/Include/internal/pycore_long.h ++++ b/Include/internal/pycore_long.h +@@ -18,9 +18,9 @@ + * everyone's existing deployed numpy test suite passes before + * https://github.com/numpy/numpy/issues/22098 is widely available. + * +- * $ python -m timeit -s 's = * "1"*4300' 'int(s)' ++ * $ python -m timeit -s 's = "1"*4300' 'int(s)' + * 2000 loops, best of 5: 125 usec per loop +- * $ python -m timeit -s 's = * "1"*4300; v = int(s)' 'str(v)' ++ * $ python -m timeit -s 's = "1"*4300; v = int(s)' 'str(v)' + * 1000 loops, best of 5: 311 usec per loop + * (zen2 cloud VM) + * +diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py +index 2f678ed..a578ca8 100644 +--- a/Lib/test/test_int.py ++++ b/Lib/test/test_int.py +@@ -1,4 +1,5 @@ + import sys ++import time + + import unittest + from test import support +@@ -623,6 +624,87 @@ def test_max_str_digits(self): + with self.assertRaises(ValueError): + str(i) + ++ def test_denial_of_service_prevented_int_to_str(self): ++ """Regression test: ensure we fail before performing O(N**2) work.""" ++ maxdigits = sys.get_int_max_str_digits() ++ assert maxdigits < 50_000, maxdigits # A test prerequisite. ++ get_time = time.process_time ++ if get_time() <= 0: # some platforms like WASM lack process_time() ++ get_time = time.monotonic ++ ++ huge_int = int(f'0x{"c"*65_000}', base=16) # 78268 decimal digits. ++ digits = 78_268 ++ with support.adjust_int_max_str_digits(digits): ++ start = get_time() ++ huge_decimal = str(huge_int) ++ seconds_to_convert = get_time() - start ++ self.assertEqual(len(huge_decimal), digits) ++ # Ensuring that we chose a slow enough conversion to measure. ++ # It takes 0.1 seconds on a Zen based cloud VM in an opt build. ++ if seconds_to_convert < 0.005: ++ raise unittest.SkipTest('"slow" conversion took only ' ++ f'{seconds_to_convert} seconds.') ++ ++ # We test with the limit almost at the size needed to check performance. ++ # The performant limit check is slightly fuzzy, give it a some room. ++ with support.adjust_int_max_str_digits(int(.995 * digits)): ++ with self.assertRaises(ValueError) as err: ++ start = get_time() ++ str(huge_int) ++ seconds_to_fail_huge = get_time() - start ++ self.assertIn('conversion', str(err.exception)) ++ self.assertLess(seconds_to_fail_huge, seconds_to_convert/8) ++ ++ # Now we test that a conversion that would take 30x as long also fails ++ # in a similarly fast fashion. ++ extra_huge_int = int(f'0x{"c"*500_000}', base=16) # 602060 digits. ++ with self.assertRaises(ValueError) as err: ++ start = get_time() ++ # If not limited, 8 seconds said Zen based cloud VM. ++ str(extra_huge_int) ++ seconds_to_fail_extra_huge = get_time() - start ++ self.assertIn('conversion', str(err.exception)) ++ self.assertLess(seconds_to_fail_extra_huge, seconds_to_convert/8) ++ ++ def test_denial_of_service_prevented_str_to_int(self): ++ """Regression test: ensure we fail before performing O(N**2) work.""" ++ maxdigits = sys.get_int_max_str_digits() ++ assert maxdigits < 100_000, maxdigits # A test prerequisite. ++ get_time = time.process_time ++ if get_time() <= 0: # some platforms like WASM lack process_time() ++ get_time = time.monotonic ++ ++ digits = 133700 ++ huge = '8'*digits ++ with support.adjust_int_max_str_digits(digits): ++ start = get_time() ++ int(huge) ++ seconds_to_convert = get_time() - start ++ # Ensuring that we chose a slow enough conversion to measure. ++ # It takes 0.1 seconds on a Zen based cloud VM in an opt build. ++ if seconds_to_convert < 0.005: ++ raise unittest.SkipTest('"slow" conversion took only ' ++ f'{seconds_to_convert} seconds.') ++ ++ with support.adjust_int_max_str_digits(digits - 1): ++ with self.assertRaises(ValueError) as err: ++ start = get_time() ++ int(huge) ++ seconds_to_fail_huge = get_time() - start ++ self.assertIn('conversion', str(err.exception)) ++ self.assertLess(seconds_to_fail_huge, seconds_to_convert/8) ++ ++ # Now we test that a conversion that would take 30x as long also fails ++ # in a similarly fast fashion. ++ extra_huge = '7'*1_200_000 ++ with self.assertRaises(ValueError) as err: ++ start = get_time() ++ # If not limited, 8 seconds in the Zen based cloud VM. ++ int(extra_huge) ++ seconds_to_fail_extra_huge = get_time() - start ++ self.assertIn('conversion', str(err.exception)) ++ self.assertLess(seconds_to_fail_extra_huge, seconds_to_convert/8) ++ + def test_power_of_two_bases_unlimited(self): + """The limit does not apply to power of 2 bases.""" + maxdigits = sys.get_int_max_str_digits() +diff --git a/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst b/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst +index ea3b85d..8eb8a34 100644 +--- a/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst ++++ b/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst +@@ -11,4 +11,4 @@ limitation ` documentation. The default limit is 4300 + digits in string form. + + Patch by Gregory P. Smith [Google] and Christian Heimes [Red Hat] with feedback +-from Victor Stinner, Thomas Wouters, Steve Dower, and Ned Deily. ++from Victor Stinner, Thomas Wouters, Steve Dower, Ned Deily, and Mark Dickinson. +diff --git a/Objects/longobject.c b/Objects/longobject.c +index 780ea81..aea5edc 100644 +--- a/Objects/longobject.c ++++ b/Objects/longobject.c +@@ -36,7 +36,8 @@ class int "PyObject *" "&PyLong_Type" + #define IS_SMALL_INT(ival) (-NSMALLNEGINTS <= (ival) && (ival) < NSMALLPOSINTS) + #define IS_SMALL_UINT(ival) ((ival) < NSMALLPOSINTS) + +-#define _MAX_STR_DIGITS_ERROR_FMT "Exceeds the limit (%d) for integer string conversion: value has %zd digits" ++#define _MAX_STR_DIGITS_ERROR_FMT_TO_INT "Exceeds the limit (%d) for integer string conversion: value has %zd digits" ++#define _MAX_STR_DIGITS_ERROR_FMT_TO_STR "Exceeds the limit (%d) for integer string conversion" + + static PyObject * + get_small_int(sdigit ival) +@@ -1604,6 +1605,23 @@ class int "PyObject *" "&PyLong_Type" + size_a = Py_ABS(Py_SIZE(a)); + negative = Py_SIZE(a) < 0; + ++ /* quick and dirty pre-check for overflowing the decimal digit limit, ++ based on the inequality 10/3 >= log2(10) ++ ++ explanation in https://github.com/python/cpython/pull/96537 ++ */ ++ if (size_a >= 10 * _PY_LONG_MAX_STR_DIGITS_THRESHOLD ++ / (3 * PyLong_SHIFT) + 2) { ++ PyInterpreterState *interp = _PyInterpreterState_GET(); ++ int max_str_digits = interp->int_max_str_digits; ++ if ((max_str_digits > 0) && ++ (max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10)) { ++ PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_STR, ++ max_str_digits); ++ return -1; ++ } ++ } ++ + /* quick and dirty upper bound for the number of digits + required to express a in base _PyLong_DECIMAL_BASE: + +@@ -1669,8 +1687,8 @@ class int "PyObject *" "&PyLong_Type" + Py_ssize_t strlen_nosign = strlen - negative; + if ((max_str_digits > 0) && (strlen_nosign > max_str_digits)) { + Py_DECREF(scratch); +- PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT, +- max_str_digits, strlen_nosign); ++ PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_STR, ++ max_str_digits); + return -1; + } + } +@@ -2344,7 +2362,7 @@ that triggers it(!). Instead the code was tested by artificially allocating + PyInterpreterState *interp = _PyInterpreterState_GET(); + int max_str_digits = interp->int_max_str_digits; + if ((max_str_digits > 0) && (digits > max_str_digits)) { +- PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT, ++ PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_INT, + max_str_digits, digits); + return NULL; + } +-- +1.8.3.1 + diff --git a/backport-CVE-2015-20107.patch b/backport-CVE-2015-20107.patch new file mode 100644 index 0000000000000000000000000000000000000000..56235c6ff2ac0936ff474e331386e0814c7ab8e2 --- /dev/null +++ b/backport-CVE-2015-20107.patch @@ -0,0 +1,149 @@ +From b9509ba7a9c668b984dab876c7926fe1dc5aa0ba Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Fri, 3 Jun 2022 11:43:35 +0200 +Subject: [PATCH] gh-68966: Make mailcap refuse to match unsafe + filenames/types/params (GH-91993) + +--- + Doc/library/mailcap.rst | 12 +++++++++ + Lib/mailcap.py | 26 +++++++++++++++++-- + Lib/test/test_mailcap.py | 8 ++++-- + ...2-04-27-18-25-30.gh-issue-68966.gjS8zs.rst | 4 +++ + 4 files changed, 46 insertions(+), 4 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst + +diff --git a/Doc/library/mailcap.rst b/Doc/library/mailcap.rst +index 5490c8468d..bfaedb4609 100644 +--- a/Doc/library/mailcap.rst ++++ b/Doc/library/mailcap.rst +@@ -60,6 +60,18 @@ standard. However, mailcap files are supported on most Unix systems. + use) to determine whether or not the mailcap line applies. :func:`findmatch` + will automatically check such conditions and skip the entry if the check fails. + ++ .. versionchanged:: 3.11 ++ ++ To prevent security issues with shell metacharacters (symbols that have ++ special effects in a shell command line), ``findmatch`` will refuse ++ to inject ASCII characters other than alphanumerics and ``@+=:,./-_`` ++ into the returned command line. ++ ++ If a disallowed character appears in *filename*, ``findmatch`` will always ++ return ``(None, None)`` as if no entry was found. ++ If such a character appears elsewhere (a value in *plist* or in *MIMEtype*), ++ ``findmatch`` will ignore all mailcap entries which use that value. ++ A :mod:`warning ` will be raised in either case. + + .. function:: getcaps() + +diff --git a/Lib/mailcap.py b/Lib/mailcap.py +index 856b6a5547..7278ea7051 100644 +--- a/Lib/mailcap.py ++++ b/Lib/mailcap.py +@@ -2,6 +2,7 @@ + + import os + import warnings ++import re + + __all__ = ["getcaps","findmatch"] + +@@ -19,6 +20,11 @@ def lineno_sort_key(entry): + else: + return 1, 0 + ++_find_unsafe = re.compile(r'[^\xa1-\U0010FFFF\w@+=:,./-]').search ++ ++class UnsafeMailcapInput(Warning): ++ """Warning raised when refusing unsafe input""" ++ + + # Part 1: top-level interface. + +@@ -171,15 +177,22 @@ def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]): + entry to use. + + """ ++ if _find_unsafe(filename): ++ msg = "Refusing to use mailcap with filename %r. Use a safe temporary filename." % (filename,) ++ warnings.warn(msg, UnsafeMailcapInput) ++ return None, None + entries = lookup(caps, MIMEtype, key) + # XXX This code should somehow check for the needsterminal flag. + for e in entries: + if 'test' in e: + test = subst(e['test'], filename, plist) ++ if test is None: ++ continue + if test and os.system(test) != 0: + continue + command = subst(e[key], MIMEtype, filename, plist) +- return command, e ++ if command is not None: ++ return command, e + return None, None + + def lookup(caps, MIMEtype, key=None): +@@ -212,6 +225,10 @@ def subst(field, MIMEtype, filename, plist=[]): + elif c == 's': + res = res + filename + elif c == 't': ++ if _find_unsafe(MIMEtype): ++ msg = "Refusing to substitute MIME type %r into a shell command." % (MIMEtype,) ++ warnings.warn(msg, UnsafeMailcapInput) ++ return None + res = res + MIMEtype + elif c == '{': + start = i +@@ -219,7 +236,12 @@ def subst(field, MIMEtype, filename, plist=[]): + i = i+1 + name = field[start:i] + i = i+1 +- res = res + findparam(name, plist) ++ param = findparam(name, plist) ++ if _find_unsafe(param): ++ msg = "Refusing to substitute parameter %r (%s) into a shell command" % (param, name) ++ warnings.warn(msg, UnsafeMailcapInput) ++ return None ++ res = res + param + # XXX To do: + # %n == number of parts if type is multipart/* + # %F == list of alternating type and filename for parts +diff --git a/Lib/test/test_mailcap.py b/Lib/test/test_mailcap.py +index d3995b1472..8185f4a780 100644 +--- a/Lib/test/test_mailcap.py ++++ b/Lib/test/test_mailcap.py +@@ -128,7 +128,8 @@ def test_subst(self): + (["", "audio/*", "foo.txt"], ""), + (["echo foo", "audio/*", "foo.txt"], "echo foo"), + (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"), +- (["echo %t", "audio/*", "foo.txt"], "echo audio/*"), ++ (["echo %t", "audio/*", "foo.txt"], None), ++ (["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"), + (["echo \\%t", "audio/*", "foo.txt"], "echo %t"), + (["echo foo", "audio/*", "foo.txt", plist], "echo foo"), + (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3") +@@ -212,7 +213,10 @@ def test_findmatch(self): + ('"An audio fragment"', audio_basic_entry)), + ([c, "audio/*"], + {"filename": fname}, +- ("/usr/local/bin/showaudio audio/*", audio_entry)), ++ (None, None)), ++ ([c, "audio/wav"], ++ {"filename": fname}, ++ ("/usr/local/bin/showaudio audio/wav", audio_entry)), + ([c, "message/external-body"], + {"plist": plist}, + ("showexternal /dev/null default john python.org /tmp foo bar", message_entry)) +diff --git a/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst b/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst +new file mode 100644 +index 0000000000..da81a1f699 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst +@@ -0,0 +1,4 @@ ++The deprecated mailcap module now refuses to inject unsafe text (filenames, ++MIME types, parameters) into shell commands. Instead of using such text, it ++will warn and act as if a match was not found (or for test commands, as if ++the test failed). +-- +2.23.0 + diff --git a/backport-CVE-2021-28861.patch b/backport-CVE-2021-28861.patch new file mode 100644 index 0000000000000000000000000000000000000000..c683f28e33b4750263b074c71895fa3b81fb375d --- /dev/null +++ b/backport-CVE-2021-28861.patch @@ -0,0 +1,131 @@ +From 5715382d3a89ca118ce2e224d8c69550d21fe51b Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Tue, 21 Jun 2022 14:36:55 -0700 +Subject: [PATCH] gh-87389: Fix an open redirection vulnerability in + http.server. (GH-93879) + +Fix an open redirection vulnerability in the `http.server` module when +an URI path starts with `//` that could produce a 301 Location header +with a misleading target. Vulnerability discovered, and logic fix +proposed, by Hamza Avvan (@hamzaavvan). + +Test and comments authored by Gregory P. Smith [Google]. +(cherry picked from commit 4abab6b603dd38bec1168e9a37c40a48ec89508e) + +Co-authored-by: Gregory P. Smith +--- + Lib/http/server.py | 7 +++ + Lib/test/test_httpservers.py | 53 +++++++++++++++++++++- + .../2022-06-15-20-09-23.gh-issue-87389.QVaC3f.rst | 3 ++ + 3 files changed, 61 insertions(+), 2 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2022-06-15-20-09-23.gh-issue-87389.QVaC3f.rst + +diff --git a/Lib/http/server.py b/Lib/http/server.py +index e985dfd..78748c6 100644 +--- a/Lib/http/server.py ++++ b/Lib/http/server.py +@@ -332,6 +332,13 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler): + return False + self.command, self.path = command, path + ++ # gh-87389: The purpose of replacing '//' with '/' is to protect ++ # against open redirect attacks possibly triggered if the path starts ++ # with '//' because http clients treat //path as an absolute URI ++ # without scheme (similar to http://path) rather than a path. ++ if self.path.startswith('//'): ++ self.path = '/' + self.path.lstrip('/') # Reduce to a single / ++ + # Examine the headers and look for a Connection directive. + try: + self.headers = http.client.parse_headers(self.rfile, +diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py +index 1cc020f..8fdbab4 100644 +--- a/Lib/test/test_httpservers.py ++++ b/Lib/test/test_httpservers.py +@@ -333,7 +333,7 @@ class SimpleHTTPServerTestCase(BaseTestCase): + pass + + def setUp(self): +- BaseTestCase.setUp(self) ++ super().setUp() + self.cwd = os.getcwd() + basetempdir = tempfile.gettempdir() + os.chdir(basetempdir) +@@ -361,7 +361,7 @@ class SimpleHTTPServerTestCase(BaseTestCase): + except: + pass + finally: +- BaseTestCase.tearDown(self) ++ super().tearDown() + + def check_status_and_reason(self, response, status, data=None): + def close_conn(): +@@ -417,6 +417,55 @@ class SimpleHTTPServerTestCase(BaseTestCase): + self.check_status_and_reason(response, HTTPStatus.OK, + data=os_helper.TESTFN_UNDECODABLE) + ++ def test_get_dir_redirect_location_domain_injection_bug(self): ++ """Ensure //evil.co/..%2f../../X does not put //evil.co/ in Location. ++ ++ //netloc/ in a Location header is a redirect to a new host. ++ https://github.com/python/cpython/issues/87389 ++ ++ This checks that a path resolving to a directory on our server cannot ++ resolve into a redirect to another server. ++ """ ++ os.mkdir(os.path.join(self.tempdir, 'existing_directory')) ++ url = f'/python.org/..%2f..%2f..%2f..%2f..%2f../%0a%0d/../{self.tempdir_name}/existing_directory' ++ expected_location = f'{url}/' # /python.org.../ single slash single prefix, trailing slash ++ # Canonicalizes to /tmp/tempdir_name/existing_directory which does ++ # exist and is a dir, triggering the 301 redirect logic. ++ response = self.request(url) ++ self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) ++ location = response.getheader('Location') ++ self.assertEqual(location, expected_location, msg='non-attack failed!') ++ ++ # //python.org... multi-slash prefix, no trailing slash ++ attack_url = f'/{url}' ++ response = self.request(attack_url) ++ self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) ++ location = response.getheader('Location') ++ self.assertFalse(location.startswith('//'), msg=location) ++ self.assertEqual(location, expected_location, ++ msg='Expected Location header to start with a single / and ' ++ 'end with a / as this is a directory redirect.') ++ ++ # ///python.org... triple-slash prefix, no trailing slash ++ attack3_url = f'//{url}' ++ response = self.request(attack3_url) ++ self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) ++ self.assertEqual(response.getheader('Location'), expected_location) ++ ++ # If the second word in the http request (Request-URI for the http ++ # method) is a full URI, we don't worry about it, as that'll be parsed ++ # and reassembled as a full URI within BaseHTTPRequestHandler.send_head ++ # so no errant scheme-less //netloc//evil.co/ domain mixup can happen. ++ attack_scheme_netloc_2slash_url = f'https://pypi.org/{url}' ++ expected_scheme_netloc_location = f'{attack_scheme_netloc_2slash_url}/' ++ response = self.request(attack_scheme_netloc_2slash_url) ++ self.check_status_and_reason(response, HTTPStatus.MOVED_PERMANENTLY) ++ location = response.getheader('Location') ++ # We're just ensuring that the scheme and domain make it through, if ++ # there are or aren't multiple slashes at the start of the path that ++ # follows that isn't important in this Location: header. ++ self.assertTrue(location.startswith('https://pypi.org/'), msg=location) ++ + def test_get(self): + #constructs the path relative to the root directory of the HTTPServer + response = self.request(self.base_url + '/test') +diff --git a/Misc/NEWS.d/next/Security/2022-06-15-20-09-23.gh-issue-87389.QVaC3f.rst b/Misc/NEWS.d/next/Security/2022-06-15-20-09-23.gh-issue-87389.QVaC3f.rst +new file mode 100644 +index 0000000..029d437 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2022-06-15-20-09-23.gh-issue-87389.QVaC3f.rst +@@ -0,0 +1,3 @@ ++:mod:`http.server`: Fix an open redirection vulnerability in the HTTP server ++when an URI path starts with ``//``. Vulnerability discovered, and initial ++fix proposed, by Hamza Avvan. +-- +1.8.3.1 + diff --git a/backport-CVE-2022-37454.patch b/backport-CVE-2022-37454.patch new file mode 100644 index 0000000000000000000000000000000000000000..c0cc6c674b5bd1b838cd1109a4abd673f55a77c6 --- /dev/null +++ b/backport-CVE-2022-37454.patch @@ -0,0 +1,103 @@ +From 0e4e058602d93b88256ff90bbef501ba20be9dd3 Mon Sep 17 00:00:00 2001 +From: Theo Buehler +Date: Fri, 21 Oct 2022 21:26:01 +0200 +Subject: [PATCH] [3.10] gh-98517: Fix buffer overflows in _sha3 module +(#98519) + +This is a port of the applicable part of XKCP's fix [1] for +CVE-2022-37454 and avoids the segmentation fault and the infinite +loop in the test cases published in [2]. + +[1]: +https://github.com/XKCP/XKCP/commit/fdc6fef075f4e81d6b1bc38364248975e08e340a +[2]: https://mouha.be/sha-3-buffer-overflow/ + +Regression test added by: Gregory P. Smith [Google LLC] + + +--- + Lib/test/test_hashlib.py | 9 +++++++++ + .../2022-10-21-13-31-47.gh-issue-98517.SXXGfV.rst | 1 + + Modules/_sha3/kcp/KeccakSponge.inc | 15 ++++++++------- + 3 files changed, 18 insertions(+), 7 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2022-10-21-13-31-47.gh-issue-98517.SXXGfV.rst + +diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py +index 110eb48..378127a 100644 +--- a/Lib/test/test_hashlib.py ++++ b/Lib/test/test_hashlib.py +@@ -481,6 +481,15 @@ class HashLibTestCase(unittest.TestCase): + def test_case_md5_uintmax(self, size): + self.check('md5', b'A'*size, '28138d306ff1b8281f1a9067e1a1a2b3') + ++ @unittest.skipIf(sys.maxsize < _4G - 1, 'test cannot run on 32-bit systems') ++ @bigmemtest(size=_4G - 1, memuse=1, dry_run=False) ++ def test_sha3_update_overflow(self, size): ++ """Regression test for gh-98517 CVE-2022-37454.""" ++ h = hashlib.sha3_224() ++ h.update(b'\x01') ++ h.update(b'\x01'*0xffff_ffff) ++ self.assertEqual(h.hexdigest(), '80762e8ce6700f114fec0f621fd97c4b9c00147fa052215294cceeed') ++ + # use the three examples from Federal Information Processing Standards + # Publication 180-1, Secure Hash Standard, 1995 April 17 + # http://www.itl.nist.gov/div897/pubs/fip180-1.htm +diff --git a/Misc/NEWS.d/next/Security/2022-10-21-13-31-47.gh-issue-98517.SXXGfV.rst b/Misc/NEWS.d/next/Security/2022-10-21-13-31-47.gh-issue-98517.SXXGfV.rst +new file mode 100644 +index 0000000..2d23a6a +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2022-10-21-13-31-47.gh-issue-98517.SXXGfV.rst +@@ -0,0 +1 @@ ++Port XKCP's fix for the buffer overflows in SHA-3 (CVE-2022-37454). +diff --git a/Modules/_sha3/kcp/KeccakSponge.inc b/Modules/_sha3/kcp/KeccakSponge.inc +index e10739d..cf92e4d 100644 +--- a/Modules/_sha3/kcp/KeccakSponge.inc ++++ b/Modules/_sha3/kcp/KeccakSponge.inc +@@ -171,7 +171,7 @@ int SpongeAbsorb(SpongeInstance *instance, const unsigned char *data, size_t dat + i = 0; + curData = data; + while(i < dataByteLen) { +- if ((instance->byteIOIndex == 0) && (dataByteLen >= (i + rateInBytes))) { ++ if ((instance->byteIOIndex == 0) && (dataByteLen-i >= rateInBytes)) { + #ifdef SnP_FastLoop_Absorb + /* processing full blocks first */ + +@@ -199,10 +199,10 @@ int SpongeAbsorb(SpongeInstance *instance, const unsigned char *data, size_t dat + } + else { + /* normal lane: using the message queue */ +- +- partialBlock = (unsigned int)(dataByteLen - i); +- if (partialBlock+instance->byteIOIndex > rateInBytes) ++ if (dataByteLen-i > rateInBytes-instance->byteIOIndex) + partialBlock = rateInBytes-instance->byteIOIndex; ++ else ++ partialBlock = (unsigned int)(dataByteLen - i); + #ifdef KeccakReference + displayBytes(1, "Block to be absorbed (part)", curData, partialBlock); + #endif +@@ -281,7 +281,7 @@ int SpongeSqueeze(SpongeInstance *instance, unsigned char *data, size_t dataByte + i = 0; + curData = data; + while(i < dataByteLen) { +- if ((instance->byteIOIndex == rateInBytes) && (dataByteLen >= (i + rateInBytes))) { ++ if ((instance->byteIOIndex == rateInBytes) && (dataByteLen-i >= rateInBytes)) { + for(j=dataByteLen-i; j>=rateInBytes; j-=rateInBytes) { + SnP_Permute(instance->state); + SnP_ExtractBytes(instance->state, curData, 0, rateInBytes); +@@ -299,9 +299,10 @@ int SpongeSqueeze(SpongeInstance *instance, unsigned char *data, size_t dataByte + SnP_Permute(instance->state); + instance->byteIOIndex = 0; + } +- partialBlock = (unsigned int)(dataByteLen - i); +- if (partialBlock+instance->byteIOIndex > rateInBytes) ++ if (dataByteLen-i > rateInBytes-instance->byteIOIndex) + partialBlock = rateInBytes-instance->byteIOIndex; ++ else ++ partialBlock = (unsigned int)(dataByteLen - i); + i += partialBlock; + + SnP_ExtractBytes(instance->state, curData, instance->byteIOIndex, partialBlock); +-- +2.27.0 + diff --git a/backport-CVE-2022-42919.patch b/backport-CVE-2022-42919.patch new file mode 100644 index 0000000000000000000000000000000000000000..ee7ec4c33995066b86938cb98455df7da98d53eb --- /dev/null +++ b/backport-CVE-2022-42919.patch @@ -0,0 +1,71 @@ +From eae692eed18892309bcc25a2c0f8980038305ea2 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Thu, 20 Oct 2022 16:55:51 -0700 +Subject: [PATCH] [3.10] gh-97514: Don't use Linux abstract sockets for + multiprocessing (GH-98501) (GH-98503) + +Linux abstract sockets are insecure as they lack any form of filesystem +permissions so their use allows anyone on the system to inject code into +the process. + +This removes the default preference for abstract sockets in +multiprocessing introduced in Python 3.9+ via +https://github.com/python/cpython/pull/18866 while fixing +https://github.com/python/cpython/issues/84031. + +Explicit use of an abstract socket by a user now generates a +RuntimeWarning. If we choose to keep this warning, it should be +backported to the 3.7 and 3.8 branches. +(cherry picked from commit 49f61068f49747164988ffc5a442d2a63874fc17) + + +Co-authored-by: Gregory P. Smith + +Automerge-Triggered-By: GH:gpshead +--- + Lib/multiprocessing/connection.py | 5 ----- + .../2022-09-07-10-42-00.gh-issue-97514.Yggdsl.rst | 15 +++++++++++++++ + 2 files changed, 15 insertions(+), 5 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2022-09-07-10-42-00.gh-issue-97514.Yggdsl.rst + +diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py +index 510e4b5aba..8e2facf92a 100644 +--- a/Lib/multiprocessing/connection.py ++++ b/Lib/multiprocessing/connection.py +@@ -73,11 +73,6 @@ def arbitrary_address(family): + if family == 'AF_INET': + return ('localhost', 0) + elif family == 'AF_UNIX': +- # Prefer abstract sockets if possible to avoid problems with the address +- # size. When coding portable applications, some implementations have +- # sun_path as short as 92 bytes in the sockaddr_un struct. +- if util.abstract_sockets_supported: +- return f"\0listener-{os.getpid()}-{next(_mmap_counter)}" + return tempfile.mktemp(prefix='listener-', dir=util.get_temp_dir()) + elif family == 'AF_PIPE': + return tempfile.mktemp(prefix=r'\\.\pipe\pyc-%d-%d-' % +diff --git a/Misc/NEWS.d/next/Security/2022-09-07-10-42-00.gh-issue-97514.Yggdsl.rst b/Misc/NEWS.d/next/Security/2022-09-07-10-42-00.gh-issue-97514.Yggdsl.rst +new file mode 100644 +index 0000000000..02d95b5705 +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2022-09-07-10-42-00.gh-issue-97514.Yggdsl.rst +@@ -0,0 +1,15 @@ ++On Linux the :mod:`multiprocessing` module returns to using filesystem backed ++unix domain sockets for communication with the *forkserver* process instead of ++the Linux abstract socket namespace. Only code that chooses to use the ++:ref:`"forkserver" start method ` is affected. ++ ++Abstract sockets have no permissions and could allow any user on the system in ++the same `network namespace ++`_ (often the ++whole system) to inject code into the multiprocessing *forkserver* process. ++This was a potential privilege escalation. Filesystem based socket permissions ++restrict this to the *forkserver* process user as was the default in Python 3.8 ++and earlier. ++ ++This prevents Linux `CVE-2022-42919 ++`_. +-- +2.27.0 + diff --git a/backport-CVE-2022-45061.patch b/backport-CVE-2022-45061.patch new file mode 100644 index 0000000000000000000000000000000000000000..7bfc0b82af53aad1dd770d631ea4ab3b9a4e423e --- /dev/null +++ b/backport-CVE-2022-45061.patch @@ -0,0 +1,98 @@ +From 9bb8e18ca46fe66fa6802602f8a7228a24dd785f Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Mon, 7 Nov 2022 19:23:16 -0800 +Subject: [PATCH] [3.11] gh-98433: Fix quadratic time idna decoding. (GH-99092) + (GH-99222) + +There was an unnecessary quadratic loop in idna decoding. This restores +the behavior to linear. + +(cherry picked from commit d315722564927c7202dd6e111dc79eaf14240b0d) + +(cherry picked from commit a6f6c3a3d6f2b580f2d87885c9b8a9350ad7bf15) + +Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> +Co-authored-by: Gregory P. Smith +--- + Lib/encodings/idna.py | 32 +++++++++---------- + Lib/test/test_codecs.py | 6 ++++ + ...2-11-04-09-29-36.gh-issue-98433.l76c5G.rst | 6 ++++ + 3 files changed, 27 insertions(+), 17 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2022-11-04-09-29-36.gh-issue-98433.l76c5G.rst + +diff --git a/Lib/encodings/idna.py b/Lib/encodings/idna.py +index ea40585..bf98f51 100644 +--- a/Lib/encodings/idna.py ++++ b/Lib/encodings/idna.py +@@ -39,23 +39,21 @@ def nameprep(label): + + # Check bidi + RandAL = [stringprep.in_table_d1(x) for x in label] +- for c in RandAL: +- if c: +- # There is a RandAL char in the string. Must perform further +- # tests: +- # 1) The characters in section 5.8 MUST be prohibited. +- # This is table C.8, which was already checked +- # 2) If a string contains any RandALCat character, the string +- # MUST NOT contain any LCat character. +- if any(stringprep.in_table_d2(x) for x in label): +- raise UnicodeError("Violation of BIDI requirement 2") +- +- # 3) If a string contains any RandALCat character, a +- # RandALCat character MUST be the first character of the +- # string, and a RandALCat character MUST be the last +- # character of the string. +- if not RandAL[0] or not RandAL[-1]: +- raise UnicodeError("Violation of BIDI requirement 3") ++ if any(RandAL): ++ # There is a RandAL char in the string. Must perform further ++ # tests: ++ # 1) The characters in section 5.8 MUST be prohibited. ++ # This is table C.8, which was already checked ++ # 2) If a string contains any RandALCat character, the string ++ # MUST NOT contain any LCat character. ++ if any(stringprep.in_table_d2(x) for x in label): ++ raise UnicodeError("Violation of BIDI requirement 2") ++ # 3) If a string contains any RandALCat character, a ++ # RandALCat character MUST be the first character of the ++ # string, and a RandALCat character MUST be the last ++ # character of the string. ++ if not RandAL[0] or not RandAL[-1]: ++ raise UnicodeError("Violation of BIDI requirement 3") + + return label + +diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py +index f7310fb..439fab7 100644 +--- a/Lib/test/test_codecs.py ++++ b/Lib/test/test_codecs.py +@@ -1534,6 +1534,12 @@ class IDNACodecTest(unittest.TestCase): + self.assertEqual("pyth\xf6n.org".encode("idna"), b"xn--pythn-mua.org") + self.assertEqual("pyth\xf6n.org.".encode("idna"), b"xn--pythn-mua.org.") + ++ def test_builtin_decode_length_limit(self): ++ with self.assertRaisesRegex(UnicodeError, "too long"): ++ (b"xn--016c"+b"a"*1100).decode("idna") ++ with self.assertRaisesRegex(UnicodeError, "too long"): ++ (b"xn--016c"+b"a"*70).decode("idna") ++ + def test_stream(self): + r = codecs.getreader("idna")(io.BytesIO(b"abc")) + r.read(3) +diff --git a/Misc/NEWS.d/next/Security/2022-11-04-09-29-36.gh-issue-98433.l76c5G.rst b/Misc/NEWS.d/next/Security/2022-11-04-09-29-36.gh-issue-98433.l76c5G.rst +new file mode 100644 +index 0000000..5185fac +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2022-11-04-09-29-36.gh-issue-98433.l76c5G.rst +@@ -0,0 +1,6 @@ ++The IDNA codec decoder used on DNS hostnames by :mod:`socket` or :mod:`asyncio` ++related name resolution functions no longer involves a quadratic algorithm. ++This prevents a potential CPU denial of service if an out-of-spec excessive ++length hostname involving bidirectional characters were decoded. Some protocols ++such as :mod:`urllib` http ``3xx`` redirects potentially allow for an attacker ++to supply such a name. +-- +2.27.0 + diff --git a/backport-bpo-46811-Make-test-suite-support-Expat-2.4.5.patch b/backport-bpo-46811-Make-test-suite-support-Expat-2.4.5.patch new file mode 100644 index 0000000000000000000000000000000000000000..cfa56e6e5bf629987cf7e0ab74fdf0160059a89e --- /dev/null +++ b/backport-bpo-46811-Make-test-suite-support-Expat-2.4.5.patch @@ -0,0 +1,104 @@ +From 7da97f61816f3cadaa6788804b22a2434b40e8c5 Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)" + <31488909+miss-islington@users.noreply.github.com> +Date: Mon, 21 Feb 2022 08:16:09 -0800 +Subject: [PATCH] bpo-46811: Make test suite support Expat >=2.4.5 (GH-31453) + (GH-31472) + +Curly brackets were never allowed in namespace URIs +according to RFC 3986, and so-called namespace-validating +XML parsers have the right to reject them a invalid URIs. + +libexpat >=2.4.5 has become strcter in that regard due to +related security issues; with ET.XML instantiating a +namespace-aware parser under the hood, this test has no +future in CPython. + +References: +- https://datatracker.ietf.org/doc/html/rfc3968 +- https://www.w3.org/TR/xml-names/ + +Also, test_minidom.py: Support Expat >=2.4.5 +(cherry picked from commit 2cae93832f46b245847bdc252456ddf7742ef45e) + +Co-authored-by: Sebastian Pipping +--- + Lib/test/test_minidom.py | 17 +++++++++++++++-- + Lib/test/test_xml_etree.py | 6 ------ + .../Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst | 1 + + 3 files changed, 16 insertions(+), 8 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst + +diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py +index 1663b1f..9762025 100644 +--- a/Lib/test/test_minidom.py ++++ b/Lib/test/test_minidom.py +@@ -6,10 +6,12 @@ import io + from test import support + import unittest + ++import pyexpat + import xml.dom.minidom + + from xml.dom.minidom import parse, Node, Document, parseString + from xml.dom.minidom import getDOMImplementation ++from xml.parsers.expat import ExpatError + + + tstfile = support.findfile("test.xml", subdir="xmltestdata") +@@ -1147,7 +1149,13 @@ class MinidomTest(unittest.TestCase): + + # Verify that character decoding errors raise exceptions instead + # of crashing +- self.assertRaises(UnicodeDecodeError, parseString, ++ if pyexpat.version_info >= (2, 4, 5): ++ self.assertRaises(ExpatError, parseString, ++ b'') ++ self.assertRaises(ExpatError, parseString, ++ b'Comment \xe7a va ? Tr\xe8s bien ?') ++ else: ++ self.assertRaises(UnicodeDecodeError, parseString, + b'Comment \xe7a va ? Tr\xe8s bien ?') + + doc.unlink() +@@ -1609,7 +1617,12 @@ class MinidomTest(unittest.TestCase): + self.confirm(doc2.namespaceURI == xml.dom.EMPTY_NAMESPACE) + + def testExceptionOnSpacesInXMLNSValue(self): +- with self.assertRaisesRegex(ValueError, 'Unsupported syntax'): ++ if pyexpat.version_info >= (2, 4, 5): ++ context = self.assertRaisesRegex(ExpatError, 'syntax error') ++ else: ++ context = self.assertRaisesRegex(ValueError, 'Unsupported syntax') ++ ++ with context: + parseString('') + + def testDocRemoveChild(self): +diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py +index 285559a..4bd4291 100644 +--- a/Lib/test/test_xml_etree.py ++++ b/Lib/test/test_xml_etree.py +@@ -2183,12 +2183,6 @@ class BugsTest(unittest.TestCase): + b"\n" + b'tãg') + +- def test_issue3151(self): +- e = ET.XML('') +- self.assertEqual(e.tag, '{${stuff}}localname') +- t = ET.ElementTree(e) +- self.assertEqual(ET.tostring(e), b'') +- + def test_issue6565(self): + elem = ET.XML("") + self.assertEqual(summarize_list(elem), ['tag']) +diff --git a/Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst b/Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst +new file mode 100644 +index 0000000..6969bd1 +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst +@@ -0,0 +1 @@ ++Make test suite support Expat >=2.4.5 +-- +1.8.3.1 + diff --git a/python3.spec b/python3.spec index 5d37dda649c2d8581cdf5193b7ee0b44a6ae152d..c72388b3eaf2c6cc54fb22fa2cd9b31301be0b56 100644 --- a/python3.spec +++ b/python3.spec @@ -2,12 +2,12 @@ Name: python3 Summary: Interpreter of the Python3 programming language URL: https://www.python.org/ -Version: 3.11.1 -Release: 1 +Version: 3.10.2 +Release: 12 License: Python-2.0 -%global branchversion 3.11 -%global pyshortver 311 +%global branchversion 3.10 +%global pyshortver 310 %ifarch %{ix86} x86_64 %bcond_with optimizations @@ -87,6 +87,15 @@ Source1: pyconfig.h Patch1: 00001-rpath.patch Patch251: 00251-change-user-install-location.patch +Patch6000: backport-bpo-46811-Make-test-suite-support-Expat-2.4.5.patch +Patch6001: backport-CVE-2015-20107.patch +Patch6002: backport-CVE-2021-28861.patch +Patch6003: backport-0001-CVE-2020-10735.patch +Patch6004: backport-0002-CVE-2020-10735.patch +Patch6005: backport-0003-CVE-2020-10735.patch +Patch6006: backport-CVE-2022-42919.patch +Patch6007: backport-CVE-2022-45061.patch +Patch6008: backport-CVE-2022-37454.patch Patch9000: add-the-sm3-method-for-obtaining-the-salt-value.patch @@ -182,6 +191,15 @@ rm configure pyconfig.h.in %patch1 -p1 %patch251 -p1 +%patch6000 -p1 +%patch6001 -p1 +%patch6002 -p1 +%patch6003 -p1 +%patch6004 -p1 +%patch6005 -p1 +%patch6006 -p1 +%patch6007 -p1 +%patch6008 -p1 %patch9000 -p1 @@ -394,8 +412,7 @@ LD_LIBRARY_PATH=$(pwd)/build/debug $(pwd)/build/debug/python -m test.regrtest \ -x test_bdist_rpm \ -x test_gdb \ -x test_socket \ - -x test_asyncio \ - -i test_freeze_simple_script + -x test_asyncio export OPENSSL_CONF=/non-existing-file LD_LIBRARY_PATH=$(pwd)/build/optimized $(pwd)/build/optimized/python -m test.pythoninfo @@ -407,8 +424,7 @@ LD_LIBRARY_PATH=$(pwd)/build/optimized $(pwd)/build/optimized/python -m test.reg -x test_bdist_rpm \ -x test_gdb \ -x test_socket \ - -x test_asyncio \ - -i test_freeze_simple_script + -x test_asyncio export BEP_WHITELIST="$BEP_WHITELIST_TMP" export BEP_GTDLIST="$BEP_GTDLIST_TMP" @@ -454,11 +470,6 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %exclude %{pylibdir}/ensurepip/_bundled -%dir %{pylibdir}/__phello__/ -%dir %{pylibdir}/__phello__/__pycache__/ -%{pylibdir}/__phello__/*.py -%{pylibdir}/__phello__/__pycache__/*%{bytecode_suffixes} - %dir %{pylibdir}/test/ %dir %{pylibdir}/test/__pycache__/ %dir %{pylibdir}/test/support/ @@ -522,7 +533,6 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %{dynload_dir}/_sqlite3.%{SOABI_optimized}.so %{dynload_dir}/_ssl.%{SOABI_optimized}.so %{dynload_dir}/_struct.%{SOABI_optimized}.so -%{dynload_dir}/_typing.%{SOABI_optimized}.so %{dynload_dir}/array.%{SOABI_optimized}.so %{dynload_dir}/audioop.%{SOABI_optimized}.so %{dynload_dir}/binascii.%{SOABI_optimized}.so @@ -605,11 +615,6 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %dir %{pylibdir}/importlib/metadata/__pycache__/ %{pylibdir}/importlib/metadata/ -%dir %{pylibdir}/importlib/resources/ -%dir %{pylibdir}/importlib/resources/__pycache__/ -%{pylibdir}/importlib/resources/*.py -%{pylibdir}/importlib/resources/__pycache__/*%{bytecode_suffixes} - %dir %{pylibdir}/json/ %dir %{pylibdir}/json/__pycache__/ %{pylibdir}/json/*.py @@ -618,16 +623,6 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %{pylibdir}/logging %{pylibdir}/multiprocessing -%dir %{pylibdir}/re/ -%dir %{pylibdir}/re/__pycache__/ -%{pylibdir}/re/*.py -%{pylibdir}/re/__pycache__/*%{bytecode_suffixes} - -%dir %{pylibdir}/tomllib/ -%dir %{pylibdir}/tomllib/__pycache__/ -%{pylibdir}/tomllib/*.py -%{pylibdir}/tomllib/__pycache__/*%{bytecode_suffixes} - %dir %{pylibdir}/sqlite3/ %dir %{pylibdir}/sqlite3/__pycache__/ %{pylibdir}/sqlite3/*.py @@ -689,6 +684,7 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %{pylibdir}/ctypes/test %{pylibdir}/distutils/tests +%{pylibdir}/sqlite3/test %{pylibdir}/test %exclude %{pylibdir}/test/capath %exclude %{pylibdir}/test/*.pem @@ -766,7 +762,6 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %{dynload_dir}/_sqlite3.%{SOABI_debug}.so %{dynload_dir}/_ssl.%{SOABI_debug}.so %{dynload_dir}/_struct.%{SOABI_debug}.so -%{dynload_dir}/_typing.%{SOABI_debug}.so %{dynload_dir}/array.%{SOABI_debug}.so %{dynload_dir}/audioop.%{SOABI_debug}.so %{dynload_dir}/binascii.%{SOABI_debug}.so @@ -822,12 +817,6 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP" %{_mandir}/*/* %changelog -* Sat Jan 28 2023 zhuofeng - 3.11.1-1 -- Type:enhancement -- ID:NA -- SUG:NA -- DESC:update version to 3.11.1 - * Mon Nov 28 2022 zhuofeng - 3.10.2-12 - Type:CVE - CVE:CVE-2022-37454