From 3d56ece7aa42ae29ba5b783341557e4cfc148065 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Mon, 20 Feb 2023 01:16:11 +0000 Subject: [PATCH 1/2] [3.10] gh-97786: Fix compiler warnings in pytime.c (GH-101826) Fixes compiler warnings in pytime.c.. (cherry picked from commit b1b375e2670a58fc37cb4c2629ed73b045159918) Co-authored-by: Mark Dickinson --- ...3-02-11-13-23-29.gh-issue-97786.QjvQ1B.rst | 2 + Python/pytime.c | 43 +++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-02-11-13-23-29.gh-issue-97786.QjvQ1B.rst diff --git a/Misc/NEWS.d/next/Library/2023-02-11-13-23-29.gh-issue-97786.QjvQ1B.rst b/Misc/NEWS.d/next/Library/2023-02-11-13-23-29.gh-issue-97786.QjvQ1B.rst new file mode 100644 index 00000000000000..df194b67590d67 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-11-13-23-29.gh-issue-97786.QjvQ1B.rst @@ -0,0 +1,2 @@ +Fix potential undefined behaviour in corner cases of floating-point-to-time +conversions. diff --git a/Python/pytime.c b/Python/pytime.c index 1ef99aee748466..5d9fdc788385c4 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -34,6 +34,25 @@ #define NS_TO_MS (1000 * 1000) #define NS_TO_US (1000) +#if SIZEOF_TIME_T == SIZEOF_LONG_LONG +# define PY_TIME_T_MAX LLONG_MAX +# define PY_TIME_T_MIN LLONG_MIN +#elif SIZEOF_TIME_T == SIZEOF_LONG +# define PY_TIME_T_MAX LONG_MAX +# define PY_TIME_T_MIN LONG_MIN +#else +# error "unsupported time_t size" +#endif + +#if PY_TIME_T_MAX + PY_TIME_T_MIN != -1 +# error "time_t is not a two's complement integer type" +#endif + +#if _PyTime_MIN + _PyTime_MAX != -1 +# error "_PyTime_t is not a two's complement integer type" +#endif + + static void error_time_t_overflow(void) { @@ -157,7 +176,21 @@ _PyTime_DoubleToDenominator(double d, time_t *sec, long *numerator, } assert(0.0 <= floatpart && floatpart < denominator); - if (!_Py_InIntegralTypeRange(time_t, intpart)) { + /* + Conversion of an out-of-range value to time_t gives undefined behaviour + (C99 ยง6.3.1.4p1), so we must guard against it. However, checking that + `intpart` is in range is delicate: the obvious expression `intpart <= + PY_TIME_T_MAX` will first convert the value `PY_TIME_T_MAX` to a double, + potentially changing its value and leading to us failing to catch some + UB-inducing values. The code below works correctly under the mild + assumption that time_t is a two's complement integer type with no trap + representation, and that `PY_TIME_T_MIN` is within the representable + range of a C double. + + Note: we want the `if` condition below to be true for NaNs; therefore, + resist any temptation to simplify by applying De Morgan's laws. + */ + if (!((double)PY_TIME_T_MIN <= intpart && intpart < -(double)PY_TIME_T_MIN)) { error_time_t_overflow(); return -1; } @@ -210,7 +243,8 @@ _PyTime_ObjectToTime_t(PyObject *obj, time_t *sec, _PyTime_round_t round) d = _PyTime_Round(d, round); (void)modf(d, &intpart); - if (!_Py_InIntegralTypeRange(time_t, intpart)) { + /* See comments in _PyTime_DoubleToDenominator */ + if (!((double)PY_TIME_T_MIN <= intpart && intpart < -(double)PY_TIME_T_MIN)) { error_time_t_overflow(); return -1; } @@ -395,7 +429,8 @@ _PyTime_FromDouble(_PyTime_t *t, double value, _PyTime_round_t round, d *= (double)unit_to_ns; d = _PyTime_Round(d, round); - if (!_Py_InIntegralTypeRange(_PyTime_t, d)) { + /* See comments in _PyTime_DoubleToDenominator */ + if (!((double)_PyTime_MIN <= d && d < -(double)_PyTime_MIN)) { _PyTime_overflow(); return -1; } @@ -722,7 +757,7 @@ py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise) info->monotonic = 0; info->adjustable = 1; if (clock_getres(CLOCK_REALTIME, &res) == 0) { - info->resolution = res.tv_sec + res.tv_nsec * 1e-9; + info->resolution = (double)res.tv_sec + (double)res.tv_nsec * 1e-9; } else { info->resolution = 1e-9; From 7e97e9118645a00dce198959bd93c752eacf0279 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 26 Feb 2023 11:40:27 +0000 Subject: [PATCH 2/2] Add comment about the casts --- Python/pytime.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/pytime.c b/Python/pytime.c index 5d9fdc788385c4..ee490b8ed915c2 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -757,6 +757,8 @@ py_get_system_clock(_PyTime_t *tp, _Py_clock_info_t *info, int raise) info->monotonic = 0; info->adjustable = 1; if (clock_getres(CLOCK_REALTIME, &res) == 0) { + /* the explicit (double) casts silence loss-of-precision warnings + on some platforms */ info->resolution = (double)res.tv_sec + (double)res.tv_nsec * 1e-9; } else {