summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp')
-rw-r--r--tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp884
1 files changed, 730 insertions, 154 deletions
diff --git a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp
index 68edfdf97d..f4ecf5bfb3 100644
--- a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp
+++ b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp
@@ -1,43 +1,23 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** 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$
-**
-****************************************************************************/
+// Copyright (C) 2020 The Qt Company Ltd.
+// Copyright (C) 2021 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QSignalSpy>
#include <QJsonArray>
#include <qdir.h>
+#include <qendian.h>
#include <qpluginloader.h>
+#include <qtemporaryfile.h>
+#include <QScopeGuard>
#include "theplugin/plugininterface.h"
#if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O)
# include <QtCore/private/qmachparser_p.h>
#endif
+using namespace Qt::StringLiterals;
+
// Helper macros to let us know if some suffixes are valid
#define bundle_VALID false
#define dylib_VALID false
@@ -53,11 +33,11 @@
# define bundle_VALID true
# define dylib_VALID true
# define so_VALID true
-//# ifdef QT_NO_DEBUG
+# ifdef QT_NO_DEBUG
# define SUFFIX ".dylib"
-//# else
-//# define SUFFIX "_debug.dylib"
-//#endif
+# else
+# define SUFFIX "_debug.dylib"
+# endif
# define PREFIX "lib"
#elif defined(Q_OS_HPUX) && !defined(__ia64)
@@ -91,14 +71,113 @@
# define PREFIX "lib"
#endif
+#if defined(Q_OF_ELF)
+#if __has_include(<elf.h>)
+# include <elf.h>
+#else
+# include <sys/elf.h>
+#endif
+# include <memory>
+# include <functional>
+
+# ifdef _LP64
+using ElfHeader = Elf64_Ehdr;
+using ElfPhdr = Elf64_Phdr;
+using ElfNhdr = Elf64_Nhdr;
+using ElfShdr = Elf64_Shdr;
+# else
+using ElfHeader = Elf32_Ehdr;
+using ElfPhdr = Elf32_Phdr;
+using ElfNhdr = Elf32_Nhdr;
+using ElfShdr = Elf32_Shdr;
+# endif
+
+struct ElfPatcher
+{
+ using FullPatcher = void(ElfHeader *, QFile *);
+ FullPatcher *f;
+
+ ElfPatcher(FullPatcher *f = nullptr) : f(f) {}
+
+ template <typename T> using IsSingleArg = std::is_invocable<T, ElfHeader *>;
+ template <typename T> static std::enable_if_t<IsSingleArg<T>::value, ElfPatcher> fromLambda(T &&t)
+ {
+ using WithoutQFile = void(*)(ElfHeader *);
+ static const WithoutQFile f = t;
+ return { [](ElfHeader *h, QFile *) { f(h);} };
+ }
+ template <typename T> static std::enable_if_t<!IsSingleArg<T>::value, ElfPatcher> fromLambda(T &&t)
+ {
+ return { t };
+ }
+};
+
+Q_DECLARE_METATYPE(ElfPatcher)
+
+static std::unique_ptr<QTemporaryFile> patchElf(const QString &source, ElfPatcher patcher)
+{
+ std::unique_ptr<QTemporaryFile> tmplib;
+
+ bool ok = false;
+ [&]() {
+ QFile srclib(source);
+ QVERIFY2(srclib.open(QIODevice::ReadOnly), qPrintable(srclib.errorString()));
+ qint64 srcsize = srclib.size();
+ const uchar *srcdata = srclib.map(0, srcsize, QFile::MapPrivateOption);
+ QVERIFY2(srcdata, qPrintable(srclib.errorString()));
+
+ // copy our source plugin so we can modify it
+ const char *basename = QTest::currentDataTag();
+ if (!basename)
+ basename = QTest::currentTestFunction();
+ tmplib.reset(new QTemporaryFile(QDir::currentPath() + u'/' + basename + u".XXXXXX" SUFFIX ""_s));
+ QVERIFY2(tmplib->open(), qPrintable(tmplib->errorString()));
+
+ // sanity-check
+ QByteArray magic = QByteArray::fromRawData(reinterpret_cast<const char *>(srcdata), SELFMAG);
+ QCOMPARE(magic, QByteArray(ELFMAG));
+
+ // copy everything via mmap()
+ QVERIFY2(tmplib->resize(srcsize), qPrintable(tmplib->errorString()));
+ uchar *dstdata = tmplib->map(0, srcsize);
+ memcpy(dstdata, srcdata, srcsize);
+
+ // now patch the file
+ patcher.f(reinterpret_cast<ElfHeader *>(dstdata), tmplib.get());
+
+ ok = true;
+ }();
+ if (!ok)
+ tmplib.reset();
+ return tmplib;
+}
+
+// All ELF systems are expected to support GCC expression statements
+#define patchElf(source, patcher) __extension__({ \
+ auto r = patchElf(source, patcher); \
+ if (QTest::currentTestFailed()) return; \
+ std::move(r); \
+ })
+#endif // Q_OF_ELF
+
static QString sys_qualifiedLibraryName(const QString &fileName)
{
+#ifdef Q_OS_ANDROID
+ // On Android all the libraries must be located in the APK's libs subdir
+ const QStringList paths = QCoreApplication::libraryPaths();
+ if (!paths.isEmpty()) {
+ return QLatin1String("%1/%2%3_%4%5").arg(paths.first(), PREFIX, fileName,
+ ANDROID_ARCH, SUFFIX);
+ }
+ return fileName;
+#else
QString name = QLatin1String("bin/") + QLatin1String(PREFIX) + fileName + QLatin1String(SUFFIX);
const QString libname = QFINDTESTDATA(name);
QFileInfo fi(libname);
if (fi.exists())
return fi.canonicalFilePath();
return libname;
+#endif
}
QT_FORWARD_DECLARE_CLASS(QPluginLoader)
@@ -111,19 +190,25 @@ private slots:
void errorString();
void loadHints();
void deleteinstanceOnUnload();
+#if defined (Q_OF_ELF)
void loadDebugObj();
+ void loadCorruptElf_data();
void loadCorruptElf();
+# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+ void loadCorruptElfOldPlugin_data();
+ void loadCorruptElfOldPlugin();
+# endif
+#endif
void loadMachO_data();
void loadMachO();
-#if defined (Q_OS_UNIX)
- void loadGarbage();
-#endif
void relativePath();
void absolutePath();
void reloadPlugin();
+ void loadSectionTableStrippedElf();
void preloadedPlugin_data();
void preloadedPlugin();
void staticPlugins();
+ void reregisteredStaticPlugins();
};
Q_IMPORT_PLUGIN(StaticPlugin)
@@ -195,7 +280,9 @@ void tst_QPluginLoader::errorString()
QVERIFY(!unloaded);
}
-#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_HPUX)
+// A bug in QNX causes the test to crash on exit after attempting to load
+// a shared library with undefined symbols (tracked as QTBUG-114682).
+#if !defined(Q_OS_WIN) && !defined(Q_OS_DARWIN) && !defined(Q_OS_HPUX) && !defined(Q_OS_QNX)
{
QPluginLoader loader( sys_qualifiedLibraryName("almostplugin")); //a plugin with unresolved symbols
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
@@ -213,29 +300,36 @@ void tst_QPluginLoader::errorString()
}
#endif
- {
- QPluginLoader loader( sys_qualifiedLibraryName("theplugin")); //a plugin
-
- // Check metadata
- const QJsonObject metaData = loader.metaData();
- QCOMPARE(metaData.value("IID").toString(), QStringLiteral("org.qt-project.Qt.autotests.plugininterface"));
- const QJsonObject kpluginObject = metaData.value("MetaData").toObject().value("KPlugin").toObject();
- QCOMPARE(kpluginObject.value("Name[mr]").toString(), QString::fromUtf8("चौकट भूमिती"));
-
- // Load
- QCOMPARE(loader.load(), true);
- QCOMPARE(loader.errorString(), unknown);
-
- QVERIFY(loader.instance() != static_cast<QObject*>(0));
- QCOMPARE(loader.errorString(), unknown);
-
- // Make sure that plugin really works
- PluginInterface* theplugin = qobject_cast<PluginInterface*>(loader.instance());
- QString pluginName = theplugin->pluginName();
- QCOMPARE(pluginName, QLatin1String("Plugin ok"));
-
- QCOMPARE(loader.unload(), true);
- QCOMPARE(loader.errorString(), unknown);
+ static constexpr std::initializer_list<const char *> validplugins = {
+ "theplugin",
+#if defined(Q_OF_ELF) && QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+ "theoldplugin"
+#endif
+ };
+ for (const char *basename : validplugins) {
+ QPluginLoader loader( sys_qualifiedLibraryName(basename)); //a plugin
+
+ // Check metadata
+ const QJsonObject metaData = loader.metaData();
+ QVERIFY2(!metaData.isEmpty(), "No metadata from " + loader.fileName().toLocal8Bit());
+ QCOMPARE(metaData.value("IID").toString(), QStringLiteral("org.qt-project.Qt.autotests.plugininterface"));
+ const QJsonObject kpluginObject = metaData.value("MetaData").toObject().value("KPlugin").toObject();
+ QCOMPARE(kpluginObject.value("Name[mr]").toString(), QString::fromUtf8("चौकट भूमिती"));
+
+ // Load
+ QVERIFY2(loader.load(), qPrintable(loader.errorString()));
+ QCOMPARE(loader.errorString(), unknown);
+
+ QVERIFY(loader.instance() != static_cast<QObject*>(0));
+ QCOMPARE(loader.errorString(), unknown);
+
+ // Make sure that plugin really works
+ PluginInterface* theplugin = qobject_cast<PluginInterface*>(loader.instance());
+ QString pluginName = theplugin->pluginName();
+ QCOMPARE(pluginName, QLatin1String("Plugin ok"));
+
+ QCOMPARE(loader.unload(), true);
+ QCOMPARE(loader.errorString(), unknown);
}
}
@@ -245,10 +339,37 @@ void tst_QPluginLoader::loadHints()
QSKIP("This test requires Qt to create shared libraries.");
#endif
QPluginLoader loader;
- QCOMPARE(loader.loadHints(), QLibrary::LoadHints{}); //Do not crash
+ QCOMPARE(loader.loadHints(), QLibrary::PreventUnloadHint); //Do not crash
+ loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
+ QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint);
+ // We can clear load hints when file name is not set.
+ loader.setLoadHints(QLibrary::LoadHints{});
+ QCOMPARE(loader.loadHints(), QLibrary::LoadHints{});
+ // Set the hints again
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
+ QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint);
loader.setFileName( sys_qualifiedLibraryName("theplugin")); //a plugin
QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint);
+
+ QPluginLoader loader4;
+ QCOMPARE(loader4.loadHints(), QLibrary::PreventUnloadHint);
+ loader4.setLoadHints(QLibrary::LoadHints{});
+ QCOMPARE(loader4.loadHints(), QLibrary::LoadHints{});
+ loader4.setFileName(sys_qualifiedLibraryName("theplugin"));
+ // Hints are merged with hints from the previous loader.
+ QCOMPARE(loader4.loadHints(), QLibrary::ResolveAllSymbolsHint);
+ // We cannot clear load hints after associating the loader with a file.
+ loader.setLoadHints(QLibrary::LoadHints{});
+ QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint);
+
+ QPluginLoader loader2;
+ QCOMPARE(loader2.loadHints(), QLibrary::PreventUnloadHint);
+ loader2.setFileName(sys_qualifiedLibraryName("theplugin"));
+ // Hints are merged with hints from previous loaders.
+ QCOMPARE(loader2.loadHints(), QLibrary::PreventUnloadHint | QLibrary::ResolveAllSymbolsHint);
+
+ QPluginLoader loader3(sys_qualifiedLibraryName("theplugin"));
+ QCOMPARE(loader3.loadHints(), QLibrary::PreventUnloadHint | QLibrary::ResolveAllSymbolsHint);
}
void tst_QPluginLoader::deleteinstanceOnUnload()
@@ -278,92 +399,485 @@ void tst_QPluginLoader::deleteinstanceOnUnload()
QVERIFY(spy2.isValid());
if (pass == 0) {
QCOMPARE(loader2.unload(), false); // refcount not reached 0, not really unloaded
- QCOMPARE(spy1.count(), 0);
- QCOMPARE(spy2.count(), 0);
+ QCOMPARE(spy1.size(), 0);
+ QCOMPARE(spy2.size(), 0);
}
QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok"));
QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok"));
QVERIFY(loader1.unload()); // refcount reached 0, did really unload
- QCOMPARE(spy1.count(), 1);
- QCOMPARE(spy2.count(), 1);
+ QCOMPARE(spy1.size(), 1);
+ QCOMPARE(spy2.size(), 1);
}
}
+#if defined(Q_OF_ELF)
+
void tst_QPluginLoader::loadDebugObj()
{
#if !defined(QT_SHARED)
QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds");
#endif
-#if defined (__ELF__)
QVERIFY(QFile::exists(QFINDTESTDATA("elftest/debugobj.so")));
QPluginLoader lib1(QFINDTESTDATA("elftest/debugobj.so"));
QCOMPARE(lib1.load(), false);
+}
+
+template <typename Lambda>
+static void newRow(const char *rowname, QString &&snippet, Lambda &&patcher)
+{
+ QTest::newRow(rowname)
+ << std::move(snippet) << ElfPatcher::fromLambda(std::forward<Lambda>(patcher));
+}
+
+static ElfPhdr *getProgramEntry(ElfHeader *h, int index)
+{
+ auto phdr = reinterpret_cast<ElfPhdr *>(h->e_phoff + reinterpret_cast<uchar *>(h));
+ return phdr + index;
+}
+
+static void loadCorruptElfCommonRows()
+{
+ QTest::addColumn<QString>("snippet");
+ QTest::addColumn<ElfPatcher>("patcher");
+
+ using H = ElfHeader *; // because I'm lazy
+ newRow("not-elf", "invalid signature", [](H h) {
+ h->e_ident[EI_MAG0] = 'Q';
+ h->e_ident[EI_MAG1] = 't';
+ });
+
+ newRow("wrong-word-size", "file is for a different word size", [](H h) {
+ h->e_ident[EI_CLASS] = sizeof(void *) == 8 ? ELFCLASS32 : ELFCLASS64;
+
+ // unnecessary, but we're doing it anyway
+# ifdef _LP64
+ Elf32_Ehdr o;
+ o.e_phentsize = sizeof(Elf32_Phdr);
+ o.e_shentsize = sizeof(Elf32_Shdr);
+# else
+ Elf64_Ehdr o;
+ o.e_phentsize = sizeof(Elf64_Phdr);
+ o.e_shentsize = sizeof(Elf64_Shdr);
+# endif
+ memcpy(o.e_ident, h->e_ident, EI_NIDENT);
+ o.e_type = h->e_type;
+ o.e_machine = h->e_machine;
+ o.e_version = h->e_version;
+ o.e_entry = h->e_entry;
+ o.e_phoff = h->e_phoff;
+ o.e_shoff = h->e_shoff;
+ o.e_flags = h->e_flags;
+ o.e_ehsize = sizeof(o);
+ o.e_phnum = h->e_phnum;
+ o.e_shnum = h->e_shnum;
+ o.e_shstrndx = h->e_shstrndx;
+ memcpy(h, &o, sizeof(o));
+ });
+ newRow("invalid-word-size", "file is for a different word size", [](H h) {
+ h->e_ident[EI_CLASS] = ELFCLASSNONE;
+ });
+ newRow("unknown-word-size", "file is for a different word size", [](H h) {
+ h->e_ident[EI_CLASS] |= 0x40;
+ });
+
+ newRow("wrong-endian", "file is for the wrong endianness", [](H h) {
+ h->e_ident[EI_DATA] = QSysInfo::ByteOrder == QSysInfo::LittleEndian ? ELFDATA2MSB : ELFDATA2LSB;
+
+ // unnecessary, but we're doing it anyway
+ h->e_type = qbswap(h->e_type);
+ h->e_machine = qbswap(h->e_machine);
+ h->e_version = qbswap(h->e_version);
+ h->e_entry = qbswap(h->e_entry);
+ h->e_phoff = qbswap(h->e_phoff);
+ h->e_shoff = qbswap(h->e_shoff);
+ h->e_flags = qbswap(h->e_flags);
+ h->e_ehsize = qbswap(h->e_ehsize);
+ h->e_phnum = qbswap(h->e_phnum);
+ h->e_phentsize = qbswap(h->e_phentsize);
+ h->e_shnum = qbswap(h->e_shnum);
+ h->e_shentsize = qbswap(h->e_shentsize);
+ h->e_shstrndx = qbswap(h->e_shstrndx);
+ });
+ newRow("invalid-endian", "file is for the wrong endianness", [](H h) {
+ h->e_ident[EI_DATA] = ELFDATANONE;
+ });
+ newRow("unknown-endian", "file is for the wrong endianness", [](H h) {
+ h->e_ident[EI_DATA] |= 0x40;
+ });
+
+ newRow("elf-version-0", "file has an unknown ELF version", [](H h) {
+ --h->e_ident[EI_VERSION];
+ });
+ newRow("elf-version-2", "file has an unknown ELF version", [](H h) {
+ ++h->e_ident[EI_VERSION];
+ });
+
+ newRow("executable", "file is not a shared object", [](H h) {
+ h->e_type = ET_EXEC;
+ });
+ newRow("relocatable", "file is not a shared object", [](H h) {
+ h->e_type = ET_REL;
+ });
+ newRow("core-file", "file is not a shared object", [](H h) {
+ h->e_type = ET_CORE;
+ });
+ newRow("invalid-type", "file is not a shared object", [](H h) {
+ h->e_type |= 0x100;
+ });
+
+ newRow("wrong-arch", "file is for a different processor", [](H h) {
+ // could just ++h->e_machine...
+# if defined(Q_PROCESSOR_X86_64)
+ h->e_machine = EM_AARCH64;
+# elif defined(Q_PROCESSOR_ARM_64)
+ h->e_machine = EM_X86_64;
+# elif defined(Q_PROCESSOR_X86_32)
+ h->e_machine = EM_ARM;
+# elif defined(Q_PROCESSOR_ARM)
+ h->e_machine = EM_386;
+# elif defined(Q_PROCESSOR_MIPS_64)
+ h->e_machine = EM_PPC64;
+# elif defined(Q_PROCESSOR_MIPS_32)
+ h->e_machine = EM_PPC;
+# elif defined(Q_PROCESSOR_POWER_64)
+ h->e_machine = EM_S390;
+# elif defined(Q_PROCESSOR_POWER_32)
+ h->e_machine = EM_MIPS;
+# endif
+ });
+
+ newRow("file-version-0", "file has an unknown ELF version", [](H h) {
+ --h->e_version;
+ });
+ newRow("file-version-2", "file has an unknown ELF version", [](H h) {
+ ++h->e_version;
+ });
+
+ newRow("program-entry-size-zero", "unexpected program header entry size", [](H h) {
+ h->e_phentsize = 0;
+ });
+ newRow("program-entry-small", "unexpected program header entry size", [](H h) {
+ h->e_phentsize = alignof(ElfPhdr);
+ });
+
+ newRow("program-table-starts-past-eof", "program header table extends past the end of the file",
+ [](H h, QFile *f) {
+ h->e_phoff = f->size();
+ });
+ newRow("program-table-ends-past-eof", "program header table extends past the end of the file",
+ [](H h, QFile *f) {
+ h->e_phoff = f->size() + 1- h->e_phentsize * h->e_phnum;
+ });
+
+ newRow("segment-starts-past-eof", "a program header entry extends past the end of the file",
+ [](H h, QFile *f) {
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type != PT_LOAD)
+ continue;
+ p->p_offset = f->size();
+ break;
+ }
+ });
+ newRow("segment-ends-past-eof", "a program header entry extends past the end of the file",
+ [](H h, QFile *f) {
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type != PT_LOAD)
+ continue;
+ p->p_filesz = f->size() + 1 - p->p_offset;
+ break;
+ }
+ });
+ newRow("segment-bounds-overflow", "a program header entry extends past the end of the file",
+ [](H h) {
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type != PT_LOAD)
+ continue;
+ p->p_filesz = ~size_t(0); // -1
+ break;
+ }
+ });
+
+ newRow("no-code", "file has no code", [](H h) {
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type == PT_LOAD)
+ p->p_flags &= ~PF_X;
+ }
+ });
+}
+
+void tst_QPluginLoader::loadCorruptElf_data()
+{
+#if !defined(QT_SHARED)
+ QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds");
#endif
+ loadCorruptElfCommonRows();
+ using H = ElfHeader *; // because I'm lazy
+
+ // PT_NOTE tests
+ // general validity is tested in the common rows, for all segments
+
+ newRow("misaligned-note-segment", "note segment start is not properly aligned", [](H h) {
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type == PT_NOTE)
+ ++p->p_offset;
+ }
+ });
+
+ static const auto getFirstNote = [](void *header, ElfPhdr *phdr) {
+ return reinterpret_cast<ElfNhdr *>(static_cast<uchar *>(header) + phdr->p_offset);
+ };
+ static const auto getNextNote = [](void *header, ElfPhdr *phdr, ElfNhdr *n) {
+ // how far into the segment are we?
+ size_t offset = reinterpret_cast<uchar *>(n) - static_cast<uchar *>(header) - phdr->p_offset;
+
+ size_t delta = sizeof(*n) + n->n_namesz + phdr->p_align - 1;
+ delta &= -phdr->p_align;
+ delta += n->n_descsz + phdr->p_align - 1;
+ delta &= -phdr->p_align;
+
+ offset += delta;
+ if (offset < phdr->p_filesz)
+ n = reinterpret_cast<ElfNhdr *>(reinterpret_cast<uchar *>(n) + delta);
+ else
+ n = nullptr;
+ return n;
+ };
+
+ // all the intra-note errors cause the notes simply to be skipped
+ auto newNoteRow = [](const char *rowname, auto &&lambda) {
+ newRow(rowname, "is not a Qt plugin (metadata not found)", std::move(lambda));
+ };
+ newNoteRow("no-notes", [](H h) {
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type == PT_NOTE)
+ p->p_type = PT_NULL;
+ }
+ });
+
+ newNoteRow("note-larger-than-segment-nonqt", [](H h) {
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type != PT_NOTE)
+ continue;
+ ElfNhdr *n = getFirstNote(h, p);
+ n->n_descsz = p->p_filesz;
+ n->n_type = 0; // ensure it's not the Qt note
+ }
+ });
+ newNoteRow("note-larger-than-segment-qt", [](H h) {
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type != PT_NOTE || p->p_align != alignof(QPluginMetaData::ElfNoteHeader))
+ continue;
+
+ // find the Qt metadata note
+ constexpr QPluginMetaData::ElfNoteHeader header(0);
+ ElfNhdr *n = getFirstNote(h, p);
+ for ( ; n; n = getNextNote(h, p, n)) {
+ if (n->n_type == header.n_type && n->n_namesz == header.n_namesz) {
+ if (memcmp(n + 1, header.name, sizeof(header.name)) == 0)
+ break;
+ }
+ }
+
+ if (!n)
+ break;
+ n->n_descsz = p->p_filesz;
+ return;
+ }
+ qWarning("Could not find the Qt metadata note in this file. Test will fail.");
+ });
+ newNoteRow("note-size-overflow1", [](H h) {
+ // due to limited range, this will not overflow on 64-bit
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type != PT_NOTE)
+ continue;
+ ElfNhdr *n = getFirstNote(h, p);
+ n->n_namesz = ~decltype(n->n_namesz)(0);
+ }
+ });
+ newNoteRow("note-size-overflow2", [](H h) {
+ // due to limited range, this will not overflow on 64-bit
+ for (int i = 0; i < h->e_phnum; ++i) {
+ ElfPhdr *p = getProgramEntry(h, i);
+ if (p->p_type != PT_NOTE)
+ continue;
+ ElfNhdr *n = getFirstNote(h, p);
+ n->n_namesz = ~decltype(n->n_namesz)(0) / 2;
+ n->n_descsz = ~decltype(n->n_descsz)(0) / 2;
+ }
+ });
+}
+
+static void loadCorruptElf_helper(const QString &origLibrary)
+{
+ QFETCH(QString, snippet);
+ QFETCH(ElfPatcher, patcher);
+
+ std::unique_ptr<QTemporaryFile> tmplib = patchElf(origLibrary, patcher);
+
+ QPluginLoader lib(tmplib->fileName());
+ QVERIFY(!lib.load());
+ QVERIFY2(lib.errorString().contains(snippet), qPrintable(lib.errorString()));
}
void tst_QPluginLoader::loadCorruptElf()
{
+ loadCorruptElf_helper(sys_qualifiedLibraryName("theplugin"));
+}
+
+# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+void tst_QPluginLoader::loadCorruptElfOldPlugin_data()
+{
#if !defined(QT_SHARED)
QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds");
#endif
-#if defined (__ELF__)
- if (sizeof(void*) == 8) {
- QVERIFY(QFile::exists(QFINDTESTDATA("elftest/corrupt1.elf64.so")));
-
- QPluginLoader lib1(QFINDTESTDATA("elftest/corrupt1.elf64.so"));
- QCOMPARE(lib1.load(), false);
- QVERIFY2(lib1.errorString().contains("not an ELF object"), qPrintable(lib1.errorString()));
-
- QPluginLoader lib2(QFINDTESTDATA("elftest/corrupt2.elf64.so"));
- QCOMPARE(lib2.load(), false);
- QVERIFY2(lib2.errorString().contains("invalid"), qPrintable(lib2.errorString()));
-
- QPluginLoader lib3(QFINDTESTDATA("elftest/corrupt3.elf64.so"));
- QCOMPARE(lib3.load(), false);
- QVERIFY2(lib3.errorString().contains("invalid"), qPrintable(lib3.errorString()));
- } else if (sizeof(void*) == 4) {
- QPluginLoader libW(QFINDTESTDATA("elftest/corrupt3.elf64.so"));
- QCOMPARE(libW.load(), false);
- QVERIFY2(libW.errorString().contains("architecture"), qPrintable(libW.errorString()));
- } else {
- QFAIL("Please port QElfParser to this platform or blacklist this test.");
- }
-#endif
+ loadCorruptElfCommonRows();
+ using H = ElfHeader *; // because I'm lazy
+
+ newRow("section-entry-size-zero", "unexpected section entry size", [](H h) {
+ h->e_shentsize = 0;
+ });
+ newRow("section-entry-small", "unexpected section entry size", [](H h) {
+ h->e_shentsize = alignof(ElfShdr);
+ });
+ newRow("section-entry-misaligned", "unexpected section entry size", [](H h) {
+ ++h->e_shentsize;
+ });
+ newRow("no-sections", "is not a Qt plugin (metadata not found)", [](H h){
+ h->e_shnum = h->e_shoff = h->e_shstrndx = 0;
+ });
+
+ // section table tests
+ newRow("section-table-starts-past-eof", "section table extends past the end of the file",
+ [](H h, QFile *f) {
+ h->e_shoff = f->size();
+ });
+ newRow("section-table-ends-past-eof", "section table extends past the end of the file",
+ [](H h, QFile *f) {
+ h->e_shoff = f->size() + 1 - h->e_shentsize * h->e_shnum;
+ });
+
+ static auto getSection = +[](H h, int index) {
+ auto sections = reinterpret_cast<ElfShdr *>(h->e_shoff + reinterpret_cast<uchar *>(h));
+ return sections + index;
+ };
+
+ // arbitrary section bounds checks
+ // section index = 0 is usually a NULL section, so we try 1
+ newRow("section1-starts-past-eof", "section contents extend past the end of the file",
+ [](H h, QFile *f) {
+ ElfShdr *s = getSection(h, 1);
+ s->sh_offset = f->size();
+ });
+ newRow("section1-ends-past-eof", "section contents extend past the end of the file",
+ [](H h, QFile *f) {
+ ElfShdr *s = getSection(h, 1);
+ s->sh_size = f->size() + 1 - s->sh_offset;
+ });
+ newRow("section1-bounds-overflow", "section contents extend past the end of the file", [](H h) {
+ ElfShdr *s = getSection(h, 1);
+ s->sh_size = -sizeof(*s);
+ });
+
+ // section header string table tests
+ newRow("shstrndx-invalid", "e_shstrndx greater than the number of sections", [](H h) {
+ h->e_shstrndx = h->e_shnum;
+ });
+ newRow("shstrtab-starts-past-eof", "section header string table extends past the end of the file",
+ [](H h, QFile *f) {
+ ElfShdr *s = getSection(h, h->e_shstrndx);
+ s->sh_offset = f->size();
+ });
+ newRow("shstrtab-ends-past-eof", "section header string table extends past the end of the file",
+ [](H h, QFile *f) {
+ ElfShdr *s = getSection(h, h->e_shstrndx);
+ s->sh_size = f->size() + 1 - s->sh_offset;
+ });
+ newRow("shstrtab-bounds-overflow", "section header string table extends past the end of the file", [](H h) {
+ ElfShdr *s = getSection(h, h->e_shstrndx);
+ s->sh_size = -sizeof(*s);
+ });
+ newRow("section-name-past-eof", "section name extends past the end of the file", [](H h, QFile *f) {
+ ElfShdr *section1 = getSection(h, 1);
+ ElfShdr *shstrtab = getSection(h, h->e_shstrndx);
+ section1->sh_name = f->size() - shstrtab->sh_offset;
+ });
+ newRow("section-name-past-end-of-shstrtab", "section name extends past the end of the file", [](H h) {
+ ElfShdr *section1 = getSection(h, 1);
+ ElfShdr *shstrtab = getSection(h, h->e_shstrndx);
+ section1->sh_name = shstrtab->sh_size;
+ });
+
+ newRow("debug-symbols", "metadata not found", [](H h) {
+ // attempt to make it look like extracted debug info
+ for (int i = 1; i < h->e_shnum; ++i) {
+ ElfShdr *s = getSection(h, i);
+ if (s->sh_type == SHT_NOBITS)
+ break;
+ if (s->sh_type != SHT_NOTE && s->sh_flags & SHF_ALLOC)
+ s->sh_type = SHT_NOBITS;
+ }
+ });
+
+ // we don't know which section is .qtmetadata, so we just apply to all of them
+ static auto applyToAllSectionFlags = +[](H h, int flag) {
+ for (int i = 0; i < h->e_shnum; ++i)
+ getSection(h, i)->sh_flags |= flag;
+ };
+ newRow("qtmetadata-executable", ".qtmetadata section is executable", [](H h) {
+ applyToAllSectionFlags(h, SHF_EXECINSTR);
+ });
+ newRow("qtmetadata-writable", ".qtmetadata section is writable", [](H h) {
+ applyToAllSectionFlags(h, SHF_WRITE);
+ });
}
+void tst_QPluginLoader::loadCorruptElfOldPlugin()
+{
+ // ### Qt7: don't forget to remove theoldplugin from the build
+ loadCorruptElf_helper(sys_qualifiedLibraryName("theoldplugin"));
+}
+# endif // Qt 7
+#endif // Q_OF_ELF
+
void tst_QPluginLoader::loadMachO_data()
{
#if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O)
- QTest::addColumn<int>("parseResult");
+ QTest::addColumn<bool>("success");
- QTest::newRow("/dev/null") << int(QMachOParser::NotSuitable);
- QTest::newRow("elftest/debugobj.so") << int(QMachOParser::NotSuitable);
- QTest::newRow("tst_qpluginloader.cpp") << int(QMachOParser::NotSuitable);
- QTest::newRow("tst_qpluginloader") << int(QMachOParser::NotSuitable);
+ QTest::newRow("/dev/null") << false;
+ QTest::newRow("elftest/debugobj.so") << false;
+ QTest::newRow("tst_qpluginloader.cpp") << false;
+ QTest::newRow("tst_qpluginloader") << false;
# ifdef Q_PROCESSOR_X86_64
- QTest::newRow("machtest/good.x86_64.dylib") << int(QMachOParser::QtMetaDataSection);
- QTest::newRow("machtest/good.i386.dylib") << int(QMachOParser::NotSuitable);
- QTest::newRow("machtest/good.fat.no-x86_64.dylib") << int(QMachOParser::NotSuitable);
- QTest::newRow("machtest/good.fat.no-i386.dylib") << int(QMachOParser::QtMetaDataSection);
-# elif defined(Q_PROCESSOR_X86_32)
- QTest::newRow("machtest/good.i386.dylib") << int(QMachOParser::QtMetaDataSection);
- QTest::newRow("machtest/good.x86_64.dylib") << int(QMachOParser::NotSuitable);
- QTest::newRow("machtest/good.fat.no-i386.dylib") << int(QMachOParser::NotSuitable);
- QTest::newRow("machtest/good.fat.no-x86_64.dylib") << int(QMachOParser::QtMetaDataSection);
-# endif
-# ifndef Q_PROCESSOR_POWER_64
- QTest::newRow("machtest/good.ppc64.dylib") << int(QMachOParser::NotSuitable);
+ QTest::newRow("machtest/good.x86_64.dylib") << true;
+ QTest::newRow("machtest/good.arm64.dylib") << false;
+ QTest::newRow("machtest/good.fat.no-x86_64.dylib") << false;
+ QTest::newRow("machtest/good.fat.no-arm64.dylib") << true;
+# elif defined(Q_PROCESSOR_ARM)
+ QTest::newRow("machtest/good.arm64.dylib") << true;
+ QTest::newRow("machtest/good.x86_64.dylib") << false;
+ QTest::newRow("machtest/good.fat.no-arm64.dylib") << false;
+ QTest::newRow("machtest/good.fat.no-x86_64.dylib") << true;
# endif
- QTest::newRow("machtest/good.fat.all.dylib") << int(QMachOParser::QtMetaDataSection);
- QTest::newRow("machtest/good.fat.stub-x86_64.dylib") << int(QMachOParser::NotSuitable);
- QTest::newRow("machtest/good.fat.stub-i386.dylib") << int(QMachOParser::NotSuitable);
+ QTest::newRow("machtest/good.fat.all.dylib") << true;
+ QTest::newRow("machtest/good.fat.stub-x86_64.dylib") << false;
+ QTest::newRow("machtest/good.fat.stub-arm64.dylib") << false;
QDir d(QFINDTESTDATA("machtest"));
- QStringList badlist = d.entryList(QStringList() << "bad*.dylib");
- foreach (const QString &bad, badlist)
- QTest::newRow(qPrintable("machtest/" + bad)) << int(QMachOParser::NotSuitable);
+ const QStringList badlist = d.entryList(QStringList() << "bad*.dylib");
+ for (const QString &bad : badlist)
+ QTest::newRow(qPrintable("machtest/" + bad)) << false;
#endif
}
@@ -374,60 +888,49 @@ void tst_QPluginLoader::loadMachO()
QVERIFY(f.open(QIODevice::ReadOnly));
QByteArray data = f.readAll();
- qsizetype pos;
- qsizetype len;
- QString errorString;
- int r = QMachOParser::parse(data.constData(), data.size(), f.fileName(), &errorString, &pos, &len);
-
- QFETCH(int, parseResult);
- QCOMPARE(r, parseResult);
+ QString errorString = f.fileName();
+ QLibraryScanResult r = QMachOParser::parse(data.constData(), data.size(), &errorString);
- if (r == QMachOParser::NotSuitable)
+ QFETCH(bool, success);
+ if (success) {
+ QVERIFY(r.length != 0);
+ } else {
+ QCOMPARE(r.length, 0);
return;
+ }
- QVERIFY(pos > 0);
- QVERIFY(size_t(len) >= sizeof(void*));
- QVERIFY(pos + long(len) < data.size());
- QCOMPARE(pos & (sizeof(void*) - 1), 0UL);
-
- void *value = *(void**)(data.constData() + pos);
- QCOMPARE(value, sizeof(void*) > 4 ? (void*)(0xc0ffeec0ffeeL) : (void*)0xc0ffee);
+ QVERIFY(r.pos > 0);
+ QVERIFY(r.pos + r.length < data.size());
// now that we know it's valid, let's try to make it invalid
- ulong offeredlen = pos;
+ ulong offeredlen = r.pos;
do {
--offeredlen;
- r = QMachOParser::parse(data.constData(), offeredlen, f.fileName(), &errorString, &pos, &len);
- QVERIFY2(r == QMachOParser::NotSuitable, qPrintable(QString("Failed at size 0x%1").arg(offeredlen, 0, 16)));
+ errorString = f.fileName();
+ r = QMachOParser::parse(data.constData(), offeredlen, &errorString);
+ QVERIFY2(r.length == 0, qPrintable(QString("Failed at size 0x%1").arg(offeredlen, 0, 16)));
} while (offeredlen);
#endif
}
-#if defined (Q_OS_UNIX)
-void tst_QPluginLoader::loadGarbage()
-{
-#if !defined(QT_SHARED)
- QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds");
-#endif
- for (int i=0; i<5; i++) {
- const QString name = QLatin1String("elftest/garbage") + QString::number(i + 1) + QLatin1String(".so");
- QPluginLoader lib(QFINDTESTDATA(name));
- QCOMPARE(lib.load(), false);
- QVERIFY(lib.errorString() != QString("Unknown error"));
- }
-}
-#endif
-
void tst_QPluginLoader::relativePath()
{
#if !defined(QT_SHARED)
QSKIP("This test requires Qt to create shared libraries.");
#endif
+#ifdef Q_OS_ANDROID
+ // On Android we do not need to explicitly set library paths, as they are
+ // already set.
+ // But we need to use ARCH suffix in pulgin name
+ const QString pluginName("theplugin_" ANDROID_ARCH SUFFIX);
+#else
// Windows binaries run from release and debug subdirs, so we can't rely on the current dir.
const QString binDir = QFINDTESTDATA("bin");
QVERIFY(!binDir.isEmpty());
QCoreApplication::addLibraryPath(binDir);
- QPluginLoader loader("theplugin");
+ const QString pluginName("theplugin" SUFFIX);
+#endif
+ QPluginLoader loader(pluginName);
loader.load(); // not recommended, instance() should do the job.
PluginInterface *instance = qobject_cast<PluginInterface*>(loader.instance());
QVERIFY(instance);
@@ -440,13 +943,27 @@ void tst_QPluginLoader::absolutePath()
#if !defined(QT_SHARED)
QSKIP("This test requires Qt to create shared libraries.");
#endif
+#ifdef Q_OS_ANDROID
+ // On Android we need to clear library paths to make sure that the absolute
+ // path works
+ const QStringList libraryPaths = QCoreApplication::libraryPaths();
+ QVERIFY(!libraryPaths.isEmpty());
+ QCoreApplication::setLibraryPaths(QStringList());
+ const QString pluginPath(libraryPaths.first() + "/" PREFIX "theplugin_" ANDROID_ARCH SUFFIX);
+#else
// Windows binaries run from release and debug subdirs, so we can't rely on the current dir.
const QString binDir = QFINDTESTDATA("bin");
QVERIFY(!binDir.isEmpty());
QVERIFY(QDir::isAbsolutePath(binDir));
- QPluginLoader loader(binDir + "/theplugin");
+ const QString pluginPath(binDir + "/" PREFIX "theplugin" SUFFIX);
+#endif
+ QPluginLoader loader(pluginPath);
loader.load(); // not recommended, instance() should do the job.
PluginInterface *instance = qobject_cast<PluginInterface*>(loader.instance());
+#ifdef Q_OS_ANDROID
+ // Restore library paths
+ QCoreApplication::setLibraryPaths(libraryPaths);
+#endif
QVERIFY(instance);
QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok"));
QVERIFY(loader.unload());
@@ -467,7 +984,7 @@ void tst_QPluginLoader::reloadPlugin()
QSignalSpy spy(loader.instance(), &QObject::destroyed);
QVERIFY(spy.isValid());
QVERIFY(loader.unload()); // refcount reached 0, did really unload
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
// reload plugin
QVERIFY(loader.load());
@@ -480,6 +997,54 @@ void tst_QPluginLoader::reloadPlugin()
QVERIFY(loader.unload());
}
+void tst_QPluginLoader::loadSectionTableStrippedElf()
+{
+#ifdef Q_OS_ANDROID
+ if (QNativeInterface::QAndroidApplication::sdkVersion() >= 24)
+ QSKIP("Android 7+ (API 24+) linker doesn't allow missing or bad section header");
+#endif
+#if !defined(QT_SHARED)
+ QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds");
+#elif !defined(Q_OF_ELF)
+ QSKIP("Test specific to the ELF file format");
+#else
+ ElfPatcher patcher { [](ElfHeader *header, QFile *f) {
+ // modify the header to make it look like the section table was stripped
+ header->e_shoff = header->e_shnum = header->e_shstrndx = 0;
+
+ // and append a bad header at the end
+ QPluginMetaData::MagicHeader badHeader = {};
+ --badHeader.header.qt_major_version;
+ f->seek(f->size());
+ f->write(reinterpret_cast<const char *>(&badHeader), sizeof(badHeader));
+ } };
+
+ QString tmpLibName;
+ {
+ std::unique_ptr<QTemporaryFile> tmplib =
+ patchElf(sys_qualifiedLibraryName("theplugin"), patcher);
+
+ tmpLibName = tmplib->fileName();
+ tmplib->setAutoRemove(false);
+ }
+#if defined(Q_OS_QNX)
+ // On QNX plugin access is still too early, even when QTemporaryFile is closed
+ QTest::qSleep(1000);
+#endif
+ auto removeTmpLib = qScopeGuard([=]{
+ QFile::remove(tmpLibName);
+ });
+
+ // now attempt to load it
+ QPluginLoader loader(tmpLibName);
+ QVERIFY2(loader.load(), qPrintable(loader.errorString()));
+ PluginInterface *instance = qobject_cast<PluginInterface*>(loader.instance());
+ QVERIFY(instance);
+ QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok"));
+ QVERIFY(loader.unload());
+#endif
+}
+
void tst_QPluginLoader::preloadedPlugin_data()
{
QTest::addColumn<bool>("doLoad");
@@ -530,19 +1095,18 @@ void tst_QPluginLoader::staticPlugins()
const QObjectList instances = QPluginLoader::staticInstances();
QVERIFY(instances.size());
- bool found = false;
- for (QObject *obj : instances) {
- found = obj->metaObject()->className() == QLatin1String("StaticPlugin");
- if (found)
- break;
- }
- QVERIFY(found);
+ // ensure the our plugin only shows up once
+ int foundCount = std::count_if(instances.begin(), instances.end(), [](QObject *obj) {
+ return obj->metaObject()->className() == QLatin1String("StaticPlugin");
+ });
+ QCOMPARE(foundCount, 1);
const auto plugins = QPluginLoader::staticPlugins();
QCOMPARE(plugins.size(), instances.size());
// find the metadata
QJsonObject metaData;
+ bool found = false;
for (const auto &p : plugins) {
metaData = p.metaData();
found = metaData.value("className").toString() == QLatin1String("StaticPlugin");
@@ -558,6 +1122,18 @@ void tst_QPluginLoader::staticPlugins()
QCOMPARE(metaData.value("URI").toString(), "qt.test.pluginloader.staticplugin");
}
+void tst_QPluginLoader::reregisteredStaticPlugins()
+{
+ // the Q_IMPORT_PLUGIN macro will have already done this
+ qRegisterStaticPluginFunction(qt_static_plugin_StaticPlugin());
+ staticPlugins();
+ if (QTest::currentTestFailed())
+ return;
+
+ qRegisterStaticPluginFunction(qt_static_plugin_StaticPlugin());
+ staticPlugins();
+}
+
QTEST_MAIN(tst_QPluginLoader)
#include "tst_qpluginloader.moc"