Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qktxhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
7#include <QtEndian>
8#include <QSize>
9#include <QMap>
10#include <QtCore/qiodevice.h>
11
12//#define KTX_DEBUG
13#ifdef KTX_DEBUG
14#include <QDebug>
15#include <QMetaEnum>
16#include <QOpenGLTexture>
17#endif
18
20
21using namespace Qt::StringLiterals;
22
23#define KTX_IDENTIFIER_LENGTH 12
24static const char ktxIdentifier[KTX_IDENTIFIER_LENGTH] = { '\xAB', 'K', 'T', 'X', ' ', '1', '1', '\xBB', '\r', '\n', '\x1A', '\n' };
25static const quint32 platformEndianIdentifier = 0x04030201;
26static const quint32 inversePlatformEndianIdentifier = 0x01020304;
27
44
45static constexpr quint32 qktxh_headerSize = sizeof(KTXHeader);
46
47// Currently unused, declared for future reference
50 /*
51 quint8 keyAndValue[keyAndValueByteSize];
52 quint8 valuePadding[3 - ((keyAndValueByteSize + 3) % 4)];
53 */
54};
55
58 /*
59 for each array_element in numberOfArrayElements*
60 for each face in numberOfFaces
61 for each z_slice in pixelDepth*
62 for each row or row_of_blocks in pixelHeight*
63 for each pixel or block_of_pixels in pixelWidth
64 Byte data[format-specific-number-of-bytes]**
65 end
66 end
67 end
68 Byte cubePadding[0-3]
69 end
70 end
71 quint8 mipPadding[3 - ((imageSize + 3) % 4)]
72 */
73};
74
75// Returns the nearest multiple of 4 greater than or equal to 'value'
76static const std::optional<quint32> nearestMultipleOf4(quint32 value)
77{
78 constexpr quint32 rounding = 4;
79 quint32 result = 0;
80 if (qAddOverflow(value, rounding - 1, &result))
81 return std::nullopt;
82 result &= ~(rounding - 1);
83 return result;
84}
85
86// Returns a view with prechecked bounds
87static QByteArrayView safeView(QByteArrayView view, quint32 start, quint32 length)
88{
89 quint32 end = 0;
90 if (qAddOverflow(start, length, &end) || end > quint32(view.length()))
91 return {};
92 return view.sliced(start, length);
93}
94
95QKtxHandler::~QKtxHandler() = default;
96
97bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block)
98{
99 Q_UNUSED(suffix);
100 return block.startsWith(ktxIdentifier);
101}
102
104{
105 if (!device())
106 return QTextureFileData();
107
108 const QByteArray buf = device()->readAll();
109 if (static_cast<size_t>(buf.size()) > std::numeric_limits<quint32>::max()) {
110 qCWarning(lcQtGuiTextureIO, "Too big KTX file %s", logName().constData());
111 return QTextureFileData();
112 }
113
114 if (!canRead(QByteArray(), buf)) {
115 qCWarning(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData());
116 return QTextureFileData();
117 }
118
119 if (buf.size() < qsizetype(qktxh_headerSize)) {
120 qCWarning(lcQtGuiTextureIO, "Invalid KTX header size in %s", logName().constData());
121 return QTextureFileData();
122 }
123
124 KTXHeader header;
125 memcpy(&header, buf.data(), qktxh_headerSize);
126 if (!checkHeader(header)) {
127 qCWarning(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData());
128 return QTextureFileData();
129 }
130
131 QTextureFileData texData;
132 texData.setData(buf);
133
134 texData.setSize(QSize(decode(header.pixelWidth), decode(header.pixelHeight)));
135 texData.setGLFormat(decode(header.glFormat));
136 texData.setGLInternalFormat(decode(header.glInternalFormat));
137 texData.setGLBaseInternalFormat(decode(header.glBaseInternalFormat));
138
139 texData.setNumLevels(decode(header.numberOfMipmapLevels));
140 texData.setNumFaces(decode(header.numberOfFaces));
141
142 const quint32 bytesOfKeyValueData = decode(header.bytesOfKeyValueData);
143 quint32 headerKeyValueSize;
144 if (qAddOverflow(qktxh_headerSize, bytesOfKeyValueData, &headerKeyValueSize)) {
145 qCWarning(lcQtGuiTextureIO, "Overflow in size of key value data in header of KTX file %s",
146 logName().constData());
147 return QTextureFileData();
148 }
149
150 if (headerKeyValueSize >= quint32(buf.size())) {
151 qCWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData());
152 return QTextureFileData();
153 }
154
155 // File contains key/values
156 if (bytesOfKeyValueData > 0) {
157 auto keyValueDataView = safeView(buf, qktxh_headerSize, bytesOfKeyValueData);
158 if (keyValueDataView.isEmpty()) {
159 qCWarning(lcQtGuiTextureIO, "Invalid view in KTX file %s", logName().constData());
160 return QTextureFileData();
161 }
162
163 auto keyValues = decodeKeyValues(keyValueDataView);
164 if (!keyValues) {
165 qCWarning(lcQtGuiTextureIO, "Could not parse key values in KTX file %s",
166 logName().constData());
167 return QTextureFileData();
168 }
169
170 texData.setKeyValueMetadata(*keyValues);
171 }
172
173 // Technically, any number of levels is allowed but if the value is bigger than
174 // what is possible in KTX V2 (and what makes sense) we return an error.
175 // maxLevels = log2(max(width, height, depth))
176 const int maxLevels = (sizeof(quint32) * 8)
177 - qCountLeadingZeroBits(std::max(
178 { header.pixelWidth, header.pixelHeight, header.pixelDepth }));
179
180 if (texData.numLevels() > maxLevels) {
181 qCWarning(lcQtGuiTextureIO, "Too many levels in KTX file %s", logName().constData());
182 return QTextureFileData();
183 }
184
185 if (texData.numFaces() != 1 && texData.numFaces() != 6) {
186 qCWarning(lcQtGuiTextureIO, "Invalid number of faces in KTX file %s", logName().constData());
187 return QTextureFileData();
188 }
189
190 quint32 offset = headerKeyValueSize;
191 for (int level = 0; level < texData.numLevels(); level++) {
192 const auto imageSizeView = safeView(buf, offset, sizeof(quint32));
193 if (imageSizeView.isEmpty()) {
194 qCWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData());
195 return QTextureFileData();
196 }
197
198 const quint32 imageSize = decode(qFromUnaligned<quint32>(imageSizeView.data()));
199 offset += sizeof(quint32); // overflow checked indirectly above
200
201 for (int face = 0; face < texData.numFaces(); face++) {
202 texData.setDataOffset(offset, level, face);
203 texData.setDataLength(imageSize, level, face);
204
205 // Add image data and padding to offset
206 const auto padded = nearestMultipleOf4(imageSize);
207 if (!padded) {
208 qCWarning(lcQtGuiTextureIO, "Overflow in KTX file %s", logName().constData());
209 return QTextureFileData();
210 }
211
212 quint32 offsetNext;
213 if (qAddOverflow(offset, *padded, &offsetNext)) {
214 qCWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData());
215 return QTextureFileData();
216 }
217
218 offset = offsetNext;
219 }
220 }
221
222 if (!texData.isValid()) {
223 qCWarning(lcQtGuiTextureIO, "Invalid values in header of KTX file %s",
224 logName().constData());
225 return QTextureFileData();
226 }
227
228 texData.setLogName(logName());
229
230#ifdef KTX_DEBUG
231 qDebug() << "KTX file handler read" << texData;
232#endif
233
234 return texData;
235}
236
237bool QKtxHandler::checkHeader(const KTXHeader &header)
238{
239 if (header.endianness != platformEndianIdentifier && header.endianness != inversePlatformEndianIdentifier)
240 return false;
241 inverseEndian = (header.endianness == inversePlatformEndianIdentifier);
242#ifdef KTX_DEBUG
243 QMetaEnum tfme = QMetaEnum::fromType<QOpenGLTexture::TextureFormat>();
244 QMetaEnum ptme = QMetaEnum::fromType<QOpenGLTexture::PixelType>();
245 qDebug("Header of %s:", logName().constData());
246 qDebug(" glType: 0x%x (%s)", decode(header.glType), ptme.valueToKey(decode(header.glType)));
247 qDebug(" glTypeSize: %u", decode(header.glTypeSize));
248 qDebug(" glFormat: 0x%x (%s)", decode(header.glFormat),
249 tfme.valueToKey(decode(header.glFormat)));
250 qDebug(" glInternalFormat: 0x%x (%s)", decode(header.glInternalFormat),
251 tfme.valueToKey(decode(header.glInternalFormat)));
252 qDebug(" glBaseInternalFormat: 0x%x (%s)", decode(header.glBaseInternalFormat),
253 tfme.valueToKey(decode(header.glBaseInternalFormat)));
254 qDebug(" pixelWidth: %u", decode(header.pixelWidth));
255 qDebug(" pixelHeight: %u", decode(header.pixelHeight));
256 qDebug(" pixelDepth: %u", decode(header.pixelDepth));
257 qDebug(" numberOfArrayElements: %u", decode(header.numberOfArrayElements));
258 qDebug(" numberOfFaces: %u", decode(header.numberOfFaces));
259 qDebug(" numberOfMipmapLevels: %u", decode(header.numberOfMipmapLevels));
260 qDebug(" bytesOfKeyValueData: %u", decode(header.bytesOfKeyValueData));
261#endif
262 const bool isCompressedImage = decode(header.glType) == 0 && decode(header.glFormat) == 0
263 && decode(header.pixelDepth) == 0;
264 const bool isCubeMap = decode(header.numberOfFaces) == 6;
265 const bool is2D = decode(header.pixelDepth) == 0 && decode(header.numberOfArrayElements) == 0;
266
267 return is2D && (isCubeMap || isCompressedImage);
268}
269
270std::optional<QMap<QByteArray, QByteArray>> QKtxHandler::decodeKeyValues(QByteArrayView view) const
271{
272 QMap<QByteArray, QByteArray> output;
273 quint32 offset = 0;
274 while (offset < quint32(view.size())) {
275 const auto keyAndValueByteSizeView = safeView(view, offset, sizeof(quint32));
276 if (keyAndValueByteSizeView.isEmpty()) {
277 qCWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value");
278 return std::nullopt;
279 }
280
281 const quint32 keyAndValueByteSize =
282 decode(qFromUnaligned<quint32>(keyAndValueByteSizeView.data()));
283
284 quint32 offsetKeyAndValueStart;
285 if (qAddOverflow(offset, quint32(sizeof(quint32)), &offsetKeyAndValueStart)) {
286 qCWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
287 return std::nullopt;
288 }
289
290 quint32 offsetKeyAndValueEnd;
291 if (qAddOverflow(offsetKeyAndValueStart, keyAndValueByteSize, &offsetKeyAndValueEnd)) {
292 qCWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
293 return std::nullopt;
294 }
295
296 const auto keyValueView = safeView(view, offsetKeyAndValueStart, keyAndValueByteSize);
297 if (keyValueView.isEmpty()) {
298 qCWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value");
299 return std::nullopt;
300 }
301
302 // 'key' is a UTF-8 string ending with a null terminator, 'value' is the rest.
303 // To separate the key and value we convert the complete data to utf-8 and find the first
304 // null terminator from the left, here we split the data into two.
305
306 const int idx = keyValueView.indexOf('\0');
307 if (idx == -1) {
308 qCWarning(lcQtGuiTextureIO, "Invalid key in KTX key-value");
309 return std::nullopt;
310 }
311
312 const QByteArrayView keyView = safeView(view, offsetKeyAndValueStart, idx);
313 if (keyView.isEmpty()) {
314 qCWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
315 return std::nullopt;
316 }
317
318 const quint32 keySize = idx + 1; // Actual data size
319
320 quint32 offsetValueStart;
321 if (qAddOverflow(offsetKeyAndValueStart, keySize, &offsetValueStart)) {
322 qCWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
323 return std::nullopt;
324 }
325
326 quint32 valueSize;
327 if (qSubOverflow(keyAndValueByteSize, keySize, &valueSize)) {
328 qCWarning(lcQtGuiTextureIO, "Underflow in KTX key-value");
329 return std::nullopt;
330 }
331
332 const QByteArrayView valueView = safeView(view, offsetValueStart, valueSize);
333 if (valueView.isEmpty()) {
334 qCWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value");
335 return std::nullopt;
336 }
337
338 output.insert(keyView.toByteArray(), valueView.toByteArray());
339
340 const auto offsetNext = nearestMultipleOf4(offsetKeyAndValueEnd);
341 if (!offsetNext) {
342 qCWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
343 return std::nullopt;
344 }
345
346 offset = *offsetNext;
347 }
348
349 return output;
350}
351
352quint32 QKtxHandler::decode(quint32 val) const
353{
354 return inverseEndian ? qbswap<quint32>(val) : val;
355}
356
357QT_END_NAMESPACE
~QKtxHandler() override
QTextureFileData read() override
static const char ktxIdentifier[KTX_IDENTIFIER_LENGTH]
static const std::optional< quint32 > nearestMultipleOf4(quint32 value)
static constexpr quint32 qktxh_headerSize
static QByteArrayView safeView(QByteArrayView view, quint32 start, quint32 length)
#define KTX_IDENTIFIER_LENGTH
static const quint32 platformEndianIdentifier
static const quint32 inversePlatformEndianIdentifier
quint32 endianness
quint32 bytesOfKeyValueData
quint32 glTypeSize
quint32 numberOfArrayElements
quint32 pixelHeight
quint32 numberOfMipmapLevels
quint8 identifier[KTX_IDENTIFIER_LENGTH]
quint32 pixelDepth
quint32 glType
quint32 glFormat
quint32 numberOfFaces
quint32 glBaseInternalFormat
quint32 glInternalFormat
quint32 pixelWidth