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 <hjk@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
Marc Mutz 2022-03-31 12:02:11 +02:00
parent 30873cc720
commit 61157c8354
2 changed files with 61 additions and 6 deletions

View File

@ -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<qsizetype>::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;

View File

@ -31,6 +31,9 @@
#include <QBuffer>
#include <QByteArray>
#include <QElapsedTimer>
#include <string>
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;