Plugins: fix crash if the binary JSON data contains invalid size
Eight bytes into the Binary JSON header there's a 32-bit little-endian size, which qJsonFromRawLibraryMetaData uses to determine the size of the stored metadata. That value is passed as a size to QByteArray, which means certain values could cause crashes due to being too big or via sign-extension in 64-bit. [ChangeLog][QtCore][QPluginLoader] Fixed an issue that could cause a crash when certain damaged or corrupt plugin files were scanned. Change-Id: I117816bf0f5e469b8d34fffd153dc5425cec39a7 Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
This commit is contained in:
parent
3c2ffd7457
commit
8a5267e4d9
@ -1,6 +1,7 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Copyright (C) 2018 Intel Corporation.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
@ -58,6 +59,29 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
static inline int metaDataSignatureLength()
|
||||
{
|
||||
return sizeof("QTMETADATA ") - 1;
|
||||
}
|
||||
|
||||
QJsonDocument qJsonFromRawLibraryMetaData(const char *raw, qsizetype sectionSize)
|
||||
{
|
||||
raw += metaDataSignatureLength();
|
||||
sectionSize -= metaDataSignatureLength();
|
||||
|
||||
// the size of the embedded JSON object can be found 8 bytes into the data (see qjson_p.h)
|
||||
uint size = qFromLittleEndian<uint>(raw + 8);
|
||||
// but the maximum size of binary JSON is 128 MB
|
||||
size = qMin(size, 128U * 1024 * 1024);
|
||||
// and it doesn't include the size of the header (8 bytes)
|
||||
size += 8;
|
||||
// finally, it can't be bigger than the file or section size
|
||||
size = qMin(sectionSize, qsizetype(size));
|
||||
|
||||
QByteArray json(raw, size);
|
||||
return QJsonDocument::fromBinaryData(json);
|
||||
}
|
||||
|
||||
class QFactoryLoaderPrivate : public QObjectPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QFactoryLoader)
|
||||
|
@ -66,14 +66,7 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
inline QJsonDocument qJsonFromRawLibraryMetaData(const char *raw)
|
||||
{
|
||||
raw += strlen("QTMETADATA ");
|
||||
// the size of the embedded JSON object can be found 8 bytes into the data (see qjson_p.h),
|
||||
// but doesn't include the size of the header (8 bytes)
|
||||
QByteArray json(raw, qFromLittleEndian<uint>(*(const uint *)(raw + 8)) + 8);
|
||||
return QJsonDocument::fromBinaryData(json);
|
||||
}
|
||||
QJsonDocument qJsonFromRawLibraryMetaData(const char *raw, qsizetype size);
|
||||
|
||||
class QFactoryLoaderPrivate;
|
||||
class Q_CORE_EXPORT QFactoryLoader : public QObject
|
||||
|
@ -317,7 +317,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
|
||||
if (pos >= 0) {
|
||||
if (hasMetaData) {
|
||||
const char *data = filedata + pos;
|
||||
QJsonDocument doc = qJsonFromRawLibraryMetaData(data);
|
||||
QJsonDocument doc = qJsonFromRawLibraryMetaData(data, qsizetype(fdlen));
|
||||
lib->metaData = doc.object();
|
||||
if (qt_debug_component())
|
||||
qWarning("Found metadata in lib %s, metadata=\n%s\n",
|
||||
@ -691,7 +691,8 @@ static bool qt_get_metadata(QtPluginQueryVerificationDataFunction pfn, QLibraryP
|
||||
if (!szData)
|
||||
return false;
|
||||
|
||||
QJsonDocument doc = qJsonFromRawLibraryMetaData(szData);
|
||||
// the data is already loaded, so the size doesn't matter
|
||||
QJsonDocument doc = qJsonFromRawLibraryMetaData(szData, INT_MAX);
|
||||
if (doc.isNull())
|
||||
return false;
|
||||
priv->metaData = doc.object();
|
||||
|
@ -1,6 +1,7 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Copyright (C) 2018 Intel Corporation.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
@ -474,7 +475,10 @@ QVector<QStaticPlugin> QPluginLoader::staticPlugins()
|
||||
*/
|
||||
QJsonObject QStaticPlugin::metaData() const
|
||||
{
|
||||
return qJsonFromRawLibraryMetaData(rawMetaData()).object();
|
||||
// the data is already loaded, so this doesn't matter
|
||||
qsizetype rawMetaDataSize = INT_MAX;
|
||||
|
||||
return qJsonFromRawLibraryMetaData(rawMetaData(), rawMetaDataSize).object();
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -0,0 +1,5 @@
|
||||
QT = core
|
||||
TEMPLATE = lib
|
||||
CONFIG += plugin
|
||||
SOURCES = main.cpp
|
||||
DESTDIR = ../plugins
|
49
tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp
Normal file
49
tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 Intel Corporation.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the test suite of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <qplugin.h>
|
||||
|
||||
QT_PLUGIN_METADATA_SECTION
|
||||
static const char pluginMetaData[512] = {
|
||||
'q', 'p', 'l', 'u', 'g', 'i', 'n', ' ',
|
||||
't', 'e', 's', 't', 'f', 'i', 'l', 'e'
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
const void *qt_plugin_query_metadata()
|
||||
{
|
||||
return pluginMetaData;
|
||||
}
|
||||
|
||||
Q_DECL_EXPORT void *qt_plugin_instance()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
TEMPLATE = subdirs
|
||||
TESTPLUGINS =
|
||||
TESTPLUGINS = invalidplugin
|
||||
|
||||
win32 {
|
||||
contains(QT_CONFIG, debug): TESTPLUGINS += debugplugin
|
||||
@ -8,7 +8,7 @@ win32 {
|
||||
CONFIG(debug, debug|release): TESTPLUGINS += debugplugin
|
||||
CONFIG(release, debug|release): TESTPLUGINS += releaseplugin
|
||||
} else {
|
||||
TESTPLUGINS = debugplugin releaseplugin
|
||||
TESTPLUGINS += debugplugin releaseplugin
|
||||
}
|
||||
|
||||
SUBDIRS += main $$TESTPLUGINS
|
||||
|
@ -37,6 +37,7 @@ class tst_QPlugin : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
QDir dir;
|
||||
QString invalidPluginName;
|
||||
|
||||
public:
|
||||
tst_QPlugin();
|
||||
@ -45,6 +46,8 @@ private slots:
|
||||
void initTestCase();
|
||||
void loadDebugPlugin();
|
||||
void loadReleasePlugin();
|
||||
void scanInvalidPlugin_data();
|
||||
void scanInvalidPlugin();
|
||||
};
|
||||
|
||||
tst_QPlugin::tst_QPlugin()
|
||||
@ -57,6 +60,10 @@ void tst_QPlugin::initTestCase()
|
||||
QVERIFY2(dir.exists(),
|
||||
qPrintable(QString::fromLatin1("Cannot find the 'plugins' directory starting from '%1'").
|
||||
arg(QDir::toNativeSeparators(QDir::currentPath()))));
|
||||
|
||||
const auto fileNames = dir.entryList({"*invalid*"}, QDir::Files);
|
||||
if (!fileNames.isEmpty())
|
||||
invalidPluginName = dir.absoluteFilePath(fileNames.first());
|
||||
}
|
||||
|
||||
void tst_QPlugin::loadDebugPlugin()
|
||||
@ -112,5 +119,105 @@ void tst_QPlugin::loadReleasePlugin()
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QPlugin::scanInvalidPlugin_data()
|
||||
{
|
||||
QTest::addColumn<QByteArray>("metadata");
|
||||
QTest::addColumn<bool>("loads");
|
||||
|
||||
QByteArray prefix = "QTMETADATA ";
|
||||
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj.insert("IID", "org.qt-project.tst_qplugin");
|
||||
obj.insert("className", "tst");
|
||||
obj.insert("version", int(QT_VERSION));
|
||||
#ifdef QT_NO_DEBUG
|
||||
obj.insert("debug", false);
|
||||
#else
|
||||
obj.insert("debug", true);
|
||||
#endif
|
||||
obj.insert("MetaData", QJsonObject());
|
||||
QTest::newRow("control") << (prefix + QJsonDocument(obj).toBinaryData()) << true;
|
||||
}
|
||||
|
||||
QTest::newRow("zeroes") << prefix << false;
|
||||
|
||||
prefix += "qbjs";
|
||||
QTest::newRow("bad-json-version0") << prefix << false;
|
||||
QTest::newRow("bad-json-version2") << (prefix + QByteArray("\2\0\0\0", 4)) << false;
|
||||
|
||||
// valid qbjs version 1
|
||||
prefix += QByteArray("\1\0\0\0");
|
||||
|
||||
// too large for the file (100 MB)
|
||||
QTest::newRow("bad-json-size-large1") << (prefix + QByteArray("\0\0\x40\x06")) << false;
|
||||
|
||||
// too large for binary JSON (512 MB)
|
||||
QTest::newRow("bad-json-size-large2") << (prefix + QByteArray("\0\0\0\x20")) << false;
|
||||
|
||||
// could overflow
|
||||
QTest::newRow("bad-json-size-large3") << (prefix + "\xff\xff\xff\x7f") << false;
|
||||
|
||||
}
|
||||
|
||||
static const char invalidPluginSignature[] = "qplugin testfile";
|
||||
static qsizetype locateMetadata(const uchar *data, qsizetype len)
|
||||
{
|
||||
const uchar *dataend = data + len - strlen(invalidPluginSignature);
|
||||
|
||||
for (const uchar *ptr = data; ptr < dataend; ++ptr) {
|
||||
if (*ptr != invalidPluginSignature[0])
|
||||
continue;
|
||||
|
||||
int r = memcmp(ptr, invalidPluginSignature, strlen(invalidPluginSignature));
|
||||
if (r)
|
||||
continue;
|
||||
|
||||
return ptr - data;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void tst_QPlugin::scanInvalidPlugin()
|
||||
{
|
||||
QVERIFY(!invalidPluginName.isEmpty());
|
||||
|
||||
// copy the file
|
||||
QFileInfo fn(invalidPluginName);
|
||||
QTemporaryDir tmpdir;
|
||||
QVERIFY(tmpdir.isValid());
|
||||
|
||||
QString newName = tmpdir.path() + '/' + fn.fileName();
|
||||
QVERIFY(QFile::copy(invalidPluginName, newName));
|
||||
|
||||
{
|
||||
QFile f(newName);
|
||||
QVERIFY(f.open(QIODevice::ReadWrite | QIODevice::Unbuffered));
|
||||
QVERIFY(f.size() > qint64(strlen(invalidPluginSignature)));
|
||||
uchar *data = f.map(0, f.size());
|
||||
QVERIFY(data);
|
||||
|
||||
static const qsizetype offset = locateMetadata(data, f.size());
|
||||
QVERIFY(offset > 0);
|
||||
|
||||
QFETCH(QByteArray, metadata);
|
||||
|
||||
// sanity check
|
||||
QVERIFY(metadata.size() < 512);
|
||||
|
||||
// replace the data
|
||||
memcpy(data + offset, metadata.constData(), metadata.size());
|
||||
memset(data + offset + metadata.size(), 0, 512 - metadata.size());
|
||||
}
|
||||
|
||||
// now try to load this
|
||||
QFETCH(bool, loads);
|
||||
QPluginLoader loader(newName);
|
||||
QCOMPARE(loader.load(), loads);
|
||||
if (loads)
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QPlugin)
|
||||
#include "tst_qplugin.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user