You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
_PyTime_FromDouble() fails to detect an integer overflow when converting a C double to a C int64_t #83458
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=Nonecreated_at=<Date2020-01-09.14:43:45.587>labels= ['3.8', 'type-bug', '3.7']
title='_PyTime_FromDouble() fails to detect an integer overflow when converting a C double to a C int64_t'updated_at=<Date2022-02-07.15:21:14.076>user='https://github.com/graingert'
_PyTime_FromDouble() fails to detect an integer overflow when converting a C double to a C int64_t
Python 3.7.5 (default, Nov 20 2019, 09:21:52)
[GCC 9.2.1 20191008] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import time
>>> time.sleep(9223372036.854777)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: sleep length must be non-negative
It's a bug in _PyTime_FromDouble() which fails to detect the integer overflow when casting a C double to a C _PyTime_T (int64_t): bug in _Py_InIntegralTypeRange(_PyTime_t, d) where d is a C double.
On my Fedora 31, double is a 64-bit IEEE 754 float, _PyTime_t is int64_t (64-bit signed integer).
_PyTime_t maximum is 9223372036854775807. But casted as C double, it becomes 9223372036854775808:
_PyTime_FromDouble() checks if!(_Py_DoubleInIntegralTypeRange(_PyTime_t, d)) with the macro:
#define_Py_InIntegralTypeRange(type, v) (_Py_IntegralTypeMin(type) <= v && v <= _Py_IntegralTypeMax(type))
and _Py_IntegralTypeMax(type)=2**63-1.
"v <= _Py_IntegralTypeMax(type)" compares a C double to a C int64_t: the compiler casts the C int64_t to a C double.
The problem is that 2**63-1 casted to a C double rounds using ROUND_HALF_EVEN rounding mode which gives a number *greater* than 2**63-1: we get 2**63.
To implement "v <= max", we have to round max towards zero (ROUND_DOWN), not round it using ROUND_HALF_EVEN.
I didn't find a way to control the rounding mode of casting C int64_t to C double, but we can round it *afterwards* using nextafter(max, 0.0) (ROUND_DOWN).
Oh, clang on FreeBSD spotted a similar bug in float.__trunc__()!
staticPyObject*float___trunc___impl(PyObject*self)
/*[clinic end generated code: output=dd3e289dd4c6b538 input=591b9ba0d650fdff]*/
{
doublex=PyFloat_AsDouble(self);
doublewholepart; /* integral portion of x, rounded toward 0 */
(void)modf(x, &wholepart);
/* Try to get out cheap if this fits in a Python int. The attempt * to cast to long must be protected, as C doesn't define what * happens if the double is too big to fit in a long. Some rare * systems raise an exception then (RISCOS was mentioned as one, * and someone using a non-default option on Sun also bumped into * that). Note that checking for >= and <= LONG_{MIN,MAX} would * still be vulnerable: if a long has more bits of precision than * a double, casting MIN/MAX to double may yield an approximation, * and if that's rounded up, then, e.g., wholepart=LONG_MAX+1 would * yield true from the C expression wholepart<=LONG_MAX, despite * that wholepart is actually greater than LONG_MAX. */if (LONG_MIN<wholepart&&wholepart<LONG_MAX) { /* <=== HERE */constlongaslong= (long)wholepart;
returnPyLong_FromLong(aslong);
}
returnPyLong_FromDouble(wholepart);
}
Objects/floatobject.c:881:45: warning: implicitconversionfrom'long'to'double'changesvaluefrom9223372036854775807to9223372036854775808
[-Wimplicit-int-float-conversion]
if (LONG_MIN<wholepart&&wholepart<LONG_MAX) {
~ ^~~~~~~~
/usr/include/sys/limits.h:64:18: note: expandedfrommacro'LONG_MAX'#defineLONG_MAX__LONG_MAX/* max for a long */
^~~~~~~~~~
/usr/include/x86/_limits.h:64:20: note: expandedfrommacro'__LONG_MAX'#define__LONG_MAX 0x7fffffffffffffff /* max for a long */
^~~~~~~~~~~~~~~~~~
1warninggenerated.
skrah commented on Jun 24, 2020
skrahmannequin
Mannequin
More actions
Is it a bug in float.__trunc__()? It seems to me that the detailed comment accounts for case of rounding up.
That's why < is used instead of <=.
skrah commented on Jun 25, 2020
skrahmannequin
Mannequin
More actions
C99, 6.3.1.4 Real floating and integer:
"""
When a value of integer type is converted to a real floating type, if the value being
converted can be represented exactly in the new type, it is unchanged. If the value being
converted is in the range of values that can be represented but cannot be represented
exactly, the result is either the nearest higher or nearest lower representable value, chosen
in an implementation-defined manner. If the value being converted is outside the range of
values that can be represented, the behavior is undefined.
"""
So int64_t => double is implementation defined, but not undefined.
float___trunc___impl() looks correct to me (of course we can squash
that warning).
Activity
graingert commentedon Jan 9, 2020
_PyTime_FromDouble() fails to detect an integer overflow when converting a C double to a C int64_t
vstinner commentedon Jan 9, 2020
It's a bug in _PyTime_FromDouble() which fails to detect the integer overflow when casting a C double to a C _PyTime_T (int64_t): bug in _Py_InIntegralTypeRange(_PyTime_t, d) where d is a C double.
On my Fedora 31, double is a 64-bit IEEE 754 float, _PyTime_t is int64_t (64-bit signed integer).
_PyTime_t maximum is 9223372036854775807. But casted as C double, it becomes 9223372036854775808:
vstinner commentedon Jan 10, 2020
_PyTime_FromDouble() checks if!(_Py_DoubleInIntegralTypeRange(_PyTime_t, d)) with the macro:
and _Py_IntegralTypeMax(type)=2**63-1.
"v <= _Py_IntegralTypeMax(type)" compares a C double to a C int64_t: the compiler casts the C int64_t to a C double.
The problem is that 2**63-1 casted to a C double rounds using ROUND_HALF_EVEN rounding mode which gives a number *greater* than 2**63-1: we get 2**63.
To implement "v <= max", we have to round max towards zero (ROUND_DOWN), not round it using ROUND_HALF_EVEN.
I didn't find a way to control the rounding mode of casting C int64_t to C double, but we can round it *afterwards* using nextafter(max, 0.0) (ROUND_DOWN).
vstinner commentedon Jan 10, 2020
Similar issue in HHVM:
vstinner commentedon Mar 12, 2020
Oh, clang on FreeBSD spotted a similar bug in float.__trunc__()!
skrah commentedon Jun 24, 2020
Is it a bug in float.__trunc__()? It seems to me that the detailed comment accounts for case of rounding up.
That's why < is used instead of <=.
skrah commentedon Jun 25, 2020
C99, 6.3.1.4 Real floating and integer:
"""
When a value of integer type is converted to a real floating type, if the value being
converted can be represented exactly in the new type, it is unchanged. If the value being
converted is in the range of values that can be represented but cannot be represented
exactly, the result is either the nearest higher or nearest lower representable value, chosen
in an implementation-defined manner. If the value being converted is outside the range of
values that can be represented, the behavior is undefined.
"""
So int64_t => double is implementation defined, but not undefined.
float___trunc___impl() looks correct to me (of course we can squash
that warning).
vstinner commentedon Feb 7, 2022
New changeset d3e53bc by Victor Stinner in branch 'main':
bpo-39277: Fix PY_TIMEOUT_MAX cast in _threadmodule.c (GH-31195)
d3e53bc
mdickinson commentedon Mar 4, 2023
This was fixed for 3.10 and later in #101826 and its backports.