qtbase/src/corelib/plugin/qelfparser_p.cpp
Thiago Macieira 17537273f0 QElfParser: use ELFOSABI_LINUX instead of ELFOSABI_GNU
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>
2023-06-08 02:23:53 +00:00

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