From 61157c8354484f607282719bbd836cb7220de109 Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Thu, 31 Mar 2022 12:02:11 +0200 Subject: [PATCH] QBuffer: fix writing more than two GiB of data In Qt 6, QByteArray can hold more than two GiB of data on 64-bit platforms, so QBuffer should be able to handle writes of more than two GiB, too. But the implementation didn't check for overflow and held sizes in int variables, so it happily reported success but wrote data only mod INT_MAX. Fix by carefully avoiding overflow and using size variables of proper type. [ChangeLog][QtCore][QBuffer] Fixed silent data truncation when writing more than two GiB at once on 64-bit platforms. Pick-to: 6.3 6.2 Fixes: QTBUG-102171 Change-Id: Ib666f9f7db24495b4ed64191a48b35edc410f7e9 Reviewed-by: hjk Reviewed-by: Qt CI Bot Reviewed-by: Lars Knoll --- src/corelib/io/qbuffer.cpp | 14 ++--- tests/auto/corelib/io/qbuffer/tst_qbuffer.cpp | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/corelib/io/qbuffer.cpp b/src/corelib/io/qbuffer.cpp index a7da9ae600..e031e1c77b 100644 --- a/src/corelib/io/qbuffer.cpp +++ b/src/corelib/io/qbuffer.cpp @@ -421,17 +421,19 @@ qint64 QBuffer::readData(char *data, qint64 len) qint64 QBuffer::writeData(const char *data, qint64 len) { Q_D(QBuffer); - int extraBytes = pos() + len - d->buf->size(); - if (extraBytes > 0) { // overflow - int newSize = d->buf->size() + extraBytes; - d->buf->resize(newSize); - if (d->buf->size() != newSize) { // could not resize + const quint64 required = quint64(pos()) + quint64(len); // cannot overflow (pos() ≥ 0, len ≥ 0) + + if (required > quint64(d->buf->size())) { // capacity exceeded + // The following must hold, since qsizetype covers half the virtual address space: + Q_ASSUME(required <= quint64((std::numeric_limits::max)())); + d->buf->resize(qsizetype(required)); + if (quint64(d->buf->size()) != required) { // could not resize qWarning("QBuffer::writeData: Memory allocation error"); return -1; } } - memcpy(d->buf->data() + pos(), data, int(len)); + memcpy(d->buf->data() + pos(), data, size_t(len)); #ifndef QT_NO_QOBJECT d->writtenSinceLastEmit += len; diff --git a/tests/auto/corelib/io/qbuffer/tst_qbuffer.cpp b/tests/auto/corelib/io/qbuffer/tst_qbuffer.cpp index 7a68da8192..fb756d9e63 100644 --- a/tests/auto/corelib/io/qbuffer/tst_qbuffer.cpp +++ b/tests/auto/corelib/io/qbuffer/tst_qbuffer.cpp @@ -31,6 +31,9 @@ #include #include +#include + +#include class tst_QBuffer : public QObject { @@ -58,6 +61,7 @@ private slots: void readLineBoundaries(); void getAndUngetChar(); void writeAfterQByteArrayResize(); + void writeOfMoreThan2GiB(); void read_null(); protected slots: @@ -599,6 +603,55 @@ void tst_QBuffer::writeAfterQByteArrayResize() QCOMPARE(buffer.buffer().size(), 1000); } +void tst_QBuffer::writeOfMoreThan2GiB() +{ + if constexpr (sizeof(void*) == 4) + QSKIP("This is a 64-bit-only test"); + + [[maybe_unused]] constexpr size_t GiB = 1024 * 1024 * 1024; + +#ifndef QT_NO_EXCEPTIONS + + try { + // + // GIVEN: an empty QBuffer open for writing + // + QBuffer buffer; + QVERIFY(buffer.open(QIODevice::WriteOnly)); + + // + // WHEN: writing more than 2GiB in a singe chunk: + // + QElapsedTimer timer; + timer.start(); + + const std::string input(2 * GiB + 1, 42); + + qDebug("created dataset in %lld ms", timer.restart()); + + const auto inputSize = qint64(input.size()); + + QCOMPARE(buffer.write(input.data(), inputSize), inputSize); + + qDebug("performed write in %lld ms", timer.restart()); + + // + // THEN: the buffer contains the written data + // + QCOMPARE(buffer.buffer().size(), inputSize); + QVERIFY(buffer.buffer() == QByteArrayView{input}); + + qDebug("verified result in %lld ms", timer.elapsed()); + + } catch (const std::bad_alloc &) { + QSKIP("Cannot allocate enough memory for this test"); + } + +#else + QSKIP("This test requires exceptions enabled."); +#endif // QT_NO_EXCEPTIONS +} + void tst_QBuffer::read_null() { QByteArray buffer;