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 <oswald.buddenhagen@qt.io> Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
parent
c820e0b117
commit
d20c980576
@ -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<qint32>(b + offset);
|
||||
offset += 4;
|
||||
|
||||
quint32 file_flags = 0;
|
||||
if (version >= 3) {
|
||||
file_flags = qFromBigEndian<qint32>(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;
|
||||
|
@ -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()) {
|
||||
|
@ -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<int>::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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
Loading…
x
Reference in New Issue
Block a user