From 2b26dea51b26fff2ea955ad2b50c2c20194f0103 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Tue, 12 Oct 2021 11:16:57 +0200 Subject: [PATCH] Fix some out-of-range issues with time-zone support The MS-Windows back-end neglected to check for overflow when mapping date and time to milliseconds from the epoch. Add the checks for that and take care not to return qint64-min as a transition time - that's the invalidMSecs() value used as a special marker. QTimeZonePrivate::dataForLocalTime() neglected to handle the case of the backend being unable to answer offsetFromUtc() for one of the times requested, which the MS backend might. Change-Id: I6d7ee2cbf9aaf6678abb24a20e18b5cdac7f5a23 Reviewed-by: Qt CI Bot Reviewed-by: Thiago Macieira --- src/corelib/time/qtimezoneprivate.cpp | 7 ++++-- src/corelib/time/qtimezoneprivate_win.cpp | 29 +++++++++++++++-------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp index 6b92d60f7f6..c90547d0ee0 100644 --- a/src/corelib/time/qtimezoneprivate.cpp +++ b/src/corelib/time/qtimezoneprivate.cpp @@ -395,9 +395,12 @@ QTimeZonePrivate::Data QTimeZonePrivate::dataForLocalTime(qint64 forLocalMSecs, int early = offsetFromUtc(recent); int late = offsetFromUtc(imminent); - if (early == late) { // > 99% of the time - if (sub_overflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) + if (early == late // > 99% of the time + || late == invalidSeconds()) { + if (early == invalidSeconds() + || sub_overflow(forLocalMSecs, early * qint64(1000), &utcEpochMSecs)) { return invalidData(); // Outside representable range + } } else { // Close to a DST transition: early > late is near a fall-back, // early < late is near a spring-forward. diff --git a/src/corelib/time/qtimezoneprivate_win.cpp b/src/corelib/time/qtimezoneprivate_win.cpp index 2e2f4f423d5..890a95c8820 100644 --- a/src/corelib/time/qtimezoneprivate_win.cpp +++ b/src/corelib/time/qtimezoneprivate_win.cpp @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2021 The Qt Company Ltd. ** Copyright (C) 2013 John Layt ** Contact: https://www.qt.io/licensing/ ** @@ -41,8 +42,8 @@ #include "qtimezoneprivate_p.h" #include "qdatetime.h" - #include "qdebug.h" +#include #include @@ -95,9 +96,9 @@ namespace { QDate msecsToDate(qint64 msecs) { qint64 jd = JULIAN_DAY_FOR_EPOCH; - - if (qAbs(msecs) >= MSECS_PER_DAY) { - jd += (msecs / MSECS_PER_DAY); + // Corner case: don't use qAbs() because msecs may be numeric_limits::min() + if (msecs >= MSECS_PER_DAY || msecs <= -MSECS_PER_DAY) { + jd += msecs / MSECS_PER_DAY; msecs %= MSECS_PER_DAY; } @@ -275,11 +276,12 @@ QDate calculateTransitionLocalDate(const SYSTEMTIME &rule, int year) return date; } -// Converts a date/time value into msecs -inline qint64 timeToMSecs(QDate date, QTime time) +// Converts a date/time value into msecs, returns true on overflow: +inline bool timeToMSecs(QDate date, QTime time, qint64 *msecs) { - return ((date.toJulianDay() - JULIAN_DAY_FOR_EPOCH) * MSECS_PER_DAY) - + time.msecsSinceStartOfDay(); + qint64 dayms = 0; + return mul_overflow(date.toJulianDay() - JULIAN_DAY_FOR_EPOCH, qint64(MSECS_PER_DAY), &dayms) + || add_overflow(dayms, qint64(time.msecsSinceStartOfDay()), msecs); } qint64 calculateTransitionForYear(const SYSTEMTIME &rule, int year, int bias) @@ -289,8 +291,15 @@ qint64 calculateTransitionForYear(const SYSTEMTIME &rule, int year, int bias) Q_ASSERT(year); const QDate date = calculateTransitionLocalDate(rule, year); const QTime time = QTime(rule.wHour, rule.wMinute, rule.wSecond); - if (date.isValid() && time.isValid()) - return timeToMSecs(date, time) + bias * 60000; + qint64 msecs = 0; + using Bound = std::numeric_limits; + if (date.isValid() && time.isValid() && !timeToMSecs(date, time, &msecs)) { + // If bias pushes us outside representable range, clip to range - and + // exclude min() from range as it's invalidMSecs(): + return bias && add_overflow(msecs, qint64(bias) * 60000, &msecs) + ? (bias < 0 ? Bound::min() + 1 : Bound::max()) + : (msecs == Bound::min() ? msecs + 1 : msecs); + } return QTimeZonePrivate::invalidMSecs(); }