Q{Elf,Mach}Parser: simplify the return codes

The multi-state return code was a legacy of how Arvid wrote the ELF
parser code back in the day, the fact that it scanned for two different
types of plugins in Qt 4 and that the metadata could exist in different
places.  None of that matters nowadays: who cares if the file is a
corrupt binary, not a valid binary, does not have the right
architecture, or has no suitable section? It's not a plugin, period.

The Qt 4 plugin mechanism was removed for Qt 5.0 in commit
7443895857fdaee132c8efc643e471f02b3d0fa4 ("Remove support for Qt 4 style
plugins").

Change-Id: I42eb903a916645db9900fffd16a442d800399b98
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
This commit is contained in:
Thiago Macieira 2021-09-12 20:07:40 -07:00
parent a03a67fbfa
commit 57960ab075
7 changed files with 90 additions and 106 deletions

View File

@ -63,8 +63,8 @@ const char *QElfParser::parseSectionHeader(const char *data, ElfSectionHeader *s
return data;
}
auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &library,
QLibraryPrivate *lib, qsizetype *pos, qsizetype *sectionlen) -> ScanResult
QLibraryScanResult QElfParser::parse(const char *dataStart, ulong fdlen, const QString &library,
QLibraryPrivate *lib)
{
#if defined(QELFPARSER_DEBUG)
qDebug() << "QElfParser::parse " << library;
@ -73,19 +73,19 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
if (fdlen < 64) {
if (lib)
lib->errorString = QLibrary::tr("'%1' is not an ELF object (%2)").arg(library, QLibrary::tr("file too small"));
return NotElf;
return {};
}
const char *data = dataStart;
if (qstrncmp(data, "\177ELF", 4) != 0) {
if (lib)
lib->errorString = QLibrary::tr("'%1' is not an ELF object").arg(library);
return NotElf;
return {};
}
// 32 or 64 bit
if (data[4] != 1 && data[4] != 2) {
if (lib)
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("odd cpu architecture"));
return Corrupt;
return {};
}
/* If you remove this check, to read ELF objects of a different arch, please make sure you modify the typedefs
@ -95,7 +95,7 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
if (data[4] != ExpectedClass) {
if (lib)
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("wrong cpu architecture"));
return Corrupt;
return {};
}
// endian
@ -103,7 +103,7 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
if (data[5] != ExpectedEndianness) {
if (lib)
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("odd endianness"));
return Corrupt;
return {};
}
data += 16 // e_ident
@ -122,7 +122,7 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
if (e_shsize > fdlen) {
if (lib)
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("unexpected e_shsize"));
return Corrupt;
return {};
}
data += sizeof(qelfhalf_t) // e_ehsize
@ -134,7 +134,7 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
if (e_shentsize % 4) {
if (lib)
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, QLibrary::tr("unexpected e_shentsize"));
return Corrupt;
return {};
}
data += sizeof(qelfhalf_t); // e_shentsize
qelfhalf_t e_shnum = qFromUnaligned<qelfhalf_t> (data);
@ -149,7 +149,7 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
nullptr, int(e_shnum)).arg(e_shentsize);
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(library, message);
}
return Corrupt;
return {};
}
#if defined(QELFPARSER_DEBUG)
@ -164,7 +164,7 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)")
.arg(library, QLibrary::tr("shstrtab section header seems to be at %1")
.arg(QString::number(soff, 16)));
return Corrupt;
return {};
}
parseSectionHeader(dataStart + soff, &strtab);
@ -175,7 +175,7 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)")
.arg(library, QLibrary::tr("string table seems to be at %1")
.arg(QString::number(strtab.offset, 16)));
return Corrupt;
return {};
}
#if defined(QELFPARSER_DEBUG)
@ -197,7 +197,7 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)")
.arg(library, QLibrary::tr("section name %1 of %2 behind end of file")
.arg(i).arg(e_shnum));
return Corrupt;
return {};
}
#if defined(QELFPARSER_DEBUG)
@ -210,7 +210,7 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
if (lib)
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)")
.arg(library, QLibrary::tr("empty .rodata. not a library."));
return Corrupt;
return {};
}
#if defined(QELFPARSER_DEBUG)
qDebug()<<"section is not program data. skipped.";
@ -223,15 +223,13 @@ auto QElfParser::parse(const char *dataStart, ulong fdlen, const QString &librar
if (lib)
lib->errorString = QLibrary::tr("'%1' is an invalid ELF object (%2)")
.arg(library, QLibrary::tr("missing section data. This is not a library."));
return Corrupt;
return {};
}
*pos = sh.offset;
*sectionlen = sh.size;
return QtMetaDataSection;
return { qsizetype(sh.offset), qsizetype(sh.size) };
}
s += e_shentsize;
}
return NoQtSection;
return {};
}
QT_END_NAMESPACE

View File

@ -52,7 +52,7 @@
//
#include <qendian.h>
#include <private/qglobal_p.h>
#include "qlibrary_p.h"
QT_REQUIRE_CONFIG(library);
@ -71,7 +71,6 @@ typedef quintptr qelfaddr_t;
class QElfParser
{
public:
enum ScanResult { QtMetaDataSection, NoQtSection, NotElf, Corrupt };
enum { ElfLittleEndian = 0, ElfBigEndian = 1 };
struct ElfSectionHeader
@ -85,7 +84,7 @@ public:
qelfoff_t m_stringTableFileOffset;
const char *parseSectionHeader(const char* s, ElfSectionHeader *sh);
ScanResult parse(const char *m_s, ulong fdlen, const QString &library, QLibraryPrivate *lib, qsizetype *pos, qsizetype *sectionlen);
QLibraryScanResult parse(const char *m_s, ulong fdlen, const QString &library, QLibraryPrivate *lib);
};
QT_END_NAMESPACE

View File

@ -254,8 +254,10 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
constexpr qint64 MaxMemoryMapSize =
Q_INT64_C(1) << (sizeof(qsizetype) > 4 ? 40 : 29);
qsizetype fdlen = qMin(file.size(), MaxMemoryMapSize);
const char *filedata = reinterpret_cast<char *>(file.map(0, fdlen));
QLibraryScanResult r;
r.pos = 0;
r.length = qMin(file.size(), MaxMemoryMapSize);
const char *filedata = reinterpret_cast<char *>(file.map(0, r.length));
#ifdef Q_OS_UNIX
if (filedata == nullptr) {
@ -274,65 +276,49 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
// the side of doing a regular read into memory (up to 64 MB).
data = file.read(64 * 1024 * 1024);
filedata = data.constData();
fdlen = data.size();
r.length = data.size();
}
#endif
/*
ELF and Mach-O binaries with GCC have .qplugin sections.
ELF and Mach-O binaries with GCC have .qtmetadata sections. Find them.
*/
bool hasMetaData = false;
qsizetype pos = 0;
char pattern[] = "qTMETADATA ";
pattern[0] = 'Q'; // Ensure the pattern "QTMETADATA" is not found in this library should QPluginLoader ever encounter it.
const ulong plen = ulong(qstrlen(pattern));
#if defined (Q_OF_ELF)
QElfParser::ScanResult r = QElfParser().parse(filedata, fdlen, library, lib, &pos, &fdlen);
if (r == QElfParser::Corrupt || r == QElfParser::NotElf) {
if (lib && qt_debug_component()) {
qWarning("QElfParser: %ls", qUtf16Printable(lib->errorString));
}
return false;
} else if (r == QElfParser::QtMetaDataSection) {
qsizetype rel = qt_find_pattern(filedata + pos, fdlen, pattern, plen);
if (rel < 0)
pos = -1;
else
pos += rel;
hasMetaData = true;
r = QElfParser().parse(filedata, r.length, library, lib);
if (r.length == 0) {
if (lib && qt_debug_component())
qWarning("QElfParser: %ls", qUtf16Printable(lib->errorString));
return false;
}
#elif defined(Q_OF_MACH_O)
{
QString errorString;
int r = QMachOParser::parse(filedata, fdlen, library, &errorString, &pos, &fdlen);
if (r == QMachOParser::NotSuitable) {
r = QMachOParser::parse(filedata, r.length, library, &errorString);
if (r.length == 0) {
if (qt_debug_component())
qWarning("QMachOParser: %ls", qUtf16Printable(errorString));
if (lib)
lib->errorString = errorString;
return false;
}
// even if the metadata section was not found, the Mach-O parser will
// at least return the boundaries of the right architecture
qsizetype rel = qt_find_pattern(filedata + pos, fdlen, pattern, plen);
if (rel < 0)
pos = -1;
else
pos += rel;
}
#endif // defined(Q_OF_ELF) && defined(Q_CC_GNU)
if (qsizetype rel = qt_find_pattern(filedata + r.pos, r.length, pattern, plen);
rel >= 0) {
r.pos += rel;
hasMetaData = true;
}
#else
pos = qt_find_pattern(filedata, fdlen, pattern, plen);
if (pos > 0)
hasMetaData = true;
#endif // defined(Q_OF_ELF) && defined(Q_CC_GNU)
bool ret = false;
if (pos >= 0 && hasMetaData) {
const char *data = filedata + pos;
if (r.pos >= 0 && hasMetaData) {
const char *data = filedata + r.pos;
QString errMsg;
QJsonDocument doc = qJsonFromRawLibraryMetaData(data, fdlen, &errMsg);
QJsonDocument doc = qJsonFromRawLibraryMetaData(data, r.length, &errMsg);
if (doc.isNull()) {
qWarning("Found invalid metadata in lib %ls: %ls",
qUtf16Printable(library), qUtf16Printable(errMsg));

View File

@ -68,6 +68,12 @@ QT_BEGIN_NAMESPACE
bool qt_debug_component();
struct QLibraryScanResult
{
qsizetype pos;
qsizetype length;
};
class QLibraryStore;
class QLibraryPrivate
{

View File

@ -42,7 +42,6 @@
#if defined(Q_OF_MACH_O)
#include <qendian.h>
#include "qlibrary_p.h"
#include <mach-o/loader.h>
#include <mach-o/fat.h>
@ -81,15 +80,16 @@ typedef section my_section;
static const uint32_t my_magic = MH_MAGIC;
#endif
static int ns(const QString &reason, const QString &library, QString *errorString)
Q_DECL_COLD_FUNCTION
static QLibraryScanResult ns(const QString &reason, const QString &library, QString *errorString)
{
if (errorString)
*errorString = QLibrary::tr("'%1' is not a valid Mach-O binary (%2)")
.arg(library, reason.isEmpty() ? QLibrary::tr("file is corrupt") : reason);
return QMachOParser::NotSuitable;
return {};
}
int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QString *errorString, qsizetype *pos, qsizetype *sectionlen)
QLibraryScanResult QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QString *errorString)
{
// The minimum size of a Mach-O binary we're interested in.
// It must have a full Mach header, at least one segment and at least one
@ -146,9 +146,7 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS
library, errorString);
}
// from this point on, fdlen is specific to this architecture
// from this point on, everything is in host byte order
*pos = reinterpret_cast<const char *>(header) - m_s;
// (re-)check the CPU type
// ### should we check the CPU subtype? Maybe on ARM?
@ -197,9 +195,8 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS
|| Q_UNLIKELY(fdlen < sect[j].offset + sect[j].size))
return ns(QString(), library, errorString);
*pos += sect[j].offset;
*sectionlen = sect[j].size;
return QtMetaDataSection;
qsizetype pos = reinterpret_cast<const char *>(header) - m_s + sect[j].offset;
return { pos, qsizetype(sect[j].size) };
}
}
@ -207,11 +204,10 @@ int QMachOParser::parse(const char *m_s, ulong fdlen, const QString &library, QS
seg = reinterpret_cast<const my_segment_command *>(reinterpret_cast<const char *>(seg) + seg->cmdsize);
}
// // No Qt section was found, but at least we know that where the proper architecture's boundaries are
// return NoQtSection;
// No .qtmetadata section was found
if (errorString)
*errorString = QLibrary::tr("'%1' is not a Qt plugin").arg(library);
return NotSuitable;
return {};
}
QT_END_NAMESPACE

View File

@ -51,8 +51,7 @@
// We mean it.
//
#include <qendian.h>
#include <private/qglobal_p.h>
#include "qlibrary_p.h"
QT_REQUIRE_CONFIG(library);
@ -66,8 +65,8 @@ class QLibraryPrivate;
class Q_AUTOTEST_EXPORT QMachOParser
{
public:
enum { QtMetaDataSection, NoQtSection, NotSuitable };
static int parse(const char *m_s, ulong fdlen, const QString &library, QString *errorString, qsizetype *pos, qsizetype *sectionlen);
static QLibraryScanResult parse(const char *m_s, ulong fdlen, const QString &library,
QString *errorString);
};
QT_END_NAMESPACE

View File

@ -334,36 +334,36 @@ void tst_QPluginLoader::loadCorruptElf()
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);
QTest::newRow("machtest/good.x86_64.dylib") << true;
QTest::newRow("machtest/good.i386.dylib") << false;
QTest::newRow("machtest/good.fat.no-x86_64.dylib") << false;
QTest::newRow("machtest/good.fat.no-i386.dylib") << true;
# 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);
QTest::newRow("machtest/good.i386.dylib") << true;
QTest::newRow("machtest/good.x86_64.dylib") << false;
QTest::newRow("machtest/good.fat.no-i386.dylib") << false;
QTest::newRow("machtest/good.fat.no-x86_64.dylib") << true;
# endif
# ifndef Q_PROCESSOR_POWER_64
QTest::newRow("machtest/good.ppc64.dylib") << int(QMachOParser::NotSuitable);
QTest::newRow("machtest/good.ppc64.dylib") << false;
# 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-i386.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);
QTest::newRow(qPrintable("machtest/" + bad)) << false;
#endif
}
@ -374,31 +374,31 @@ 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);
QLibraryScanResult r = QMachOParser::parse(data.constData(), data.size(), f.fileName(), &errorString);
QFETCH(int, parseResult);
QCOMPARE(r, parseResult);
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);
QVERIFY(r.pos > 0);
QVERIFY(size_t(r.length) >= sizeof(void*));
QVERIFY(r.pos + r.length < data.size());
QCOMPARE(r.pos & (sizeof(void*) - 1), 0UL);
void *value = *(void**)(data.constData() + pos);
void *value = *(void**)(data.constData() + r.pos);
QCOMPARE(value, sizeof(void*) > 4 ? (void*)(0xc0ffeec0ffeeL) : (void*)0xc0ffee);
// 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)));
r = QMachOParser::parse(data.constData(), offeredlen, f.fileName(), &errorString);
QVERIFY2(r.length == 0, qPrintable(QString("Failed at size 0x%1").arg(offeredlen, 0, 16)));
} while (offeredlen);
#endif
}