-
Notifications
You must be signed in to change notification settings - Fork 6.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[qtbase] add official patch for CVE-2024-25580 (#36822)
Fixes #36821. - [x] Changes comply with the [maintainer guide](https://github.com/microsoft/vcpkg-docs/blob/main/vcpkg/contributing/maintainer-guide.md). - [x] SHA512s are updated for each updated download. - [x] The "supports" clause reflects platforms that may be fixed by this new version. - [x] Any fixed [CI baseline](https://github.com/microsoft/vcpkg/blob/master/scripts/ci.baseline.txt) entries are removed from that file. - [x] Any patches that are no longer applied are deleted from the port's directory. - [x] The version database is fixed by rerunning `./vcpkg x-add-version --all` and committing the result. - [x] Only one version is added to each modified port's versions file.
- Loading branch information
1 parent
62f4702
commit 4bee3f5
Showing
5 changed files
with
333 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,325 @@ | ||
diff --git a/src/gui/util/qktxhandler.cpp b/src/gui/util/qktxhandler.cpp | ||
index f04da929c3..d6948cc01d 100644 | ||
--- a/src/gui/util/qktxhandler.cpp | ||
+++ b/src/gui/util/qktxhandler.cpp | ||
@@ -41,7 +41,7 @@ struct KTXHeader { | ||
quint32 bytesOfKeyValueData; | ||
}; | ||
|
||
-static const quint32 qktxh_headerSize = sizeof(KTXHeader); | ||
+static constexpr quint32 qktxh_headerSize = sizeof(KTXHeader); | ||
|
||
// Currently unused, declared for future reference | ||
struct KTXKeyValuePairItem { | ||
@@ -71,11 +71,24 @@ struct KTXMipmapLevel { | ||
*/ | ||
}; | ||
|
||
-// Returns the nearest multiple of 'rounding' greater than or equal to 'value' | ||
-constexpr quint32 withPadding(quint32 value, quint32 rounding) | ||
+// Returns the nearest multiple of 4 greater than or equal to 'value' | ||
+static const std::optional<quint32> nearestMultipleOf4(quint32 value) | ||
{ | ||
- Q_ASSERT(rounding > 1); | ||
- return value + (rounding - 1) - ((value + (rounding - 1)) % rounding); | ||
+ constexpr quint32 rounding = 4; | ||
+ quint32 result = 0; | ||
+ if (qAddOverflow(value, rounding - 1, &result)) | ||
+ return std::nullopt; | ||
+ result &= ~(rounding - 1); | ||
+ return result; | ||
+} | ||
+ | ||
+// Returns a view with prechecked bounds | ||
+static QByteArrayView safeView(QByteArrayView view, quint32 start, quint32 length) | ||
+{ | ||
+ quint32 end = 0; | ||
+ if (qAddOverflow(start, length, &end) || end > quint32(view.length())) | ||
+ return {}; | ||
+ return view.sliced(start, length); | ||
} | ||
|
||
QKtxHandler::~QKtxHandler() = default; | ||
@@ -83,8 +96,7 @@ QKtxHandler::~QKtxHandler() = default; | ||
bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block) | ||
{ | ||
Q_UNUSED(suffix); | ||
- | ||
- return (qstrncmp(block.constData(), ktxIdentifier, KTX_IDENTIFIER_LENGTH) == 0); | ||
+ return block.startsWith(ktxIdentifier); | ||
} | ||
|
||
QTextureFileData QKtxHandler::read() | ||
@@ -93,55 +105,122 @@ QTextureFileData QKtxHandler::read() | ||
return QTextureFileData(); | ||
|
||
const QByteArray buf = device()->readAll(); | ||
- const quint32 dataSize = quint32(buf.size()); | ||
- if (dataSize < qktxh_headerSize || !canRead(QByteArray(), buf)) { | ||
- qCDebug(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData()); | ||
+ if (buf.size() > std::numeric_limits<quint32>::max()) { | ||
+ qWarning(lcQtGuiTextureIO, "Too big KTX file %s", logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
+ | ||
+ if (!canRead(QByteArray(), buf)) { | ||
+ qWarning(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData()); | ||
return QTextureFileData(); | ||
} | ||
|
||
- const KTXHeader *header = reinterpret_cast<const KTXHeader *>(buf.data()); | ||
- if (!checkHeader(*header)) { | ||
- qCDebug(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData()); | ||
+ if (buf.size() < qsizetype(qktxh_headerSize)) { | ||
+ qWarning(lcQtGuiTextureIO, "Invalid KTX header size in %s", logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
+ | ||
+ KTXHeader header; | ||
+ memcpy(&header, buf.data(), qktxh_headerSize); | ||
+ if (!checkHeader(header)) { | ||
+ qWarning(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData()); | ||
return QTextureFileData(); | ||
} | ||
|
||
QTextureFileData texData; | ||
texData.setData(buf); | ||
|
||
- texData.setSize(QSize(decode(header->pixelWidth), decode(header->pixelHeight))); | ||
- texData.setGLFormat(decode(header->glFormat)); | ||
- texData.setGLInternalFormat(decode(header->glInternalFormat)); | ||
- texData.setGLBaseInternalFormat(decode(header->glBaseInternalFormat)); | ||
+ texData.setSize(QSize(decode(header.pixelWidth), decode(header.pixelHeight))); | ||
+ texData.setGLFormat(decode(header.glFormat)); | ||
+ texData.setGLInternalFormat(decode(header.glInternalFormat)); | ||
+ texData.setGLBaseInternalFormat(decode(header.glBaseInternalFormat)); | ||
|
||
- texData.setNumLevels(decode(header->numberOfMipmapLevels)); | ||
- texData.setNumFaces(decode(header->numberOfFaces)); | ||
+ texData.setNumLevels(decode(header.numberOfMipmapLevels)); | ||
+ texData.setNumFaces(decode(header.numberOfFaces)); | ||
+ | ||
+ const quint32 bytesOfKeyValueData = decode(header.bytesOfKeyValueData); | ||
+ quint32 headerKeyValueSize; | ||
+ if (qAddOverflow(qktxh_headerSize, bytesOfKeyValueData, &headerKeyValueSize)) { | ||
+ qWarning(lcQtGuiTextureIO, "Overflow in size of key value data in header of KTX file %s", | ||
+ logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
|
||
- const quint32 bytesOfKeyValueData = decode(header->bytesOfKeyValueData); | ||
- if (qktxh_headerSize + bytesOfKeyValueData < quint64(buf.size())) // oob check | ||
- texData.setKeyValueMetadata(decodeKeyValues( | ||
- QByteArrayView(buf.data() + qktxh_headerSize, bytesOfKeyValueData))); | ||
- quint32 offset = qktxh_headerSize + bytesOfKeyValueData; | ||
+ if (headerKeyValueSize >= quint32(buf.size())) { | ||
+ qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
+ | ||
+ // File contains key/values | ||
+ if (bytesOfKeyValueData > 0) { | ||
+ auto keyValueDataView = safeView(buf, qktxh_headerSize, bytesOfKeyValueData); | ||
+ if (keyValueDataView.isEmpty()) { | ||
+ qWarning(lcQtGuiTextureIO, "Invalid view in KTX file %s", logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
+ | ||
+ auto keyValues = decodeKeyValues(keyValueDataView); | ||
+ if (!keyValues) { | ||
+ qWarning(lcQtGuiTextureIO, "Could not parse key values in KTX file %s", | ||
+ logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
+ | ||
+ texData.setKeyValueMetadata(*keyValues); | ||
+ } | ||
+ | ||
+ // Technically, any number of levels is allowed but if the value is bigger than | ||
+ // what is possible in KTX V2 (and what makes sense) we return an error. | ||
+ // maxLevels = log2(max(width, height, depth)) | ||
+ const int maxLevels = (sizeof(quint32) * 8) | ||
+ - qCountLeadingZeroBits(std::max( | ||
+ { header.pixelWidth, header.pixelHeight, header.pixelDepth })); | ||
+ | ||
+ if (texData.numLevels() > maxLevels) { | ||
+ qWarning(lcQtGuiTextureIO, "Too many levels in KTX file %s", logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
|
||
- constexpr int MAX_ITERATIONS = 32; // cap iterations in case of corrupt data | ||
+ if (texData.numFaces() != 1 && texData.numFaces() != 6) { | ||
+ qWarning(lcQtGuiTextureIO, "Invalid number of faces in KTX file %s", logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
|
||
- for (int level = 0; level < qMin(texData.numLevels(), MAX_ITERATIONS); level++) { | ||
- if (offset + sizeof(quint32) > dataSize) // Corrupt file; avoid oob read | ||
- break; | ||
+ quint32 offset = headerKeyValueSize; | ||
+ for (int level = 0; level < texData.numLevels(); level++) { | ||
+ const auto imageSizeView = safeView(buf, offset, sizeof(quint32)); | ||
+ if (imageSizeView.isEmpty()) { | ||
+ qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
|
||
- const quint32 imageSize = decode(qFromUnaligned<quint32>(buf.data() + offset)); | ||
- offset += sizeof(quint32); | ||
+ const quint32 imageSize = decode(qFromUnaligned<quint32>(imageSizeView.data())); | ||
+ offset += sizeof(quint32); // overflow checked indirectly above | ||
|
||
- for (int face = 0; face < qMin(texData.numFaces(), MAX_ITERATIONS); face++) { | ||
+ for (int face = 0; face < texData.numFaces(); face++) { | ||
texData.setDataOffset(offset, level, face); | ||
texData.setDataLength(imageSize, level, face); | ||
|
||
// Add image data and padding to offset | ||
- offset += withPadding(imageSize, 4); | ||
+ const auto padded = nearestMultipleOf4(imageSize); | ||
+ if (!padded) { | ||
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX file %s", logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
+ | ||
+ quint32 offsetNext; | ||
+ if (qAddOverflow(offset, *padded, &offsetNext)) { | ||
+ qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); | ||
+ return QTextureFileData(); | ||
+ } | ||
+ | ||
+ offset = offsetNext; | ||
} | ||
} | ||
|
||
if (!texData.isValid()) { | ||
- qCDebug(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", logName().constData()); | ||
+ qWarning(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", | ||
+ logName().constData()); | ||
return QTextureFileData(); | ||
} | ||
|
||
@@ -187,33 +266,83 @@ bool QKtxHandler::checkHeader(const KTXHeader &header) | ||
return is2D && (isCubeMap || isCompressedImage); | ||
} | ||
|
||
-QMap<QByteArray, QByteArray> QKtxHandler::decodeKeyValues(QByteArrayView view) const | ||
+std::optional<QMap<QByteArray, QByteArray>> QKtxHandler::decodeKeyValues(QByteArrayView view) const | ||
{ | ||
QMap<QByteArray, QByteArray> output; | ||
quint32 offset = 0; | ||
- while (offset < view.size() + sizeof(quint32)) { | ||
+ while (offset < quint32(view.size())) { | ||
+ const auto keyAndValueByteSizeView = safeView(view, offset, sizeof(quint32)); | ||
+ if (keyAndValueByteSizeView.isEmpty()) { | ||
+ qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
+ | ||
const quint32 keyAndValueByteSize = | ||
- decode(qFromUnaligned<quint32>(view.constData() + offset)); | ||
- offset += sizeof(quint32); | ||
+ decode(qFromUnaligned<quint32>(keyAndValueByteSizeView.data())); | ||
|
||
- if (offset + keyAndValueByteSize > quint64(view.size())) | ||
- break; // oob read | ||
+ quint32 offsetKeyAndValueStart; | ||
+ if (qAddOverflow(offset, quint32(sizeof(quint32)), &offsetKeyAndValueStart)) { | ||
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
+ | ||
+ quint32 offsetKeyAndValueEnd; | ||
+ if (qAddOverflow(offsetKeyAndValueStart, keyAndValueByteSize, &offsetKeyAndValueEnd)) { | ||
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
+ | ||
+ const auto keyValueView = safeView(view, offsetKeyAndValueStart, keyAndValueByteSize); | ||
+ if (keyValueView.isEmpty()) { | ||
+ qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
|
||
// 'key' is a UTF-8 string ending with a null terminator, 'value' is the rest. | ||
// To separate the key and value we convert the complete data to utf-8 and find the first | ||
// null terminator from the left, here we split the data into two. | ||
- const auto str = QString::fromUtf8(view.constData() + offset, keyAndValueByteSize); | ||
- const int idx = str.indexOf('\0'_L1); | ||
- if (idx == -1) | ||
- continue; | ||
- | ||
- const QByteArray key = str.left(idx).toUtf8(); | ||
- const size_t keySize = key.size() + 1; // Actual data size | ||
- const QByteArray value = QByteArray::fromRawData(view.constData() + offset + keySize, | ||
- keyAndValueByteSize - keySize); | ||
- | ||
- offset = withPadding(offset + keyAndValueByteSize, 4); | ||
- output.insert(key, value); | ||
+ | ||
+ const int idx = keyValueView.indexOf('\0'); | ||
+ if (idx == -1) { | ||
+ qWarning(lcQtGuiTextureIO, "Invalid key in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
+ | ||
+ const QByteArrayView keyView = safeView(view, offsetKeyAndValueStart, idx); | ||
+ if (keyView.isEmpty()) { | ||
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
+ | ||
+ const quint32 keySize = idx + 1; // Actual data size | ||
+ | ||
+ quint32 offsetValueStart; | ||
+ if (qAddOverflow(offsetKeyAndValueStart, keySize, &offsetValueStart)) { | ||
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
+ | ||
+ quint32 valueSize; | ||
+ if (qSubOverflow(keyAndValueByteSize, keySize, &valueSize)) { | ||
+ qWarning(lcQtGuiTextureIO, "Underflow in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
+ | ||
+ const QByteArrayView valueView = safeView(view, offsetValueStart, valueSize); | ||
+ if (valueView.isEmpty()) { | ||
+ qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
+ | ||
+ output.insert(keyView.toByteArray(), valueView.toByteArray()); | ||
+ | ||
+ const auto offsetNext = nearestMultipleOf4(offsetKeyAndValueEnd); | ||
+ if (!offsetNext) { | ||
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); | ||
+ return std::nullopt; | ||
+ } | ||
+ | ||
+ offset = *offsetNext; | ||
} | ||
|
||
return output; | ||
diff --git a/src/gui/util/qktxhandler_p.h b/src/gui/util/qktxhandler_p.h | ||
index 7d54b20922..3a0b8fcf7e 100644 | ||
--- a/src/gui/util/qktxhandler_p.h | ||
+++ b/src/gui/util/qktxhandler_p.h | ||
@@ -17,6 +17,8 @@ | ||
|
||
#include "qtexturefilehandler_p.h" | ||
|
||
+#include <optional> | ||
+ | ||
QT_BEGIN_NAMESPACE | ||
|
||
struct KTXHeader; | ||
@@ -33,7 +35,7 @@ public: | ||
|
||
private: | ||
bool checkHeader(const KTXHeader &header); | ||
- QMap<QByteArray, QByteArray> decodeKeyValues(QByteArrayView view) const; | ||
+ std::optional<QMap<QByteArray, QByteArray>> decodeKeyValues(QByteArrayView view) const; | ||
quint32 decode(quint32 val) const; | ||
|
||
bool inverseEndian = false; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters