mirror of
https://github.com/python/cpython.git
synced 2025-07-27 13:14:41 +00:00

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>
116 lines
3.5 KiB
C
116 lines
3.5 KiB
C
#ifndef Py_INTERNAL_LONG_H
|
|
#define Py_INTERNAL_LONG_H
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#ifndef Py_BUILD_CORE
|
|
# error "this header requires Py_BUILD_CORE define"
|
|
#endif
|
|
|
|
#include "pycore_global_objects.h" // _PY_NSMALLNEGINTS
|
|
#include "pycore_runtime.h" // _PyRuntime
|
|
|
|
/*
|
|
* Default int base conversion size limitation: Denial of Service prevention.
|
|
*
|
|
* Chosen such that this isn't wildly slow on modern hardware and so that
|
|
* everyone's existing deployed numpy test suite passes before
|
|
* https://github.com/numpy/numpy/issues/22098 is widely available.
|
|
*
|
|
* $ python -m timeit -s 's = "1"*4300' 'int(s)'
|
|
* 2000 loops, best of 5: 125 usec per loop
|
|
* $ python -m timeit -s 's = "1"*4300; v = int(s)' 'str(v)'
|
|
* 1000 loops, best of 5: 311 usec per loop
|
|
* (zen2 cloud VM)
|
|
*
|
|
* 4300 decimal digits fits a ~14284 bit number.
|
|
*/
|
|
#define _PY_LONG_DEFAULT_MAX_STR_DIGITS 4300
|
|
/*
|
|
* Threshold for max digits check. For performance reasons int() and
|
|
* int.__str__() don't checks values that are smaller than this
|
|
* threshold. Acts as a guaranteed minimum size limit for bignums that
|
|
* applications can expect from CPython.
|
|
*
|
|
* % python -m timeit -s 's = "1"*640; v = int(s)' 'str(int(s))'
|
|
* 20000 loops, best of 5: 12 usec per loop
|
|
*
|
|
* "640 digits should be enough for anyone." - gps
|
|
* fits a ~2126 bit decimal number.
|
|
*/
|
|
#define _PY_LONG_MAX_STR_DIGITS_THRESHOLD 640
|
|
|
|
#if ((_PY_LONG_DEFAULT_MAX_STR_DIGITS != 0) && \
|
|
(_PY_LONG_DEFAULT_MAX_STR_DIGITS < _PY_LONG_MAX_STR_DIGITS_THRESHOLD))
|
|
# error "_PY_LONG_DEFAULT_MAX_STR_DIGITS smaller than threshold."
|
|
#endif
|
|
|
|
|
|
/* runtime lifecycle */
|
|
|
|
extern PyStatus _PyLong_InitTypes(PyInterpreterState *);
|
|
extern void _PyLong_FiniTypes(PyInterpreterState *interp);
|
|
|
|
|
|
/* other API */
|
|
|
|
#define _PyLong_SMALL_INTS _Py_SINGLETON(small_ints)
|
|
|
|
// _PyLong_GetZero() and _PyLong_GetOne() must always be available
|
|
// _PyLong_FromUnsignedChar must always be available
|
|
#if _PY_NSMALLPOSINTS < 257
|
|
# error "_PY_NSMALLPOSINTS must be greater than or equal to 257"
|
|
#endif
|
|
|
|
// Return a borrowed reference to the zero singleton.
|
|
// The function cannot return NULL.
|
|
static inline PyObject* _PyLong_GetZero(void)
|
|
{ return (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS]; }
|
|
|
|
// Return a borrowed reference to the one singleton.
|
|
// The function cannot return NULL.
|
|
static inline PyObject* _PyLong_GetOne(void)
|
|
{ return (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS+1]; }
|
|
|
|
static inline PyObject* _PyLong_FromUnsignedChar(unsigned char i)
|
|
{
|
|
return Py_NewRef((PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS+i]);
|
|
}
|
|
|
|
PyObject *_PyLong_Add(PyLongObject *left, PyLongObject *right);
|
|
PyObject *_PyLong_Multiply(PyLongObject *left, PyLongObject *right);
|
|
PyObject *_PyLong_Subtract(PyLongObject *left, PyLongObject *right);
|
|
|
|
int _PyLong_AssignValue(PyObject **target, Py_ssize_t value);
|
|
|
|
/* Used by Python/mystrtoul.c, _PyBytes_FromHex(),
|
|
_PyBytes_DecodeEscape(), etc. */
|
|
PyAPI_DATA(unsigned char) _PyLong_DigitValue[256];
|
|
|
|
/* Format the object based on the format_spec, as defined in PEP 3101
|
|
(Advanced String Formatting). */
|
|
PyAPI_FUNC(int) _PyLong_FormatAdvancedWriter(
|
|
_PyUnicodeWriter *writer,
|
|
PyObject *obj,
|
|
PyObject *format_spec,
|
|
Py_ssize_t start,
|
|
Py_ssize_t end);
|
|
|
|
PyAPI_FUNC(int) _PyLong_FormatWriter(
|
|
_PyUnicodeWriter *writer,
|
|
PyObject *obj,
|
|
int base,
|
|
int alternate);
|
|
|
|
PyAPI_FUNC(char*) _PyLong_FormatBytesWriter(
|
|
_PyBytesWriter *writer,
|
|
char *str,
|
|
PyObject *obj,
|
|
int base,
|
|
int alternate);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif /* !Py_INTERNAL_LONG_H */
|