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:
Thiago Macieira 2018-11-08 14:16:33 -08:00
parent c820e0b117
commit d20c980576
9 changed files with 136 additions and 30 deletions

View File

@ -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;

View File

@ -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()) {

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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());