QBitArray: avoid overflow in storage-to-size calculations

Unlike other containers, a QBitArray's size() is not limited by
storage, but, esp. on 32-bit platforms, its size_type: A INT_MAX
size() QBitArray only requires 256MiB of storage.

So we can't rely on "won't happen in practice" here and need to avoid
the potential UB (signed overflow) in the (d.size() * 8 - *d.data())
storage-to-logical-size calculation by using unsigned arithmetic.

Use the opportunity to Extract Method adjust_head_and_tail(),
centralizing the bit fiddling.

Pick-to: 6.6 6.5 6.2 5.15
Change-Id: I485eafdf3ce2087a81c683672ff98a43f97c9968
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 78f8dfc5427457783ceef7d85885cddbec035ebe)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Marc Mutz 2024-01-30 11:15:13 +01:00 committed by Qt Cherry-pick Bot
parent e69a151573
commit e576d50d7c
2 changed files with 22 additions and 23 deletions

View File

@ -118,6 +118,16 @@ static constexpr qsizetype allocation_size(qsizetype size)
return size <= 0 ? 0 : storage_size(size) + 1;
}
static void adjust_head_and_tail(char *data, qsizetype storageSize, qsizetype logicalSize)
{
quint8 *c = reinterpret_cast<quint8 *>(data);
// store the difference between storage and logical size in d[0]:
*c = quint8(size_t(storageSize) * 8 - logicalSize);
// reset unallocated bits to 0:
if (logicalSize & 7)
*(c + 1 + logicalSize / 8) &= (1 << (logicalSize & 7)) - 1;
}
/*!
Constructs a bit array containing \a size bits. The bits are
initialized with \a value, which defaults to false (0).
@ -131,9 +141,7 @@ QBitArray::QBitArray(qsizetype size, bool value)
uchar *c = reinterpret_cast<uchar *>(d.data());
memset(c + 1, value ? 0xff : 0, d.size() - 1);
*c = d.size() * 8 - size;
if (value && size && size & 7)
*(c + 1 + size / 8) &= (1 << (size & 7)) - 1;
adjust_head_and_tail(d.data(), d.size(), size);
}
/*! \fn qsizetype QBitArray::size() const
@ -203,11 +211,9 @@ void QBitArray::resize(qsizetype size)
qsizetype s = d.size();
d.resize(allocation_size(size));
uchar *c = reinterpret_cast<uchar *>(d.data());
if (size > (s << 3))
if (d.size() > s)
memset(c + s, 0, d.size() - s);
else if (size & 7)
*(c + 1 + size / 8) &= (1 << (size & 7)) - 1;
*c = d.size() * 8 - size;
adjust_head_and_tail(d.data(), d.size(), size);
}
}
@ -306,17 +312,11 @@ QBitArray QBitArray::fromBits(const char *data, qsizetype size)
QBitArray result;
if (size == 0)
return result;
qsizetype nbytes = storage_size(size);
result.d = QByteArray(nbytes + 1, Qt::Uninitialized);
char *bits = result.d.data();
memcpy(bits + 1, data, nbytes);
// clear any unused bits from the last byte
if (size & 7)
bits[nbytes] &= 0xffU >> (8 - (size & 7));
*bits = result.d.size() * 8 - size;
auto &d = result.d;
d.resize(allocation_size(size));
memcpy(d.data() + 1, data, d.size() - 1);
adjust_head_and_tail(d.data(), d.size(), size);
return result;
}
@ -958,14 +958,13 @@ QDataStream &operator>>(QDataStream &in, QBitArray &ba)
allocated += blockSize;
}
qsizetype paddingMask = ~((0x1 << (len & 0x7)) - 1);
if (paddingMask != ~0x0 && (ba.d.constData()[ba.d.size() - 1] & paddingMask)) {
const auto fromStream = ba.d.back();
adjust_head_and_tail(ba.d.data(), ba.d.size(), len);
if (ba.d.back() != fromStream) {
ba.clear();
in.setStatus(QDataStream::ReadCorruptData);
return in;
}
*ba.d.data() = ba.d.size() * 8 - len;
return in;
}
#endif // QT_NO_DATASTREAM

View File

@ -70,8 +70,8 @@ public:
void swap(QBitArray &other) noexcept { d.swap(other.d); }
inline qsizetype size() const { return (d.size() << 3) - *d.constData(); }
inline qsizetype count() const { return (d.size() << 3) - *d.constData(); }
qsizetype size() const { return qsizetype((size_t(d.size()) << 3) - *d.constData()); }
qsizetype count() const { return size(); }
qsizetype count(bool on) const;
inline bool isEmpty() const { return d.isEmpty(); }