It seems to be the preferred constant in other OSes. In particular, OpenBSD does not appear to have ELFOSABI_GNU[1]. Found while researching if commit 9caac0f176040b4da48d3ea289683b0b082cf729 was still necessary for OpenBSD. This reverts commit ded2fd9ff4fea06af881229bacaeff5d0ecfa6de, which implied NetBSD 9 doesn't have ELFOSABI_GNU either. [1] https://github.com/openbsd/ports/blob/master/x11/qt6/qtbase/patches/patch-src_corelib_plugin_qelfparser_p_cpp Change-Id: I63b988479db546dabffcfffd17662020d722af20 Reviewed-by: Lars Knoll <lars@knoll.priv.no> Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io> (cherry picked from commit 4e2f8bad0a4e83ac6890bf6f38f26d9857c0d79b) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
784 lines
26 KiB
C++
784 lines
26 KiB
C++
// Copyright (C) 2017 The Qt Company Ltd.
|
|
// Copyright (C) 2021 Intel Corporation.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
|
|
#include "qelfparser_p.h"
|
|
|
|
#ifdef Q_OF_ELF
|
|
|
|
#include "qlibrary_p.h"
|
|
|
|
#include <qloggingcategory.h>
|
|
#include <qnumeric.h>
|
|
#include <qsysinfo.h>
|
|
|
|
#if __has_include(<elf.h>)
|
|
# include <elf.h>
|
|
#elif __has_include(<sys/elf.h>)
|
|
# include <sys/elf.h>
|
|
#else
|
|
# error "Need ELF header to parse plugins."
|
|
#endif
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
// ### Qt7: propagate the constant and eliminate dead code
|
|
static constexpr bool ElfNotesAreMandatory = QT_VERSION >= QT_VERSION_CHECK(7,0,0);
|
|
|
|
// 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
|
|
|
|
#ifndef PT_GNU_EH_FRAME
|
|
# define PT_GNU_EH_FRAME 0x6474e550
|
|
#endif
|
|
#ifndef PT_GNU_STACK
|
|
# define PT_GNU_STACK 0x6474e551
|
|
#endif
|
|
#ifndef PT_GNU_RELRO
|
|
# define PT_GNU_RELRO 0x6474e552
|
|
#endif
|
|
#ifndef PT_GNU_PROPERTY
|
|
# define PT_GNU_PROPERTY 0x6474e553
|
|
#endif
|
|
|
|
QT_WARNING_PUSH
|
|
QT_WARNING_DISABLE_CLANG("-Wunused-const-variable")
|
|
|
|
namespace {
|
|
template <QSysInfo::Endian Order> struct ElfEndianTraits
|
|
{
|
|
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); }
|
|
};
|
|
|
|
template <typename EquivalentPointerType> struct ElfTypeTraits
|
|
{
|
|
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_ALPHA)
|
|
EM_ALPHA
|
|
#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_HPPA)
|
|
EM_PARISC
|
|
#elif defined(Q_PROCESSOR_IA64)
|
|
EM_IA_64
|
|
#elif defined(Q_PROCESSOR_LOONGARCH)
|
|
EM_LOONGARCH
|
|
#elif defined(Q_PROCESSOR_M68K)
|
|
EM_68K
|
|
#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)
|
|
EM_SPARCV9
|
|
#elif defined(Q_PROCESSOR_SPARC_64)
|
|
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
|
|
;
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
// byte 6
|
|
static bool checkElfVersion(const uchar *ident)
|
|
{
|
|
uchar elfversion = ident[EI_VERSION];
|
|
return elfversion == EV_CURRENT;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// byte 5
|
|
static bool checkDataOrder(const uchar *ident)
|
|
{
|
|
uchar data = ident[EI_DATA];
|
|
return data == EndianTraits::DataOrder;
|
|
}
|
|
|
|
// byte 7
|
|
static bool checkOsAbi(const uchar *ident)
|
|
{
|
|
uchar osabi = ident[EI_OSABI];
|
|
// we don't check
|
|
Q_UNUSED(osabi);
|
|
return true;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// bytes 9-16
|
|
static bool checkPadding(const uchar *ident)
|
|
{
|
|
// why would we check this?
|
|
Q_UNUSED(ident);
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static bool checkType(const Ehdr &header)
|
|
{
|
|
return header.e_type == ET_DYN;
|
|
}
|
|
|
|
static bool checkMachine(const Ehdr &header)
|
|
{
|
|
return header.e_machine == ElfMachineCheck::ExpectedMachine;
|
|
}
|
|
|
|
static bool checkFileVersion(const Ehdr &header)
|
|
{
|
|
return header.e_version == EV_CURRENT;
|
|
}
|
|
|
|
static bool checkHeader(const Ehdr &header)
|
|
{
|
|
if (!checkIdent(header))
|
|
return false;
|
|
if (!IncludeValidityChecks)
|
|
return true;
|
|
return checkType(header)
|
|
&& checkMachine(header)
|
|
&& checkFileVersion(header);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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_LINUX: 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;
|
|
}
|
|
|
|
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_ALPHA: d << ", Alpha"; break;
|
|
case EM_68K: d << ", MC68000"; break;
|
|
case EM_ARM: d << ", ARM"; break;
|
|
case EM_AARCH64: d << ", AArch64"; break;
|
|
#ifdef EM_BLACKFIN
|
|
case EM_BLACKFIN: d << ", Blackfin"; break;
|
|
#endif
|
|
case EM_IA_64: d << ", IA-64"; break;
|
|
#ifdef EM_LOONGARCH
|
|
case EM_LOONGARCH: d << ", LoongArch"; break;
|
|
#endif
|
|
case EM_MIPS: d << ", MIPS"; break;
|
|
case EM_PARISC: d << ", HPPA"; break;
|
|
case EM_PPC: d << ", PowerPC"; break;
|
|
case EM_PPC64: d << ", PowerPC 64-bit"; break;
|
|
#ifdef EM_RISCV
|
|
case EM_RISCV: d << ", RISC-V"; break;
|
|
#endif
|
|
#ifdef EM_S390
|
|
case EM_S390: d << ", S/390"; break;
|
|
#endif
|
|
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 ElfProgramDebug { const ElfHeaderCheck<>::TypeTraits::Phdr *phdr; };
|
|
Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, ElfProgramDebug p)
|
|
{
|
|
QDebugStateSaver saved(d);
|
|
d << Qt::hex << Qt::showbase << "program";
|
|
switch (p.phdr->p_type) {
|
|
case PT_NULL: d << "NULL"; break;
|
|
case PT_LOAD: d << "LOAD"; break;
|
|
case PT_DYNAMIC: d << "DYNAMIC"; break;
|
|
case PT_INTERP: d << "INTERP"; break;
|
|
case PT_NOTE: d << "NOTE"; break;
|
|
case PT_PHDR: d << "PHDR"; break;
|
|
case PT_TLS: d << "TLS"; break;
|
|
case PT_GNU_EH_FRAME: d << "GNU_EH_FRAME"; break;
|
|
case PT_GNU_STACK: d << "GNU_STACK"; break;
|
|
case PT_GNU_RELRO: d << "GNU_RELRO"; break;
|
|
case PT_GNU_PROPERTY: d << "GNU_PROPERTY"; break;
|
|
default: d << "type" << p.phdr->p_type; break;
|
|
}
|
|
|
|
d << "offset" << p.phdr->p_offset
|
|
<< "virtaddr" << p.phdr->p_vaddr
|
|
<< "filesz" << p.phdr->p_filesz
|
|
<< "memsz" << p.phdr->p_memsz
|
|
<< "align" << p.phdr->p_align
|
|
<< "flags";
|
|
|
|
d.nospace();
|
|
if (p.phdr->p_flags & PF_R)
|
|
d << 'R';
|
|
if (p.phdr->p_flags & PF_W)
|
|
d << 'W';
|
|
if (p.phdr->p_flags & PF_X)
|
|
d << 'X';
|
|
|
|
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 {};
|
|
}
|
|
|
|
Q_DECL_COLD_FUNCTION QLibraryScanResult notplugin(QString &&explanation) const
|
|
{
|
|
*errMsg = QLibrary::tr("'%1' is not a Qt plugin (%2)").arg(*errMsg, explanation);
|
|
return {};
|
|
}
|
|
|
|
Q_DECL_COLD_FUNCTION QLibraryScanResult notfound() const
|
|
{
|
|
return notplugin(QLibrary::tr("metadata not found"));
|
|
}
|
|
};
|
|
} // unnamed namespace
|
|
|
|
QT_WARNING_POP
|
|
|
|
using T = ElfHeaderCheck<>::TypeTraits;
|
|
|
|
template <typename F>
|
|
static bool scanProgramHeaders(QByteArrayView data, const ErrorMaker &error, F f)
|
|
{
|
|
auto header = reinterpret_cast<const T::Ehdr *>(data.data());
|
|
Q_UNUSED(error);
|
|
|
|
auto phdr = reinterpret_cast<const T::Phdr *>(data.data() + header->e_phoff);
|
|
auto phdr_end = phdr + header->e_phnum;
|
|
for ( ; phdr != phdr_end; ++phdr) {
|
|
if (!f(phdr))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool preScanProgramHeaders(QByteArrayView data, const ErrorMaker &error)
|
|
{
|
|
auto header = reinterpret_cast<const T::Ehdr *>(data.data());
|
|
|
|
// first, validate the extent of the full program header table
|
|
T::Word e_phnum = header->e_phnum;
|
|
T::Off offset = e_phnum * sizeof(T::Phdr); // can't overflow due to size of T::Half
|
|
if (qAddOverflow(offset, header->e_phoff, &offset) || offset > size_t(data.size()))
|
|
return error(QLibrary::tr("program header table extends past the end of the file")), false;
|
|
|
|
// confirm validity
|
|
bool hasCode = false;
|
|
auto checker = [&](const T::Phdr *phdr) {
|
|
qEDebug << ElfProgramDebug{phdr};
|
|
|
|
if (T::Off end; qAddOverflow(phdr->p_offset, phdr->p_filesz, &end)
|
|
|| end > size_t(data.size()))
|
|
return error(QLibrary::tr("a program header entry extends past the end of the file")), false;
|
|
|
|
// this is not a validity check, it's to exclude debug symbol files
|
|
if (phdr->p_type == PT_LOAD && phdr->p_filesz != 0 && (phdr->p_flags & PF_X))
|
|
hasCode = true;
|
|
|
|
// this probably applies to all segments, but we'll only apply it to notes
|
|
if (phdr->p_type == PT_NOTE && qPopulationCount(phdr->p_align) == 1
|
|
&& phdr->p_offset & (phdr->p_align - 1)) {
|
|
return error(QLibrary::tr("a note segment start is not properly aligned "
|
|
"(offset 0x%1, alignment %2)")
|
|
.arg(phdr->p_offset, 6, 16, QChar(u'0'))
|
|
.arg(phdr->p_align)), false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
if (!scanProgramHeaders(data, error, checker))
|
|
return false;
|
|
if (!hasCode)
|
|
return error.notplugin(QLibrary::tr("file has no code")), false;
|
|
return true;
|
|
}
|
|
|
|
static QLibraryScanResult scanProgramHeadersForNotes(QByteArrayView data, const ErrorMaker &error)
|
|
{
|
|
// minimum metadata payload is 2 bytes
|
|
constexpr size_t MinPayloadSize = sizeof(QPluginMetaData::Header) + 2;
|
|
constexpr qptrdiff MinNoteSize = sizeof(QPluginMetaData::ElfNoteHeader) + 2;
|
|
constexpr size_t NoteNameSize = sizeof(QPluginMetaData::ElfNoteHeader::name);
|
|
constexpr size_t NoteAlignment = alignof(QPluginMetaData::ElfNoteHeader);
|
|
constexpr qptrdiff PayloadStartDelta = offsetof(QPluginMetaData::ElfNoteHeader, header);
|
|
static_assert(MinNoteSize > PayloadStartDelta);
|
|
static_assert((PayloadStartDelta & (NoteAlignment - 1)) == 0);
|
|
|
|
QLibraryScanResult r = {};
|
|
auto noteFinder = [&](const T::Phdr *phdr) {
|
|
if (phdr->p_type != PT_NOTE || phdr->p_align != NoteAlignment)
|
|
return true;
|
|
|
|
// check for signed integer overflows, to avoid issues with the
|
|
// arithmetic below
|
|
if (qptrdiff(phdr->p_filesz) < 0) {
|
|
auto h = reinterpret_cast<const T::Ehdr *>(data.data());
|
|
auto segments = reinterpret_cast<const T::Phdr *>(data.data() + h->e_phoff);
|
|
qEDebug << "segment" << (phdr - segments) << "contains a note with size"
|
|
<< Qt::hex << Qt::showbase << phdr->p_filesz
|
|
<< "which is larger than half the virtual memory space";
|
|
return true;
|
|
}
|
|
|
|
// iterate over the notes in this segment
|
|
T::Off offset = phdr->p_offset;
|
|
const T::Off end_offset = offset + phdr->p_filesz;
|
|
while (qptrdiff(end_offset - offset) >= MinNoteSize) {
|
|
auto nhdr = reinterpret_cast<const T::Nhdr *>(data.data() + offset);
|
|
T::Word n_namesz = nhdr->n_namesz;
|
|
T::Word n_descsz = nhdr->n_descsz;
|
|
T::Word n_type = nhdr->n_type;
|
|
|
|
// overflow check: calculate where the next note will be, if it exists
|
|
T::Off next_offset = offset;
|
|
next_offset += sizeof(T::Nhdr); // can't overflow (we checked above)
|
|
next_offset += NoteAlignment - 3; // offset is aligned, this can't overflow
|
|
if (qAddOverflow<T::Off>(next_offset, n_namesz, &next_offset))
|
|
break;
|
|
next_offset &= -NoteAlignment;
|
|
|
|
next_offset += NoteAlignment - 3; // offset is aligned, this can't overflow
|
|
if (qAddOverflow<T::Off>(next_offset, n_descsz, &next_offset))
|
|
break;
|
|
next_offset &= -NoteAlignment;
|
|
if (next_offset > end_offset)
|
|
break;
|
|
|
|
if (n_namesz == NoteNameSize && n_descsz >= MinPayloadSize
|
|
&& n_type == QPluginMetaData::ElfNoteHeader::NoteType
|
|
&& memcmp(nhdr + 1, QPluginMetaData::ElfNoteHeader::NoteName, NoteNameSize) == 0) {
|
|
// yes, it's our note
|
|
r.pos = offset + PayloadStartDelta;
|
|
r.length = nhdr->n_descsz;
|
|
return false;
|
|
}
|
|
offset = next_offset;
|
|
}
|
|
return true;
|
|
};
|
|
scanProgramHeaders(data, error, noteFinder);
|
|
|
|
if (!r.length)
|
|
return r;
|
|
|
|
qEDebug << "found Qt metadata in ELF note at"
|
|
<< Qt::hex << Qt::showbase << r.pos << "size" << Qt::reset << r.length;
|
|
return r;
|
|
}
|
|
|
|
static QLibraryScanResult scanSections(QByteArrayView data, const ErrorMaker &error)
|
|
{
|
|
auto header = reinterpret_cast<const T::Ehdr *>(data.data());
|
|
|
|
// 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) {
|
|
QLatin1StringView 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 = QLatin1StringView(namestart, len);
|
|
}
|
|
qEDebug << "section" << section << "name" << name << ElfSectionDebug{shdr};
|
|
|
|
// sanity check the section
|
|
if (name.isNull())
|
|
return error(QLibrary::tr("a section name extends past the end of the file"));
|
|
|
|
// 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("section contents extend past the end of the file"));
|
|
}
|
|
|
|
if (name != ".qtmetadata"_L1)
|
|
continue;
|
|
qEDebug << "found .qtmetadata section";
|
|
if (shdr->sh_size < sizeof(QPluginMetaData::MagicHeader))
|
|
return error(QLibrary::tr(".qtmetadata section is too small"));
|
|
|
|
if (IncludeValidityChecks) {
|
|
QByteArrayView expectedMagic = QByteArrayView::fromArray(QPluginMetaData::MagicString);
|
|
QByteArrayView actualMagic = data.sliced(shdr->sh_offset, expectedMagic.size());
|
|
if (expectedMagic != actualMagic)
|
|
return error(QLibrary::tr(".qtmetadata section has incorrect magic"));
|
|
|
|
if (shdr->sh_flags & SHF_WRITE)
|
|
return error(QLibrary::tr(".qtmetadata section is writable"));
|
|
if (shdr->sh_flags & SHF_EXECINSTR)
|
|
return error(QLibrary::tr(".qtmetadata section is executable"));
|
|
}
|
|
|
|
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_phnum << "program headers of"
|
|
<< header->e_phentsize << "bytes at offset" << header->e_phoff;
|
|
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_phentsize != sizeof(T::Phdr))
|
|
return error(QLibrary::tr("unexpected program header entry size (%1)")
|
|
.arg(header->e_phentsize));
|
|
}
|
|
|
|
if (!preScanProgramHeaders(data, error))
|
|
return {};
|
|
|
|
if (QLibraryScanResult r = scanProgramHeadersForNotes(data, error); r.length)
|
|
return r;
|
|
|
|
if (!ElfNotesAreMandatory) {
|
|
if constexpr (IncludeValidityChecks) {
|
|
if (header->e_shentsize != sizeof(T::Shdr))
|
|
return error(QLibrary::tr("unexpected section entry size (%1)")
|
|
.arg(header->e_shentsize));
|
|
}
|
|
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);
|
|
}
|
|
return error.notfound();
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#endif // Q_OF_ELF
|