|
| 1 | +From 35e9912533b298ee913f9250f80160e534457d57 Mon Sep 17 00:00:00 2001 |
| 2 | +From: archana25-ms <v-shettigara@microsoft.com> |
| 3 | +Date: Sun, 16 Feb 2025 07:55:11 +0000 |
| 4 | +Subject: [PATCH] Address CVE-2024-25580 |
| 5 | + |
| 6 | +Source link: https://download.qt.io/official_releases/qt/5.15/CVE-2024-25580-qtbase-5.15.diff |
| 7 | + |
| 8 | +--- |
| 9 | + src/gui/util/qktxhandler.cpp | 138 +++++++++++++++++++++++++++-------- |
| 10 | + src/gui/util/qktxhandler_p.h | 2 +- |
| 11 | + 2 files changed, 110 insertions(+), 30 deletions(-) |
| 12 | + |
| 13 | +diff --git a/src/gui/util/qktxhandler.cpp b/src/gui/util/qktxhandler.cpp |
| 14 | +index 7eda4c46..2853e46c 100644 |
| 15 | +--- a/src/gui/util/qktxhandler.cpp |
| 16 | ++++ b/src/gui/util/qktxhandler.cpp |
| 17 | +@@ -73,7 +73,7 @@ struct KTXHeader { |
| 18 | + quint32 bytesOfKeyValueData; |
| 19 | + }; |
| 20 | + |
| 21 | +-static const quint32 headerSize = sizeof(KTXHeader); |
| 22 | ++static constexpr quint32 qktxh_headerSize = sizeof(KTXHeader); |
| 23 | + |
| 24 | + // Currently unused, declared for future reference |
| 25 | + struct KTXKeyValuePairItem { |
| 26 | +@@ -103,11 +103,36 @@ struct KTXMipmapLevel { |
| 27 | + */ |
| 28 | + }; |
| 29 | + |
| 30 | +-bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block) |
| 31 | ++static bool qAddOverflow(quint32 v1, quint32 v2, quint32 *r) { |
| 32 | ++ // unsigned additions are well-defined |
| 33 | ++ *r = v1 + v2; |
| 34 | ++ return v1 > quint32(v1 + v2); |
| 35 | ++} |
| 36 | ++ |
| 37 | ++// Returns the nearest multiple of 4 greater than or equal to 'value' |
| 38 | ++static bool nearestMultipleOf4(quint32 value, quint32 *result) |
| 39 | ++{ |
| 40 | ++ constexpr quint32 rounding = 4; |
| 41 | ++ *result = 0; |
| 42 | ++ if (qAddOverflow(value, rounding - 1, result)) |
| 43 | ++ return true; |
| 44 | ++ *result &= ~(rounding - 1); |
| 45 | ++ return false; |
| 46 | ++} |
| 47 | ++ |
| 48 | ++// Returns a slice with prechecked bounds |
| 49 | ++static QByteArray safeSlice(const QByteArray& array, quint32 start, quint32 length) |
| 50 | + { |
| 51 | +- Q_UNUSED(suffix) |
| 52 | ++ quint32 end = 0; |
| 53 | ++ if (qAddOverflow(start, length, &end) || end > quint32(array.length())) |
| 54 | ++ return {}; |
| 55 | ++ return QByteArray(array.data() + start, length); |
| 56 | ++} |
| 57 | + |
| 58 | +- return (qstrncmp(block.constData(), ktxIdentifier, KTX_IDENTIFIER_LENGTH) == 0); |
| 59 | ++bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block) |
| 60 | ++{ |
| 61 | ++ Q_UNUSED(suffix); |
| 62 | ++ return block.startsWith(QByteArray::fromRawData(ktxIdentifier, KTX_IDENTIFIER_LENGTH)); |
| 63 | + } |
| 64 | + |
| 65 | + QTextureFileData QKtxHandler::read() |
| 66 | +@@ -115,42 +140,97 @@ QTextureFileData QKtxHandler::read() |
| 67 | + if (!device()) |
| 68 | + return QTextureFileData(); |
| 69 | + |
| 70 | +- QByteArray buf = device()->readAll(); |
| 71 | +- const quint32 dataSize = quint32(buf.size()); |
| 72 | +- if (dataSize < headerSize || !canRead(QByteArray(), buf)) { |
| 73 | +- qCDebug(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData()); |
| 74 | ++ const QByteArray buf = device()->readAll(); |
| 75 | ++ if (size_t(buf.size()) > std::numeric_limits<quint32>::max()) { |
| 76 | ++ qWarning(lcQtGuiTextureIO, "Too big KTX file %s", logName().constData()); |
| 77 | ++ return QTextureFileData(); |
| 78 | ++ } |
| 79 | ++ |
| 80 | ++ if (!canRead(QByteArray(), buf)) { |
| 81 | ++ qWarning(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData()); |
| 82 | ++ return QTextureFileData(); |
| 83 | ++ } |
| 84 | ++ |
| 85 | ++ if (buf.size() < qsizetype(qktxh_headerSize)) { |
| 86 | ++ qWarning(lcQtGuiTextureIO, "Invalid KTX header size in %s", logName().constData()); |
| 87 | + return QTextureFileData(); |
| 88 | + } |
| 89 | + |
| 90 | +- const KTXHeader *header = reinterpret_cast<const KTXHeader *>(buf.constData()); |
| 91 | +- if (!checkHeader(*header)) { |
| 92 | +- qCDebug(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData()); |
| 93 | ++ KTXHeader header; |
| 94 | ++ memcpy(&header, buf.data(), qktxh_headerSize); |
| 95 | ++ if (!checkHeader(header)) { |
| 96 | ++ qWarning(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData()); |
| 97 | + return QTextureFileData(); |
| 98 | + } |
| 99 | + |
| 100 | + QTextureFileData texData; |
| 101 | + texData.setData(buf); |
| 102 | + |
| 103 | +- texData.setSize(QSize(decode(header->pixelWidth), decode(header->pixelHeight))); |
| 104 | +- texData.setGLFormat(decode(header->glFormat)); |
| 105 | +- texData.setGLInternalFormat(decode(header->glInternalFormat)); |
| 106 | +- texData.setGLBaseInternalFormat(decode(header->glBaseInternalFormat)); |
| 107 | +- |
| 108 | +- texData.setNumLevels(decode(header->numberOfMipmapLevels)); |
| 109 | +- quint32 offset = headerSize + decode(header->bytesOfKeyValueData); |
| 110 | +- const int maxLevels = qMin(texData.numLevels(), 32); // Cap iterations in case of corrupt file. |
| 111 | +- for (int i = 0; i < maxLevels; i++) { |
| 112 | +- if (offset + sizeof(KTXMipmapLevel) > dataSize) // Corrupt file; avoid oob read |
| 113 | +- break; |
| 114 | +- const KTXMipmapLevel *level = reinterpret_cast<const KTXMipmapLevel *>(buf.constData() + offset); |
| 115 | +- quint32 levelLen = decode(level->imageSize); |
| 116 | +- texData.setDataOffset(offset + sizeof(KTXMipmapLevel::imageSize), i); |
| 117 | +- texData.setDataLength(levelLen, i); |
| 118 | +- offset += sizeof(KTXMipmapLevel::imageSize) + levelLen + (3 - ((levelLen + 3) % 4)); |
| 119 | ++ texData.setSize(QSize(decode(header.pixelWidth), decode(header.pixelHeight))); |
| 120 | ++ texData.setGLFormat(decode(header.glFormat)); |
| 121 | ++ texData.setGLInternalFormat(decode(header.glInternalFormat)); |
| 122 | ++ texData.setGLBaseInternalFormat(decode(header.glBaseInternalFormat)); |
| 123 | ++ |
| 124 | ++ texData.setNumLevels(decode(header.numberOfMipmapLevels)); |
| 125 | ++ |
| 126 | ++ const quint32 bytesOfKeyValueData = decode(header.bytesOfKeyValueData); |
| 127 | ++ quint32 headerKeyValueSize; |
| 128 | ++ if (qAddOverflow(qktxh_headerSize, bytesOfKeyValueData, &headerKeyValueSize)) { |
| 129 | ++ qWarning(lcQtGuiTextureIO, "Overflow in size of key value data in header of KTX file %s", |
| 130 | ++ logName().constData()); |
| 131 | ++ return QTextureFileData(); |
| 132 | ++ } |
| 133 | ++ |
| 134 | ++ if (headerKeyValueSize >= quint32(buf.size())) { |
| 135 | ++ qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); |
| 136 | ++ return QTextureFileData(); |
| 137 | ++ } |
| 138 | ++ |
| 139 | ++ // Technically, any number of levels is allowed but if the value is bigger than |
| 140 | ++ // what is possible in KTX V2 (and what makes sense) we return an error. |
| 141 | ++ // maxLevels = log2(max(width, height, depth)) |
| 142 | ++ const int maxLevels = (sizeof(quint32) * 8) |
| 143 | ++ - qCountLeadingZeroBits(std::max( |
| 144 | ++ { header.pixelWidth, header.pixelHeight, header.pixelDepth })); |
| 145 | ++ |
| 146 | ++ if (texData.numLevels() > maxLevels) { |
| 147 | ++ qWarning(lcQtGuiTextureIO, "Too many levels in KTX file %s", logName().constData()); |
| 148 | ++ return QTextureFileData(); |
| 149 | ++ } |
| 150 | ++ |
| 151 | ++ quint32 offset = headerKeyValueSize; |
| 152 | ++ for (int level = 0; level < texData.numLevels(); level++) { |
| 153 | ++ const auto imageSizeSlice = safeSlice(buf, offset, sizeof(quint32)); |
| 154 | ++ if (imageSizeSlice.isEmpty()) { |
| 155 | ++ qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); |
| 156 | ++ return QTextureFileData(); |
| 157 | ++ } |
| 158 | ++ |
| 159 | ++ const quint32 imageSize = decode(qFromUnaligned<quint32>(imageSizeSlice.data())); |
| 160 | ++ offset += sizeof(quint32); // overflow checked indirectly above |
| 161 | ++ |
| 162 | ++ texData.setDataOffset(offset, level); |
| 163 | ++ texData.setDataLength(imageSize, level); |
| 164 | ++ |
| 165 | ++ // Add image data and padding to offset |
| 166 | ++ quint32 padded = 0; |
| 167 | ++ if (nearestMultipleOf4(imageSize, &padded)) { |
| 168 | ++ qWarning(lcQtGuiTextureIO, "Overflow in KTX file %s", logName().constData()); |
| 169 | ++ return QTextureFileData(); |
| 170 | ++ } |
| 171 | ++ |
| 172 | ++ quint32 offsetNext; |
| 173 | ++ if (qAddOverflow(offset, padded, &offsetNext)) { |
| 174 | ++ qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); |
| 175 | ++ return QTextureFileData(); |
| 176 | ++ } |
| 177 | ++ |
| 178 | ++ offset = offsetNext; |
| 179 | + } |
| 180 | + |
| 181 | + if (!texData.isValid()) { |
| 182 | +- qCDebug(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", logName().constData()); |
| 183 | ++ qWarning(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", |
| 184 | ++ logName().constData()); |
| 185 | + return QTextureFileData(); |
| 186 | + } |
| 187 | + |
| 188 | +@@ -191,7 +271,7 @@ bool QKtxHandler::checkHeader(const KTXHeader &header) |
| 189 | + (decode(header.numberOfFaces) == 1)); |
| 190 | + } |
| 191 | + |
| 192 | +-quint32 QKtxHandler::decode(quint32 val) |
| 193 | ++quint32 QKtxHandler::decode(quint32 val) const |
| 194 | + { |
| 195 | + return inverseEndian ? qbswap<quint32>(val) : val; |
| 196 | + } |
| 197 | +diff --git a/src/gui/util/qktxhandler_p.h b/src/gui/util/qktxhandler_p.h |
| 198 | +index 19f7b0e7..8da990aa 100644 |
| 199 | +--- a/src/gui/util/qktxhandler_p.h |
| 200 | ++++ b/src/gui/util/qktxhandler_p.h |
| 201 | +@@ -68,7 +68,7 @@ public: |
| 202 | + |
| 203 | + private: |
| 204 | + bool checkHeader(const KTXHeader &header); |
| 205 | +- quint32 decode(quint32 val); |
| 206 | ++ quint32 decode(quint32 val) const; |
| 207 | + |
| 208 | + bool inverseEndian = false; |
| 209 | + }; |
| 210 | +-- |
| 211 | +2.45.3 |
| 212 | + |
0 commit comments