QCborValue: avoid allocating result if data is insufficient
Similar to the previous commit which applied to QCborStreamReader, don't allocate too much data before checking that the stream actually has that much. Fixes: QTBUG-88256 Change-Id: I7b9b97ae9b32412abdc6fffd16454b7568a063ba Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io> (cherry picked from commit 638171eb10cfb186a6c47ec052a3b0c5b6449386) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
3caacb2f2b
commit
c5623e5bb1
@ -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) {
|
||||
|
@ -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();
|
||||
// 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();
|
||||
|
||||
// calculate the increment we want
|
||||
if (add_overflow(increment, len, &increment))
|
||||
// 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;
|
||||
|
||||
// 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)
|
||||
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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user