summaryrefslogtreecommitdiffstats
path: root/src/corelib/plugin
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2021-09-10 20:30:08 -0700
committerThiago Macieira <thiago.macieira@intel.com>2021-10-06 12:12:53 -0700
commit46fc01d7ca10ab95e53206077c7c710d2032b4f0 (patch)
treef238483011dd05e2948b6b49883d9bb85437efc1 /src/corelib/plugin
parent3abcff49eb962cb087498626d77929a870c82929 (diff)
QElfParser: rewrite using elf.h
This rewrite uses the actual structures supplied by the system's C library, so it should be easier to read. It removes hardcoded constants with little evident meaning in favor of sizeof() and the macros from that header. It also removes advancing the data pointer in favor of having absolute offsets. The resulting implementation is stricter than the original, checking more fields in the header. Because the QPluginLoader and QFactoryLoader users may make decisions based on availability of plugins before attempting to load them, it's better to be stricter here than to fail later when trying to dlopen() them. Debugging and testing are much improved. Instead of stored artifacts, I added a routine to modify a valid plugin to make it invalid, given the conditions we've found so far. If you turn debugging on for this category, you'll see things like: not-elf.fcqdMq.so : Not an ELF file (invalid signature) wrong-word-size.QrnSAx.so : ELF 32-bit LSB (GNU/Linux), version 1, shared library or PIC executable, x86-64 invalid-word-size.bOkXvp.so : Invalid ELF file (class 0), LSB (GNU/Linux) unknown-word-size.ogYKeF.so : Invalid ELF file (class 66), LSB (GNU/Linux) wrong-endian.owiElX.so : ELF 64-bit MSB (GNU/Linux), version 1, shared library or PIC executable, x86-64 invalid-endian.FRxClR.so : ELF 64-bit invalid endianness (0) (GNU/Linux) unknown-endian.FfvRrP.so : ELF 64-bit invalid endianness (65) (GNU/Linux) elf-version-0.gPTdpQ.so : ELF 64-bit LSB (GNU/Linux), file version 0 elf-version-2.jlIUUg.so : ELF 64-bit LSB (GNU/Linux), file version 2 executable.LlXiFp.so : ELF 64-bit LSB (GNU/Linux), version 1, executable, x86-64 relocatable.UsOYuy.so : ELF 64-bit LSB (GNU/Linux), version 1, relocatable, x86-64 core-file.hqvNRz.so : ELF 64-bit LSB (GNU/Linux), version 1, core dump, x86-64 invalid-type.CIJgfS.so : ELF 64-bit LSB (GNU/Linux), version 1, unknown type 259, x86-64 wrong-arch.UcNmgz.so : ELF 64-bit LSB (GNU/Linux), version 1, shared library or PIC executable, AArch64 file-version-0.lZYuda.so : ELF 64-bit LSB (GNU/Linux), version 0, shared library or PIC executable, x86-64 file-version-2.ucfdwL.so : ELF 64-bit LSB (GNU/Linux), version 2, shared library or PIC executable, x86-64 no-sections.rSjsHh.so : ELF 64-bit LSB (GNU/Linux), version 1, shared library or PIC executable, x86-64 no-sections.rSjsHh.so : contains 0 sections of 64 bytes at offset 0 ; section header string table (shstrtab) is entry 0 no-sections.rSjsHh.so : no section table present, not able to find Qt metadata qtmetadata-executable.vrxcIf.so : ELF 64-bit LSB (GNU/Linux), version 1, shared library or PIC executable, x86-64 qtmetadata-executable.vrxcIf.so : contains 42 sections of 64 bytes at offset 997256 ; section header string table (shstrtab) is entry 41 qtmetadata-executable.vrxcIf.so : shstrtab section is located at offset 996831 size 423 qtmetadata-executable.vrxcIf.so : section 0 name "" type NULL flags X offset 0x0 size 0x0 qtmetadata-executable.vrxcIf.so : section 1 name ".note.gnu.property" type NOTE flags AX offset 0x2a8 size 0x30 qtmetadata-executable.vrxcIf.so : section 2 name ".note.gnu.build-id" type NOTE flags AX offset 0x2d8 size 0x24 qtmetadata-executable.vrxcIf.so : section 3 name ".hash" type HASH flags AX offset 0x300 size 0x44c qtmetadata-executable.vrxcIf.so : section 4 name ".gnu.hash" type 0x6ffffff6 flags AX offset 0x750 size 0x3b8 qtmetadata-executable.vrxcIf.so : section 5 name ".dynsym" type DYNSYM flags AX offset 0xb08 size 0xd50 qtmetadata-executable.vrxcIf.so : section 6 name ".dynstr" type STRTAB flags AX offset 0x1858 size 0x15d8 qtmetadata-executable.vrxcIf.so : section 7 name ".gnu.version" type 0x6fffffff flags AX offset 0x2e30 size 0x11c qtmetadata-executable.vrxcIf.so : section 8 name ".gnu.version_r" type 0x6ffffffe flags AX offset 0x2f50 size 0xb0 qtmetadata-executable.vrxcIf.so : section 9 name ".rela.dyn" type RELA flags AX offset 0x3000 size 0x480 qtmetadata-executable.vrxcIf.so : section 10 name ".rela.plt" type RELA flags AX offset 0x3480 size 0x7e0 qtmetadata-executable.vrxcIf.so : section 11 name ".init" type PROGBITS flags AX offset 0x4000 size 0x1b qtmetadata-executable.vrxcIf.so : section 12 name ".plt" type PROGBITS flags AX offset 0x4020 size 0x550 qtmetadata-executable.vrxcIf.so : section 13 name ".plt.got" type PROGBITS flags AX offset 0x4570 size 0x8 qtmetadata-executable.vrxcIf.so : section 14 name ".text" type PROGBITS flags AX offset 0x4580 size 0x110e qtmetadata-executable.vrxcIf.so : section 15 name ".fini" type PROGBITS flags AX offset 0x5690 size 0xd qtmetadata-executable.vrxcIf.so : section 16 name ".rodata" type PROGBITS flags AX offset 0x6000 size 0x473 qtmetadata-executable.vrxcIf.so : section 17 name ".qtversion" type PROGBITS flags AX offset 0x6478 size 0x10 qtmetadata-executable.vrxcIf.so : section 18 name ".qtmetadata" type PROGBITS flags AX offset 0x64a0 size 0x19b qtmetadata-executable.vrxcIf.so : found .qtmetadata section qtmetadata-writable.stzwrk.so : ELF 64-bit LSB (GNU/Linux), version 1, shared library or PIC executable, x86-64 Change-Id: I42eb903a916645db9900fffd16a4437af9728eea Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/corelib/plugin')
-rw-r--r--src/corelib/plugin/qelfparser_p.cpp634
-rw-r--r--src/corelib/plugin/qelfparser_p.h28
-rw-r--r--src/corelib/plugin/qlibrary.cpp4
3 files changed, 499 insertions, 167 deletions
diff --git a/src/corelib/plugin/qelfparser_p.cpp b/src/corelib/plugin/qelfparser_p.cpp
index 0f30e8b20a..e50ad6b16c 100644
--- a/src/corelib/plugin/qelfparser_p.cpp
+++ b/src/corelib/plugin/qelfparser_p.cpp
@@ -1,6 +1,7 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Copyright (C) 2021 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
@@ -39,189 +40,542 @@
#include "qelfparser_p.h"
-#if defined (Q_OF_ELF) && defined(Q_CC_GNU)
+#if defined (Q_OF_ELF) && __has_include(<elf.h>)
#include "qlibrary_p.h"
-#include <qdebug.h>
+
+#include <qloggingcategory.h>
+#include <qnumeric.h>
+#include <qsysinfo.h>
+
+#include <elf.h>
QT_BEGIN_NAMESPACE
-// #define QELFPARSER_DEBUG 1
+// Whether we include some extra validity checks
+// (checks to ensure we don't read out-of-bounds are always included)
+static constexpr bool IncludeValidityChecks = true;
+
+#ifdef QT_BUILD_INTERNAL
+# define QELFPARSER_DEBUG
+#endif
+#if defined(QELFPARSER_DEBUG)
+static Q_LOGGING_CATEGORY(lcElfParser, "qt.core.plugin.elfparser")
+# define qEDebug qCDebug(lcElfParser) << reinterpret_cast<const char16_t *>(error.errMsg->constData()) << ':'
+#else
+# define qEDebug if (false) {} else QNoDebug()
+#endif
+
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_CLANG("-Wunused-const-variable")
-const char *QElfParser::parseSectionHeader(const char *data, ElfSectionHeader *sh)
+namespace {
+template <QSysInfo::Endian Order> struct ElfEndianTraits
{
- sh->name = qFromUnaligned<qelfword_t>(data);
- data += sizeof(qelfword_t); // sh_name
- sh->type = qFromUnaligned<qelfword_t>(data);
- data += sizeof(qelfword_t) // sh_type
- + sizeof(qelfaddr_t) // sh_flags
- + sizeof(qelfaddr_t); // sh_addr
- sh->offset = qFromUnaligned<qelfoff_t>(data);
- data += sizeof(qelfoff_t); // sh_offset
- sh->size = qFromUnaligned<qelfoff_t>(data);
- data += sizeof(qelfoff_t); // sh_size
- return data;
-}
+ static constexpr unsigned char DataOrder = ELFDATA2LSB;
+ template <typename T> static T fromEndian(T value) { return qFromLittleEndian(value); }
+};
+template <> struct ElfEndianTraits<QSysInfo::BigEndian>
+{
+ static constexpr unsigned char DataOrder = ELFDATA2MSB;
+ template <typename T> static T fromEndian(T value) { return qFromBigEndian(value); }
+};
-QLibraryScanResult QElfParser::parse(const char *dataStart, ulong fdlen, QString *errMsg)
+template <typename EquivalentPointerType> struct ElfTypeTraits
{
-#if defined(QELFPARSER_DEBUG)
- qDebug() << "QElfParser::parse " << library;
+ static constexpr unsigned char Class = ELFCLASS64;
+
+ // integer types
+ using Half = Elf64_Half;
+ using Word = Elf64_Word;
+ using Addr = Elf64_Addr;
+ using Off = Elf64_Off;
+
+ // structure types
+ using Ehdr = Elf64_Ehdr;
+ using Shdr = Elf64_Shdr;
+ using Phdr = Elf64_Phdr;
+ using Nhdr = Elf64_Nhdr;
+};
+template <> struct ElfTypeTraits<quint32>
+{
+ static constexpr unsigned char Class = ELFCLASS32;
+
+ // integer types
+ using Half = Elf32_Half;
+ using Word = Elf32_Word;
+ using Addr = Elf32_Addr;
+ using Off = Elf32_Off;
+
+ // structure types
+ using Ehdr = Elf32_Ehdr;
+ using Shdr = Elf32_Shdr;
+ using Phdr = Elf32_Phdr;
+ using Nhdr = Elf32_Nhdr;
+};
+
+struct ElfMachineCheck
+{
+ static const Elf32_Half ExpectedMachine =
+#if 0
+ // nothing
+#elif defined(Q_PROCESSOR_ARM_32)
+ EM_ARM
+#elif defined(Q_PROCESSOR_ARM_64)
+ EM_AARCH64
+#elif defined(Q_PROCESSOR_BLACKFIN)
+ EM_BLACKFIN
+#elif defined(Q_PROCESSOR_IA64)
+ EM_IA_64
+#elif defined(Q_PROCESSOR_MIPS)
+ EM_MIPS
+#elif defined(Q_PROCESSOR_POWER_32)
+ EM_PPC
+#elif defined(Q_PROCESSOR_POWER_64)
+ EM_PPC64
+#elif defined(Q_PROCESSOR_RISCV)
+ EM_RISCV
+#elif defined(Q_PROCESSOR_S390)
+ EM_S390
+#elif defined(Q_PROCESSOR_SH)
+ EM_SH
+#elif defined(Q_PROCESSOR_SPARC_V9)
+# warning "Please confirm that this is correct for Linux and Solaris"
+ EM_SPARCV9
+#elif defined(Q_PROCESSOR_SPARC_64)
+# warning "Please confirm that this is correct for Linux and Solaris"
+ EM_SPARCV9
+#elif defined(Q_PROCESSOR_SPARC)
+ EM_SPARC
+#elif defined(Q_PROCESSOR_WASM)
+#elif defined(Q_PROCESSOR_X86_32)
+ EM_386
+#elif defined(Q_PROCESSOR_X86_64)
+ EM_X86_64
+#else
+# error "Unknown Q_PROCESSOR_xxx macro, please update."
+ EM_NONE
#endif
+ ;
+};
- if (fdlen < 64) {
- *errMsg = QLibrary::tr("'%1' is not an ELF object (%2)").arg(*errMsg, QLibrary::tr("file too small"));
- return {};
+struct ElfHeaderCommonCheck
+{
+ static_assert(std::is_same_v<decltype(Elf32_Ehdr::e_ident), decltype(Elf64_Ehdr::e_ident)>,
+ "e_ident field is not the same in both Elf32_Ehdr and Elf64_Ehdr");
+
+ // bytes 0-3
+ static bool checkElfMagic(const uchar *ident)
+ {
+ return memcmp(ident, ELFMAG, SELFMAG) == 0;
}
- const char *data = dataStart;
- if (qstrncmp(data, "\177ELF", 4) != 0) {
- *errMsg = QLibrary::tr("'%1' is not an ELF object").arg(*errMsg);
- return {};
+
+ // byte 6
+ static bool checkElfVersion(const uchar *ident)
+ {
+ uchar elfversion = ident[EI_VERSION];
+ return elfversion == EV_CURRENT;
}
- // 32 or 64 bit
- if (data[4] != 1 && data[4] != 2) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(*errMsg, QLibrary::tr("odd cpu architecture"));
- return {};
+
+ struct CommonHeader {
+ Elf32_Half type;
+ Elf32_Half machine;
+ Elf32_Word version;
+ };
+};
+
+template <typename EquivalentPointerType = quintptr, QSysInfo::Endian Order = QSysInfo::ByteOrder>
+struct ElfHeaderCheck : public ElfHeaderCommonCheck
+{
+ using TypeTraits = ElfTypeTraits<EquivalentPointerType>;
+ using EndianTraits = ElfEndianTraits<Order>;
+ using Ehdr = typename TypeTraits::Ehdr;
+
+ // byte 4
+ static bool checkClass(const uchar *ident)
+ {
+ uchar klass = ident[EI_CLASS];
+ return klass == TypeTraits::Class;
}
- /* If you remove this check, to read ELF objects of a different arch, please make sure you modify the typedefs
- to match the _plugin_ architecture.
- */
- constexpr int ExpectedClass = (sizeof(void *) == 4) ? 1 : 2;
- if (data[4] != ExpectedClass) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(*errMsg, QLibrary::tr("wrong cpu architecture"));
- return {};
+ // byte 5
+ static bool checkDataOrder(const uchar *ident)
+ {
+ uchar data = ident[EI_DATA];
+ return data == EndianTraits::DataOrder;
}
- // endian
- constexpr int ExpectedEndianness = (Q_BYTE_ORDER == Q_LITTLE_ENDIAN) ? 1 : 2;
- if (data[5] != ExpectedEndianness) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(*errMsg, QLibrary::tr("odd endianness"));
- return {};
+ // byte 7
+ static bool checkOsAbi(const uchar *ident)
+ {
+ uchar osabi = ident[EI_OSABI];
+ // we don't check
+ Q_UNUSED(osabi);
+ return true;
}
- data += 16 // e_ident
- + sizeof(qelfhalf_t) // e_type
- + sizeof(qelfhalf_t) // e_machine
- + sizeof(qelfword_t) // e_version
- + sizeof(qelfaddr_t) // e_entry
- + sizeof(qelfoff_t); // e_phoff
+ // byte 8
+ static bool checkAbiVersion(const uchar *ident)
+ {
+ uchar abiversion = ident[EI_ABIVERSION];
+ // we don't check (and I don't know anyone who uses this)
+ Q_UNUSED(abiversion);
+ return true;
+ }
- qelfoff_t e_shoff = qFromUnaligned<qelfoff_t> (data);
- data += sizeof(qelfoff_t) // e_shoff
- + sizeof(qelfword_t); // e_flags
+ // bytes 9-16
+ static bool checkPadding(const uchar *ident)
+ {
+ // why would we check this?
+ Q_UNUSED(ident);
+ return true;
+ }
- qelfhalf_t e_shsize = qFromUnaligned<qelfhalf_t> (data);
+ static bool checkIdent(const Ehdr &header)
+ {
+ return checkElfMagic(header.e_ident)
+ && checkClass(header.e_ident)
+ && checkDataOrder(header.e_ident)
+ && checkElfVersion(header.e_ident)
+ && checkOsAbi(header.e_ident)
+ && checkAbiVersion(header.e_ident)
+ && checkPadding(header.e_ident);
+ }
- if (e_shsize > fdlen) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(*errMsg, QLibrary::tr("unexpected e_shsize"));
- return {};
+ static bool checkType(const Ehdr &header)
+ {
+ return header.e_type == ET_DYN;
}
- data += sizeof(qelfhalf_t) // e_ehsize
- + sizeof(qelfhalf_t) // e_phentsize
- + sizeof(qelfhalf_t); // e_phnum
+ static bool checkMachine(const Ehdr &header)
+ {
+ return header.e_machine == ElfMachineCheck::ExpectedMachine;
+ }
- qelfhalf_t e_shentsize = qFromUnaligned<qelfhalf_t> (data);
+ static bool checkFileVersion(const Ehdr &header)
+ {
+ return header.e_version == EV_CURRENT;
+ }
- if (e_shentsize % 4) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(*errMsg, QLibrary::tr("unexpected e_shentsize"));
- return {};
+ static bool checkHeader(const Ehdr &header)
+ {
+ if (!checkIdent(header))
+ return false;
+ if (!IncludeValidityChecks)
+ return true;
+ return checkType(header)
+ && checkMachine(header)
+ && checkFileVersion(header);
}
- data += sizeof(qelfhalf_t); // e_shentsize
- qelfhalf_t e_shnum = qFromUnaligned<qelfhalf_t> (data);
- data += sizeof(qelfhalf_t); // e_shnum
- qelfhalf_t e_shtrndx = qFromUnaligned<qelfhalf_t> (data);
- data += sizeof(qelfhalf_t); // e_shtrndx
-
- if ((quint32)(e_shnum * e_shentsize) > fdlen) {
- const QString message =
- QLibrary::tr("announced %n section(s), each %1 byte(s), exceed file size",
- nullptr, int(e_shnum)).arg(e_shentsize);
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(*errMsg, message);
- return {};
+
+ Q_DECL_COLD_FUNCTION static QString explainCheckFailure(const Ehdr &header)
+ {
+ if (!checkElfMagic(header.e_ident))
+ return QLibrary::tr("invalid signature");
+ if (!checkClass(header.e_ident))
+ return QLibrary::tr("file is for a different word size");
+ if (!checkDataOrder(header.e_ident))
+ return QLibrary::tr("file is for the wrong endianness");
+ if (!checkElfVersion(header.e_ident) || !checkFileVersion(header))
+ return QLibrary::tr("file has an unknown ELF version");
+ if (!checkOsAbi(header.e_ident) || !checkAbiVersion(header.e_ident))
+ return QLibrary::tr("file has an unexpected ABI");
+ if (!checkType(header))
+ return QLibrary::tr("file is not a shared object");
+ if (!checkMachine(header))
+ return QLibrary::tr("file is for a different processor");
+ return QString();
}
-#if defined(QELFPARSER_DEBUG)
- qDebug() << e_shnum << "sections starting at " << ("0x" + QByteArray::number(e_shoff, 16)).data() << "each" << e_shentsize << "bytes";
-#endif
+ static CommonHeader extractCommonHeader(const uchar *data)
+ {
+ auto header = reinterpret_cast<const Ehdr *>(data);
+ CommonHeader r;
+ r.type = EndianTraits::fromEndian(header->e_type);
+ r.machine = EndianTraits::fromEndian(header->e_machine);
+ r.version = EndianTraits::fromEndian(header->e_version);
+ return r;
+ }
+};
- ElfSectionHeader strtab;
- qulonglong soff = e_shoff + qelfword_t(e_shentsize) * qelfword_t(e_shtrndx);
+struct ElfHeaderDebug { const uchar *e_ident; };
+Q_DECL_UNUSED Q_DECL_COLD_FUNCTION static QDebug &operator<<(QDebug &d, ElfHeaderDebug h)
+{
+ const uchar *e_ident = h.e_ident;
+ if (!ElfHeaderCommonCheck::checkElfMagic(e_ident)) {
+ d << "Not an ELF file (invalid signature)";
+ return d;
+ }
- if ((soff + e_shentsize) > fdlen || soff % 4 || soff == 0) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)")
- .arg(*errMsg, QLibrary::tr("shstrtab section header seems to be at %1")
- .arg(QString::number(soff, 16)));
- return {};
+ QDebugStateSaver saver(d);
+ d.nospace();
+ quint8 elfclass = e_ident[EI_CLASS];
+ switch (elfclass) {
+ case ELFCLASSNONE:
+ default:
+ d << "Invalid ELF file (class " << e_ident[EI_CLASS] << "), ";
+ break;
+ case ELFCLASS32:
+ d << "ELF 32-bit ";
+ break;
+ case ELFCLASS64:
+ d << "ELF 64-bit ";
+ break;
}
- parseSectionHeader(dataStart + soff, &strtab);
- m_stringTableFileOffset = strtab.offset;
+ quint8 dataorder = e_ident[EI_DATA];
+ switch (dataorder) {
+ case ELFDATANONE:
+ default:
+ d << "invalid endianness (" << e_ident[EI_DATA] << ')';
+ break;
+ case ELFDATA2LSB:
+ d << "LSB";
+ break;
+ case ELFDATA2MSB:
+ d << "MSB";
+ break;
+ }
+
+ switch (e_ident[EI_OSABI]) {
+ case ELFOSABI_SYSV: d << " (SYSV"; break;
+ case ELFOSABI_HPUX: d << " (HP-UX"; break;
+ case ELFOSABI_NETBSD: d << " (NetBSD"; break;
+ case ELFOSABI_GNU: d << " (GNU/Linux"; break;
+ case ELFOSABI_SOLARIS: d << " (Solaris"; break;
+ case ELFOSABI_AIX: d << " (AIX"; break;
+ case ELFOSABI_IRIX: d << " (IRIX"; break;
+ case ELFOSABI_FREEBSD: d << " (FreeBSD"; break;
+ case ELFOSABI_OPENBSD: d << " (OpenBSD"; break;
+ default: d << " (OS ABI " << e_ident[EI_VERSION]; break;
+ }
+
+ if (e_ident[EI_ABIVERSION])
+ d << " v" << e_ident[EI_ABIVERSION];
+ d << ')';
+
+ if (e_ident[EI_VERSION] != 1) {
+ d << ", file version " << e_ident[EI_VERSION];
+ return d;
+ }
- if ((quint32)(strtab.offset + strtab.size) > fdlen || strtab.offset == 0) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)")
- .arg(*errMsg, QLibrary::tr("string table seems to be at %1")
- .arg(QString::number(strtab.offset, 16)));
+ ElfHeaderCommonCheck::CommonHeader r;
+ if (elfclass == ELFCLASS64 && dataorder == ELFDATA2LSB)
+ r = ElfHeaderCheck<quint64, QSysInfo::LittleEndian>::extractCommonHeader(e_ident);
+ else if (elfclass == ELFCLASS32 && dataorder == ELFDATA2LSB)
+ r = ElfHeaderCheck<quint32, QSysInfo::LittleEndian>::extractCommonHeader(e_ident);
+ else if (elfclass == ELFCLASS64 && dataorder == ELFDATA2MSB)
+ r = ElfHeaderCheck<quint64, QSysInfo::BigEndian>::extractCommonHeader(e_ident);
+ else if (elfclass == ELFCLASS32 && dataorder == ELFDATA2MSB)
+ r = ElfHeaderCheck<quint32, QSysInfo::BigEndian>::extractCommonHeader(e_ident);
+ else
+ return d;
+
+ d << ", version " << r.version;
+
+ switch (r.type) {
+ case ET_NONE: d << ", no type"; break;
+ case ET_REL: d << ", relocatable"; break;
+ case ET_EXEC: d << ", executable"; break;
+ case ET_DYN: d << ", shared library or PIC executable"; break;
+ case ET_CORE: d << ", core dump"; break;
+ default: d << ", unknown type " << r.type; break;
+ }
+
+ switch (r.machine) {
+ // list definitely not exhaustive!
+ case EM_NONE: d << ", no machine"; break;
+ case EM_ARM: d << ", ARM"; break;
+ case EM_AARCH64: d << ", AArch64"; break;
+ case EM_BLACKFIN: d << ", Blackfin"; break;
+ case EM_IA_64: d << ", IA-64"; break;
+ case EM_MIPS: d << ", MIPS"; break;
+ case EM_PPC: d << ", PowerPC"; break;
+ case EM_PPC64: d << ", PowerPC 64-bit"; break;
+ case EM_RISCV: d << ", RISC-V"; break;
+ case EM_S390: d << ", S/390"; break;
+ case EM_SH: d << ", SuperH"; break;
+ case EM_SPARC: d << ", SPARC"; break;
+ case EM_SPARCV9: d << ", SPARCv9"; break;
+ case EM_386: d << ", i386"; break;
+ case EM_X86_64: d << ", x86-64"; break;
+ default: d << ", other machine type " << r.machine; break;
+ }
+
+ return d;
+}
+
+struct ElfSectionDebug { const ElfHeaderCheck<>::TypeTraits::Shdr *shdr; };
+Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, ElfSectionDebug s)
+{
+ // not exhaustive, just a few common things
+ QDebugStateSaver saver(d);
+ d << Qt::hex << Qt::showbase;
+ d << "type";
+ switch (s.shdr->sh_type) {
+ case SHT_NULL: d << "NULL"; break;
+ case SHT_PROGBITS: d << "PROGBITS"; break;
+ case SHT_SYMTAB: d << "SYMTAB"; break;
+ case SHT_STRTAB: d << "STRTAB"; break;
+ case SHT_RELA: d << "RELA"; break;
+ case SHT_HASH: d << "HASH"; break;
+ case SHT_DYNAMIC: d << "DYNAMIC"; break;
+ case SHT_NOTE: d << "NOTE"; break;
+ case SHT_NOBITS: d << "NOBITS"; break;
+ case SHT_DYNSYM: d << "DYNSYM"; break;
+ case SHT_INIT_ARRAY: d << "INIT_ARRAY"; break;
+ case SHT_FINI_ARRAY: d << "FINI_ARRAY"; break;
+ default: d << s.shdr->sh_type;
+ }
+
+ d << "flags";
+ d.nospace();
+ if (s.shdr->sh_flags & SHF_WRITE)
+ d << 'W';
+ if (s.shdr->sh_flags & SHF_ALLOC)
+ d << 'A';
+ if (s.shdr->sh_flags & SHF_EXECINSTR)
+ d << 'X';
+ if (s.shdr->sh_flags & SHF_STRINGS)
+ d << 'S';
+ if (s.shdr->sh_flags & SHF_TLS)
+ d << 'T';
+
+ d.space() << "offset" << s.shdr->sh_offset << "size" << s.shdr->sh_size;
+ return d;
+}
+
+struct ErrorMaker
+{
+ QString *errMsg;
+ constexpr ErrorMaker(QString *errMsg) : errMsg(errMsg) {}
+
+ Q_DECL_COLD_FUNCTION QLibraryScanResult operator()(QString &&text) const
+ {
+ *errMsg = QLibrary::tr("'%1' is not a valid ELF object (%2)")
+ .arg(*errMsg, std::move(text));
return {};
}
-#if defined(QELFPARSER_DEBUG)
- qDebug(".shstrtab at 0x%s", QByteArray::number(m_stringTableFileOffset, 16).data());
-#endif
+ QLibraryScanResult notfound() const
+ {
+ *errMsg = QLibrary::tr("'%1' is not a Qt plugin (.qtmetadata section not found)")
+ .arg(*errMsg);
+ return {};
+ }
+};
+} // unnamed namespace
- const char *s = dataStart + e_shoff;
- for (int i = 0; i < e_shnum; ++i) {
- ElfSectionHeader sh;
- parseSectionHeader(s, &sh);
- if (sh.name == 0) {
- s += e_shentsize;
- continue;
- }
- const char *shnam = dataStart + m_stringTableFileOffset + sh.name;
+QT_WARNING_POP
+
+using T = ElfHeaderCheck<>::TypeTraits;
+
+static QLibraryScanResult scanSections(QByteArrayView data, const ErrorMaker &error)
+{
+ auto header = reinterpret_cast<const T::Ehdr *>(data.data());
- if (m_stringTableFileOffset + sh.name > fdlen) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)")
- .arg(*errMsg, QLibrary::tr("section name %1 of %2 behind end of file")
- .arg(i).arg(e_shnum));
- return {};
+ // in order to find the .qtmetadata section, we need to:
+ // a) find the section table
+ // it's located at offset header->e_shoff
+ // validate it
+ T::Word e_shnum = header->e_shnum;
+ T::Off offset = e_shnum * sizeof(T::Shdr); // can't overflow due to size of T::Half
+ if (qAddOverflow(offset, header->e_shoff, &offset) || offset > size_t(data.size()))
+ return error(QLibrary::tr("section table extends past the end of the file"));
+
+ // b) find the section entry for the section header string table (shstrab)
+ // it's a section whose entry is pointed by e_shstrndx
+ auto sections = reinterpret_cast<const T::Shdr *>(data.data() + header->e_shoff);
+ auto sections_end = sections + e_shnum;
+ auto shdr = sections + header->e_shstrndx;
+
+ // validate the shstrtab
+ offset = shdr->sh_offset;
+ T::Off shstrtab_size = shdr->sh_size;
+ qEDebug << "shstrtab section is located at offset" << offset << "size" << shstrtab_size;
+ if (T::Off end; qAddOverflow<T::Off>(offset, shstrtab_size, &end)
+ || end > size_t(data.size()))
+ return error(QLibrary::tr("section header string table extends past the end of the file"));
+
+ // c) iterate over the sections to find .qtmetadata
+ const char *shstrtab_start = data.data() + offset;
+ shdr = sections;
+ for (int section = 0; shdr != sections_end; ++section, ++shdr) {
+ QLatin1String name;
+ if (shdr->sh_name < shstrtab_size) {
+ const char *namestart = shstrtab_start + shdr->sh_name;
+ size_t len = qstrnlen(namestart, shstrtab_size - shdr->sh_name);
+ name = QLatin1String(namestart, len);
}
+ qEDebug << "section" << section << "name" << name << ElfSectionDebug{shdr};
-#if defined(QELFPARSER_DEBUG)
- qDebug() << "++++" << i << shnam;
-#endif
+ // sanity check the section
+ if (name.isNull())
+ return error(QLibrary::tr("a section name extends past the end of the file"));
- if (qstrcmp(shnam, ".qtmetadata") == 0 ) {
- if (!(sh.type & 0x1)) {
- if (shnam[1] == 'r') {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)")
- .arg(*errMsg, QLibrary::tr("empty .rodata. not a library."));
- return {};
- }
-#if defined(QELFPARSER_DEBUG)
- qDebug()<<"section is not program data. skipped.";
-#endif
- s += e_shentsize;
- continue;
- }
-
- if (sh.offset == 0 || (sh.offset + sh.size) > fdlen || sh.size < 1) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)")
- .arg(*errMsg, QLibrary::tr("missing section data. This is not a library."));
- return {};
- }
- if (sh.size < sizeof(QPluginMetaData::MagicHeader)) {
- *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)")
- .arg(*errMsg, QLibrary::tr("section .qtmetadata is too small"));
- return {};
- }
- sh.offset += sizeof(QPluginMetaData::MagicString);
- sh.size -= sizeof(QPluginMetaData::MagicString);
- return { qsizetype(sh.offset), qsizetype(sh.size) };
+ // sections aren't allowed to extend past the end of the file, unless
+ // they are NOBITS sections
+ if (shdr->sh_type == SHT_NOBITS)
+ continue;;
+ if (T::Off end; qAddOverflow(shdr->sh_offset, shdr->sh_size, &end)
+ || end > size_t(data.size())) {
+ return error(QLibrary::tr("a section data extends past the end of the file"));
+ }
+
+ if (name != QLatin1String(".qtmetadata"))
+ continue;
+ qEDebug << "found .qtmetadata section";
+ if (IncludeValidityChecks && shdr->sh_flags & (SHF_WRITE | SHF_EXECINSTR)) {
+ if (shdr->sh_flags & SHF_WRITE)
+ return error(QLibrary::tr(".qtmetadata section is writable"));
+ return error(QLibrary::tr(".qtmetadata section is executable"));
}
- s += e_shentsize;
+ if (shdr->sh_size < sizeof(QPluginMetaData::MagicHeader))
+ return error(QLibrary::tr("section .qtmetadata is too small"));
+
+ return { qsizetype(shdr->sh_offset + sizeof(QPluginMetaData::MagicString)),
+ qsizetype(shdr->sh_size - sizeof(QPluginMetaData::MagicString)) };
+ }
+
+ // section .qtmetadata not found
+ return error.notfound();
+}
+
+QLibraryScanResult QElfParser::parse(QByteArrayView data, QString *errMsg)
+{
+ ErrorMaker error(errMsg);
+ if (size_t(data.size()) < sizeof(T::Ehdr)) {
+ qEDebug << "file too small:" << size_t(data.size());
+ return error(QLibrary::tr("file too small"));
+ }
+
+ qEDebug << ElfHeaderDebug{ reinterpret_cast<const uchar *>(data.data()) };
+
+ auto header = reinterpret_cast<const T::Ehdr *>(data.data());
+ if (!ElfHeaderCheck<>::checkHeader(*header))
+ return error(ElfHeaderCheck<>::explainCheckFailure(*header));
+
+ qEDebug << "contains" << header->e_shnum << "sections of" << header->e_shentsize
+ << "bytes at offset" << header->e_shoff
+ << "; section header string table (shstrtab) is entry" << header->e_shstrndx;
+
+ // some sanity checks
+ if constexpr (IncludeValidityChecks) {
+ if (header->e_shentsize != sizeof(T::Shdr))
+ return error(QLibrary::tr("unexpected section entry size (%1)")
+ .arg(header->e_shentsize));
}
- return {};
+ if (header->e_shoff == 0 || header->e_shnum == 0) {
+ // this is still a valid ELF file but we don't have a section table
+ qEDebug << "no section table present, not able to find Qt metadata";
+ return error.notfound();
+ }
+
+ if (header->e_shnum && header->e_shstrndx >= header->e_shnum)
+ return error(QLibrary::tr("e_shstrndx greater than the number of sections e_shnum (%1 >= %2)")
+ .arg(header->e_shstrndx).arg(header->e_shnum));
+
+ return scanSections(data, error);
}
QT_END_NAMESPACE
diff --git a/src/corelib/plugin/qelfparser_p.h b/src/corelib/plugin/qelfparser_p.h
index d91b1d4867..42eb212ee1 100644
--- a/src/corelib/plugin/qelfparser_p.h
+++ b/src/corelib/plugin/qelfparser_p.h
@@ -56,35 +56,13 @@
QT_REQUIRE_CONFIG(library);
-#if defined(Q_OF_ELF) && defined(Q_CC_GNU)
+#if defined(Q_OF_ELF) && __has_include(<elf.h>)
QT_BEGIN_NAMESPACE
-class QString;
-class QLibraryPrivate;
-
-typedef quint16 qelfhalf_t;
-typedef quint32 qelfword_t;
-typedef quintptr qelfoff_t;
-typedef quintptr qelfaddr_t;
-
-class QElfParser
+struct QElfParser
{
-public:
- enum { ElfLittleEndian = 0, ElfBigEndian = 1 };
-
- struct ElfSectionHeader
- {
- qelfword_t name;
- qelfword_t type;
- qelfoff_t offset;
- qelfoff_t size;
- };
-
- qelfoff_t m_stringTableFileOffset;
-
- const char *parseSectionHeader(const char* s, ElfSectionHeader *sh);
- QLibraryScanResult parse(const char *m_s, ulong fdlen, QString *errMsg);
+ static QLibraryScanResult parse(QByteArrayView data, QString *errMsg);
};
QT_END_NAMESPACE
diff --git a/src/corelib/plugin/qlibrary.cpp b/src/corelib/plugin/qlibrary.cpp
index 1df6dfeb08..a8c6644bfd 100644
--- a/src/corelib/plugin/qlibrary.cpp
+++ b/src/corelib/plugin/qlibrary.cpp
@@ -193,8 +193,8 @@ static QLibraryScanResult qt_find_pattern(const char *s, qsizetype s_len, QStrin
More importantly, the pattern string may exist in the debug information due
to it being used in the plugin in the first place.
*/
-#if defined (Q_OF_ELF)
- return QElfParser().parse(s, s_len, errMsg);
+#if defined (Q_OF_ELF) && __has_include(<elf.h>)
+ return QElfParser::parse({s, s_len}, errMsg);
#elif defined(Q_OF_MACH_O)
return QMachOParser::parse(s, s_len, errMsg);
#endif