From d20c9805763ab3dc504ebf2cefd33499d89ef22c Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Thu, 8 Nov 2018 14:16:33 -0800 Subject: Resources: reject compressed content we can't decompress This solution is composed of two features: 1) C++ code generated by RCC uses two symbols exported from QtCore that are only present if the feature was compiled in. If the feature was not compiled in, this will cause a linker error either at build time or at load time (if they were functions, the error could be at runtime). 2) Binary files generated by RCC have a new header field containing flags. We're currently using two flags, one for Zlib and one for Zstandard. This means we now have binary RCC format version 3. Change-Id: I42a48bd64ccc41aebf84fffd156545fb6a4f72d9 Reviewed-by: Oswald Buddenhagen Reviewed-by: Lars Knoll --- src/corelib/io/qresource.cpp | 44 +++++++++++- src/tools/rcc/main.cpp | 6 +- src/tools/rcc/rcc.cpp | 78 +++++++++++++++++++--- src/tools/rcc/rcc.h | 2 + tests/auto/tools/rcc/data/images/images.expected | 7 +- tests/auto/tools/rcc/data/sizes/size-0.expected | 7 +- tests/auto/tools/rcc/data/sizes/size-1.expected | 7 +- .../tools/rcc/data/sizes/size-2-0-35-1.expected | 7 +- tests/auto/tools/rcc/tst_rcc.cpp | 8 ++- 9 files changed, 136 insertions(+), 30 deletions(-) diff --git a/src/corelib/io/qresource.cpp b/src/corelib/io/qresource.cpp index 52a20191a9..804029725a 100644 --- a/src/corelib/io/qresource.cpp +++ b/src/corelib/io/qresource.cpp @@ -77,6 +77,26 @@ QT_BEGIN_NAMESPACE +// Symbols used by code generated by RCC. +// They cause compilation errors if the RCC content couldn't +// be interpreted by this QtCore version. +#if defined(__ELF__) || defined(__APPLE__) // same as RCC generates +# define RCC_FEATURE_SYMBOL(feature) \ + extern Q_CORE_EXPORT const quint8 qt_resourceFeature ## feature; \ + const quint8 qt_resourceFeature ## feature = 0; +#else +# define RCC_FEATURE_SYMBOL(feature) \ + Q_CORE_EXPORT quint8 qResourceFeature ## feature() { return 0; } +#endif + +#ifndef QT_NO_COMPRESS +RCC_FEATURE_SYMBOL(Zlib) +#endif +#if QT_CONFIG(zstd) +RCC_FEATURE_SYMBOL(Zstd) +#endif + +#undef RCC_FEATURE_SYMBOL class QStringSplitter { @@ -109,6 +129,7 @@ public: //resource glue class QResourceRoot { +public: enum Flags { // must match rcc.h @@ -116,6 +137,7 @@ class QResourceRoot Directory = 0x02, CompressedZstd = 0x04 }; +private: const uchar *tree, *names, *payloads; int version; inline int findOffset(int node) const { return node * (14 + (version >= 0x02 ? 8 : 0)); } //sizeof each tree element @@ -917,7 +939,7 @@ Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree, const unsigned char *name, const unsigned char *data) { QMutexLocker lock(resourceMutex()); - if ((version == 0x01 || version == 0x2) && resourceList()) { + if (version >= 0x01 && version <= 0x3 && resourceList()) { bool found = false; QResourceRoot res(version, tree, name, data); for(int i = 0; i < resourceList()->size(); ++i) { @@ -943,7 +965,7 @@ Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tre return false; QMutexLocker lock(resourceMutex()); - if ((version == 0x01 || version == 0x02) && resourceList()) { + if (version >= 0x01 && version <= 0x3 && resourceList()) { QResourceRoot res(version, tree, name, data); for(int i = 0; i < resourceList()->size(); ) { if(*resourceList()->at(i) == res) { @@ -1002,11 +1024,27 @@ public: const int name_offset = qFromBigEndian(b + offset); offset += 4; + quint32 file_flags = 0; + if (version >= 3) { + file_flags = qFromBigEndian(b + offset); + offset += 4; + } + // Some sanity checking for sizes. This is _not_ a security measure. if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size)) return false; - if (version == 0x01 || version == 0x02) { + // And some sanity checking for features + quint32 acceptableFlags = 0; +#ifndef QT_NO_COMPRESS + acceptableFlags |= Compressed; +#endif + if (QT_CONFIG(zstd)) + acceptableFlags |= CompressedZstd; + if (file_flags & ~acceptableFlags) + return false; + + if (version >= 0x01 && version <= 0x03) { buffer = b; setSource(version, b+tree_offset, b+name_offset, b+data_offset); return true; diff --git a/src/tools/rcc/main.cpp b/src/tools/rcc/main.cpp index 71836b81bd..6e8c13be15 100644 --- a/src/tools/rcc/main.cpp +++ b/src/tools/rcc/main.cpp @@ -185,13 +185,13 @@ int runRcc(int argc, char *argv[]) QString errorMsg; - quint8 formatVersion = 2; + quint8 formatVersion = 3; if (parser.isSet(formatVersionOption)) { bool ok = false; formatVersion = parser.value(formatVersionOption).toUInt(&ok); if (!ok) { errorMsg = QLatin1String("Invalid format version specified"); - } else if (formatVersion != 1 && formatVersion != 2) { + } else if (formatVersion < 1 || formatVersion > 3) { errorMsg = QLatin1String("Unsupported format version specified"); } } @@ -208,6 +208,8 @@ int runRcc(int argc, char *argv[]) if (parser.isSet(compressionAlgoOption)) library.setCompressionAlgorithm(RCCResourceLibrary::parseCompressionAlgorithm(parser.value(compressionAlgoOption), &errorMsg)); + if (formatVersion < 3 && library.compressionAlgorithm() == RCCResourceLibrary::CompressionAlgorithm::Zstd) + errorMsg = QLatin1String("Zstandard compression requires format version 3 or higher"); if (parser.isSet(nocompressOption)) library.setCompressionAlgorithm(RCCResourceLibrary::CompressionAlgorithm::None); if (parser.isSet(compressOption) && errorMsg.isEmpty()) { diff --git a/src/tools/rcc/rcc.cpp b/src/tools/rcc/rcc.cpp index 5cbc771994..f284749781 100644 --- a/src/tools/rcc/rcc.cpp +++ b/src/tools/rcc/rcc.cpp @@ -295,6 +295,7 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, lib.m_errorDevice->write(msg.toUtf8()); } + lib.m_overallFlags |= CompressedZstd; m_flags |= CompressedZstd; data = std::move(compressed); data.truncate(n); @@ -321,6 +322,7 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, lib.m_errorDevice->write(msg.toUtf8()); } data = compressed; + lib.m_overallFlags |= Compressed; m_flags |= Compressed; } else if (lib.verbose()) { QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); @@ -438,6 +440,7 @@ RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion) m_treeOffset(0), m_namesOffset(0), m_dataOffset(0), + m_overallFlags(0), m_useNameSpace(CONSTANT_USENAMESPACE), m_errorDevice(0), m_outDevice(0), @@ -945,6 +948,14 @@ bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIO return true; } +void RCCResourceLibrary::writeDecimal(int value) +{ + Q_ASSERT(m_format != RCCResourceLibrary::Binary); + char buf[std::numeric_limits::digits10 + 2]; + int n = snprintf(buf, sizeof(buf), "%d", value); + write(buf, n + 1); // write() takes a size including terminating NUL +} + void RCCResourceLibrary::writeHex(quint8 tmp) { const char digits[] = "0123456789abcdef"; @@ -1039,6 +1050,8 @@ bool RCCResourceLibrary::writeHeader() writeNumber4(0); writeNumber4(0); writeNumber4(0); + if (m_formatVersion >= 3) + writeNumber4(m_overallFlags); } return true; } @@ -1241,10 +1254,35 @@ bool RCCResourceLibrary::writeInitializer() if (m_root) { writeString("bool qRegisterResourceData" "(int, const unsigned char *, " - "const unsigned char *, const unsigned char *);\n\n"); + "const unsigned char *, const unsigned char *);\n"); writeString("bool qUnregisterResourceData" "(int, const unsigned char *, " "const unsigned char *, const unsigned char *);\n\n"); + + if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) { + // use variable relocations with ELF and Mach-O + writeString("#if defined(__ELF__) || defined(__APPLE__)\n"); + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("static inline unsigned char qResourceFeatureZlib()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZlib;\n" + " return qt_resourceFeatureZlib;\n" + "}\n"); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("static inline unsigned char qResourceFeatureZstd()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZstd;\n" + " return qt_resourceFeatureZstd;\n" + "}\n"); + } + writeString("#else\n"); + if (m_overallFlags & RCCFileInfo::Compressed) + writeString("unsigned char qResourceFeatureZlib();\n"); + if (m_overallFlags & RCCFileInfo::CompressedZstd) + writeString("unsigned char qResourceFeatureZstd();\n"); + writeString("#endif\n\n"); + } } if (m_useNameSpace) @@ -1263,12 +1301,12 @@ bool RCCResourceLibrary::writeInitializer() writeString("()\n{\n"); if (m_root) { - writeString(" "); + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); writeAddNamespaceFunction("qRegisterResourceData"); - writeString("\n ("); - writeHex(m_formatVersion); - writeString(" qt_resource_struct, " - "qt_resource_name, qt_resource_data);\n"); + writeString("\n (version, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); } writeString(" return 1;\n"); writeString("}\n\n"); @@ -1286,11 +1324,24 @@ bool RCCResourceLibrary::writeInitializer() writeMangleNamespaceFunction(cleanResources); writeString("()\n{\n"); if (m_root) { - writeString(" "); + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); + + // ODR-use certain symbols from QtCore if we require optional features + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZlib()"); + writeString(";\n "); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZstd()"); + writeString(";\n "); + } + writeAddNamespaceFunction("qUnregisterResourceData"); - writeString("\n ("); - writeHex(m_formatVersion); - writeString(" qt_resource_struct, " + writeString("\n (version, qt_resource_struct, " "qt_resource_name, qt_resource_data);\n"); } writeString(" return 1;\n"); @@ -1326,6 +1377,13 @@ bool RCCResourceLibrary::writeInitializer() p[i++] = (m_namesOffset >> 16) & 0xff; p[i++] = (m_namesOffset >> 8) & 0xff; p[i++] = (m_namesOffset >> 0) & 0xff; + + if (m_formatVersion >= 3) { + p[i++] = (m_overallFlags >> 24) & 0xff; + p[i++] = (m_overallFlags >> 16) & 0xff; + p[i++] = (m_overallFlags >> 8) & 0xff; + p[i++] = (m_overallFlags >> 0) & 0xff; + } } return true; } diff --git a/src/tools/rcc/rcc.h b/src/tools/rcc/rcc.h index 7603a41cde..ad1c5cd166 100644 --- a/src/tools/rcc/rcc.h +++ b/src/tools/rcc/rcc.h @@ -134,6 +134,7 @@ private: bool writeInitializer(); void writeMangleNamespaceFunction(const QByteArray &name); void writeAddNamespaceFunction(const QByteArray &name); + void writeDecimal(int value); void writeHex(quint8 number); void writeNumber2(quint16 number); void writeNumber4(quint32 number); @@ -160,6 +161,7 @@ private: int m_treeOffset; int m_namesOffset; int m_dataOffset; + quint32 m_overallFlags; bool m_useNameSpace; QStringList m_failedResources; QIODevice *m_errorDevice; diff --git a/tests/auto/tools/rcc/data/images/images.expected b/tests/auto/tools/rcc/data/images/images.expected index 45e96dccd0..2af071812e 100644 --- a/tests/auto/tools/rcc/data/images/images.expected +++ b/tests/auto/tools/rcc/data/images/images.expected @@ -115,7 +115,6 @@ namespace QT_NAMESPACE { #endif bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); - bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); #ifdef QT_NAMESPACE @@ -125,16 +124,18 @@ bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, int QT_RCC_MANGLE_NAMESPACE(qInitResources)(); int QT_RCC_MANGLE_NAMESPACE(qInitResources)() { + int version = 3; QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) - (0x2, qt_resource_struct, qt_resource_name, qt_resource_data); + (version, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)() { + int version = 3; QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) - (0x2, qt_resource_struct, qt_resource_name, qt_resource_data); + (version, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } diff --git a/tests/auto/tools/rcc/data/sizes/size-0.expected b/tests/auto/tools/rcc/data/sizes/size-0.expected index e862310153..2f70a607ab 100644 --- a/tests/auto/tools/rcc/data/sizes/size-0.expected +++ b/tests/auto/tools/rcc/data/sizes/size-0.expected @@ -57,7 +57,6 @@ namespace QT_NAMESPACE { #endif bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); - bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); #ifdef QT_NAMESPACE @@ -67,16 +66,18 @@ bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, int QT_RCC_MANGLE_NAMESPACE(qInitResources)(); int QT_RCC_MANGLE_NAMESPACE(qInitResources)() { + int version = 3; QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) - (0x2, qt_resource_struct, qt_resource_name, qt_resource_data); + (version, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)() { + int version = 3; QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) - (0x2, qt_resource_struct, qt_resource_name, qt_resource_data); + (version, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } diff --git a/tests/auto/tools/rcc/data/sizes/size-1.expected b/tests/auto/tools/rcc/data/sizes/size-1.expected index 9e5161272a..d1717a9255 100644 --- a/tests/auto/tools/rcc/data/sizes/size-1.expected +++ b/tests/auto/tools/rcc/data/sizes/size-1.expected @@ -58,7 +58,6 @@ namespace QT_NAMESPACE { #endif bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); - bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); #ifdef QT_NAMESPACE @@ -68,16 +67,18 @@ bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, int QT_RCC_MANGLE_NAMESPACE(qInitResources)(); int QT_RCC_MANGLE_NAMESPACE(qInitResources)() { + int version = 3; QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) - (0x2, qt_resource_struct, qt_resource_name, qt_resource_data); + (version, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)() { + int version = 3; QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) - (0x2, qt_resource_struct, qt_resource_name, qt_resource_data); + (version, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } diff --git a/tests/auto/tools/rcc/data/sizes/size-2-0-35-1.expected b/tests/auto/tools/rcc/data/sizes/size-2-0-35-1.expected index 0fc953351a..7a7cc93df1 100644 --- a/tests/auto/tools/rcc/data/sizes/size-2-0-35-1.expected +++ b/tests/auto/tools/rcc/data/sizes/size-2-0-35-1.expected @@ -95,7 +95,6 @@ namespace QT_NAMESPACE { #endif bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); - bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); #ifdef QT_NAMESPACE @@ -105,16 +104,18 @@ bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, int QT_RCC_MANGLE_NAMESPACE(qInitResources)(); int QT_RCC_MANGLE_NAMESPACE(qInitResources)() { + int version = 3; QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) - (0x2, qt_resource_struct, qt_resource_name, qt_resource_data); + (version, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)() { + int version = 3; QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) - (0x2, qt_resource_struct, qt_resource_name, qt_resource_data); + (version, qt_resource_struct, qt_resource_name, qt_resource_data); return 1; } diff --git a/tests/auto/tools/rcc/tst_rcc.cpp b/tests/auto/tools/rcc/tst_rcc.cpp index 0876af6759..c810c6e364 100644 --- a/tests/auto/tools/rcc/tst_rcc.cpp +++ b/tests/auto/tools/rcc/tst_rcc.cpp @@ -159,9 +159,10 @@ void tst_rcc::rcc() return; } - // Launch + // Launch; force no compression, otherwise the output would be different + // depending on the compression algorithm we're using QProcess process; - process.start(m_rcc, QStringList(qrcfile)); + process.start(m_rcc, { "-no-compress", qrcfile }); if (!process.waitForFinished()) { const QString path = QString::fromLocal8Bit(qgetenv("PATH")); QString message = QString::fromLatin1("'%1' could not be found when run from '%2'. Path: '%3' "). @@ -196,8 +197,9 @@ static void createRccBinaryData(const QString &rcc, const QString &baseDir, QString currentDir = QDir::currentPath(); QDir::setCurrent(baseDir); + // same as above: force no compression QProcess rccProcess; - rccProcess.start(rcc, QStringList() << "-binary" << "-o" << rccFileName << qrcFileName); + rccProcess.start(rcc, { "-binary", "-no-compress", "-o", rccFileName, qrcFileName }); bool ok = rccProcess.waitForFinished(); if (!ok) { QString errorString = QString::fromLatin1("Could not start rcc (is it in PATH?): %1").arg(rccProcess.errorString()); -- cgit v1.2.3