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>
This commit is contained in:
parent
1eeabc6652
commit
52a2505672
@ -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),
|
// The data is supposed to be US-ASCII. If it isn't (contains UTF-8),
|
||||||
// QDateTime::fromString will fail anyway.
|
// QDateTime::fromString will fail anyway.
|
||||||
dt = QDateTime::fromString(b->asLatin1(), Qt::ISODateWithMs);
|
dt = QDateTime::fromString(b->asLatin1(), Qt::ISODateWithMs);
|
||||||
} else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Integer) {
|
} else if (tag == qint64(QCborKnownTags::UnixTime_t)) {
|
||||||
dt = QDateTime::fromSecsSinceEpoch(e.value, Qt::UTC);
|
qint64 msecs;
|
||||||
} else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Double) {
|
bool ok = false;
|
||||||
dt = QDateTime::fromMSecsSinceEpoch(qint64(e.fpvalue() * 1000), Qt::UTC);
|
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()) {
|
if (dt.isValid()) {
|
||||||
QByteArray text = dt.toString(Qt::ISODateWithMs).toLatin1();
|
QByteArray text = dt.toString(Qt::ISODateWithMs).toLatin1();
|
||||||
|
@ -106,6 +106,8 @@ private slots:
|
|||||||
void fromCborStreamReaderIODevice();
|
void fromCborStreamReaderIODevice();
|
||||||
void validation_data();
|
void validation_data();
|
||||||
void validation();
|
void validation();
|
||||||
|
void extendedTypeValidation_data();
|
||||||
|
void extendedTypeValidation();
|
||||||
void hugeDeviceValidation_data();
|
void hugeDeviceValidation_data();
|
||||||
void hugeDeviceValidation();
|
void hugeDeviceValidation();
|
||||||
void recursionLimit_data();
|
void recursionLimit_data();
|
||||||
@ -118,6 +120,68 @@ private slots:
|
|||||||
void streamVariantSerialization();
|
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)
|
// Get the validation data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp)
|
||||||
#include "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()
|
void tst_QCborValue::hugeDeviceValidation_data()
|
||||||
{
|
{
|
||||||
addValidationHugeDevice(MaxByteArraySize + 1, MaxStringSize + 1);
|
addValidationHugeDevice(MaxByteArraySize + 1, MaxStringSize + 1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user