summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/serialization/qcborstreamreader.cpp28
-rw-r--r--src/corelib/serialization/qcborvalue.cpp82
-rw-r--r--src/corelib/serialization/qcborvalue_p.h10
-rw-r--r--tests/auto/corelib/serialization/cborlargedatavalidation.cpp15
-rw-r--r--tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp6
5 files changed, 82 insertions, 59 deletions
diff --git a/src/corelib/serialization/qcborstreamreader.cpp b/src/corelib/serialization/qcborstreamreader.cpp
index ddc93e469f..d097979bda 100644
--- a/src/corelib/serialization/qcborstreamreader.cpp
+++ b/src/corelib/serialization/qcborstreamreader.cpp
@@ -681,6 +681,7 @@ public:
bool isPlainPointer() const { return maxlen_or_type >= 0; }
};
+ static QCborStreamReader::StringResultCode appendStringChunk(QCborStreamReader &reader, QByteArray *data);
QCborStreamReader::StringResult<qsizetype> readStringChunk(ReadStringChunk params);
bool ensureStringIteration();
};
@@ -1479,6 +1480,21 @@ QCborStreamReader::readStringChunk(char *ptr, qsizetype maxlen)
return r;
}
+// used by qcborvalue.cpp
+QCborStreamReader::StringResultCode qt_cbor_append_string_chunk(QCborStreamReader &reader, QByteArray *data)
+{
+ return QCborStreamReaderPrivate::appendStringChunk(reader, data);
+}
+
+inline QCborStreamReader::StringResultCode
+QCborStreamReaderPrivate::appendStringChunk(QCborStreamReader &reader, QByteArray *data)
+{
+ auto status = reader.d->readStringChunk(data).status;
+ if (status == QCborStreamReader::EndOfString && reader.lastError() == QCborError::NoError)
+ reader.preparse();
+ return status;
+}
+
Q_NEVER_INLINE QCborStreamReader::StringResult<qsizetype>
QCborStreamReaderPrivate::readStringChunk(ReadStringChunk params)
{
@@ -1547,17 +1563,23 @@ QCborStreamReaderPrivate::readStringChunk(ReadStringChunk params)
ptr = params.ptr;
} else if (params.isByteArray()) {
// See note above on having ensured there is enough incoming data.
+ auto oldSize = params.array->size();
+ auto newSize = oldSize;
+ if (add_overflow<decltype(newSize)>(oldSize, toRead, &newSize)) {
+ handleError(CborErrorDataTooLarge);
+ return result;
+ }
try {
- params.array->resize(toRead);
+ params.array->resize(newSize);
} catch (const std::bad_alloc &) {
// the distinction between DataTooLarge and OOM is mostly for
// compatibility with Qt 5; in Qt 6, we could consider everything
// to be OOM.
- handleError(toRead > MaxByteArraySize ? CborErrorDataTooLarge: CborErrorOutOfMemory);
+ handleError(newSize > MaxByteArraySize ? CborErrorDataTooLarge: CborErrorOutOfMemory);
return result;
}
- ptr = const_cast<char *>(params.array->constData());
+ ptr = const_cast<char *>(params.array->constData()) + oldSize;
}
if (device) {
diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp
index c7a2198644..f27ef94b4a 100644
--- a/src/corelib/serialization/qcborvalue.cpp
+++ b/src/corelib/serialization/qcborvalue.cpp
@@ -1556,31 +1556,37 @@ inline void QCborContainerPrivate::setErrorInReader(QCborStreamReader &reader, Q
qt_cbor_stream_set_error(reader.d.data(), error);
}
+extern QCborStreamReader::StringResultCode qt_cbor_append_string_chunk(QCborStreamReader &reader, QByteArray *data);
+
void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader)
{
- auto addByteData_local = [this](QByteArray::size_type len) -> qint64 {
- // this duplicates a lot of addByteData, but with overflow checking
- QByteArray::size_type newSize;
- QByteArray::size_type increment = sizeof(QtCbor::ByteData);
- QByteArray::size_type alignment = alignof(QtCbor::ByteData);
- QByteArray::size_type offset = data.size();
-
- // calculate the increment we want
- if (add_overflow(increment, len, &increment))
- return -1;
-
- // align offset
- if (add_overflow(offset, alignment - 1, &offset))
- return -1;
- offset &= ~(alignment - 1);
-
- // and calculate the final size
- if (add_overflow(offset, increment, &newSize))
- return -1;
- if (newSize > MaxByteArraySize)
+ // The use of size_t means none of the operations here can overflow because
+ // all inputs are less than half SIZE_MAX.
+ auto addByteData_local = [this](size_t len) -> qint64 {
+ constexpr size_t EstimatedOverhead = 16;
+ constexpr size_t MaxMemoryIncrement = 16384;
+ size_t offset = data.size();
+
+ // add space for aligned ByteData (this can't overflow)
+ offset += sizeof(QtCbor::ByteData) + alignof(QtCbor::ByteData);
+ offset &= ~(alignof(QtCbor::ByteData) - 1);
+ if (offset > size_t(MaxByteArraySize))
return -1;
- data.resize(newSize);
+ // and calculate the size we want to have
+ size_t newCapacity = offset + len; // can't overflow
+ if (len > MaxMemoryIncrement - EstimatedOverhead) {
+ // there's a non-zero chance that we won't need this memory at all,
+ // so capa how much we allocate
+ newCapacity = offset + MaxMemoryIncrement - EstimatedOverhead;
+ }
+ if (newCapacity > size_t(MaxByteArraySize)) {
+ // this may cause an allocation failure
+ newCapacity = MaxByteArraySize;
+ }
+ if (newCapacity > size_t(data.capacity()))
+ data.reserve(newCapacity);
+ data.resize(offset + sizeof(QtCbor::ByteData));
return offset;
};
auto dataPtr = [this]() {
@@ -1617,42 +1623,32 @@ void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader)
// read chunks
bool isAscii = (e.type == QCborValue::String);
- auto r = reader.readStringChunk(dataPtr() + e.value + sizeof(ByteData), len);
- while (r.status == QCborStreamReader::Ok) {
+ QCborStreamReader::StringResultCode status = qt_cbor_append_string_chunk(reader, &data);
+ while (status == QCborStreamReader::Ok) {
if (e.type == QCborValue::String && len) {
// verify UTF-8 string validity
auto utf8result = QUtf8::isValidUtf8(QByteArrayView(dataPtr(), data.size()).last(len));
if (!utf8result.isValidUtf8) {
- r.status = QCborStreamReader::Error;
+ status = QCborStreamReader::Error;
setErrorInReader(reader, { QCborError::InvalidUtf8String });
break;
}
isAscii = isAscii && utf8result.isValidAscii;
}
- // allocate space for the next chunk
rawlen = reader.currentStringChunkSize();
len = rawlen;
if (len == rawlen) {
- auto oldSize = data.size();
- auto newSize = oldSize;
- if (!add_overflow(newSize, len, &newSize) && newSize < MaxByteArraySize) {
- if (newSize != oldSize)
- data.resize(newSize);
-
- // read the chunk
- r = reader.readStringChunk(dataPtr() + oldSize, len);
- continue;
- }
+ status = qt_cbor_append_string_chunk(reader, &data);
+ } else {
+ // error
+ status = QCborStreamReader::Error;
+ setErrorInReader(reader, { QCborError::DataTooLarge });
}
-
- // error
- r.status = QCborStreamReader::Error;
- setErrorInReader(reader, { QCborError::DataTooLarge });
}
// update size
- if (r.status == QCborStreamReader::EndOfString && e.flags & Element::HasByteData) {
+ if (status == QCborStreamReader::EndOfString) {
auto b = new (dataPtr() + e.value) ByteData;
b->len = data.size() - e.value - int(sizeof(*b));
usedData += b->len;
@@ -1667,14 +1663,12 @@ void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader)
if (e.type == QCborValue::String) {
if (Q_UNLIKELY(b->len > MaxStringSize)) {
setErrorInReader(reader, { QCborError::DataTooLarge });
- r.status = QCborStreamReader::Error;
+ status = QCborStreamReader::Error;
}
}
}
- if (r.status == QCborStreamReader::Error) {
- // There can only be errors if there was data to be read.
- Q_ASSERT(e.flags & Element::HasByteData);
+ if (status == QCborStreamReader::Error) {
data.truncate(e.value);
return;
}
diff --git a/src/corelib/serialization/qcborvalue_p.h b/src/corelib/serialization/qcborvalue_p.h
index d68d44e7be..5c8f244ad8 100644
--- a/src/corelib/serialization/qcborvalue_p.h
+++ b/src/corelib/serialization/qcborvalue_p.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2018 Intel Corporation.
+** Copyright (C) 2020 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
@@ -53,6 +53,10 @@
#include "qcborvalue.h"
+#if QT_CONFIG(cborstreamreader)
+# include "qcborstreamreader.h"
+#endif
+
#include <private/qglobal_p.h>
#include <private/qstringconverter_p.h>
@@ -414,9 +418,11 @@ public:
elements.remove(idx);
}
- void decodeValueFromCbor(QCborStreamReader &reader, int remainiingStackDepth);
+#if QT_CONFIG(cborstreamreader)
+ void decodeValueFromCbor(QCborStreamReader &reader, int remainingStackDepth);
void decodeStringFromCbor(QCborStreamReader &reader);
static inline void setErrorInReader(QCborStreamReader &reader, QCborError error);
+#endif
};
QT_END_NAMESPACE
diff --git a/tests/auto/corelib/serialization/cborlargedatavalidation.cpp b/tests/auto/corelib/serialization/cborlargedatavalidation.cpp
index 64191db1ef..c5cf1dc04e 100644
--- a/tests/auto/corelib/serialization/cborlargedatavalidation.cpp
+++ b/tests/auto/corelib/serialization/cborlargedatavalidation.cpp
@@ -87,25 +87,28 @@ void addValidationLargeData(qsizetype minInvalid, qsizetype maxInvalid)
toolong[0] = sizeof(v) > 4 ? 0x5b : 0x5a;
qToBigEndian(v, toolong + 1);
+ bool overflows = v > std::numeric_limits<qsizetype>::max() - 1 - qsizetype(sizeof(v));
+ CborError err = overflows ? CborErrorDataTooLarge : CborErrorUnexpectedEOF;
+
QTest::addRow("bytearray-too-big-for-qbytearray-%llx", v)
- << QByteArray(toolong, sizeof(toolong)) << 0 << CborErrorUnexpectedEOF;
+ << QByteArray(toolong, sizeof(toolong)) << 0 << err;
QTest::addRow("bytearray-chunked-too-big-for-qbytearray-%llx", v)
<< ('\x5f' + QByteArray(toolong, sizeof(toolong)) + '\xff')
- << 0 << CborErrorUnexpectedEOF;
+ << 0 << err;
QTest::addRow("bytearray-2chunked-too-big-for-qbytearray-%llx", v)
<< ("\x5f\x40" + QByteArray(toolong, sizeof(toolong)) + '\xff')
- << 0 << CborErrorUnexpectedEOF;
+ << 0 << err;
toolong[0] |= 0x20;
// QCborStreamReader::readString copies to a QByteArray first
QTest::addRow("string-too-big-for-qbytearray-%llx", v)
- << QByteArray(toolong, sizeof(toolong)) << 0 << CborErrorUnexpectedEOF;
+ << QByteArray(toolong, sizeof(toolong)) << 0 << err;
QTest::addRow("string-chunked-too-big-for-qbytearray-%llx", v)
<< ('\x7f' + QByteArray(toolong, sizeof(toolong)) + '\xff')
- << 0 << CborErrorUnexpectedEOF;
+ << 0 << err;
QTest::addRow("string-2chunked-too-big-for-qbytearray-%llx", v)
<< ("\x7f\x60" + QByteArray(toolong, sizeof(toolong)) + '\xff')
- << 0 << CborErrorUnexpectedEOF;
+ << 0 << err;
}
}
diff --git a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp
index 64d4451f08..4ec5095f1c 100644
--- a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp
+++ b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp
@@ -2007,11 +2007,11 @@ void tst_QCborValue::validation_data()
qToBigEndian(MinInvalid - 1, toolong + 1);
QTest::addRow("bytearray-2chunked+1-too-big-for-qbytearray-%llx", MinInvalid)
<< ("\x5f\x41z" + QByteArray(toolong, sizeof(toolong)) + '\xff')
- << 0 << CborErrorDataTooLarge;
+ << 0 << CborErrorUnexpectedEOF;
toolong[0] |= 0x20;
QTest::addRow("string-2chunked+1-too-big-for-qbytearray-%llx", MinInvalid)
<< ("\x7f\x61z" + QByteArray(toolong, sizeof(toolong)) + '\xff')
- << 0 << CborErrorDataTooLarge;
+ << 0 << CborErrorUnexpectedEOF;
// These tests say we have arrays and maps with very large item counts.
// They are meant to ensure we don't pre-allocate a lot of memory
@@ -2042,7 +2042,6 @@ void tst_QCborValue::validation()
QCborParserError parserError;
QCborValue decoded = QCborValue::fromCbor(data, &parserError);
- if (parserError.error != QCborError::DataTooLarge) // ### temporary!!
QCOMPARE(parserError.error, error);
if (data.startsWith('\x81')) {
@@ -2050,7 +2049,6 @@ void tst_QCborValue::validation()
char *ptr = const_cast<char *>(data.constData());
QByteArray mid = QByteArray::fromRawData(ptr + 1, data.size() - 1);
decoded = QCborValue::fromCbor(mid, &parserError);
- if (parserError.error != QCborError::DataTooLarge) // ### temporary!!
QCOMPARE(parserError.error, error);
}
}