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:
Thiago Macieira 2018-07-02 22:38:57 -07:00 committed by Simon Hausmann
parent 3c2ffd7457
commit 8a5267e4d9
8 changed files with 196 additions and 13 deletions

View File

@ -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)

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -0,0 +1,5 @@
QT = core
TEMPLATE = lib
CONFIG += plugin
SOURCES = main.cpp
DESTDIR = ../plugins

View 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;
}
}

View File

@ -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

View File

@ -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"