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

gh-95778: CVE-2020-10735: Prevent DoS by very large int() #96499

Merged
merged 45 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
19b28fc
CVE-2020-10735: Prevent DoS by very large int()
tiran May 5, 2020
0a96b20
Default to disable, improve tests and docs
tiran Jan 19, 2022
88f6d5d
fix typo
tiran Jan 19, 2022
70c195e
More docs (WIP)
tiran Jan 19, 2022
e17e93b
Basic documentation for sys functions
tiran Jan 19, 2022
fbd14b7
Use ValueError, ignore underscore, scale limit
tiran Jan 20, 2022
dd74d70
Fix CI
tiran Jan 20, 2022
0e01461
Address Greg's review
tiran Aug 1, 2022
0b21e5f
Fix sys.flags len and docs
tiran Aug 1, 2022
3b38abe
Keep the warning, but remove advice about limiting input length in th…
gpshead Aug 2, 2022
37193ed
Renamed the APIs & too many other refactorings.
gpshead Aug 5, 2022
c90b79f
Improve the configuring docs.
gpshead Aug 7, 2022
fea25ea
Stop tying to base10, just use string digits.
gpshead Aug 7, 2022
ac9f22f
Remove the added now-unneeded helper log tbl fn.
gpshead Aug 7, 2022
da72dd1
prevent intdostimeit from emitting errors in test_tools.
gpshead Aug 7, 2022
d7e4d7b
Remove a leftover base 10 reference. clarify.
gpshead Aug 7, 2022
5c7e6d5
versionadded/changed to 3.12
gpshead Aug 7, 2022
61a5bc9
Link to the CVE from the main doc.
gpshead Aug 7, 2022
c15adde
Add a What's New entry.
gpshead Aug 7, 2022
76ae1c2
Add a Misc/NEWS.d entry.
gpshead Aug 7, 2022
1ad88f5
Undo addition to PyConfig to ease backporting.
gpshead Aug 8, 2022
0c83111
Remove the Tools/scripts/ example and timing code.
gpshead Aug 8, 2022
5d39ab6
un-add the <math.h> include (not needed for PR anymore)
gpshead Aug 8, 2022
5b77b3e
Remove added unused imports.
gpshead Aug 8, 2022
de00cdc
Tabs -> Spaces
gpshead Aug 8, 2022
3cc8553
make html and make doctest in Doc pass.
gpshead Aug 8, 2022
da97e65
Raise the default limit and the threshold.
gpshead Aug 10, 2022
ef03a16
Remove xmlrpc.client changes, test-only.
gpshead Aug 12, 2022
e916845
Rearrange the new stdtypes docs, w/limits + caution.
gpshead Aug 13, 2022
101502e
Make a huge int a SyntaxError with lineno when parsing.
gpshead Aug 16, 2022
fa8a58a
Mention the chosen default in the NEWS entry.
gpshead Aug 16, 2022
313ab6d
Properly clear & free the prior exception.
gpshead Aug 16, 2022
614cd02
Add a note to the float.as_integer_ratio() docs.
gpshead Aug 17, 2022
16ad090
Clarify the documentation wording and error msg.
gpshead Aug 17, 2022
4eb72e6
Fix test_idle, it used a long int on a line.
gpshead Aug 17, 2022
da36550
Rename the test.support context manager and document it.
gpshead Aug 19, 2022
f4372cc
Documentation cleanup.
gpshead Aug 19, 2022
c421853
Update attribution in Misc/NEWS.d
gpshead Aug 25, 2022
9f2168a
Regen global strings
tiran Sep 1, 2022
3c8504b
Make the doctest actually run & fix it.
gpshead Sep 1, 2022
1586419
Fix the docs build.
gpshead Sep 2, 2022
94bd3ee
Rename the news file to appease the Bedevere bot.
gpshead Sep 2, 2022
0b91f65
Regen argument clinic after the rebase merge.
gpshead Sep 2, 2022
02776f9
Hexi hexa
tiran Sep 2, 2022
173fa4e
Hexi hexa 2
tiran Sep 2, 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
2 changes: 2 additions & 0 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,8 @@ are always available. They are listed here in alphabetical order.
.. versionchanged:: 3.11
The delegation to :meth:`__trunc__` is deprecated.

.. versionchanged:: 3.12
:class:`int` are now limited, :func:`sys.setintmaxdigits` TODO

.. function:: isinstance(object, classinfo)

Expand Down
9 changes: 9 additions & 0 deletions Doc/library/json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,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. It's advised to limit the input to a sensible length.

:mod:`json` exposes an API familiar to users of the standard library
:mod:`marshal` and :mod:`pickle` modules.

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

.. versionchanged:: 3.9
The default implementation of *parse_int* limits the input string to
5,000 digits to prevent 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
12 changes: 12 additions & 0 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,12 @@ always available.

.. versionadded:: 3.6

.. function:: getintmaxdigits()

Return limit for int digits, :func:`setintmaxdigits` TODO

.. versionadded:: 3.9

.. function:: getrefcount(object)

Return the reference count of the *object*. The count returned is generally one
Expand Down Expand Up @@ -1308,6 +1314,12 @@ always available.

.. availability:: Unix.

.. function:: setintmaxdigits(n)

Set maximum amount of int digits, :func:`getintmaxdigits` TODO

.. versionadded:: 3.9

.. function:: setprofile(profilefunc)

.. index::
Expand Down
4 changes: 4 additions & 0 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ typedef struct PyConfig {

// If non-zero, we believe we're running from a source tree.
int _is_python_build;

/* global limit for long digits */
Py_ssize_t intmaxdigits;

} PyConfig;

PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config);
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ struct _is {
struct types_state types;
struct callable_cache callable_cache;

Py_ssize_t intmaxdigits;

/* The following fields are here to avoid allocation during init.
The data is exposed through PyInterpreterState pointer fields.
These fields should not be accessed directly outside of init.
Expand Down
5 changes: 5 additions & 0 deletions Include/longobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ PyAPI_FUNC(unsigned long long) PyLong_AsUnsignedLongLong(PyObject *);
PyAPI_FUNC(unsigned long long) PyLong_AsUnsignedLongLongMask(PyObject *);
PyAPI_FUNC(long long) PyLong_AsLongLongAndOverflow(PyObject *, int *);

/* Default limitation */
#define _PY_LONG_DEFAULT_MAX_DIGITS 5000
/* Don't check unless input / output is larger than threshold */
#define _PY_LONG_MAX_DIGITS_TRESHOLD 1024

PyAPI_FUNC(PyObject *) PyLong_FromString(const char *, char **, int);

/* These aren't really part of the int object, but they're handy. The
Expand Down
46 changes: 46 additions & 0 deletions Lib/test/test_int.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import sys

import unittest
Expand Down Expand Up @@ -26,6 +27,17 @@
("\u0200", ValueError)
]


@contextlib.contextmanager
def setintmaxdigits(maxdigits):
current = sys.getintmaxdigits()
try:
sys.setintmaxdigits(maxdigits)
yield
finally:
sys.setintmaxdigits(current)


class IntSubclass(int):
pass

Expand Down Expand Up @@ -576,6 +588,40 @@ def test_issue31619(self):
self.assertEqual(int('1_2_3_4_5_6_7_8_9', 16), 0x123456789)
self.assertEqual(int('1_2_3_4_5_6_7', 32), 1144132807)

def _test_maxdigits(self, c):
maxdigits = sys.getintmaxdigits()
# edge cases
c('1' * maxdigits)
c(' ' + '1' * maxdigits)
c('+' + '1' * maxdigits)
self.assertEqual(len(str(10 ** (maxdigits - 1))), maxdigits)

# disable limitation
with setintmaxdigits(0):
c('1' * (maxdigits + 1))
c('1' * (maxdigits + 1))

# OverflowError
def check(i, base=None):
with self.assertRaises(OverflowError):
if base is None:
c(i)
else:
c(i, base)

with setintmaxdigits(1024):
maxdigits = 1024
check('1' * (maxdigits + 1))
check('+' + '1' * (maxdigits + 1))
check('1' * (maxdigits + 1))

i = 10 ** maxdigits
with self.assertRaises(OverflowError):
str(i)

def test_maxdigits(self):
self._test_maxdigits(int)
self._test_maxdigits(IntSubclass)

if __name__ == "__main__":
unittest.main()
8 changes: 8 additions & 0 deletions Lib/test/test_json/test_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from io import StringIO
from collections import OrderedDict
from test.test_json import PyTest, CTest
import sys


class TestDecode:
Expand Down Expand Up @@ -95,5 +96,12 @@ def test_negative_index(self):
d = self.json.JSONDecoder()
self.assertRaises(ValueError, d.raw_decode, 'a'*42, -50000)

def test_limit_int(self):
maxdigits = sys.getintmaxdigits()
self.loads('1' * maxdigits)
with self.assertRaises(OverflowError):
self.loads('1' * (maxdigits + 1))


class TestPyDecode(TestDecode, PyTest): pass
class TestCDecode(TestDecode, CTest): pass
8 changes: 8 additions & 0 deletions Lib/test/test_xmlrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,14 @@ def test_load_extension_types(self):
check('<bigdecimal>9876543210.0123456789</bigdecimal>',
decimal.Decimal('9876543210.0123456789'))

def test_limit_int(self):
check = self.check_loads
with self.assertRaises(OverflowError):
check('<int>123456780123456789</int>', None)
with self.assertRaises(OverflowError):
s = '1' * (sys.getintmaxdigits() + 1)
check(f'<biginteger>{s}</biginteger>', None)

def test_get_host_info(self):
# see bug #3613, this raised a TypeError
transp = xmlrpc.client.Transport()
Expand Down
10 changes: 9 additions & 1 deletion Lib/xmlrpc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -742,14 +742,22 @@ def end_boolean(self, data):
dispatch["boolean"] = end_boolean

def end_int(self, data):
if len(data.strip()) > 16:
# XML-RPC ints are signed int32 with 11 chars text max
raise OverflowError("int exceeds XML-RPC limits")
self.append(int(data))
self._value = 0

dispatch["i1"] = end_int
dispatch["i2"] = end_int
dispatch["i4"] = end_int
dispatch["i8"] = end_int
dispatch["int"] = end_int
dispatch["biginteger"] = end_int

def end_bigint(self, data):
self.append(int(data))
self._value = 0
dispatch["biginteger"] = end_bigint

def end_double(self, data):
self.append(float(data))
Expand Down
48 changes: 39 additions & 9 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1815,6 +1815,15 @@ long_to_decimal_string_internal(PyObject *aa,
tenpow *= 10;
strlen++;
}
if (strlen > _PY_LONG_MAX_DIGITS_TRESHOLD) {
PyInterpreterState *interp = _PyInterpreterState_GET();
if ((interp->intmaxdigits > 0) && (strlen > interp->intmaxdigits)) {
Py_DECREF(scratch);
PyErr_SetString(PyExc_OverflowError,
"too many digits in integer");
return -1;
}
}
if (writer) {
if (_PyUnicodeWriter_Prepare(writer, strlen, '9') == -1) {
Py_DECREF(scratch);
Expand Down Expand Up @@ -2267,6 +2276,7 @@ long_from_binary_base(const char **str, int base, PyLongObject **res)
*
* If unsuccessful, NULL will be returned.
*/

PyObject *
PyLong_FromString(const char *str, char **pend, int base)
{
Expand Down Expand Up @@ -2328,6 +2338,7 @@ PyLong_FromString(const char *str, char **pend, int base)

start = str;
if ((base & (base - 1)) == 0) {
/* binary bases are not limited by intmaxdigits */
int res = long_from_binary_base(&str, base, &z);
if (res < 0) {
/* Syntax error. */
Expand Down Expand Up @@ -2479,6 +2490,16 @@ digit beyond the first.
goto onError;
}

slen = scan - str;
if (slen > _PY_LONG_MAX_DIGITS_TRESHOLD) {
PyInterpreterState *interp = _PyInterpreterState_GET();
if ((interp->intmaxdigits > 0 ) && (slen > interp->intmaxdigits)) {
PyErr_SetString(PyExc_OverflowError,
"too many digits in integer");
return NULL;
}
}

/* Create an int object that can contain the largest possible
* integer with this base and length. Note that there's no
* need to initialize z->ob_digit -- no slot is read up before
Expand Down Expand Up @@ -5355,18 +5376,20 @@ long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase)
}
return PyLong_FromLong(0L);
}
/* default base and limit, forward to standard implementation */
if (obase == NULL)
return PyNumber_Long(x);

base = PyNumber_AsSsize_t(obase, NULL);
if (base == -1 && PyErr_Occurred())
return NULL;
if ((base != 0 && base < 2) || base > 36) {
PyErr_SetString(PyExc_ValueError,
"int() base must be >= 2 and <= 36, or 0");
return NULL;
if (obase != NULL) {
base = PyNumber_AsSsize_t(obase, NULL);
if (base == -1 && PyErr_Occurred())
return NULL;
if ((base != 0 && base < 2) || base > 36) {
PyErr_SetString(PyExc_ValueError,
"int() base must be >= 2 and <= 36, or 0");
return NULL;
}
}

if (PyUnicode_Check(x))
return PyLong_FromUnicodeObject(x, (int)base);
else if (PyByteArray_Check(x) || PyBytes_Check(x)) {
Expand Down Expand Up @@ -6090,14 +6113,16 @@ internal representation of integers. The attributes are read only.");
static PyStructSequence_Field int_info_fields[] = {
{"bits_per_digit", "size of a digit in bits"},
{"sizeof_digit", "size in bytes of the C type used to represent a digit"},
{"default_max_digits", "maximum digits limitation"},
{"max_digits_threshold", "minimum threshold to check for max digits"},
{NULL, NULL}
};

static PyStructSequence_Desc int_info_desc = {
"sys.int_info", /* name */
int_info__doc__, /* doc */
int_info_fields, /* fields */
2 /* number of fields */
4 /* number of fields */
};

PyObject *
Expand All @@ -6112,6 +6137,10 @@ PyLong_GetInfo(void)
PyLong_FromLong(PyLong_SHIFT));
PyStructSequence_SET_ITEM(int_info, field++,
PyLong_FromLong(sizeof(digit)));
PyStructSequence_SET_ITEM(int_info, field++,
PyLong_FromLong(_PY_LONG_DEFAULT_MAX_DIGITS));
PyStructSequence_SET_ITEM(int_info, field++,
PyLong_FromLong(_PY_LONG_MAX_DIGITS_TRESHOLD));
if (PyErr_Occurred()) {
Py_CLEAR(int_info);
return NULL;
Expand Down Expand Up @@ -6139,6 +6168,7 @@ _PyLong_InitTypes(PyInterpreterState *interp)
return _PyStatus_ERR("can't init int info type");
}
}
interp->intmaxdigits = _PyInterpreterState_GetConfig(interp)->intmaxdigits;

return _PyStatus_OK();
}
Expand Down
61 changes: 61 additions & 0 deletions Python/clinic/sysmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading