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) 2016 The Qt Company Ltd.
|
||||||
|
** Copyright (C) 2018 Intel Corporation.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the QtCore module of the Qt Toolkit.
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
@ -58,6 +59,29 @@
|
|||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
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
|
class QFactoryLoaderPrivate : public QObjectPrivate
|
||||||
{
|
{
|
||||||
Q_DECLARE_PUBLIC(QFactoryLoader)
|
Q_DECLARE_PUBLIC(QFactoryLoader)
|
||||||
|
@ -66,14 +66,7 @@
|
|||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
inline QJsonDocument qJsonFromRawLibraryMetaData(const char *raw)
|
QJsonDocument qJsonFromRawLibraryMetaData(const char *raw, qsizetype size);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
class QFactoryLoaderPrivate;
|
class QFactoryLoaderPrivate;
|
||||||
class Q_CORE_EXPORT QFactoryLoader : public QObject
|
class Q_CORE_EXPORT QFactoryLoader : public QObject
|
||||||
|
@ -317,7 +317,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
|
|||||||
if (pos >= 0) {
|
if (pos >= 0) {
|
||||||
if (hasMetaData) {
|
if (hasMetaData) {
|
||||||
const char *data = filedata + pos;
|
const char *data = filedata + pos;
|
||||||
QJsonDocument doc = qJsonFromRawLibraryMetaData(data);
|
QJsonDocument doc = qJsonFromRawLibraryMetaData(data, qsizetype(fdlen));
|
||||||
lib->metaData = doc.object();
|
lib->metaData = doc.object();
|
||||||
if (qt_debug_component())
|
if (qt_debug_component())
|
||||||
qWarning("Found metadata in lib %s, metadata=\n%s\n",
|
qWarning("Found metadata in lib %s, metadata=\n%s\n",
|
||||||
@ -691,7 +691,8 @@ static bool qt_get_metadata(QtPluginQueryVerificationDataFunction pfn, QLibraryP
|
|||||||
if (!szData)
|
if (!szData)
|
||||||
return false;
|
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())
|
if (doc.isNull())
|
||||||
return false;
|
return false;
|
||||||
priv->metaData = doc.object();
|
priv->metaData = doc.object();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
|
** Copyright (C) 2018 Intel Corporation.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the QtCore module of the Qt Toolkit.
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
@ -474,7 +475,10 @@ QVector<QStaticPlugin> QPluginLoader::staticPlugins()
|
|||||||
*/
|
*/
|
||||||
QJsonObject QStaticPlugin::metaData() const
|
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
|
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
|
TEMPLATE = subdirs
|
||||||
TESTPLUGINS =
|
TESTPLUGINS = invalidplugin
|
||||||
|
|
||||||
win32 {
|
win32 {
|
||||||
contains(QT_CONFIG, debug): TESTPLUGINS += debugplugin
|
contains(QT_CONFIG, debug): TESTPLUGINS += debugplugin
|
||||||
@ -8,7 +8,7 @@ win32 {
|
|||||||
CONFIG(debug, debug|release): TESTPLUGINS += debugplugin
|
CONFIG(debug, debug|release): TESTPLUGINS += debugplugin
|
||||||
CONFIG(release, debug|release): TESTPLUGINS += releaseplugin
|
CONFIG(release, debug|release): TESTPLUGINS += releaseplugin
|
||||||
} else {
|
} else {
|
||||||
TESTPLUGINS = debugplugin releaseplugin
|
TESTPLUGINS += debugplugin releaseplugin
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBDIRS += main $$TESTPLUGINS
|
SUBDIRS += main $$TESTPLUGINS
|
||||||
|
@ -37,6 +37,7 @@ class tst_QPlugin : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
QDir dir;
|
QDir dir;
|
||||||
|
QString invalidPluginName;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
tst_QPlugin();
|
tst_QPlugin();
|
||||||
@ -45,6 +46,8 @@ private slots:
|
|||||||
void initTestCase();
|
void initTestCase();
|
||||||
void loadDebugPlugin();
|
void loadDebugPlugin();
|
||||||
void loadReleasePlugin();
|
void loadReleasePlugin();
|
||||||
|
void scanInvalidPlugin_data();
|
||||||
|
void scanInvalidPlugin();
|
||||||
};
|
};
|
||||||
|
|
||||||
tst_QPlugin::tst_QPlugin()
|
tst_QPlugin::tst_QPlugin()
|
||||||
@ -57,6 +60,10 @@ void tst_QPlugin::initTestCase()
|
|||||||
QVERIFY2(dir.exists(),
|
QVERIFY2(dir.exists(),
|
||||||
qPrintable(QString::fromLatin1("Cannot find the 'plugins' directory starting from '%1'").
|
qPrintable(QString::fromLatin1("Cannot find the 'plugins' directory starting from '%1'").
|
||||||
arg(QDir::toNativeSeparators(QDir::currentPath()))));
|
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()
|
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)
|
QTEST_MAIN(tst_QPlugin)
|
||||||
#include "tst_qplugin.moc"
|
#include "tst_qplugin.moc"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user