From 8a5267e4d96438aa74672ca1bf25d187c6c45bd2 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 2 Jul 2018 22:38:57 -0700 Subject: 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 --- .../plugin/qplugin/invalidplugin/invalidplugin.pro | 5 + .../corelib/plugin/qplugin/invalidplugin/main.cpp | 49 ++++++++++ tests/auto/corelib/plugin/qplugin/qplugin.pro | 4 +- tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp | 107 +++++++++++++++++++++ 4 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 tests/auto/corelib/plugin/qplugin/invalidplugin/invalidplugin.pro create mode 100644 tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp (limited to 'tests') 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 + +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("metadata"); + QTest::addColumn("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" -- cgit v1.2.3