diff options
author | Thiago Macieira <thiago.macieira@intel.com> | 2020-04-15 13:58:33 -0300 |
---|---|---|
committer | Edward Welbourne <edward.welbourne@qt.io> | 2020-04-30 11:00:29 +0000 |
commit | 52a2505672cbb1ca8b5b32f7bc1259485c65483b (patch) | |
tree | 9acd649a7bbdc4a76f80466c1357a3f830fef2b6 | |
parent | 1eeabc6652220ff263eaa63872cd52c1693cbf69 (diff) |
QCborValue: avoid signed integer oveflows when decoding time_t
QDateTime::fromSecsSinceEpoch() multiplies by 1000 but does not check
for overflow. That means we must do so in QCborValue validation. We
can't use mul_overflow<qint64> on 32-bit platforms, so we do a compare-
and-branch there. For 64-bit platforms, we prefer to do the
multiplication with checked overflow, as the common case is that it will
not overflow and we'll need the multiplication anyway.
Change-Id: Ibdc95e9af7bd456a94ecfffd16060cba6f1c86b8
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
-rw-r--r-- | src/corelib/serialization/qcborvalue.cpp | 23 | ||||
-rw-r--r-- | tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp | 109 |
2 files changed, 128 insertions, 4 deletions
diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index a3729b4ef9..f5cccf1be1 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -788,10 +788,25 @@ static QCborValue::Type convertToExtendedType(QCborContainerPrivate *d) // The data is supposed to be US-ASCII. If it isn't (contains UTF-8), // QDateTime::fromString will fail anyway. dt = QDateTime::fromString(b->asLatin1(), Qt::ISODateWithMs); - } else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Integer) { - dt = QDateTime::fromSecsSinceEpoch(e.value, Qt::UTC); - } else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Double) { - dt = QDateTime::fromMSecsSinceEpoch(qint64(e.fpvalue() * 1000), Qt::UTC); + } else if (tag == qint64(QCborKnownTags::UnixTime_t)) { + qint64 msecs; + bool ok = false; + if (e.type == QCborValue::Integer) { +#if QT_POINTER_SIZE == 8 + // we don't have a fast 64-bit mul_overflow implementation on + // 32-bit architectures. + ok = !mul_overflow(e.value, qint64(1000), &msecs); +#else + static const qint64 Limit = std::numeric_limits<qint64>::max() / 1000; + ok = (e.value > -Limit && e.value < Limit); + if (ok) + msecs = e.value * 1000; +#endif + } else if (e.type == QCborValue::Double) { + ok = convertDoubleTo(round(e.fpvalue() * 1000), &msecs); + } + if (ok) + dt = QDateTime::fromMSecsSinceEpoch(msecs, Qt::UTC); } if (dt.isValid()) { QByteArray text = dt.toString(Qt::ISODateWithMs).toLatin1(); diff --git a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp index 64321c11fa..0b3046fbdc 100644 --- a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp +++ b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp @@ -106,6 +106,8 @@ private slots: void fromCborStreamReaderIODevice(); void validation_data(); void validation(); + void extendedTypeValidation_data(); + void extendedTypeValidation(); void hugeDeviceValidation_data(); void hugeDeviceValidation(); void recursionLimit_data(); @@ -118,6 +120,68 @@ private slots: void streamVariantSerialization(); }; +namespace SimpleEncodeToCbor { +inline size_t lengthOf(int) +{ + return 1; // encode as byte +} + +template <typename T> inline size_t lengthOf(T) +{ + return sizeof(T); +} + +static void encodeOneAt(char *ptr, int v) +{ + // encode as byte + *ptr = char(v); +} + +template <typename T> +static typename std::enable_if<std::is_unsigned<T>::value>::type +encodeOneAt(char *ptr, T v) +{ + qToBigEndian(v, ptr); +} + +template <typename T> +static typename std::enable_if<std::is_floating_point<T>::value || + std::is_same<T, qfloat16>::value>::type +encodeOneAt(char *ptr, T v) +{ + typename QIntegerForSizeof<T>::Unsigned u; + memcpy(&u, &v, sizeof(u)); + qToBigEndian(u, ptr); +} + +static char *encodeAt(char *ptr) +{ + return ptr; +} + +template <typename Arg0, typename... Args> +static char *encodeAt(char *ptr, Arg0 a0, Args... a) +{ + encodeOneAt(ptr, a0); + return encodeAt(ptr + lengthOf(a0), a...); +} + +} // namespace SimpleEncodetoCbor + +template <typename... Args> +static QByteArray encode(Args... a) +{ + // this would be much easier with C++17 fold expressions... + using namespace SimpleEncodeToCbor; + using namespace std; + size_t lengths[] = { lengthOf(a)... }; + size_t total = accumulate(begin(lengths), end(lengths), size_t(0), plus<size_t>{}); + QByteArray result(QByteArray::size_type(total), Qt::Uninitialized); + char *ptr = result.data(); + encodeAt(ptr, a...); + return result; +} + // Get the validation data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp) #include "data.cpp" @@ -1882,6 +1946,51 @@ void tst_QCborValue::validation() } } +void tst_QCborValue::extendedTypeValidation_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QCborValue>("expected"); + + // QDateTime currently stores time in milliseconds, so make sure + // we don't overflow + { + quint64 limit = std::numeric_limits<quint64>::max() / 1000; + QTest::newRow("UnixTime_t:integer-overflow-positive") + << encode(0xc1, 0x1b, limit + 1) + << QCborValue(QCborKnownTags::UnixTime_t, qint64(limit) + 1); + QTest::newRow("UnixTime_t:integer-overflow-negative") + << encode(0xc1, 0x3b, limit) + << QCborValue(QCborKnownTags::UnixTime_t, -qint64(limit) - 1); + + double fplimit = std::numeric_limits<qint64>::min() / (-1000.); // 2^63 ms + QTest::newRow("UnixTime_t:fp-overflow-positive") + << encode(0xc1, 0xfb, fplimit) + << QCborValue(QCborKnownTags::UnixTime_t, fplimit); + QTest::newRow("UnixTime_t:fp-overflow-negative") + << encode(0xc1, 0xfb, -fplimit) + << QCborValue(QCborKnownTags::UnixTime_t, -fplimit); + } +} + +void tst_QCborValue::extendedTypeValidation() +{ + QFETCH(QByteArray, data); + QFETCH(QCborValue, expected); + + QCborParserError error; + QCborValue decoded = QCborValue::fromCbor(data, &error); + QVERIFY2(error.error == QCborError(), qPrintable(error.errorString())); + QCOMPARE(error.offset, data.size()); + QCOMPARE(decoded, expected); + + QByteArray encoded = decoded.toCbor(); +#if QT_VERSION < QT_VERSION_CHECK(6,0,0) + // behavior change, see qdatetime.cpp:fromIsoTimeString + QEXPECT_FAIL("DateTime:Null-at-19", "QDateTime parsing fixed, but only in 6.0", Abort); +#endif + QCOMPARE(encoded, data); +} + void tst_QCborValue::hugeDeviceValidation_data() { addValidationHugeDevice(MaxByteArraySize + 1, MaxStringSize + 1); |