diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index a2368991364..96c961475c3 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -25,6 +25,21 @@ QT_BEGIN_NAMESPACE +// Worst case memory allocation for a corrupt stream: 256 MB for 32-bit, 1 GB for 64-bit +static constexpr quint64 MaxAcceptableMemoryUse = (sizeof(void*) == 4 ? 256 : 1024) * 1024 * 1024; + +// Internal limits to ensure we don't blow up the memory when parsing a corrupt +// (possibly crafted to exploit) CBOR stream. The recursion impacts both the +// maps/arrays we'll open when parsing and the thread's stack, as the parser is +// itself recursive. If someone really needs more than 1024 layers of nesting, +// they probably have a weird use-case for which custom parsing and +// serialisation code would make sense. The limit on element count is the +// preallocated limit: if the stream does actually have more elements, we will +// grow the container. +Q_DECL_UNUSED static constexpr int MaximumRecursionDepth = 1024; +Q_DECL_UNUSED static constexpr quint64 MaximumPreallocatedElementCount = + MaxAcceptableMemoryUse / MaximumRecursionDepth / sizeof(QtCbor::Element) - 1; + /*! \class QCborValue \inmodule QtCore @@ -1458,6 +1473,17 @@ static Element decodeBasicValueFromCbor(QCborStreamReader &reader) return e; } +// Clamp allocation to avoid crashing due to corrupt stream. This also +// ensures we never overflow qsizetype. The returned length is doubled for Map +// entries to account for key-value pairs. +static qsizetype clampedContainerLength(const QCborStreamReader &reader) +{ + int mapShift = reader.isMap() ? 1 : 0; + quint64 shiftedMaxElements = MaximumPreallocatedElementCount >> mapShift; + qsizetype len = qsizetype(qMin(reader.length(), shiftedMaxElements)); + return len << mapShift; +} + static inline QCborContainerPrivate *createContainerFromCbor(QCborStreamReader &reader, int remainingRecursionDepth) { if (Q_UNLIKELY(remainingRecursionDepth == 0)) { @@ -1466,18 +1492,11 @@ static inline QCborContainerPrivate *createContainerFromCbor(QCborStreamReader & } QCborContainerPrivate *d = nullptr; - int mapShift = reader.isMap() ? 1 : 0; if (reader.isLengthKnown()) { - quint64 len = reader.length(); - - // Clamp allocation to 1M elements (avoids crashing due to corrupt - // stream or loss of precision when converting from quint64 to - // QList::size_type). - len = qMin(len, quint64(1024 * 1024 - 1)); - if (len) { + if (qsizetype len = clampedContainerLength(reader)) { d = new QCborContainerPrivate; d->ref.storeRelaxed(1); - d->elements.reserve(qsizetype(len) << mapShift); + d->elements.reserve(len); } } else { d = new QCborContainerPrivate; @@ -2344,8 +2363,6 @@ QCborValueRef QCborValue::operator[](qint64 key) } #if QT_CONFIG(cborstreamreader) -enum { MaximumRecursionDepth = 1024 }; - /*! Decodes one item from the CBOR stream found in \a reader and returns the equivalent representation. This function is recursive: if the item is a map