Skip to content

Commit

Permalink
Plugins: fix crash if the binary JSON data contains invalid size
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
thiagomacieira authored and tronical committed Jul 19, 2018
1 parent 3c2ffd7 commit 8a5267e
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 13 deletions.
24 changes: 24 additions & 0 deletions src/corelib/plugin/qfactoryloader.cpp
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 1 addition & 8 deletions src/corelib/plugin/qfactoryloader_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/corelib/plugin/qlibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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();
Expand Down
6 changes: 5 additions & 1 deletion src/corelib/plugin/qpluginloader.cpp
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
QT = core
TEMPLATE = lib
CONFIG += plugin
SOURCES = main.cpp
DESTDIR = ../plugins
49 changes: 49 additions & 0 deletions tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp
Original file line number Diff line number Diff line change
@@ -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;
}

}
4 changes: 2 additions & 2 deletions tests/auto/corelib/plugin/qplugin/qplugin.pro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
TEMPLATE = subdirs
TESTPLUGINS =
TESTPLUGINS = invalidplugin

win32 {
contains(QT_CONFIG, debug): TESTPLUGINS += debugplugin
Expand All @@ -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
Expand Down
107 changes: 107 additions & 0 deletions tests/auto/corelib/plugin/qplugin/tst_qplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class tst_QPlugin : public QObject
Q_OBJECT

QDir dir;
QString invalidPluginName;

public:
tst_QPlugin();
Expand All @@ -45,6 +46,8 @@ private slots:
void initTestCase();
void loadDebugPlugin();
void loadReleasePlugin();
void scanInvalidPlugin_data();
void scanInvalidPlugin();
};

tst_QPlugin::tst_QPlugin()
Expand All @@ -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()
Expand Down Expand Up @@ -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"

0 comments on commit 8a5267e

Please sign in to comment.