Blame SOURCES/00387-cve-2020-10735-prevent-dos-by-very-large-int.patch

bb807a
From 2b6758f55e3b0a4141a54f3c35a0d0cd377ce3cf Mon Sep 17 00:00:00 2001
bb807a
From: "Gregory P. Smith" <greg@krypto.org>
bb807a
Date: Mon, 5 Sep 2022 02:21:03 -0700
bb807a
Subject: [PATCH] gh-95778: CVE-2020-10735: Prevent DoS by very large int()
bb807a
 (#96502)
bb807a
bb807a
* Correctly pre-check for int-to-str conversion (#96537)
bb807a
bb807a
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! =)
bb807a
bb807a
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.
bb807a
bb807a
The justification for the current check. The C code check is:
bb807a
```c
bb807a
max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10
bb807a
```
bb807a
bb807a
In GitHub markdown math-speak, writing $M$ for `max_str_digits`, $L$ for `PyLong_SHIFT` and $s$ for `size_a`, that check is:
bb807a
$$\left\lfloor\frac{M}{3L}\right\rfloor \le \left\lfloor\frac{s - 11}{10}\right\rfloor$$
bb807a
bb807a
From this it follows that
bb807a
$$\frac{M}{3L} < \frac{s-1}{10}$$
bb807a
hence that
bb807a
$$\frac{L(s-1)}{M} > \frac{10}{3} > \log_2(10).$$
bb807a
So
bb807a
$$2^{L(s-1)} > 10^M.$$
bb807a
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.
bb807a
bb807a
bb807a
* Issue: gh-95778
bb807a
bb807a
bb807a
Co-authored-by: Gregory P. Smith [Google LLC] <greg@krypto.org>
bb807a
Co-authored-by: Christian Heimes <christian@python.org>
bb807a
Co-authored-by: Mark Dickinson <dickinsm@gmail.com>
bb807a
---
bb807a
 Doc/data/python3.9.abi                        |   5 +-
bb807a
 Doc/library/functions.rst                     |   8 +
bb807a
 Doc/library/json.rst                          |  11 +
bb807a
 Doc/library/stdtypes.rst                      | 159 ++++++++++++++
bb807a
 Doc/library/sys.rst                           |  59 ++++--
bb807a
 Doc/library/test.rst                          |  10 +
bb807a
 Doc/using/cmdline.rst                         |  13 ++
bb807a
 Doc/whatsnew/3.9.rst                          |  14 ++
bb807a
 Include/internal/pycore_initconfig.h          |   2 +
bb807a
 Include/internal/pycore_interp.h              |   2 +
bb807a
 Include/internal/pycore_long.h                |  49 +++++
bb807a
 Lib/test/support/__init__.py                  |  11 +
bb807a
 Lib/test/test_ast.py                          |   8 +
bb807a
 Lib/test/test_cmd_line.py                     |  33 +++
bb807a
 Lib/test/test_compile.py                      |  13 ++
bb807a
 Lib/test/test_decimal.py                      |  18 ++
bb807a
 Lib/test/test_int.py                          | 196 ++++++++++++++++++
bb807a
 Lib/test/test_json/test_decode.py             |   9 +
bb807a
 Lib/test/test_sys.py                          |  11 +-
bb807a
 Lib/test/test_xmlrpc.py                       |  10 +
bb807a
 Makefile.pre.in                               |   1 +
bb807a
 ...08-07-16-53-38.gh-issue-95778.ch010gps.rst |  14 ++
bb807a
 Objects/longobject.c                          |  65 +++++-
bb807a
 Parser/pegen/pegen.c                          |  18 ++
bb807a
 Python/clinic/sysmodule.c.h                   |  60 +++++-
bb807a
 Python/initconfig.c                           |  60 ++++++
bb807a
 Python/sysmodule.c                            |  46 +++-
bb807a
 27 files changed, 886 insertions(+), 19 deletions(-)
bb807a
 create mode 100644 Include/internal/pycore_long.h
bb807a
 create mode 100644 Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst
bb807a
bb807a
diff --git a/Doc/data/python3.9.abi b/Doc/data/python3.9.abi
bb807a
index e203743..cca9779 100644
bb807a
--- a/Doc/data/python3.9.abi
bb807a
+++ b/Doc/data/python3.9.abi
bb807a
@@ -5653,7 +5653,7 @@
bb807a
         <var-decl name='id' type-id='type-id-238' visibility='default' filepath='./Include/cpython/pystate.h' line='137' column='1'/>
bb807a
       </data-member>
bb807a
     </class-decl>
bb807a
-    <class-decl name='_is' size-in-bits='45184' is-struct='yes' visibility='default' filepath='./Include/internal/pycore_interp.h' line='71' column='1' id='type-id-313'>
bb807a
+    <class-decl name='_is' size-in-bits='45248' is-struct='yes' visibility='default' filepath='./Include/internal/pycore_interp.h' line='71' column='1' id='type-id-313'>
bb807a
       <data-member access='public' layout-offset-in-bits='0'>
bb807a
         <var-decl name='next' type-id='type-id-314' visibility='default' filepath='./Include/internal/pycore_interp.h' line='73' column='1'/>
bb807a
       </data-member>
bb807a
@@ -5774,6 +5774,9 @@
bb807a
       <data-member access='public' layout-offset-in-bits='28416'>
bb807a
         <var-decl name='small_ints' type-id='type-id-326' visibility='default' filepath='./Include/internal/pycore_interp.h' line='155' column='1'/>
bb807a
       </data-member>
bb807a
+      <data-member access='public' layout-offset-in-bits='45184'>
bb807a
+        <var-decl name='int_max_str_digits' type-id='type-id-8' visibility='default' filepath='./Include/internal/pycore_interp.h' line='158' column='1'/>
bb807a
+      </data-member>
bb807a
     </class-decl>
bb807a
     <pointer-type-def type-id='type-id-313' size-in-bits='64' id='type-id-314'/>
bb807a
     <class-decl name='pyruntimestate' size-in-bits='5248' is-struct='yes' visibility='default' filepath='./Include/internal/pycore_runtime.h' line='52' column='1' id='type-id-327'>
bb807a
diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
bb807a
index 8df557e..820b313 100644
bb807a
--- a/Doc/library/functions.rst
bb807a
+++ b/Doc/library/functions.rst
bb807a
@@ -844,6 +844,14 @@ are always available.  They are listed here in alphabetical order.
bb807a
    .. versionchanged:: 3.8
bb807a
       Falls back to :meth:`__index__` if :meth:`__int__` is not defined.
bb807a
 
bb807a
+   .. versionchanged:: 3.9.14
bb807a
+      :class:`int` string inputs and string representations can be limited to
bb807a
+      help avoid denial of service attacks. A :exc:`ValueError` is raised when
bb807a
+      the limit is exceeded while converting a string *x* to an :class:`int` or
bb807a
+      when converting an :class:`int` into a string would exceed the limit.
bb807a
+      See the :ref:`integer string conversion length limitation
bb807a
+      <int_max_str_digits>` documentation.
bb807a
+
bb807a
 
bb807a
 .. function:: isinstance(object, classinfo)
bb807a
 
bb807a
diff --git a/Doc/library/json.rst b/Doc/library/json.rst
bb807a
index 1810e04..6b715b5 100644
bb807a
--- a/Doc/library/json.rst
bb807a
+++ b/Doc/library/json.rst
bb807a
@@ -18,6 +18,11 @@ is a lightweight data interchange format inspired by
bb807a
 `JavaScript <https://en.wikipedia.org/wiki/JavaScript>`_ object literal syntax
bb807a
 (although it is not a strict subset of JavaScript [#rfc-errata]_ ).
bb807a
 
bb807a
+.. warning::
bb807a
+   Be cautious when parsing JSON data from untrusted sources. A malicious
bb807a
+   JSON string may cause the decoder to consume considerable CPU and memory
bb807a
+   resources. Limiting the size of data to be parsed is recommended.
bb807a
+
bb807a
 :mod:`json` exposes an API familiar to users of the standard library
bb807a
 :mod:`marshal` and :mod:`pickle` modules.
bb807a
 
bb807a
@@ -255,6 +260,12 @@ Basic Usage
bb807a
    be used to use another datatype or parser for JSON integers
bb807a
    (e.g. :class:`float`).
bb807a
 
bb807a
+   .. versionchanged:: 3.9.14
bb807a
+      The default *parse_int* of :func:`int` now limits the maximum length of
bb807a
+      the integer string via the interpreter's :ref:`integer string
bb807a
+      conversion length limitation <int_max_str_digits>` to help avoid denial
bb807a
+      of service attacks.
bb807a
+
bb807a
    *parse_constant*, if specified, will be called with one of the following
bb807a
    strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``.
bb807a
    This can be used to raise an exception if invalid JSON numbers
bb807a
diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
bb807a
index bfa0e74..f88e81f 100644
bb807a
--- a/Doc/library/stdtypes.rst
bb807a
+++ b/Doc/library/stdtypes.rst
bb807a
@@ -5206,6 +5206,165 @@ types, where they are relevant.  Some of these are not reported by the
bb807a
       [<class 'bool'>]
bb807a
 
bb807a
 
bb807a
+.. _int_max_str_digits:
bb807a
+
bb807a
+Integer string conversion length limitation
bb807a
+===========================================
bb807a
+
bb807a
+CPython has a global limit for converting between :class:`int` and :class:`str`
bb807a
+to mitigate denial of service attacks. This limit *only* applies to decimal or
bb807a
+other non-power-of-two number bases. Hexadecimal, octal, and binary conversions
bb807a
+are unlimited. The limit can be configured.
bb807a
+
bb807a
+The :class:`int` type in CPython is an abitrary length number stored in binary
bb807a
+form (commonly known as a "bignum"). There exists no algorithm that can convert
bb807a
+a string to a binary integer or a binary integer to a string in linear time,
bb807a
+*unless* the base is a power of 2. Even the best known algorithms for base 10
bb807a
+have sub-quadratic complexity. Converting a large value such as ``int('1' *
bb807a
+500_000)`` can take over a second on a fast CPU.
bb807a
+
bb807a
+Limiting conversion size offers a practical way to avoid `CVE-2020-10735
bb807a
+<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735>`_.
bb807a
+
bb807a
+The limit is applied to the number of digit characters in the input or output
bb807a
+string when a non-linear conversion algorithm would be involved.  Underscores
bb807a
+and the sign are not counted towards the limit.
bb807a
+
bb807a
+When an operation would exceed the limit, a :exc:`ValueError` is raised:
bb807a
+
bb807a
+.. doctest::
bb807a
+
bb807a
+   >>> import sys
bb807a
+   >>> sys.set_int_max_str_digits(4300)  # Illustrative, this is the default.
bb807a
+   >>> _ = int('2' * 5432)
bb807a
+   Traceback (most recent call last):
bb807a
+   ...
bb807a
+   ValueError: Exceeds the limit (4300) for integer string conversion: value has 5432 digits.
bb807a
+   >>> i = int('2' * 4300)
bb807a
+   >>> len(str(i))
bb807a
+   4300
bb807a
+   >>> i_squared = i*i
bb807a
+   >>> len(str(i_squared))
bb807a
+   Traceback (most recent call last):
bb807a
+   ...
bb807a
+   ValueError: Exceeds the limit (4300) for integer string conversion: value has 8599 digits.
bb807a
+   >>> len(hex(i_squared))
bb807a
+   7144
bb807a
+   >>> assert int(hex(i_squared), base=16) == i*i  # Hexadecimal is unlimited.
bb807a
+
bb807a
+The default limit is 4300 digits as provided in
bb807a
+:data:`sys.int_info.default_max_str_digits <sys.int_info>`.
bb807a
+The lowest limit that can be configured is 640 digits as provided in
bb807a
+:data:`sys.int_info.str_digits_check_threshold <sys.int_info>`.
bb807a
+
bb807a
+Verification:
bb807a
+
bb807a
+.. doctest::
bb807a
+
bb807a
+   >>> import sys
bb807a
+   >>> assert sys.int_info.default_max_str_digits == 4300, sys.int_info
bb807a
+   >>> assert sys.int_info.str_digits_check_threshold == 640, sys.int_info
bb807a
+   >>> msg = int('578966293710682886880994035146873798396722250538762761564'
bb807a
+   ...           '9252925514383915483333812743580549779436104706260696366600'
bb807a
+   ...           '571186405732').to_bytes(53, 'big')
bb807a
+   ...
bb807a
+
bb807a
+.. versionadded:: 3.9.14
bb807a
+
bb807a
+Affected APIs
bb807a
+-------------
bb807a
+
bb807a
+The limitation only applies to potentially slow conversions between :class:`int`
bb807a
+and :class:`str` or :class:`bytes`:
bb807a
+
bb807a
+* ``int(string)`` with default base 10.
bb807a
+* ``int(string, base)`` for all bases that are not a power of 2.
bb807a
+* ``str(integer)``.
bb807a
+* ``repr(integer)``
bb807a
+* any other string conversion to base 10, for example ``f"{integer}"``,
bb807a
+  ``"{}".format(integer)``, or ``b"%d" % integer``.
bb807a
+
bb807a
+The limitations do not apply to functions with a linear algorithm:
bb807a
+
bb807a
+* ``int(string, base)`` with base 2, 4, 8, 16, or 32.
bb807a
+* :func:`int.from_bytes` and :func:`int.to_bytes`.
bb807a
+* :func:`hex`, :func:`oct`, :func:`bin`.
bb807a
+* :ref:`formatspec` for hex, octal, and binary numbers.
bb807a
+* :class:`str` to :class:`float`.
bb807a
+* :class:`str` to :class:`decimal.Decimal`.
bb807a
+
bb807a
+Configuring the limit
bb807a
+---------------------
bb807a
+
bb807a
+Before Python starts up you can use an environment variable or an interpreter
bb807a
+command line flag to configure the limit:
bb807a
+
bb807a
+* :envvar:`PYTHONINTMAXSTRDIGITS`, e.g.
bb807a
+  ``PYTHONINTMAXSTRDIGITS=640 python3`` to set the limit to 640 or
bb807a
+  ``PYTHONINTMAXSTRDIGITS=0 python3`` to disable the limitation.
bb807a
+* :option:`-X int_max_str_digits <-X>`, e.g.
bb807a
+  ``python3 -X int_max_str_digits=640``
bb807a
+* :data:`sys.flags.int_max_str_digits` contains the value of
bb807a
+  :envvar:`PYTHONINTMAXSTRDIGITS` or :option:`-X int_max_str_digits <-X>`.
bb807a
+  If both the env var and the ``-X`` option are set, the ``-X`` option takes
bb807a
+  precedence. A value of *-1* indicates that both were unset, thus a value of
bb807a
+  :data:`sys.int_info.default_max_str_digits` was used during initilization.
bb807a
+
bb807a
+From code, you can inspect the current limit and set a new one using these
bb807a
+:mod:`sys` APIs:
bb807a
+
bb807a
+* :func:`sys.get_int_max_str_digits` and :func:`sys.set_int_max_str_digits` are
bb807a
+  a getter and setter for the interpreter-wide limit. Subinterpreters have
bb807a
+  their own limit.
bb807a
+
bb807a
+Information about the default and minimum can be found in :attr:`sys.int_info`:
bb807a
+
bb807a
+* :data:`sys.int_info.default_max_str_digits <sys.int_info>` is the compiled-in
bb807a
+  default limit.
bb807a
+* :data:`sys.int_info.str_digits_check_threshold <sys.int_info>` is the lowest
bb807a
+  accepted value for the limit (other than 0 which disables it).
bb807a
+
bb807a
+.. versionadded:: 3.9.14
bb807a
+
bb807a
+.. caution::
bb807a
+
bb807a
+   Setting a low limit *can* lead to problems. While rare, code exists that
bb807a
+   contains integer constants in decimal in their source that exceed the
bb807a
+   minimum threshold. A consequence of setting the limit is that Python source
bb807a
+   code containing decimal integer literals longer than the limit will
bb807a
+   encounter an error during parsing, usually at startup time or import time or
bb807a
+   even at installation time - anytime an up to date ``.pyc`` does not already
bb807a
+   exist for the code. A workaround for source that contains such large
bb807a
+   constants is to convert them to ``0x`` hexadecimal form as it has no limit.
bb807a
+
bb807a
+   Test your application thoroughly if you use a low limit. Ensure your tests
bb807a
+   run with the limit set early via the environment or flag so that it applies
bb807a
+   during startup and even during any installation step that may invoke Python
bb807a
+   to precompile ``.py`` sources to ``.pyc`` files.
bb807a
+
bb807a
+Recommended configuration
bb807a
+-------------------------
bb807a
+
bb807a
+The default :data:`sys.int_info.default_max_str_digits` is expected to be
bb807a
+reasonable for most applications. If your application requires a different
bb807a
+limit, set it from your main entry point using Python version agnostic code as
bb807a
+these APIs were added in security patch releases in versions before 3.11.
bb807a
+
bb807a
+Example::
bb807a
+
bb807a
+   >>> import sys
bb807a
+   >>> if hasattr(sys, "set_int_max_str_digits"):
bb807a
+   ...     upper_bound = 68000
bb807a
+   ...     lower_bound = 4004
bb807a
+   ...     current_limit = sys.get_int_max_str_digits()
bb807a
+   ...     if current_limit == 0 or current_limit > upper_bound:
bb807a
+   ...         sys.set_int_max_str_digits(upper_bound)
bb807a
+   ...     elif current_limit < lower_bound:
bb807a
+   ...         sys.set_int_max_str_digits(lower_bound)
bb807a
+
bb807a
+If you need to disable it entirely, set it to ``0``.
bb807a
+
bb807a
+
bb807a
 .. rubric:: Footnotes
bb807a
 
bb807a
 .. [1] Additional information on these special methods may be found in the Python
bb807a
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
bb807a
index 9e18282..9b98bc5 100644
bb807a
--- a/Doc/library/sys.rst
bb807a
+++ b/Doc/library/sys.rst
bb807a
@@ -445,9 +445,9 @@ always available.
bb807a
    The :term:`named tuple` *flags* exposes the status of command line
bb807a
    flags. The attributes are read only.
bb807a
 
bb807a
-   ============================= ================================================================
bb807a
+   ============================= ==============================================================================================================
bb807a
    attribute                     flag
bb807a
-   ============================= ================================================================
bb807a
+   ============================= ==============================================================================================================
bb807a
    :const:`debug`                :option:`-d`
bb807a
    :const:`inspect`              :option:`-i`
bb807a
    :const:`interactive`          :option:`-i`
bb807a
@@ -463,7 +463,8 @@ always available.
bb807a
    :const:`hash_randomization`   :option:`-R`
bb807a
    :const:`dev_mode`             :option:`-X dev <-X>` (:ref:`Python Development Mode <devmode>`)
bb807a
    :const:`utf8_mode`            :option:`-X utf8 <-X>`
bb807a
-   ============================= ================================================================
bb807a
+   :const:`int_max_str_digits`   :option:`-X int_max_str_digits <-X>` (:ref:`integer string conversion length limitation <int_max_str_digits>`)
bb807a
+   ============================= ==============================================================================================================
bb807a
 
bb807a
    .. versionchanged:: 3.2
bb807a
       Added ``quiet`` attribute for the new :option:`-q` flag.
bb807a
@@ -482,6 +483,9 @@ always available.
bb807a
       Mode <devmode>` and the ``utf8_mode`` attribute for the new  :option:`-X`
bb807a
       ``utf8`` flag.
bb807a
 
bb807a
+   .. versionchanged:: 3.9.14
bb807a
+      Added the ``int_max_str_digits`` attribute.
bb807a
+
bb807a
 
bb807a
 .. data:: float_info
bb807a
 
bb807a
@@ -660,6 +664,15 @@ always available.
bb807a
 
bb807a
    .. versionadded:: 3.6
bb807a
 
bb807a
+
bb807a
+.. function:: get_int_max_str_digits()
bb807a
+
bb807a
+   Returns the current value for the :ref:`integer string conversion length
bb807a
+   limitation <int_max_str_digits>`. See also :func:`set_int_max_str_digits`.
bb807a
+
bb807a
+   .. versionadded:: 3.9.14
bb807a
+
bb807a
+
bb807a
 .. function:: getrefcount(object)
bb807a
 
bb807a
    Return the reference count of the *object*.  The count returned is generally one
bb807a
@@ -933,19 +946,31 @@ always available.
bb807a
 
bb807a
    .. tabularcolumns:: |l|L|
bb807a
 
bb807a
-   +-------------------------+----------------------------------------------+
bb807a
-   | Attribute               | Explanation                                  |
bb807a
-   +=========================+==============================================+
bb807a
-   | :const:`bits_per_digit` | number of bits held in each digit.  Python   |
bb807a
-   |                         | integers are stored internally in base       |
bb807a
-   |                         | ``2**int_info.bits_per_digit``               |
bb807a
-   +-------------------------+----------------------------------------------+
bb807a
-   | :const:`sizeof_digit`   | size in bytes of the C type used to          |
bb807a
-   |                         | represent a digit                            |
bb807a
-   +-------------------------+----------------------------------------------+
bb807a
+   +----------------------------------------+-----------------------------------------------+
bb807a
+   | Attribute                              | Explanation                                   |
bb807a
+   +========================================+===============================================+
bb807a
+   | :const:`bits_per_digit`                | number of bits held in each digit.  Python    |
bb807a
+   |                                        | integers are stored internally in base        |
bb807a
+   |                                        | ``2**int_info.bits_per_digit``                |
bb807a
+   +----------------------------------------+-----------------------------------------------+
bb807a
+   | :const:`sizeof_digit`                  | size in bytes of the C type used to           |
bb807a
+   |                                        | represent a digit                             |
bb807a
+   +----------------------------------------+-----------------------------------------------+
bb807a
+   | :const:`default_max_str_digits`        | default value for                             |
bb807a
+   |                                        | :func:`sys.get_int_max_str_digits` when it    |
bb807a
+   |                                        | is not otherwise explicitly configured.       |
bb807a
+   +----------------------------------------+-----------------------------------------------+
bb807a
+   | :const:`str_digits_check_threshold`    | minimum non-zero value for                    |
bb807a
+   |                                        | :func:`sys.set_int_max_str_digits`,           |
bb807a
+   |                                        | :envvar:`PYTHONINTMAXSTRDIGITS`, or           |
bb807a
+   |                                        | :option:`-X int_max_str_digits <-X>`.         |
bb807a
+   +----------------------------------------+-----------------------------------------------+
bb807a
 
bb807a
    .. versionadded:: 3.1
bb807a
 
bb807a
+   .. versionchanged:: 3.9.14
bb807a
+      Added ``default_max_str_digits`` and ``str_digits_check_threshold``.
bb807a
+
bb807a
 
bb807a
 .. data:: __interactivehook__
bb807a
 
bb807a
@@ -1223,6 +1248,14 @@ always available.
bb807a
 
bb807a
    .. availability:: Unix.
bb807a
 
bb807a
+.. function:: set_int_max_str_digits(n)
bb807a
+
bb807a
+   Set the :ref:`integer string conversion length limitation
bb807a
+   <int_max_str_digits>` used by this interpreter. See also
bb807a
+   :func:`get_int_max_str_digits`.
bb807a
+
bb807a
+   .. versionadded:: 3.9.14
bb807a
+
bb807a
 .. function:: setprofile(profilefunc)
bb807a
 
bb807a
    .. index::
bb807a
diff --git a/Doc/library/test.rst b/Doc/library/test.rst
bb807a
index 16f908c..563197f 100644
bb807a
--- a/Doc/library/test.rst
bb807a
+++ b/Doc/library/test.rst
bb807a
@@ -1302,6 +1302,16 @@ The :mod:`test.support` module defines the following functions:
bb807a
    .. versionadded:: 3.6
bb807a
 
bb807a
 
bb807a
+.. function:: adjust_int_max_str_digits(max_digits)
bb807a
+
bb807a
+   This function returns a context manager that will change the global
bb807a
+   :func:`sys.set_int_max_str_digits` setting for the duration of the
bb807a
+   context to allow execution of test code that needs a different limit
bb807a
+   on the number of digits when converting between an integer and string.
bb807a
+
bb807a
+   .. versionadded:: 3.9.14
bb807a
+
bb807a
+
bb807a
 The :mod:`test.support` module defines the following classes:
bb807a
 
bb807a
 .. class:: TransientResource(exc, **kwargs)
bb807a
diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst
bb807a
index 5739388..66d8d57 100644
bb807a
--- a/Doc/using/cmdline.rst
bb807a
+++ b/Doc/using/cmdline.rst
bb807a
@@ -436,6 +436,9 @@ Miscellaneous options
bb807a
      stored in a traceback of a trace. Use ``-X tracemalloc=NFRAME`` to start
bb807a
      tracing with a traceback limit of *NFRAME* frames. See the
bb807a
      :func:`tracemalloc.start` for more information.
bb807a
+   * ``-X int_max_str_digits`` configures the :ref:`integer string conversion
bb807a
+     length limitation <int_max_str_digits>`.  See also
bb807a
+     :envvar:`PYTHONINTMAXSTRDIGITS`.
bb807a
    * ``-X importtime`` to show how long each import takes. It shows module
bb807a
      name, cumulative time (including nested imports) and self time (excluding
bb807a
      nested imports).  Note that its output may be broken in multi-threaded
bb807a
@@ -480,6 +483,9 @@ Miscellaneous options
bb807a
 
bb807a
       The ``-X showalloccount`` option has been removed.
bb807a
 
bb807a
+   .. versionadded:: 3.9.14
bb807a
+      The ``-X int_max_str_digits`` option.
bb807a
+
bb807a
    .. deprecated-removed:: 3.9 3.10
bb807a
       The ``-X oldparser`` option.
bb807a
 
bb807a
@@ -659,6 +665,13 @@ conflict.
bb807a
 
bb807a
    .. versionadded:: 3.2.3
bb807a
 
bb807a
+.. envvar:: PYTHONINTMAXSTRDIGITS
bb807a
+
bb807a
+   If this variable is set to an integer, it is used to configure the
bb807a
+   interpreter's global :ref:`integer string conversion length limitation
bb807a
+   <int_max_str_digits>`.
bb807a
+
bb807a
+   .. versionadded:: 3.9.14
bb807a
 
bb807a
 .. envvar:: PYTHONIOENCODING
bb807a
 
bb807a
diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst
bb807a
index 0662adb..2270d3e 100644
bb807a
--- a/Doc/whatsnew/3.9.rst
bb807a
+++ b/Doc/whatsnew/3.9.rst
bb807a
@@ -1570,3 +1570,17 @@ URL by the parser in :mod:`urllib.parse` preventing such attacks. The removal
bb807a
 characters are controlled by a new module level variable
bb807a
 ``urllib.parse._UNSAFE_URL_BYTES_TO_REMOVE``. (See :issue:`43882`)
bb807a
 
bb807a
+Notable security feature in 3.9.14
bb807a
+==================================
bb807a
+
bb807a
+Converting between :class:`int` and :class:`str` in bases other than 2
bb807a
+(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal)
bb807a
+now raises a :exc:`ValueError` if the number of digits in string form is
bb807a
+above a limit to avoid potential denial of service attacks due to the
bb807a
+algorithmic complexity. This is a mitigation for `CVE-2020-10735
bb807a
+<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735>`_.
bb807a
+This limit can be configured or disabled by environment variable, command
bb807a
+line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion
bb807a
+length limitation <int_max_str_digits>` documentation.  The default limit
bb807a
+is 4300 digits in string form.
bb807a
+
bb807a
diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h
bb807a
index 457a005..ad1b7e5 100644
bb807a
--- a/Include/internal/pycore_initconfig.h
bb807a
+++ b/Include/internal/pycore_initconfig.h
bb807a
@@ -156,6 +156,8 @@ extern PyStatus _PyConfig_SetPyArgv(
bb807a
     PyConfig *config,
bb807a
     const _PyArgv *args);
bb807a
 
bb807a
+extern int _Py_global_config_int_max_str_digits;
bb807a
+
bb807a
 
bb807a
 /* --- Function used for testing ---------------------------------- */
bb807a
 
bb807a
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
bb807a
index 551ad83..304d704 100644
bb807a
--- a/Include/internal/pycore_interp.h
bb807a
+++ b/Include/internal/pycore_interp.h
bb807a
@@ -154,6 +154,8 @@ struct _is {
bb807a
     */
bb807a
     PyLongObject* small_ints[_PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS];
bb807a
 #endif
bb807a
+
bb807a
+    int int_max_str_digits;
bb807a
 };
bb807a
 
bb807a
 /* Used by _PyImport_Cleanup() */
bb807a
diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h
bb807a
new file mode 100644
bb807a
index 0000000..ae04332
bb807a
--- /dev/null
bb807a
+++ b/Include/internal/pycore_long.h
bb807a
@@ -0,0 +1,49 @@
bb807a
+#ifndef Py_INTERNAL_LONG_H
bb807a
+#define Py_INTERNAL_LONG_H
bb807a
+#ifdef __cplusplus
bb807a
+extern "C" {
bb807a
+#endif
bb807a
+
bb807a
+#ifndef Py_BUILD_CORE
bb807a
+#  error "this header requires Py_BUILD_CORE define"
bb807a
+#endif
bb807a
+
bb807a
+/*
bb807a
+ * Default int base conversion size limitation: Denial of Service prevention.
bb807a
+ *
bb807a
+ * Chosen such that this isn't wildly slow on modern hardware and so that
bb807a
+ * everyone's existing deployed numpy test suite passes before
bb807a
+ * https://github.com/numpy/numpy/issues/22098 is widely available.
bb807a
+ *
bb807a
+ * $ python -m timeit -s 's = "1"*4300' 'int(s)'
bb807a
+ * 2000 loops, best of 5: 125 usec per loop
bb807a
+ * $ python -m timeit -s 's = "1"*4300; v = int(s)' 'str(v)'
bb807a
+ * 1000 loops, best of 5: 311 usec per loop
bb807a
+ * (zen2 cloud VM)
bb807a
+ *
bb807a
+ * 4300 decimal digits fits a ~14284 bit number.
bb807a
+ */
bb807a
+#define _PY_LONG_DEFAULT_MAX_STR_DIGITS 4300
bb807a
+/*
bb807a
+ * Threshold for max digits check.  For performance reasons int() and
bb807a
+ * int.__str__() don't checks values that are smaller than this
bb807a
+ * threshold.  Acts as a guaranteed minimum size limit for bignums that
bb807a
+ * applications can expect from CPython.
bb807a
+ *
bb807a
+ * % python -m timeit -s 's = "1"*640; v = int(s)' 'str(int(s))'
bb807a
+ * 20000 loops, best of 5: 12 usec per loop
bb807a
+ *
bb807a
+ * "640 digits should be enough for anyone." - gps
bb807a
+ * fits a ~2126 bit decimal number.
bb807a
+ */
bb807a
+#define _PY_LONG_MAX_STR_DIGITS_THRESHOLD 640
bb807a
+
bb807a
+#if ((_PY_LONG_DEFAULT_MAX_STR_DIGITS != 0) && \
bb807a
+   (_PY_LONG_DEFAULT_MAX_STR_DIGITS < _PY_LONG_MAX_STR_DIGITS_THRESHOLD))
bb807a
+# error "_PY_LONG_DEFAULT_MAX_STR_DIGITS smaller than threshold."
bb807a
+#endif
bb807a
+
bb807a
+#ifdef __cplusplus
bb807a
+}
bb807a
+#endif
bb807a
+#endif /* !Py_INTERNAL_LONG_H */
bb807a
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
bb807a
index 11818ac..e5d6e2b 100644
bb807a
--- a/Lib/test/support/__init__.py
bb807a
+++ b/Lib/test/support/__init__.py
bb807a
@@ -3251,6 +3251,17 @@ def clear_ignored_deprecations(*tokens: object) -> None:
bb807a
         warnings._filters_mutated()
bb807a
 
bb807a
 
bb807a
+@contextlib.contextmanager
bb807a
+def adjust_int_max_str_digits(max_digits):
bb807a
+    """Temporarily change the integer string conversion length limit."""
bb807a
+    current = sys.get_int_max_str_digits()
bb807a
+    try:
bb807a
+        sys.set_int_max_str_digits(max_digits)
bb807a
+        yield
bb807a
+    finally:
bb807a
+        sys.set_int_max_str_digits(current)
bb807a
+
bb807a
+
bb807a
 def fails_in_fips_mode(expected_error):
bb807a
     import _hashlib
bb807a
     if _hashlib.get_fips_mode():
bb807a
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
bb807a
index c3e3be6..a048d38 100644
bb807a
--- a/Lib/test/test_ast.py
bb807a
+++ b/Lib/test/test_ast.py
bb807a
@@ -978,6 +978,14 @@ Module(
bb807a
         self.assertRaises(ValueError, ast.literal_eval, '+True')
bb807a
         self.assertRaises(ValueError, ast.literal_eval, '2+3')
bb807a
 
bb807a
+    def test_literal_eval_str_int_limit(self):
bb807a
+        with support.adjust_int_max_str_digits(4000):
bb807a
+            ast.literal_eval('3'*4000)  # no error
bb807a
+            with self.assertRaises(SyntaxError) as err_ctx:
bb807a
+                ast.literal_eval('3'*4001)
bb807a
+            self.assertIn('Exceeds the limit ', str(err_ctx.exception))
bb807a
+            self.assertIn(' Consider hexadecimal ', str(err_ctx.exception))
bb807a
+
bb807a
     def test_literal_eval_complex(self):
bb807a
         # Issue #4907
bb807a
         self.assertEqual(ast.literal_eval('6j'), 6j)
bb807a
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
bb807a
index 712d861..7ad9263 100644
bb807a
--- a/Lib/test/test_cmd_line.py
bb807a
+++ b/Lib/test/test_cmd_line.py
bb807a
@@ -804,6 +804,39 @@ class CmdLineTest(unittest.TestCase):
bb807a
         self.assertTrue(proc.stderr.startswith(err_msg), proc.stderr)
bb807a
         self.assertNotEqual(proc.returncode, 0)
bb807a
 
bb807a
+    def test_int_max_str_digits(self):
bb807a
+        code = "import sys; print(sys.flags.int_max_str_digits, sys.get_int_max_str_digits())"
bb807a
+
bb807a
+        assert_python_failure('-X', 'int_max_str_digits', '-c', code)
bb807a
+        assert_python_failure('-X', 'int_max_str_digits=foo', '-c', code)
bb807a
+        assert_python_failure('-X', 'int_max_str_digits=100', '-c', code)
bb807a
+
bb807a
+        assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='foo')
bb807a
+        assert_python_failure('-c', code, PYTHONINTMAXSTRDIGITS='100')
bb807a
+
bb807a
+        def res2int(res):
bb807a
+            out = res.out.strip().decode("utf-8")
bb807a
+            return tuple(int(i) for i in out.split())
bb807a
+
bb807a
+        res = assert_python_ok('-c', code)
bb807a
+        self.assertEqual(res2int(res), (-1, sys.get_int_max_str_digits()))
bb807a
+        res = assert_python_ok('-X', 'int_max_str_digits=0', '-c', code)
bb807a
+        self.assertEqual(res2int(res), (0, 0))
bb807a
+        res = assert_python_ok('-X', 'int_max_str_digits=4000', '-c', code)
bb807a
+        self.assertEqual(res2int(res), (4000, 4000))
bb807a
+        res = assert_python_ok('-X', 'int_max_str_digits=100000', '-c', code)
bb807a
+        self.assertEqual(res2int(res), (100000, 100000))
bb807a
+
bb807a
+        res = assert_python_ok('-c', code, PYTHONINTMAXSTRDIGITS='0')
bb807a
+        self.assertEqual(res2int(res), (0, 0))
bb807a
+        res = assert_python_ok('-c', code, PYTHONINTMAXSTRDIGITS='4000')
bb807a
+        self.assertEqual(res2int(res), (4000, 4000))
bb807a
+        res = assert_python_ok(
bb807a
+            '-X', 'int_max_str_digits=6000', '-c', code,
bb807a
+            PYTHONINTMAXSTRDIGITS='4000'
bb807a
+        )
bb807a
+        self.assertEqual(res2int(res), (6000, 6000))
bb807a
+
bb807a
 
bb807a
 @unittest.skipIf(interpreter_requires_environment(),
bb807a
                  'Cannot run -I tests when PYTHON env vars are required.')
bb807a
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
bb807a
index 55716fd..ec776b9 100644
bb807a
--- a/Lib/test/test_compile.py
bb807a
+++ b/Lib/test/test_compile.py
bb807a
@@ -189,6 +189,19 @@ if 1:
bb807a
         self.assertEqual(eval("0o777"), 511)
bb807a
         self.assertEqual(eval("-0o0000010"), -8)
bb807a
 
bb807a
+    def test_int_literals_too_long(self):
bb807a
+        n = 3000
bb807a
+        source = f"a = 1\nb = 2\nc = {'3'*n}\nd = 4"
bb807a
+        with support.adjust_int_max_str_digits(n):
bb807a
+            compile(source, "<long_int_pass>", "exec")  # no errors.
bb807a
+        with support.adjust_int_max_str_digits(n-1):
bb807a
+            with self.assertRaises(SyntaxError) as err_ctx:
bb807a
+                compile(source, "<long_int_fail>", "exec")
bb807a
+            exc = err_ctx.exception
bb807a
+            self.assertEqual(exc.lineno, 3)
bb807a
+            self.assertIn('Exceeds the limit ', str(exc))
bb807a
+            self.assertIn(' Consider hexadecimal ', str(exc))
bb807a
+
bb807a
     def test_unary_minus(self):
bb807a
         # Verify treatment of unary minus on negative numbers SF bug #660455
bb807a
         if sys.maxsize == 2147483647:
bb807a
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
bb807a
index 3f30a93..c992815 100644
bb807a
--- a/Lib/test/test_decimal.py
bb807a
+++ b/Lib/test/test_decimal.py
bb807a
@@ -2462,6 +2462,15 @@ class CUsabilityTest(UsabilityTest):
bb807a
 class PyUsabilityTest(UsabilityTest):
bb807a
     decimal = P
bb807a
 
bb807a
+    def setUp(self):
bb807a
+        super().setUp()
bb807a
+        self._previous_int_limit = sys.get_int_max_str_digits()
bb807a
+        sys.set_int_max_str_digits(7000)
bb807a
+
bb807a
+    def tearDown(self):
bb807a
+        sys.set_int_max_str_digits(self._previous_int_limit)
bb807a
+        super().tearDown()
bb807a
+
bb807a
 class PythonAPItests(unittest.TestCase):
bb807a
 
bb807a
     def test_abc(self):
bb807a
@@ -4519,6 +4528,15 @@ class CCoverage(Coverage):
bb807a
 class PyCoverage(Coverage):
bb807a
     decimal = P
bb807a
 
bb807a
+    def setUp(self):
bb807a
+        super().setUp()
bb807a
+        self._previous_int_limit = sys.get_int_max_str_digits()
bb807a
+        sys.set_int_max_str_digits(7000)
bb807a
+
bb807a
+    def tearDown(self):
bb807a
+        sys.set_int_max_str_digits(self._previous_int_limit)
bb807a
+        super().tearDown()
bb807a
+
bb807a
 class PyFunctionality(unittest.TestCase):
bb807a
     """Extra functionality in decimal.py"""
bb807a
 
bb807a
diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py
bb807a
index 6fdf52e..cbbddf5 100644
bb807a
--- a/Lib/test/test_int.py
bb807a
+++ b/Lib/test/test_int.py
bb807a
@@ -1,4 +1,5 @@
bb807a
 import sys
bb807a
+import time
bb807a
 
bb807a
 import unittest
bb807a
 from test import support
bb807a
@@ -571,5 +572,200 @@ class IntTestCases(unittest.TestCase):
bb807a
         self.assertEqual(int('1_2_3_4_5_6_7', 32), 1144132807)
bb807a
 
bb807a
 
bb807a
+class IntStrDigitLimitsTests(unittest.TestCase):
bb807a
+
bb807a
+    int_class = int  # Override this in subclasses to reuse the suite.
bb807a
+
bb807a
+    def setUp(self):
bb807a
+        super().setUp()
bb807a
+        self._previous_limit = sys.get_int_max_str_digits()
bb807a
+        sys.set_int_max_str_digits(2048)
bb807a
+
bb807a
+    def tearDown(self):
bb807a
+        sys.set_int_max_str_digits(self._previous_limit)
bb807a
+        super().tearDown()
bb807a
+
bb807a
+    def test_disabled_limit(self):
bb807a
+        self.assertGreater(sys.get_int_max_str_digits(), 0)
bb807a
+        self.assertLess(sys.get_int_max_str_digits(), 20_000)
bb807a
+        with support.adjust_int_max_str_digits(0):
bb807a
+            self.assertEqual(sys.get_int_max_str_digits(), 0)
bb807a
+            i = self.int_class('1' * 20_000)
bb807a
+            str(i)
bb807a
+        self.assertGreater(sys.get_int_max_str_digits(), 0)
bb807a
+
bb807a
+    def test_max_str_digits_edge_cases(self):
bb807a
+        """Ignore the +/- sign and space padding."""
bb807a
+        int_class = self.int_class
bb807a
+        maxdigits = sys.get_int_max_str_digits()
bb807a
+
bb807a
+        int_class('1' * maxdigits)
bb807a
+        int_class(' ' + '1' * maxdigits)
bb807a
+        int_class('1' * maxdigits + ' ')
bb807a
+        int_class('+' + '1' * maxdigits)
bb807a
+        int_class('-' + '1' * maxdigits)
bb807a
+        self.assertEqual(len(str(10 ** (maxdigits - 1))), maxdigits)
bb807a
+
bb807a
+    def check(self, i, base=None):
bb807a
+        with self.assertRaises(ValueError):
bb807a
+            if base is None:
bb807a
+                self.int_class(i)
bb807a
+            else:
bb807a
+                self.int_class(i, base)
bb807a
+
bb807a
+    def test_max_str_digits(self):
bb807a
+        maxdigits = sys.get_int_max_str_digits()
bb807a
+
bb807a
+        self.check('1' * (maxdigits + 1))
bb807a
+        self.check(' ' + '1' * (maxdigits + 1))
bb807a
+        self.check('1' * (maxdigits + 1) + ' ')
bb807a
+        self.check('+' + '1' * (maxdigits + 1))
bb807a
+        self.check('-' + '1' * (maxdigits + 1))
bb807a
+        self.check('1' * (maxdigits + 1))
bb807a
+
bb807a
+        i = 10 ** maxdigits
bb807a
+        with self.assertRaises(ValueError):
bb807a
+            str(i)
bb807a
+
bb807a
+    def test_denial_of_service_prevented_int_to_str(self):
bb807a
+        """Regression test: ensure we fail before performing O(N**2) work."""
bb807a
+        maxdigits = sys.get_int_max_str_digits()
bb807a
+        assert maxdigits < 50_000, maxdigits  # A test prerequisite.
bb807a
+        get_time = time.process_time
bb807a
+        if get_time() <= 0:  # some platforms like WASM lack process_time()
bb807a
+            get_time = time.monotonic
bb807a
+
bb807a
+        huge_int = int(f'0x{"c"*65_000}', base=16)  # 78268 decimal digits.
bb807a
+        digits = 78_268
bb807a
+        with support.adjust_int_max_str_digits(digits):
bb807a
+            start = get_time()
bb807a
+            huge_decimal = str(huge_int)
bb807a
+        seconds_to_convert = get_time() - start
bb807a
+        self.assertEqual(len(huge_decimal), digits)
bb807a
+        # Ensuring that we chose a slow enough conversion to measure.
bb807a
+        # It takes 0.1 seconds on a Zen based cloud VM in an opt build.
bb807a
+        if seconds_to_convert < 0.005:
bb807a
+            raise unittest.SkipTest('"slow" conversion took only '
bb807a
+                                    f'{seconds_to_convert} seconds.')
bb807a
+
bb807a
+        # We test with the limit almost at the size needed to check performance.
bb807a
+        # The performant limit check is slightly fuzzy, give it a some room.
bb807a
+        with support.adjust_int_max_str_digits(int(.995 * digits)):
bb807a
+            with self.assertRaises(ValueError) as err:
bb807a
+                start = get_time()
bb807a
+                str(huge_int)
bb807a
+            seconds_to_fail_huge = get_time() - start
bb807a
+        self.assertIn('conversion', str(err.exception))
bb807a
+        self.assertLess(seconds_to_fail_huge, seconds_to_convert/8)
bb807a
+
bb807a
+        # Now we test that a conversion that would take 30x as long also fails
bb807a
+        # in a similarly fast fashion.
bb807a
+        extra_huge_int = int(f'0x{"c"*500_000}', base=16)  # 602060 digits.
bb807a
+        with self.assertRaises(ValueError) as err:
bb807a
+            start = get_time()
bb807a
+            # If not limited, 8 seconds said Zen based cloud VM.
bb807a
+            str(extra_huge_int)
bb807a
+        seconds_to_fail_extra_huge = get_time() - start
bb807a
+        self.assertIn('conversion', str(err.exception))
bb807a
+        self.assertLess(seconds_to_fail_extra_huge, seconds_to_convert/8)
bb807a
+
bb807a
+    def test_denial_of_service_prevented_str_to_int(self):
bb807a
+        """Regression test: ensure we fail before performing O(N**2) work."""
bb807a
+        maxdigits = sys.get_int_max_str_digits()
bb807a
+        assert maxdigits < 100_000, maxdigits  # A test prerequisite.
bb807a
+        get_time = time.process_time
bb807a
+        if get_time() <= 0:  # some platforms like WASM lack process_time()
bb807a
+            get_time = time.monotonic
bb807a
+
bb807a
+        digits = 133700
bb807a
+        huge = '8'*digits
bb807a
+        with support.adjust_int_max_str_digits(digits):
bb807a
+            start = get_time()
bb807a
+            int(huge)
bb807a
+        seconds_to_convert = get_time() - start
bb807a
+        # Ensuring that we chose a slow enough conversion to measure.
bb807a
+        # It takes 0.1 seconds on a Zen based cloud VM in an opt build.
bb807a
+        if seconds_to_convert < 0.005:
bb807a
+            raise unittest.SkipTest('"slow" conversion took only '
bb807a
+                                    f'{seconds_to_convert} seconds.')
bb807a
+
bb807a
+        with support.adjust_int_max_str_digits(digits - 1):
bb807a
+            with self.assertRaises(ValueError) as err:
bb807a
+                start = get_time()
bb807a
+                int(huge)
bb807a
+            seconds_to_fail_huge = get_time() - start
bb807a
+        self.assertIn('conversion', str(err.exception))
bb807a
+        self.assertLess(seconds_to_fail_huge, seconds_to_convert/8)
bb807a
+
bb807a
+        # Now we test that a conversion that would take 30x as long also fails
bb807a
+        # in a similarly fast fashion.
bb807a
+        extra_huge = '7'*1_200_000
bb807a
+        with self.assertRaises(ValueError) as err:
bb807a
+            start = get_time()
bb807a
+            # If not limited, 8 seconds in the Zen based cloud VM.
bb807a
+            int(extra_huge)
bb807a
+        seconds_to_fail_extra_huge = get_time() - start
bb807a
+        self.assertIn('conversion', str(err.exception))
bb807a
+        self.assertLess(seconds_to_fail_extra_huge, seconds_to_convert/8)
bb807a
+
bb807a
+    def test_power_of_two_bases_unlimited(self):
bb807a
+        """The limit does not apply to power of 2 bases."""
bb807a
+        maxdigits = sys.get_int_max_str_digits()
bb807a
+
bb807a
+        for base in (2, 4, 8, 16, 32):
bb807a
+            with self.subTest(base=base):
bb807a
+                self.int_class('1' * (maxdigits + 1), base)
bb807a
+                assert maxdigits < 100_000
bb807a
+                self.int_class('1' * 100_000, base)
bb807a
+
bb807a
+    def test_underscores_ignored(self):
bb807a
+        maxdigits = sys.get_int_max_str_digits()
bb807a
+
bb807a
+        triples = maxdigits // 3
bb807a
+        s = '111' * triples
bb807a
+        s_ = '1_11' * triples
bb807a
+        self.int_class(s)  # succeeds
bb807a
+        self.int_class(s_)  # succeeds
bb807a
+        self.check(f'{s}111')
bb807a
+        self.check(f'{s_}_111')
bb807a
+
bb807a
+    def test_sign_not_counted(self):
bb807a
+        int_class = self.int_class
bb807a
+        max_digits = sys.get_int_max_str_digits()
bb807a
+        s = '5' * max_digits
bb807a
+        i = int_class(s)
bb807a
+        pos_i = int_class(f'+{s}')
bb807a
+        assert i == pos_i
bb807a
+        neg_i = int_class(f'-{s}')
bb807a
+        assert -pos_i == neg_i
bb807a
+        str(pos_i)
bb807a
+        str(neg_i)
bb807a
+
bb807a
+    def _other_base_helper(self, base):
bb807a
+        int_class = self.int_class
bb807a
+        max_digits = sys.get_int_max_str_digits()
bb807a
+        s = '2' * max_digits
bb807a
+        i = int_class(s, base)
bb807a
+        if base > 10:
bb807a
+            with self.assertRaises(ValueError):
bb807a
+                str(i)
bb807a
+        elif base < 10:
bb807a
+            str(i)
bb807a
+        with self.assertRaises(ValueError) as err:
bb807a
+            int_class(f'{s}1', base)
bb807a
+
bb807a
+    def test_int_from_other_bases(self):
bb807a
+        base = 3
bb807a
+        with self.subTest(base=base):
bb807a
+            self._other_base_helper(base)
bb807a
+        base = 36
bb807a
+        with self.subTest(base=base):
bb807a
+            self._other_base_helper(base)
bb807a
+
bb807a
+
bb807a
+class IntSubclassStrDigitLimitsTests(IntStrDigitLimitsTests):
bb807a
+    int_class = IntSubclass
bb807a
+
bb807a
+
bb807a
 if __name__ == "__main__":
bb807a
     unittest.main()
bb807a
diff --git a/Lib/test/test_json/test_decode.py b/Lib/test/test_json/test_decode.py
bb807a
index fdb9e62..124045b 100644
bb807a
--- a/Lib/test/test_json/test_decode.py
bb807a
+++ b/Lib/test/test_json/test_decode.py
bb807a
@@ -2,6 +2,7 @@ import decimal
bb807a
 from io import StringIO
bb807a
 from collections import OrderedDict
bb807a
 from test.test_json import PyTest, CTest
bb807a
+from test import support
bb807a
 
bb807a
 
bb807a
 class TestDecode:
bb807a
@@ -95,5 +96,13 @@ class TestDecode:
bb807a
         d = self.json.JSONDecoder()
bb807a
         self.assertRaises(ValueError, d.raw_decode, 'a'*42, -50000)
bb807a
 
bb807a
+    def test_limit_int(self):
bb807a
+        maxdigits = 5000
bb807a
+        with support.adjust_int_max_str_digits(maxdigits):
bb807a
+            self.loads('1' * maxdigits)
bb807a
+            with self.assertRaises(ValueError):
bb807a
+                self.loads('1' * (maxdigits + 1))
bb807a
+
bb807a
+
bb807a
 class TestPyDecode(TestDecode, PyTest): pass
bb807a
 class TestCDecode(TestDecode, CTest): pass
bb807a
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
bb807a
index 2f1e5e9..b11f1ff 100644
bb807a
--- a/Lib/test/test_sys.py
bb807a
+++ b/Lib/test/test_sys.py
bb807a
@@ -409,11 +409,17 @@ class SysModuleTest(unittest.TestCase):
bb807a
         self.assertIsInstance(sys.executable, str)
bb807a
         self.assertEqual(len(sys.float_info), 11)
bb807a
         self.assertEqual(sys.float_info.radix, 2)
bb807a
-        self.assertEqual(len(sys.int_info), 2)
bb807a
+        self.assertEqual(len(sys.int_info), 4)
bb807a
         self.assertTrue(sys.int_info.bits_per_digit % 5 == 0)
bb807a
         self.assertTrue(sys.int_info.sizeof_digit >= 1)
bb807a
+        self.assertGreaterEqual(sys.int_info.default_max_str_digits, 500)
bb807a
+        self.assertGreaterEqual(sys.int_info.str_digits_check_threshold, 100)
bb807a
+        self.assertGreater(sys.int_info.default_max_str_digits,
bb807a
+                           sys.int_info.str_digits_check_threshold)
bb807a
         self.assertEqual(type(sys.int_info.bits_per_digit), int)
bb807a
         self.assertEqual(type(sys.int_info.sizeof_digit), int)
bb807a
+        self.assertIsInstance(sys.int_info.default_max_str_digits, int)
bb807a
+        self.assertIsInstance(sys.int_info.str_digits_check_threshold, int)
bb807a
         self.assertIsInstance(sys.hexversion, int)
bb807a
 
bb807a
         self.assertEqual(len(sys.hash_info), 9)
bb807a
@@ -517,7 +523,8 @@ class SysModuleTest(unittest.TestCase):
bb807a
                  "inspect", "interactive", "optimize",
bb807a
                  "dont_write_bytecode", "no_user_site", "no_site",
bb807a
                  "ignore_environment", "verbose", "bytes_warning", "quiet",
bb807a
-                 "hash_randomization", "isolated", "dev_mode", "utf8_mode")
bb807a
+                 "hash_randomization", "isolated", "dev_mode", "utf8_mode",
bb807a
+                 "int_max_str_digits")
bb807a
         for attr in attrs:
bb807a
             self.assertTrue(hasattr(sys.flags, attr), attr)
bb807a
             attr_type = bool if attr == "dev_mode" else int
bb807a
diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py
bb807a
index f714b77..d12da2f 100644
bb807a
--- a/Lib/test/test_xmlrpc.py
bb807a
+++ b/Lib/test/test_xmlrpc.py
bb807a
@@ -285,6 +285,16 @@ class XMLRPCTestCase(unittest.TestCase):
bb807a
         check('<bigdecimal>9876543210.0123456789</bigdecimal>',
bb807a
               decimal.Decimal('9876543210.0123456789'))
bb807a
 
bb807a
+    def test_limit_int(self):
bb807a
+        check = self.check_loads
bb807a
+        maxdigits = 5000
bb807a
+        with support.adjust_int_max_str_digits(maxdigits):
bb807a
+            s = '1' * (maxdigits + 1)
bb807a
+            with self.assertRaises(ValueError):
bb807a
+                check(f'<int>{s}</int>', None)
bb807a
+            with self.assertRaises(ValueError):
bb807a
+                check(f'<biginteger>{s}</biginteger>', None)
bb807a
+
bb807a
     def test_get_host_info(self):
bb807a
         # see bug #3613, this raised a TypeError
bb807a
         transp = xmlrpc.client.Transport()
bb807a
diff --git a/Makefile.pre.in b/Makefile.pre.in
bb807a
index c1cf158..b64837c 100644
bb807a
--- a/Makefile.pre.in
bb807a
+++ b/Makefile.pre.in
bb807a
@@ -1153,6 +1153,7 @@ PYTHON_HEADERS= \
bb807a
 		$(srcdir)/Include/internal/pycore_import.h \
bb807a
 		$(srcdir)/Include/internal/pycore_initconfig.h \
bb807a
 		$(srcdir)/Include/internal/pycore_interp.h \
bb807a
+		$(srcdir)/Include/internal/pycore_long.h \
bb807a
 		$(srcdir)/Include/internal/pycore_object.h \
bb807a
 		$(srcdir)/Include/internal/pycore_pathconfig.h \
bb807a
 		$(srcdir)/Include/internal/pycore_pyerrors.h \
bb807a
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
bb807a
new file mode 100644
bb807a
index 0000000..8eb8a34
bb807a
--- /dev/null
bb807a
+++ b/Misc/NEWS.d/next/Security/2022-08-07-16-53-38.gh-issue-95778.ch010gps.rst
bb807a
@@ -0,0 +1,14 @@
bb807a
+Converting between :class:`int` and :class:`str` in bases other than 2
bb807a
+(binary), 4, 8 (octal), 16 (hexadecimal), or 32 such as base 10 (decimal) now
bb807a
+raises a :exc:`ValueError` if the number of digits in string form is above a
bb807a
+limit to avoid potential denial of service attacks due to the algorithmic
bb807a
+complexity. This is a mitigation for `CVE-2020-10735
bb807a
+<https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735>`_.
bb807a
+
bb807a
+This new limit can be configured or disabled by environment variable, command
bb807a
+line flag, or :mod:`sys` APIs. See the :ref:`integer string conversion length
bb807a
+limitation <int_max_str_digits>` documentation.  The default limit is 4300
bb807a
+digits in string form.
bb807a
+
bb807a
+Patch by Gregory P. Smith [Google] and Christian Heimes [Red Hat] with feedback
bb807a
+from Victor Stinner, Thomas Wouters, Steve Dower, Ned Deily, and Mark Dickinson.
bb807a
diff --git a/Objects/longobject.c b/Objects/longobject.c
bb807a
index cf13b2c..ec18ec3 100644
bb807a
--- a/Objects/longobject.c
bb807a
+++ b/Objects/longobject.c
bb807a
@@ -3,7 +3,9 @@
bb807a
 /* XXX The functional organization of this file is terrible */
bb807a
 
bb807a
 #include "Python.h"
bb807a
+#include "pycore_initconfig.h" // _Py_global_config_int_max_str_digits
bb807a
 #include "pycore_interp.h"    // _PY_NSMALLPOSINTS
bb807a
+#include "pycore_long.h"
bb807a
 #include "pycore_pystate.h"   // _Py_IsMainInterpreter()
bb807a
 #include "longintrepr.h"
bb807a
 
bb807a
@@ -36,6 +38,9 @@ PyObject *_PyLong_One = NULL;
bb807a
 #define IS_SMALL_INT(ival) (-NSMALLNEGINTS <= (ival) && (ival) < NSMALLPOSINTS)
bb807a
 #define IS_SMALL_UINT(ival) ((ival) < NSMALLPOSINTS)
bb807a
 
bb807a
+#define _MAX_STR_DIGITS_ERROR_FMT_TO_INT "Exceeds the limit (%d) for integer string conversion: value has %zd digits"
bb807a
+#define _MAX_STR_DIGITS_ERROR_FMT_TO_STR "Exceeds the limit (%d) for integer string conversion"
bb807a
+
bb807a
 static PyObject *
bb807a
 get_small_int(sdigit ival)
bb807a
 {
bb807a
@@ -1718,6 +1723,23 @@ long_to_decimal_string_internal(PyObject *aa,
bb807a
     size_a = Py_ABS(Py_SIZE(a));
bb807a
     negative = Py_SIZE(a) < 0;
bb807a
 
bb807a
+    /* quick and dirty pre-check for overflowing the decimal digit limit,
bb807a
+       based on the inequality 10/3 >= log2(10)
bb807a
+
bb807a
+       explanation in https://github.com/python/cpython/pull/96537
bb807a
+    */
bb807a
+    if (size_a >= 10 * _PY_LONG_MAX_STR_DIGITS_THRESHOLD
bb807a
+                  / (3 * PyLong_SHIFT) + 2) {
bb807a
+        PyInterpreterState *interp = _PyInterpreterState_GET();
bb807a
+        int max_str_digits = interp->int_max_str_digits;
bb807a
+        if ((max_str_digits > 0) &&
bb807a
+            (max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10)) {
bb807a
+            PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_STR,
bb807a
+                         max_str_digits);
bb807a
+            return -1;
bb807a
+        }
bb807a
+    }
bb807a
+
bb807a
     /* quick and dirty upper bound for the number of digits
bb807a
        required to express a in base _PyLong_DECIMAL_BASE:
bb807a
 
bb807a
@@ -1777,6 +1799,17 @@ long_to_decimal_string_internal(PyObject *aa,
bb807a
         tenpow *= 10;
bb807a
         strlen++;
bb807a
     }
bb807a
+    if (strlen > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) {
bb807a
+        PyInterpreterState *interp = _PyInterpreterState_GET();
bb807a
+        int max_str_digits = interp->int_max_str_digits;
bb807a
+        Py_ssize_t strlen_nosign = strlen - negative;
bb807a
+        if ((max_str_digits > 0) && (strlen_nosign > max_str_digits)) {
bb807a
+            Py_DECREF(scratch);
bb807a
+            PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_STR,
bb807a
+                         max_str_digits);
bb807a
+            return -1;
bb807a
+        }
bb807a
+    }
bb807a
     if (writer) {
bb807a
         if (_PyUnicodeWriter_Prepare(writer, strlen, '9') == -1) {
bb807a
             Py_DECREF(scratch);
bb807a
@@ -2290,6 +2323,7 @@ PyLong_FromString(const char *str, char **pend, int base)
bb807a
 
bb807a
     start = str;
bb807a
     if ((base & (base - 1)) == 0) {
bb807a
+        /* binary bases are not limited by int_max_str_digits */
bb807a
         int res = long_from_binary_base(&str, base, &z);
bb807a
         if (res < 0) {
bb807a
             /* Syntax error. */
bb807a
@@ -2441,6 +2475,17 @@ digit beyond the first.
bb807a
             goto onError;
bb807a
         }
bb807a
 
bb807a
+        /* Limit the size to avoid excessive computation attacks. */
bb807a
+        if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) {
bb807a
+            PyInterpreterState *interp = _PyInterpreterState_GET();
bb807a
+            int max_str_digits = interp->int_max_str_digits;
bb807a
+            if ((max_str_digits > 0) && (digits > max_str_digits)) {
bb807a
+                PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_INT,
bb807a
+                             max_str_digits, digits);
bb807a
+                return NULL;
bb807a
+            }
bb807a
+        }
bb807a
+
bb807a
         /* Create an int object that can contain the largest possible
bb807a
          * integer with this base and length.  Note that there's no
bb807a
          * need to initialize z->ob_digit -- no slot is read up before
bb807a
@@ -5071,6 +5116,7 @@ long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase)
bb807a
         }
bb807a
         return PyLong_FromLong(0L);
bb807a
     }
bb807a
+    /* default base and limit, forward to standard implementation */
bb807a
     if (obase == NULL)
bb807a
         return PyNumber_Long(x);
bb807a
 
bb807a
@@ -5723,6 +5769,8 @@ internal representation of integers.  The attributes are read only.");
bb807a
 static PyStructSequence_Field int_info_fields[] = {
bb807a
     {"bits_per_digit", "size of a digit in bits"},
bb807a
     {"sizeof_digit", "size in bytes of the C type used to represent a digit"},
bb807a
+    {"default_max_str_digits", "maximum string conversion digits limitation"},
bb807a
+    {"str_digits_check_threshold", "minimum positive value for int_max_str_digits"},
bb807a
     {NULL, NULL}
bb807a
 };
bb807a
 
bb807a
@@ -5730,7 +5778,7 @@ static PyStructSequence_Desc int_info_desc = {
bb807a
     "sys.int_info",   /* name */
bb807a
     int_info__doc__,  /* doc */
bb807a
     int_info_fields,  /* fields */
bb807a
-    2                 /* number of fields */
bb807a
+    4                 /* number of fields */
bb807a
 };
bb807a
 
bb807a
 PyObject *
bb807a
@@ -5745,6 +5793,17 @@ PyLong_GetInfo(void)
bb807a
                               PyLong_FromLong(PyLong_SHIFT));
bb807a
     PyStructSequence_SET_ITEM(int_info, field++,
bb807a
                               PyLong_FromLong(sizeof(digit)));
bb807a
+    /*
bb807a
+     * The following two fields were added after investigating uses of
bb807a
+     * sys.int_info in the wild: Exceedingly rarely used. The ONLY use found was
bb807a
+     * numba using sys.int_info.bits_per_digit as attribute access rather than
bb807a
+     * sequence unpacking. Cython and sympy also refer to sys.int_info but only
bb807a
+     * as info for debugging. No concern about adding these in a backport.
bb807a
+     */
bb807a
+    PyStructSequence_SET_ITEM(int_info, field++,
bb807a
+                              PyLong_FromLong(_PY_LONG_DEFAULT_MAX_STR_DIGITS));
bb807a
+    PyStructSequence_SET_ITEM(int_info, field++,
bb807a
+                              PyLong_FromLong(_PY_LONG_MAX_STR_DIGITS_THRESHOLD));
bb807a
     if (PyErr_Occurred()) {
bb807a
         Py_CLEAR(int_info);
bb807a
         return NULL;
bb807a
@@ -5790,6 +5849,10 @@ _PyLong_Init(PyThreadState *tstate)
bb807a
             }
bb807a
         }
bb807a
     }
bb807a
+    tstate->interp->int_max_str_digits = _Py_global_config_int_max_str_digits;
bb807a
+    if (tstate->interp->int_max_str_digits == -1) {
bb807a
+        tstate->interp->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS;
bb807a
+    }
bb807a
 
bb807a
     return 1;
bb807a
 }
bb807a
diff --git a/Parser/pegen/pegen.c b/Parser/pegen/pegen.c
bb807a
index cdfbc12..15b06ce 100644
bb807a
--- a/Parser/pegen/pegen.c
bb807a
+++ b/Parser/pegen/pegen.c
bb807a
@@ -967,6 +967,24 @@ _PyPegen_number_token(Parser *p)
bb807a
 
bb807a
     if (c == NULL) {
bb807a
         p->error_indicator = 1;
bb807a
+        PyObject *exc_type, *exc_value, *exc_tb;
bb807a
+        PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
bb807a
+        // The only way a ValueError should happen in _this_ code is via
bb807a
+        // PyLong_FromString hitting a length limit.
bb807a
+        if (exc_type == PyExc_ValueError && exc_value != NULL) {
bb807a
+            // The Fetch acted as PyErr_Clear(), we're replacing the exception.
bb807a
+            Py_XDECREF(exc_tb);
bb807a
+            Py_DECREF(exc_type);
bb807a
+            RAISE_ERROR_KNOWN_LOCATION(
bb807a
+                p, PyExc_SyntaxError,
bb807a
+                t->lineno, 0 /* col_offset */,
bb807a
+                "%S - Consider hexadecimal for huge integer literals "
bb807a
+                "to avoid decimal conversion limits.",
bb807a
+                exc_value);
bb807a
+            Py_DECREF(exc_value);
bb807a
+        } else {
bb807a
+            PyErr_Restore(exc_type, exc_value, exc_tb);
bb807a
+        }
bb807a
         return NULL;
bb807a
     }
bb807a
 
bb807a
diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
bb807a
index 4615eba..4144408 100644
bb807a
--- a/Python/clinic/sysmodule.c.h
bb807a
+++ b/Python/clinic/sysmodule.c.h
bb807a
@@ -667,6 +667,64 @@ exit:
bb807a
 
bb807a
 #endif /* defined(USE_MALLOPT) */
bb807a
 
bb807a
+PyDoc_STRVAR(sys_get_int_max_str_digits__doc__,
bb807a
+"get_int_max_str_digits($module, /)\n"
bb807a
+"--\n"
bb807a
+"\n"
bb807a
+"Set the maximum string digits limit for non-binary int<->str conversions.");
bb807a
+
bb807a
+#define SYS_GET_INT_MAX_STR_DIGITS_METHODDEF    \
bb807a
+    {"get_int_max_str_digits", (PyCFunction)sys_get_int_max_str_digits, METH_NOARGS, sys_get_int_max_str_digits__doc__},
bb807a
+
bb807a
+static PyObject *
bb807a
+sys_get_int_max_str_digits_impl(PyObject *module);
bb807a
+
bb807a
+static PyObject *
bb807a
+sys_get_int_max_str_digits(PyObject *module, PyObject *Py_UNUSED(ignored))
bb807a
+{
bb807a
+    return sys_get_int_max_str_digits_impl(module);
bb807a
+}
bb807a
+
bb807a
+PyDoc_STRVAR(sys_set_int_max_str_digits__doc__,
bb807a
+"set_int_max_str_digits($module, /, maxdigits)\n"
bb807a
+"--\n"
bb807a
+"\n"
bb807a
+"Set the maximum string digits limit for non-binary int<->str conversions.");
bb807a
+
bb807a
+#define SYS_SET_INT_MAX_STR_DIGITS_METHODDEF    \
bb807a
+    {"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__},
bb807a
+
bb807a
+static PyObject *
bb807a
+sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits);
bb807a
+
bb807a
+static PyObject *
bb807a
+sys_set_int_max_str_digits(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
bb807a
+{
bb807a
+    PyObject *return_value = NULL;
bb807a
+    static const char * const _keywords[] = {"maxdigits", NULL};
bb807a
+    static _PyArg_Parser _parser = {NULL, _keywords, "set_int_max_str_digits", 0};
bb807a
+    PyObject *argsbuf[1];
bb807a
+    int maxdigits;
bb807a
+
bb807a
+    args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
bb807a
+    if (!args) {
bb807a
+        goto exit;
bb807a
+    }
bb807a
+    if (PyFloat_Check(args[0])) {
bb807a
+        PyErr_SetString(PyExc_TypeError,
bb807a
+                        "integer argument expected, got float" );
bb807a
+        goto exit;
bb807a
+    }
bb807a
+    maxdigits = _PyLong_AsInt(args[0]);
bb807a
+    if (maxdigits == -1 && PyErr_Occurred()) {
bb807a
+        goto exit;
bb807a
+    }
bb807a
+    return_value = sys_set_int_max_str_digits_impl(module, maxdigits);
bb807a
+
bb807a
+exit:
bb807a
+    return return_value;
bb807a
+}
bb807a
+
bb807a
 PyDoc_STRVAR(sys_getrefcount__doc__,
bb807a
 "getrefcount($module, object, /)\n"
bb807a
 "--\n"
bb807a
@@ -970,4 +1028,4 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored))
bb807a
 #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
bb807a
     #define SYS_GETANDROIDAPILEVEL_METHODDEF
bb807a
 #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
bb807a
-/*[clinic end generated code: output=39eb34a01fb9a919 input=a9049054013a1b77]*/
bb807a
+/*[clinic end generated code: output=401254a595859ac6 input=a9049054013a1b77]*/
bb807a
diff --git a/Python/initconfig.c b/Python/initconfig.c
bb807a
index 116ee33..a2c435f 100644
bb807a
--- a/Python/initconfig.c
bb807a
+++ b/Python/initconfig.c
bb807a
@@ -3,6 +3,7 @@
bb807a
 #include "pycore_getopt.h"        // _PyOS_GetOpt()
bb807a
 #include "pycore_initconfig.h"    // _PyStatus_OK()
bb807a
 #include "pycore_interp.h"        // _PyInterpreterState.runtime
bb807a
+#include "pycore_long.h"          // _PY_LONG_MAX_STR_DIGITS_THRESHOLD
bb807a
 #include "pycore_pathconfig.h"    // _Py_path_config
bb807a
 #include "pycore_pyerrors.h"      // _PyErr_Fetch()
bb807a
 #include "pycore_pylifecycle.h"   // _Py_PreInitializeFromConfig()
bb807a
@@ -99,6 +100,9 @@ static const char usage_3[] = "\
bb807a
              otherwise activate automatically)\n\
bb807a
          -X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the\n\
bb807a
              given directory instead of to the code tree\n\
bb807a
+         -X int_max_str_digits=number: limit the size of int<->str conversions.\n\
bb807a
+             This helps avoid denial of service attacks when parsing untrusted data.\n\
bb807a
+             The default is sys.int_info.default_max_str_digits.  0 disables.\n\
bb807a
 \n\
bb807a
 --check-hash-based-pycs always|default|never:\n\
bb807a
     control how Python invalidates hash-based .pyc files\n\
bb807a
@@ -125,6 +129,10 @@ static const char usage_6[] =
bb807a
 "   to seed the hashes of str and bytes objects.  It can also be set to an\n"
bb807a
 "   integer in the range [0,4294967295] to get hash values with a\n"
bb807a
 "   predictable seed.\n"
bb807a
+"PYTHONINTMAXSTRDIGITS: limits the maximum digit characters in an int value\n"
bb807a
+"   when converting from a string and when converting an int back to a str.\n"
bb807a
+"   A value of 0 disables the limit.  Conversions to or from bases 2, 4, 8,\n"
bb807a
+"   16, and 32 are never limited.\n"
bb807a
 "PYTHONMALLOC: set the Python memory allocators and/or install debug hooks\n"
bb807a
 "   on Python memory allocators. Use PYTHONMALLOC=debug to install debug\n"
bb807a
 "   hooks.\n"
bb807a
@@ -646,6 +654,10 @@ _PyConfig_InitCompatConfig(PyConfig *config)
bb807a
     config->_use_peg_parser = 1;
bb807a
 }
bb807a
 
bb807a
+/* Excluded from public struct PyConfig for backporting reasons. */
bb807a
+/* default to unconfigured, _PyLong_Init() does the rest */
bb807a
+int _Py_global_config_int_max_str_digits = -1;
bb807a
+
bb807a
 
bb807a
 static void
bb807a
 config_init_defaults(PyConfig *config)
bb807a
@@ -1410,6 +1422,48 @@ config_init_tracemalloc(PyConfig *config)
bb807a
     return _PyStatus_OK();
bb807a
 }
bb807a
 
bb807a
+static PyStatus
bb807a
+config_init_int_max_str_digits(PyConfig *config)
bb807a
+{
bb807a
+    int maxdigits;
bb807a
+    int valid = 0;
bb807a
+
bb807a
+    const char *env = config_get_env(config, "PYTHONINTMAXSTRDIGITS");
bb807a
+    if (env) {
bb807a
+        if (!_Py_str_to_int(env, &maxdigits)) {
bb807a
+            valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD));
bb807a
+        }
bb807a
+        if (!valid) {
bb807a
+#define STRINGIFY(VAL) _STRINGIFY(VAL)
bb807a
+#define _STRINGIFY(VAL) #VAL
bb807a
+            return _PyStatus_ERR(
bb807a
+                    "PYTHONINTMAXSTRDIGITS: invalid limit; must be >= "
bb807a
+                    STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD)
bb807a
+                    " or 0 for unlimited.");
bb807a
+        }
bb807a
+        _Py_global_config_int_max_str_digits = maxdigits;
bb807a
+    }
bb807a
+
bb807a
+    const wchar_t *xoption = config_get_xoption(config, L"int_max_str_digits");
bb807a
+    if (xoption) {
bb807a
+        const wchar_t *sep = wcschr(xoption, L'=');
bb807a
+        if (sep) {
bb807a
+            if (!config_wstr_to_int(sep + 1, &maxdigits)) {
bb807a
+                valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD));
bb807a
+            }
bb807a
+        }
bb807a
+        if (!valid) {
bb807a
+            return _PyStatus_ERR(
bb807a
+                    "-X int_max_str_digits: invalid limit; must be >= "
bb807a
+                    STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD)
bb807a
+                    " or 0 for unlimited.");
bb807a
+#undef _STRINGIFY
bb807a
+#undef STRINGIFY
bb807a
+        }
bb807a
+        _Py_global_config_int_max_str_digits = maxdigits;
bb807a
+    }
bb807a
+    return _PyStatus_OK();
bb807a
+}
bb807a
 
bb807a
 static PyStatus
bb807a
 config_init_pycache_prefix(PyConfig *config)
bb807a
@@ -1466,6 +1520,12 @@ config_read_complex_options(PyConfig *config)
bb807a
             return status;
bb807a
         }
bb807a
     }
bb807a
+    if (_Py_global_config_int_max_str_digits < 0) {
bb807a
+        status = config_init_int_max_str_digits(config);
bb807a
+        if (_PyStatus_EXCEPTION(status)) {
bb807a
+            return status;
bb807a
+        }
bb807a
+    }
bb807a
 
bb807a
     if (config->pycache_prefix == NULL) {
bb807a
         status = config_init_pycache_prefix(config);
bb807a
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
bb807a
index a52b299..8efa850 100644
bb807a
--- a/Python/sysmodule.c
bb807a
+++ b/Python/sysmodule.c
bb807a
@@ -19,6 +19,7 @@ Data members:
bb807a
 #include "frameobject.h"          // PyFrame_GetBack()
bb807a
 #include "pycore_ceval.h"
bb807a
 #include "pycore_initconfig.h"
bb807a
+#include "pycore_long.h"          // _PY_LONG_MAX_STR_DIGITS_THRESHOLD
bb807a
 #include "pycore_object.h"
bb807a
 #include "pycore_pathconfig.h"
bb807a
 #include "pycore_pyerrors.h"
bb807a
@@ -1636,6 +1637,45 @@ sys_mdebug_impl(PyObject *module, int flag)
bb807a
 }
bb807a
 #endif /* USE_MALLOPT */
bb807a
 
bb807a
+
bb807a
+/*[clinic input]
bb807a
+sys.get_int_max_str_digits
bb807a
+
bb807a
+Set the maximum string digits limit for non-binary int<->str conversions.
bb807a
+[clinic start generated code]*/
bb807a
+
bb807a
+static PyObject *
bb807a
+sys_get_int_max_str_digits_impl(PyObject *module)
bb807a
+/*[clinic end generated code: output=0042f5e8ae0e8631 input=8dab13e2023e60d5]*/
bb807a
+{
bb807a
+    PyInterpreterState *interp = _PyInterpreterState_GET();
bb807a
+    return PyLong_FromSsize_t(interp->int_max_str_digits);
bb807a
+}
bb807a
+
bb807a
+/*[clinic input]
bb807a
+sys.set_int_max_str_digits
bb807a
+
bb807a
+    maxdigits: int
bb807a
+
bb807a
+Set the maximum string digits limit for non-binary int<->str conversions.
bb807a
+[clinic start generated code]*/
bb807a
+
bb807a
+static PyObject *
bb807a
+sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits)
bb807a
+/*[clinic end generated code: output=734d4c2511f2a56d input=d7e3f325db6910c5]*/
bb807a
+{
bb807a
+    PyThreadState *tstate = _PyThreadState_GET();
bb807a
+    if ((!maxdigits) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)) {
bb807a
+        tstate->interp->int_max_str_digits = maxdigits;
bb807a
+        Py_RETURN_NONE;
bb807a
+    } else {
bb807a
+        PyErr_Format(
bb807a
+            PyExc_ValueError, "maxdigits must be 0 or larger than %d",
bb807a
+            _PY_LONG_MAX_STR_DIGITS_THRESHOLD);
bb807a
+        return NULL;
bb807a
+    }
bb807a
+}
bb807a
+
bb807a
 size_t
bb807a
 _PySys_GetSizeOf(PyObject *o)
bb807a
 {
bb807a
@@ -1980,6 +2020,8 @@ static PyMethodDef sys_methods[] = {
bb807a
     SYS_GET_ASYNCGEN_HOOKS_METHODDEF
bb807a
     SYS_GETANDROIDAPILEVEL_METHODDEF
bb807a
     SYS_UNRAISABLEHOOK_METHODDEF
bb807a
+    SYS_GET_INT_MAX_STR_DIGITS_METHODDEF
bb807a
+    SYS_SET_INT_MAX_STR_DIGITS_METHODDEF
bb807a
     {NULL,              NULL}           /* sentinel */
bb807a
 };
bb807a
 
bb807a
@@ -2440,6 +2482,7 @@ static PyStructSequence_Field flags_fields[] = {
bb807a
     {"isolated",                "-I"},
bb807a
     {"dev_mode",                "-X dev"},
bb807a
     {"utf8_mode",               "-X utf8"},
bb807a
+    {"int_max_str_digits",      "-X int_max_str_digits"},
bb807a
     {0}
bb807a
 };
bb807a
 
bb807a
@@ -2447,7 +2490,7 @@ static PyStructSequence_Desc flags_desc = {
bb807a
     "sys.flags",        /* name */
bb807a
     flags__doc__,       /* doc */
bb807a
     flags_fields,       /* fields */
bb807a
-    15
bb807a
+    16
bb807a
 };
bb807a
 
bb807a
 static PyObject*
bb807a
@@ -2483,6 +2526,7 @@ make_flags(PyThreadState *tstate)
bb807a
     SetFlag(config->isolated);
bb807a
     PyStructSequence_SET_ITEM(seq, pos++, PyBool_FromLong(config->dev_mode));
bb807a
     SetFlag(preconfig->utf8_mode);
bb807a
+    SetFlag(_Py_global_config_int_max_str_digits);
bb807a
 #undef SetFlag
bb807a
 
bb807a
     if (_PyErr_Occurred(tstate)) {
bb807a
-- 
bb807a
2.37.3
bb807a