mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
[3.9] gh-95778: CVE-2020-10735: Prevent DoS by very large int() (#96502)
* Correctly pre-check for int-to-str conversion (#96537) Converting a large enough `int` to a decimal string raises `ValueError` as expected. However, the raise comes _after_ the quadratic-time base-conversion algorithm has run to completion. For effective DOS prevention, we need some kind of check before entering the quadratic-time loop. Oops! =) The quick fix: essentially we catch _most_ values that exceed the threshold up front. Those that slip through will still be on the small side (read: sufficiently fast), and will get caught by the existing check so that the limit remains exact. The justification for the current check. The C code check is: ```c max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10 ``` In GitHub markdown math-speak, writing $M$ for `max_str_digits`, $L$ for `PyLong_SHIFT` and $s$ for `size_a`, that check is: $$\left\lfloor\frac{M}{3L}\right\rfloor \le \left\lfloor\frac{s - 11}{10}\right\rfloor$$ From this it follows that $$\frac{M}{3L} < \frac{s-1}{10}$$ hence that $$\frac{L(s-1)}{M} > \frac{10}{3} > \log_2(10).$$ So $$2^{L(s-1)} > 10^M.$$ But our input integer $a$ satisfies $|a| \ge 2^{L(s-1)}$, so $|a|$ is larger than $10^M$. This shows that we don't accidentally capture anything _below_ the intended limit in the check. <!-- gh-issue-number: gh-95778 --> * Issue: gh-95778 <!-- /gh-issue-number --> Co-authored-by: Gregory P. Smith [Google LLC] <greg@krypto.org> Co-authored-by: Christian Heimes <christian@python.org> Co-authored-by: Mark Dickinson <dickinsm@gmail.com>
This commit is contained in:
parent
d348afa15d
commit
cec1e9dfd7
27 changed files with 886 additions and 19 deletions
|
|
@ -3,6 +3,7 @@
|
|||
#include "pycore_getopt.h" // _PyOS_GetOpt()
|
||||
#include "pycore_initconfig.h" // _PyStatus_OK()
|
||||
#include "pycore_interp.h" // _PyInterpreterState.runtime
|
||||
#include "pycore_long.h" // _PY_LONG_MAX_STR_DIGITS_THRESHOLD
|
||||
#include "pycore_pathconfig.h" // _Py_path_config
|
||||
#include "pycore_pyerrors.h" // _PyErr_Fetch()
|
||||
#include "pycore_pylifecycle.h" // _Py_PreInitializeFromConfig()
|
||||
|
|
@ -99,6 +100,9 @@ static const char usage_3[] = "\
|
|||
otherwise activate automatically)\n\
|
||||
-X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the\n\
|
||||
given directory instead of to the code tree\n\
|
||||
-X int_max_str_digits=number: limit the size of int<->str conversions.\n\
|
||||
This helps avoid denial of service attacks when parsing untrusted data.\n\
|
||||
The default is sys.int_info.default_max_str_digits. 0 disables.\n\
|
||||
\n\
|
||||
--check-hash-based-pycs always|default|never:\n\
|
||||
control how Python invalidates hash-based .pyc files\n\
|
||||
|
|
@ -125,6 +129,10 @@ static const char usage_6[] =
|
|||
" to seed the hashes of str and bytes objects. It can also be set to an\n"
|
||||
" integer in the range [0,4294967295] to get hash values with a\n"
|
||||
" predictable seed.\n"
|
||||
"PYTHONINTMAXSTRDIGITS: limits the maximum digit characters in an int value\n"
|
||||
" when converting from a string and when converting an int back to a str.\n"
|
||||
" A value of 0 disables the limit. Conversions to or from bases 2, 4, 8,\n"
|
||||
" 16, and 32 are never limited.\n"
|
||||
"PYTHONMALLOC: set the Python memory allocators and/or install debug hooks\n"
|
||||
" on Python memory allocators. Use PYTHONMALLOC=debug to install debug\n"
|
||||
" hooks.\n"
|
||||
|
|
@ -646,6 +654,10 @@ _PyConfig_InitCompatConfig(PyConfig *config)
|
|||
config->_use_peg_parser = 1;
|
||||
}
|
||||
|
||||
/* Excluded from public struct PyConfig for backporting reasons. */
|
||||
/* default to unconfigured, _PyLong_Init() does the rest */
|
||||
int _Py_global_config_int_max_str_digits = -1;
|
||||
|
||||
|
||||
static void
|
||||
config_init_defaults(PyConfig *config)
|
||||
|
|
@ -1410,6 +1422,48 @@ config_init_tracemalloc(PyConfig *config)
|
|||
return _PyStatus_OK();
|
||||
}
|
||||
|
||||
static PyStatus
|
||||
config_init_int_max_str_digits(PyConfig *config)
|
||||
{
|
||||
int maxdigits;
|
||||
int valid = 0;
|
||||
|
||||
const char *env = config_get_env(config, "PYTHONINTMAXSTRDIGITS");
|
||||
if (env) {
|
||||
if (!_Py_str_to_int(env, &maxdigits)) {
|
||||
valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD));
|
||||
}
|
||||
if (!valid) {
|
||||
#define STRINGIFY(VAL) _STRINGIFY(VAL)
|
||||
#define _STRINGIFY(VAL) #VAL
|
||||
return _PyStatus_ERR(
|
||||
"PYTHONINTMAXSTRDIGITS: invalid limit; must be >= "
|
||||
STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD)
|
||||
" or 0 for unlimited.");
|
||||
}
|
||||
_Py_global_config_int_max_str_digits = maxdigits;
|
||||
}
|
||||
|
||||
const wchar_t *xoption = config_get_xoption(config, L"int_max_str_digits");
|
||||
if (xoption) {
|
||||
const wchar_t *sep = wcschr(xoption, L'=');
|
||||
if (sep) {
|
||||
if (!config_wstr_to_int(sep + 1, &maxdigits)) {
|
||||
valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD));
|
||||
}
|
||||
}
|
||||
if (!valid) {
|
||||
return _PyStatus_ERR(
|
||||
"-X int_max_str_digits: invalid limit; must be >= "
|
||||
STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD)
|
||||
" or 0 for unlimited.");
|
||||
#undef _STRINGIFY
|
||||
#undef STRINGIFY
|
||||
}
|
||||
_Py_global_config_int_max_str_digits = maxdigits;
|
||||
}
|
||||
return _PyStatus_OK();
|
||||
}
|
||||
|
||||
static PyStatus
|
||||
config_init_pycache_prefix(PyConfig *config)
|
||||
|
|
@ -1466,6 +1520,12 @@ config_read_complex_options(PyConfig *config)
|
|||
return status;
|
||||
}
|
||||
}
|
||||
if (_Py_global_config_int_max_str_digits < 0) {
|
||||
status = config_init_int_max_str_digits(config);
|
||||
if (_PyStatus_EXCEPTION(status)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
if (config->pycache_prefix == NULL) {
|
||||
status = config_init_pycache_prefix(config);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue