https://github.com/python/cpython/commit/824836b6606b956ef65a7b51bdff475a0d3baba8
commit: 824836b6606b956ef65a7b51bdff475a0d3baba8
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: gpshead <[email protected]>
date: 2026-09-30T05:47:43Z
summary:
[3.13] gh-158446: Reject float format precision near INT_MAX (GH-158474)
(#158478)
gh-158446: Reject float format precision near INT_MAX (GH-158474)
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 b7b4f3ecf2fce4451ee1a8e8c1b6e92dea60ce3d)
Co-authored-by: Gregory P. Smith <[email protected]>
files:
A Misc/NEWS.d/next/Security/2026-09-29-16-32-46.gh-issue-158446.dToaPr.rst
M Lib/test/test_format.py
M Python/dtoa.c
M Python/pystrtod.c
diff --git a/Lib/test/test_format.py b/Lib/test/test_format.py
index c18dffe61a7370c..4186208888ec62f 100644
--- a/Lib/test/test_format.py
+++ b/Lib/test/test_format.py
@@ -490,6 +490,28 @@ def test_precision_c_limits(self):
with self.assertRaises(ValueError) as cm:
format(c, ".%sf" % (INT_MAX + 1))
+ @support.cpython_only
+ def test_precision_near_int_max(self):
+ # gh-158446: Precisions just below INT_MAX are rejected before any
+ # output buffer size is computed from them.
+ _testcapi = import_module("_testcapi")
+ INT_MAX = _testcapi.INT_MAX
+
+ f = 1e300
+ c = complex(f)
+ for prec in (INT_MAX, INT_MAX - 1023):
+ for code in "feg":
+ spec = ".%d%s" % (prec, code)
+ with self.subTest(spec=spec):
+ with self.assertRaises(ValueError):
+ format(f, spec)
+ with self.assertRaises(ValueError):
+ format(c, spec)
+ with self.assertRaises(ValueError):
+ ("%" + spec) % f
+ with self.assertRaises(ValueError):
+ ("%" + spec).encode() % f
+
def test_g_format_has_no_trailing_zeros(self):
# regression test for bugs.python.org/issue40780
self.assertEqual("%.3g" % 1505.0, "1.5e+03")
diff --git
a/Misc/NEWS.d/next/Security/2026-09-29-16-32-46.gh-issue-158446.dToaPr.rst
b/Misc/NEWS.d/next/Security/2026-09-29-16-32-46.gh-issue-158446.dToaPr.rst
new file mode 100644
index 000000000000000..f17a3f68b93e855
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-09-29-16-32-46.gh-issue-158446.dToaPr.rst
@@ -0,0 +1,5 @@
+Fix a crash or incorrect output that could occur when formatting a
+:class:`float` or :class:`complex` with a precision close to the platform's
+``INT_MAX``. :c:func:`PyOS_double_to_string` now raises :exc:`ValueError` for
+any precision of that magnitude, regardless of presentation type or value, as
+the format string parsers already did for precisions above ``INT_MAX``.
diff --git a/Python/dtoa.c b/Python/dtoa.c
index 8bba06d3b232898..a173f8707282712 100644
--- a/Python/dtoa.c
+++ b/Python/dtoa.c
@@ -67,6 +67,10 @@
* 8. A corner case where _Py_dg_dtoa didn't strip trailing zeros has been
* fixed. (bugs.python.org/issue40780)
*
+ * 9. _Py_dg_dtoa clamps ndigits in modes 3 and 5 so that its buffer size
+ * arithmetic cannot exceed the int range, and rv_alloc's size doubling
+ * uses size_t. (gh-158446)
+ *
***************************************************************/
/* Please send bug reports for the original dtoa.c code to David M. Gay (dmg
@@ -2108,7 +2112,8 @@ _Py_dg_strtod(const char *s00, char **se)
static char *
rv_alloc(int i)
{
- int j, k, *r;
+ int k, *r;
+ size_t j; /* size_t so that j <<= 1 cannot overflow for i near INT_MAX */
j = sizeof(ULong);
for(k = 0;
@@ -2372,6 +2377,15 @@ _Py_dg_dtoa(double dd, int mode, int ndigits,
leftright = 0;
/* fall through */
case 5:
+ /* -330 < k < 330 for any finite nonzero double. Clamp ndigits so
+ that ndigits + k + 1 stays within int range; no double has
+ anywhere near this many decimal digits so the digits returned
+ are unaffected (*decpt saturates in the no_digits case). Same
+ bound as DOUBLE_TO_STRING_PRECISION_MAX in pystrtod.c. */
+ if (ndigits > INT_MAX - 1024)
+ ndigits = INT_MAX - 1024;
+ else if (ndigits < -(INT_MAX - 1024))
+ ndigits = -(INT_MAX - 1024);
i = ndigits + k + 1;
ilim = i;
ilim1 = i - 1;
diff --git a/Python/pystrtod.c b/Python/pystrtod.c
index 16bf06f0e6cca2f..f927465d3b452b7 100644
--- a/Python/pystrtod.c
+++ b/Python/pystrtod.c
@@ -401,6 +401,15 @@ _Py_string_to_number_with_underscores(
return NULL;
}
+/* Largest precision magnitude accepted by PyOS_double_to_string(). The
+ output buffer sizes computed below and within _Py_dg_dtoa() use int and
+ Py_ssize_t arithmetic on roughly precision + (digits before the point, at
+ most DBL_MAX_10_EXP + 1 == 309) + a few bytes of sign, point and exponent.
+ Staying this far inside the int range keeps all of those sums in range.
+ (Only C callers can pass a negative precision.) _Py_dg_dtoa() applies
+ the same bound to its ndigits argument. */
+#define DOUBLE_TO_STRING_PRECISION_MAX (INT_MAX - 1024)
+
#if _PY_SHORT_FLOAT_REPR == 0
/* Given a string that may have a decimal point in the current
@@ -766,6 +775,13 @@ char * PyOS_double_to_string(double val,
int t, exp;
int upper = 0;
+ if (precision > DOUBLE_TO_STRING_PRECISION_MAX
+ || precision < -DOUBLE_TO_STRING_PRECISION_MAX)
+ {
+ PyErr_SetString(PyExc_ValueError, "precision too big");
+ return NULL;
+ }
+
/* Validate format_code, and map upper and lower case */
switch (format_code) {
case 'e': /* exponent */
@@ -1227,6 +1243,13 @@ char * PyOS_double_to_string(double val,
const char * const *float_strings = lc_float_strings;
int mode;
+ if (precision > DOUBLE_TO_STRING_PRECISION_MAX
+ || precision < -DOUBLE_TO_STRING_PRECISION_MAX)
+ {
+ PyErr_SetString(PyExc_ValueError, "precision too big");
+ return NULL;
+ }
+
/* Validate format_code, and map upper and lower case. Compute the
mode and make any adjustments as needed. */
switch (format_code) {
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]