Skip to content

Commit 8eb079f

Browse files
authored
[3.10] gh-158446: Reject float format precision near INT_MAX (GH-158474) (#158482)
Formatting a float or complex with a precision within about 1000 of INT_MAX could crash or produce incorrect output. PyOS_double_to_string() now raises ValueError("precision too big") for such precisions, as the format string parsers already do for precisions above INT_MAX. The limit applies regardless of presentation type or value, so a few calls that previously succeeded (inf, nan, or 'g' with such a precision) now raise as well. (cherry picked from commit b7b4f3e)
1 parent ec44b5a commit 8eb079f

4 files changed

Lines changed: 64 additions & 1 deletion

File tree

‎Lib/test/test_format.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,27 @@ def test_precision_c_limits(self):
488488
with self.assertRaises(ValueError) as cm:
489489
format(c, ".%sf" % (INT_MAX + 1))
490490

491+
@support.cpython_only
492+
def test_precision_near_int_max(self):
493+
# gh-158446: Precisions just below INT_MAX are rejected before any
494+
# output buffer size is computed from them.
495+
from _testcapi import INT_MAX
496+
497+
f = 1e300
498+
c = complex(f)
499+
for prec in (INT_MAX, INT_MAX - 1023):
500+
for code in "feg":
501+
spec = ".%d%s" % (prec, code)
502+
with self.subTest(spec=spec):
503+
with self.assertRaises(ValueError):
504+
format(f, spec)
505+
with self.assertRaises(ValueError):
506+
format(c, spec)
507+
with self.assertRaises(ValueError):
508+
("%" + spec) % f
509+
with self.assertRaises(ValueError):
510+
("%" + spec).encode() % f
511+
491512
def test_g_format_has_no_trailing_zeros(self):
492513
# regression test for bugs.python.org/issue40780
493514
self.assertEqual("%.3g" % 1505.0, "1.5e+03")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix a crash or incorrect output that could occur when formatting a
2+
:class:`float` or :class:`complex` with a precision close to the platform's
3+
``INT_MAX``. :c:func:`PyOS_double_to_string` now raises :exc:`ValueError` for
4+
any precision of that magnitude, regardless of presentation type or value, as
5+
the format string parsers already did for precisions above ``INT_MAX``.

‎Python/dtoa.c‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
* 8. A corner case where _Py_dg_dtoa didn't strip trailing zeros has been
6868
* fixed. (bugs.python.org/issue40780)
6969
*
70+
* 9. _Py_dg_dtoa clamps ndigits in modes 3 and 5 so that its buffer size
71+
* arithmetic cannot exceed the int range, and rv_alloc's size doubling
72+
* uses size_t. (gh-158446)
73+
*
7074
***************************************************************/
7175

7276
/* Please send bug reports for the original dtoa.c code to David M. Gay (dmg
@@ -2162,7 +2166,8 @@ _Py_dg_strtod(const char *s00, char **se)
21622166
static char *
21632167
rv_alloc(int i)
21642168
{
2165-
int j, k, *r;
2169+
int k, *r;
2170+
size_t j; /* size_t so that j <<= 1 cannot overflow for i near INT_MAX */
21662171

21672172
j = sizeof(ULong);
21682173
for(k = 0;
@@ -2426,6 +2431,15 @@ _Py_dg_dtoa(double dd, int mode, int ndigits,
24262431
leftright = 0;
24272432
/* fall through */
24282433
case 5:
2434+
/* -330 < k < 330 for any finite nonzero double. Clamp ndigits so
2435+
that ndigits + k + 1 stays within int range; no double has
2436+
anywhere near this many decimal digits so the digits returned
2437+
are unaffected (*decpt saturates in the no_digits case). Same
2438+
bound as DOUBLE_TO_STRING_PRECISION_MAX in pystrtod.c. */
2439+
if (ndigits > INT_MAX - 1024)
2440+
ndigits = INT_MAX - 1024;
2441+
else if (ndigits < -(INT_MAX - 1024))
2442+
ndigits = -(INT_MAX - 1024);
24292443
i = ndigits + k + 1;
24302444
ilim = i;
24312445
ilim1 = i - 1;

‎Python/pystrtod.c‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,15 @@ _Py_string_to_number_with_underscores(
440440
return NULL;
441441
}
442442

443+
/* Largest precision magnitude accepted by PyOS_double_to_string(). The
444+
output buffer sizes computed below and within _Py_dg_dtoa() use int and
445+
Py_ssize_t arithmetic on roughly precision + (digits before the point, at
446+
most DBL_MAX_10_EXP + 1 == 309) + a few bytes of sign, point and exponent.
447+
Staying this far inside the int range keeps all of those sums in range.
448+
(Only C callers can pass a negative precision.) _Py_dg_dtoa() applies
449+
the same bound to its ndigits argument. */
450+
#define DOUBLE_TO_STRING_PRECISION_MAX (INT_MAX - 1024)
451+
443452
#ifdef PY_NO_SHORT_FLOAT_REPR
444453

445454
/* Given a string that may have a decimal point in the current
@@ -805,6 +814,13 @@ char * PyOS_double_to_string(double val,
805814
int t, exp;
806815
int upper = 0;
807816

817+
if (precision > DOUBLE_TO_STRING_PRECISION_MAX
818+
|| precision < -DOUBLE_TO_STRING_PRECISION_MAX)
819+
{
820+
PyErr_SetString(PyExc_ValueError, "precision too big");
821+
return NULL;
822+
}
823+
808824
/* Validate format_code, and map upper and lower case */
809825
switch (format_code) {
810826
case 'e': /* exponent */
@@ -1249,6 +1265,13 @@ char * PyOS_double_to_string(double val,
12491265
const char * const *float_strings = lc_float_strings;
12501266
int mode;
12511267

1268+
if (precision > DOUBLE_TO_STRING_PRECISION_MAX
1269+
|| precision < -DOUBLE_TO_STRING_PRECISION_MAX)
1270+
{
1271+
PyErr_SetString(PyExc_ValueError, "precision too big");
1272+
return NULL;
1273+
}
1274+
12521275
/* Validate format_code, and map upper and lower case. Compute the
12531276
mode and make any adjustments as needed. */
12541277
switch (format_code) {

0 commit comments

Comments
 (0)