Skip to content

Commit 2f1e70c

Browse files
committed
gh-158446: Reject float format precision near INT_MAX
PyOS_double_to_string() now raises ValueError("precision too big") for precisions above INT_MAX - 1024, matching what the format parsers already do above INT_MAX. Buffer size computations in pystrtod.c and dtoa.c add a few hundred to the precision using int / Py_ssize_t arithmetic and could wrap for such values, leading to a crash or incorrect output. Also harden _Py_dg_dtoa() and rv_alloc() so their size arithmetic stays in range for any C caller.
1 parent 115c297 commit 2f1e70c

4 files changed

Lines changed: 53 additions & 1 deletion

File tree

‎Lib/test/test_format.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,28 @@ def test_precision_c_limits(self):
639639
with self.assertRaises(ValueError) as cm:
640640
format(c, ".%sf" % (INT_MAX + 1))
641641

642+
@support.cpython_only
643+
def test_precision_near_int_max(self):
644+
# gh-158446: Precisions just below INT_MAX are rejected before any output
645+
# buffer size is computed from them.
646+
_testcapi = import_module("_testcapi")
647+
INT_MAX = _testcapi.INT_MAX
648+
649+
f = 1e300
650+
c = complex(f)
651+
for prec in (INT_MAX, INT_MAX - 1023):
652+
for code in "feg":
653+
spec = ".%d%s" % (prec, code)
654+
with self.subTest(spec=spec):
655+
with self.assertRaises(ValueError):
656+
format(f, spec)
657+
with self.assertRaises(ValueError):
658+
format(c, spec)
659+
with self.assertRaises(ValueError):
660+
("%" + spec) % f
661+
with self.assertRaises(ValueError):
662+
("%" + spec).encode() % f
663+
642664
def test_g_format_has_no_trailing_zeros(self):
643665
# regression test for bugs.python.org/issue40780
644666
self.assertEqual("%.3g" % 1505.0, "1.5e+03")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a crash or incorrect output when formatting a :class:`float` using the
2+
``'f'``, ``'e'`` or ``'g'`` presentation types with a precision close to the
3+
platform's ``INT_MAX``. Such precisions now raise :exc:`ValueError`, as
4+
precisions above ``INT_MAX`` already did.

‎Python/dtoa.c‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2108,7 +2108,8 @@ _Py_dg_strtod(const char *s00, char **se)
21082108
static char *
21092109
rv_alloc(int i)
21102110
{
2111-
int j, k, *r;
2111+
int k, *r;
2112+
size_t j; /* size_t so that j <<= 1 cannot overflow for i near INT_MAX */
21122113

21132114
j = sizeof(ULong);
21142115
for(k = 0;
@@ -2372,6 +2373,14 @@ _Py_dg_dtoa(double dd, int mode, int ndigits,
23722373
leftright = 0;
23732374
_Py_FALLTHROUGH;
23742375
case 5:
2376+
/* -330 < k < 330 for any finite nonzero double. Clamp ndigits so
2377+
that ndigits + k + 1 stays within int range; no double has
2378+
anywhere near this many decimal digits so the digits returned
2379+
are unaffected. */
2380+
if (ndigits > INT_MAX - 1024)
2381+
ndigits = INT_MAX - 1024;
2382+
else if (ndigits < -(INT_MAX - 1024))
2383+
ndigits = -(INT_MAX - 1024);
23752384
i = ndigits + k + 1;
23762385
ilim = i;
23772386
ilim1 = i - 1;

‎Python/pystrtod.c‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,13 @@ _Py_string_to_number_with_underscores(
401401
return NULL;
402402
}
403403

404+
/* Largest precision accepted by PyOS_double_to_string(). The output buffer
405+
sizes computed below and within _Py_dg_dtoa() use int and Py_ssize_t
406+
arithmetic on roughly precision + (digits before the point, at most
407+
DBL_MAX_10_EXP + 1 == 309) + a few bytes of sign, point and exponent.
408+
Staying this far below INT_MAX keeps all of those sums in range. */
409+
#define DOUBLE_TO_STRING_PRECISION_MAX (INT_MAX - 1024)
410+
404411
#if _PY_SHORT_FLOAT_REPR == 0
405412

406413
/* Given a string that may have a decimal point in the current
@@ -766,6 +773,11 @@ char * PyOS_double_to_string(double val,
766773
int t, exp;
767774
int upper = 0;
768775

776+
if (precision > DOUBLE_TO_STRING_PRECISION_MAX) {
777+
PyErr_SetString(PyExc_ValueError, "precision too big");
778+
return NULL;
779+
}
780+
769781
/* Validate format_code, and map upper and lower case */
770782
switch (format_code) {
771783
case 'e': /* exponent */
@@ -1227,6 +1239,11 @@ char * PyOS_double_to_string(double val,
12271239
const char * const *float_strings = lc_float_strings;
12281240
int mode;
12291241

1242+
if (precision > DOUBLE_TO_STRING_PRECISION_MAX) {
1243+
PyErr_SetString(PyExc_ValueError, "precision too big");
1244+
return NULL;
1245+
}
1246+
12301247
/* Validate format_code, and map upper and lower case. Compute the
12311248
mode and make any adjustments as needed. */
12321249
switch (format_code) {

0 commit comments

Comments
 (0)