Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.7] gh-95778: CVE-2020-10735: Prevent DoS by very large int() #96504

Merged
merged 23 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ca77774
Backport CVE-2020-10735 to 3.7 from 3.8.
gpshead Aug 30, 2022
f145128
Add What's New entry.
gpshead Aug 30, 2022
00a5114
Hack: Force CI run
tiran Sep 1, 2022
7eaad20
revert 1dae140b610a465b4d3e6fb2109ec13da6093e6d CI hack
gpshead Sep 1, 2022
635e292
Backport ctypes test_macholib fix from b29d0a5a7811418c0a1082ca188fd4…
gpshead Sep 1, 2022
a2956f3
annotate test_bad_password @requires_zlib.
gpshead Sep 1, 2022
95645b6
disable MachOTest.test_find unless macOS 11+ support is backported.
gpshead Sep 1, 2022
2cc321e
Move the whatsnew 3.7.14 text per review.
gpshead Sep 1, 2022
bc83515
LOL at my typo
gpshead Sep 1, 2022
76c9c2b
Make the doctest actually run & fix it.
gpshead Sep 1, 2022
e7bc47e
remove a line that prevents doctest error reporting.
gpshead Sep 2, 2022
0ef7ec0
Fix the docs build.
gpshead Sep 2, 2022
ad13c50
Update the ABI dump with the new private symbols.
gpshead Sep 2, 2022
2788f3f
Merge branch '3.7' into CVE-2020-10735-3.7backport
gpshead Sep 2, 2022
ca92fd2
Rename the news file to appease the Bedevere bot.
gpshead Sep 2, 2022
67905b2
Merge branch 'CVE-2020-10735-3.7backport' of github.com:gpshead/cpyth…
gpshead Sep 2, 2022
db48ddc
hexadecimal spelling =)
gpshead Sep 2, 2022
38ec6a9
Work around Windows Yield macro vs Python-ast.h
gpshead Sep 2, 2022
feaded8
doc typo: limitation
gpshead Sep 4, 2022
c9f2c57
Misc: Fix a typo in the header comment.
gpshead Sep 4, 2022
f69b587
remove unneeded doc note on float.as_integer_ratio
gpshead Sep 4, 2022
39837b6
gh-95778: Correctly pre-check for int-to-str conversion (#96537)
mdickinson Sep 4, 2022
7f911c1
backport cherry pick fix: lookup max from the right place.
gpshead Sep 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Doc/data/python3.7m.abi
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,7 @@
<elf-symbol name='_Py_endswith__doc__' size='264' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='_Py_expandtabs__doc__' size='173' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='_Py_find__doc__' size='239' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='_Py_global_config_int_max_str_digits' size='4' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='_Py_index__doc__' size='270' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='_Py_isalnum__doc__' size='132' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='_Py_isalpha__doc__' size='130' type='object-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
Expand Down Expand Up @@ -11958,6 +11959,9 @@
<data-member access='public' layout-offset-in-bits='11776'>
<var-decl name='gilstate' type-id='type-id-600' visibility='default' filepath='../gpshead/Include/internal/pystate.h' line='105' column='1'/>
</data-member>
<data-member access='public' layout-offset-in-bits='12096'>
<var-decl name='int_max_str_digits' type-id='type-id-7' visibility='default' filepath='../gpshead/Include/internal/pystate.h' line='109' column='1'/>
</data-member>
</class-decl>
<class-decl name='pyinterpreters' size-in-bits='256' is-struct='yes' visibility='default' filepath='../gpshead/Include/internal/pystate.h' line='83' column='1' id='type-id-596'>
<data-member access='public' layout-offset-in-bits='0'>
Expand Down
8 changes: 8 additions & 0 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,14 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.7
*x* is now a positional-only parameter.

.. versionchanged:: 3.7.14
: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
<int_max_str_digits>` documentation.


.. function:: isinstance(object, classinfo)

Expand Down
11 changes: 11 additions & 0 deletions Doc/library/json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ is a lightweight data interchange format inspired by
`JavaScript <https://en.wikipedia.org/wiki/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.

Expand Down Expand Up @@ -243,6 +248,12 @@ Basic Usage
be used to use another datatype or parser for JSON integers
(e.g. :class:`float`).

.. versionchanged:: 3.7.14
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 <int_max_str_digits>` 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
Expand Down
159 changes: 159 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4741,6 +4741,165 @@ types, where they are relevant. Some of these are not reported by the
[<class 'bool'>]


.. _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
<https://cve.mitre.org/cgi-bin/cvename.cgi?name=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 <sys.int_info>`.
The lowest limit that can be configured is 640 digits as provided in
:data:`sys.int_info.str_digits_check_threshold <sys.int_info>`.

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.7.14

Affected APIs
-------------

The limitation 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 <sys.int_info>` is the compiled-in
default limit.
* :data:`sys.int_info.str_digits_check_threshold <sys.int_info>` is the lowest
accepted value for the limit (other than 0 which disables it).

.. versionadded:: 3.7.14

.. 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
Expand Down
59 changes: 46 additions & 13 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,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`
Expand All @@ -345,7 +345,8 @@ always available.
:const:`hash_randomization` :option:`-R`
:const:`dev_mode` :option:`-X` ``dev``
:const:`utf8_mode` :option:`-X` ``utf8``
============================= =============================
:const:`int_max_str_digits` :option:`-X int_max_str_digits <-X>` (:ref:`integer string conversion length limitation <int_max_str_digits>`)
============================= ==============================================================================================================

.. versionchanged:: 3.2
Added ``quiet`` attribute for the new :option:`-q` flag.
Expand All @@ -363,6 +364,9 @@ always available.
Added ``dev_mode`` attribute for the new :option:`-X` ``dev`` flag
and ``utf8_mode`` attribute for the new :option:`-X` ``utf8`` flag.

.. versionchanged:: 3.7.14
Added the ``int_max_str_digits`` attribute.


.. data:: float_info

Expand Down Expand Up @@ -539,6 +543,15 @@ always available.

.. versionadded:: 3.6


.. function:: get_int_max_str_digits()

Returns the current value for the :ref:`integer string conversion length
limitation <int_max_str_digits>`. See also :func:`set_int_max_str_digits`.

.. versionadded:: 3.7.14


.. function:: getrefcount(object)

Return the reference count of the *object*. The count returned is generally one
Expand Down Expand Up @@ -821,19 +834,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.7.14
Added ``default_max_str_digits`` and ``str_digits_check_threshold``.


.. data:: __interactivehook__

Expand Down Expand Up @@ -1092,6 +1117,14 @@ always available.

.. availability:: Unix.

.. function:: set_int_max_str_digits(n)

Set the :ref:`integer string conversion length limitation
<int_max_str_digits>` used by this interpreter. See also
:func:`get_int_max_str_digits`.

.. versionadded:: 3.7.14

.. function:: setprofile(profilefunc)

.. index::
Expand Down
10 changes: 10 additions & 0 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,16 @@ The :mod:`test.support` module defines the following functions:
.. versionadded:: 3.6


.. 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.7.14


The :mod:`test.support` module defines the following classes:

.. class:: TransientResource(exc, **kwargs)
Expand Down
13 changes: 13 additions & 0 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,9 @@ Miscellaneous options
* ``-X showalloccount`` to output the total count of allocated objects for
each type when the program finishes. This only works when Python was built with
``COUNT_ALLOCS`` defined.
* ``-X int_max_str_digits`` configures the :ref:`integer string conversion
length limitation <int_max_str_digits>`. 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
Expand Down Expand Up @@ -474,6 +477,9 @@ Miscellaneous options
.. versionadded:: 3.7
The ``-X importtime``, ``-X dev`` and ``-X utf8`` options.

.. versionadded:: 3.7.14
The ``-X int_max_str_digits`` option.


Options you shouldn't use
~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -618,6 +624,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
<int_max_str_digits>`.

.. versionadded:: 3.7.14

.. envvar:: PYTHONIOENCODING

Expand Down
Loading