summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2018-07-02 22:38:57 -0700
committerSimon Hausmann <simon.hausmann@qt.io>2018-07-19 07:27:02 +0000
commit8a5267e4d96438aa74672ca1bf25d187c6c45bd2 (patch)
treeabb5b76025fac8489a68fae3d61708ba3c3ef930
parent3c2ffd7457688bd8ae9d5fca688843e2029504b2 (diff)
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>
-rw-r--r--src/corelib/plugin/qfactoryloader.cpp24
-rw-r--r--src/corelib/plugin/qfactoryloader_p.h9
-rw-r--r--src/corelib/plugin/qlibrary.cpp5
-rw-r--r--src/corelib/plugin/qpluginloader.cpp6
-rw-r--r--tests/auto/corelib/plugin/qplugin/invalidplugin/invalidplugin.pro5
-rw-r--r--tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp49
-rw-r--r--tests/auto/corelib/plugin/qplugin/qplugin.pro4
-rw-r--r--tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp107
8 files changed, 196 insertions, 13 deletions
diff --git a/src/corelib/plugin/qfactoryloader.cpp b/src/corelib/plugin/qfactoryloader.cpp
index a4be18a67f..dc1424fd0c 100644
--- a/src/corelib/plugin/qfactoryloader.cpp
+++ b/src/corelib/plugin/qfactoryloader.cpp
@@ -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)
diff --git a/src/corelib/plugin/qfactoryloader_p.h b/src/corelib/plugin/qfactoryloader_p.h
index 7be18942ae..fe722999ae 100644
--- a/src/corelib/plugin/qfactoryloader_p.h
+++ b/src/corelib/plugin/qfactoryloader_p.h
@@ -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
diff --git a/src/corelib/plugin/qlibrary.cpp b/src/corelib/plugin/qlibrary.cpp
index 31abeaffe4..5256a09ff2 100644
--- a/src/corelib/plugin/qlibrary.cpp
+++ b/src/corelib/plugin/qlibrary.cpp
@@ -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();
diff --git a/src/corelib/plugin/qpluginloader.cpp b/src/corelib/plugin/qpluginloader.cpp
index aab00cc7eb..83cbcd2b44 100644
--- a/src/corelib/plugin/qpluginloader.cpp
+++ b/src/corelib/plugin/qpluginloader.cpp
@@ -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
diff --git a/tests/auto/corelib/plugin/qplugin/invalidplugin/invalidplugin.pro b/tests/auto/corelib/plugin/qplugin/invalidplugin/invalidplugin.pro
new file mode 100644
index 0000000000..d953c6d367
--- /dev/null
+++ b/tests/auto/corelib/plugin/qplugin/invalidplugin/invalidplugin.pro
@@ -0,0 +1,5 @@
+QT = core
+TEMPLATE = lib
+CONFIG += plugin
+SOURCES = main.cpp
+DESTDIR = ../plugins
diff --git a/tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp b/tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp
new file mode 100644
index 0000000000..e6603ec89f
--- /dev/null
+++ b/tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp
@@ -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;
+}
+
+}
diff --git a/tests/auto/corelib/plugin/qplugin/qplugin.pro b/tests/auto/corelib/plugin/qplugin/qplugin.pro
index 5283c2d52b..96fc704c07 100644
--- a/tests/auto/corelib/plugin/qplugin/qplugin.pro
+++ b/tests/auto/corelib/plugin/qplugin/qplugin.pro
@@ -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
diff --git a/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp b/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp
index ee7cf7ded8..c00f2c76ba 100644
--- a/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp
+++ b/tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp
@@ -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"