diff --git a/src/corelib/plugin/qelfparser_p.cpp b/src/corelib/plugin/qelfparser_p.cpp index 0f30e8b20ad..e50ad6b16c3 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() #include "qlibrary_p.h" -#include + +#include +#include +#include + +#include 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; -const char *QElfParser::parseSectionHeader(const char *data, ElfSectionHeader *sh) +#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(error.errMsg->constData()) << ':' +#else +# define qEDebug if (false) {} else QNoDebug() +#endif + +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wunused-const-variable") + +namespace { +template struct ElfEndianTraits { - sh->name = qFromUnaligned(data); - data += sizeof(qelfword_t); // sh_name - sh->type = qFromUnaligned(data); - data += sizeof(qelfword_t) // sh_type - + sizeof(qelfaddr_t) // sh_flags - + sizeof(qelfaddr_t); // sh_addr - sh->offset = qFromUnaligned(data); - data += sizeof(qelfoff_t); // sh_offset - sh->size = qFromUnaligned(data); - data += sizeof(qelfoff_t); // sh_size - return data; + static constexpr unsigned char DataOrder = ELFDATA2LSB; + template static T fromEndian(T value) { return qFromLittleEndian(value); } +}; +template <> struct ElfEndianTraits +{ + static constexpr unsigned char DataOrder = ELFDATA2MSB; + template static T fromEndian(T value) { return qFromBigEndian(value); } +}; + +template 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 +{ + 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 + ; +}; + +struct ElfHeaderCommonCheck +{ + static_assert(std::is_same_v, + "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 +struct ElfHeaderCheck : public ElfHeaderCommonCheck +{ + using TypeTraits = ElfTypeTraits; + using EndianTraits = ElfEndianTraits; + 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(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_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; + } + + ElfHeaderCommonCheck::CommonHeader r; + if (elfclass == ELFCLASS64 && dataorder == ELFDATA2LSB) + r = ElfHeaderCheck::extractCommonHeader(e_ident); + else if (elfclass == ELFCLASS32 && dataorder == ELFDATA2LSB) + r = ElfHeaderCheck::extractCommonHeader(e_ident); + else if (elfclass == ELFCLASS64 && dataorder == ELFDATA2MSB) + r = ElfHeaderCheck::extractCommonHeader(e_ident); + else if (elfclass == ELFCLASS32 && dataorder == ELFDATA2MSB) + r = ElfHeaderCheck::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; } -QLibraryScanResult QElfParser::parse(const char *dataStart, ulong fdlen, QString *errMsg) +struct ElfSectionDebug { const ElfHeaderCheck<>::TypeTraits::Shdr *shdr; }; +Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, ElfSectionDebug s) { -#if defined(QELFPARSER_DEBUG) - qDebug() << "QElfParser::parse " << library; -#endif + // 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; + } - if (fdlen < 64) { - *errMsg = QLibrary::tr("'%1' is not an ELF object (%2)").arg(*errMsg, QLibrary::tr("file too small")); - return {}; - } - const char *data = dataStart; - if (qstrncmp(data, "\177ELF", 4) != 0) { - *errMsg = QLibrary::tr("'%1' is not an ELF object").arg(*errMsg); - return {}; - } - // 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")); + 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 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")); + QLibraryScanResult notfound() const + { + *errMsg = QLibrary::tr("'%1' is not a Qt plugin (.qtmetadata section not found)") + .arg(*errMsg); return {}; } +}; +} // unnamed namespace - // 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 {}; - } +QT_WARNING_POP - 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 +using T = ElfHeaderCheck<>::TypeTraits; - qelfoff_t e_shoff = qFromUnaligned (data); - data += sizeof(qelfoff_t) // e_shoff - + sizeof(qelfword_t); // e_flags +static QLibraryScanResult scanSections(QByteArrayView data, const ErrorMaker &error) +{ + auto header = reinterpret_cast(data.data()); - qelfhalf_t e_shsize = qFromUnaligned (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")); - if (e_shsize > fdlen) { - *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(*errMsg, QLibrary::tr("unexpected e_shsize")); - return {}; - } + // 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(data.data() + header->e_shoff); + auto sections_end = sections + e_shnum; + auto shdr = sections + header->e_shstrndx; - data += sizeof(qelfhalf_t) // e_ehsize - + sizeof(qelfhalf_t) // e_phentsize - + sizeof(qelfhalf_t); // e_phnum + // 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(offset, shstrtab_size, &end) + || end > size_t(data.size())) + return error(QLibrary::tr("section header string table extends past the end of the file")); - qelfhalf_t e_shentsize = qFromUnaligned (data); + // 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 (e_shentsize % 4) { - *errMsg = QLibrary::tr("'%1' is an invalid ELF object (%2)").arg(*errMsg, QLibrary::tr("unexpected e_shentsize")); - return {}; - } - data += sizeof(qelfhalf_t); // e_shentsize - qelfhalf_t e_shnum = qFromUnaligned (data); - data += sizeof(qelfhalf_t); // e_shnum - qelfhalf_t e_shtrndx = qFromUnaligned (data); - data += sizeof(qelfhalf_t); // e_shtrndx + // sanity check the section + if (name.isNull()) + return error(QLibrary::tr("a section name extends past the end of the file")); - 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 {}; - } + // 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 defined(QELFPARSER_DEBUG) - qDebug() << e_shnum << "sections starting at " << ("0x" + QByteArray::number(e_shoff, 16)).data() << "each" << e_shentsize << "bytes"; -#endif - - ElfSectionHeader strtab; - qulonglong soff = e_shoff + qelfword_t(e_shentsize) * qelfword_t(e_shtrndx); - - 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 {}; - } - - parseSectionHeader(dataStart + soff, &strtab); - m_stringTableFileOffset = strtab.offset; - - 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))); - return {}; - } - -#if defined(QELFPARSER_DEBUG) - qDebug(".shstrtab at 0x%s", QByteArray::number(m_stringTableFileOffset, 16).data()); -#endif - - 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; + 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")); } - const char *shnam = dataStart + m_stringTableFileOffset + sh.name; + if (shdr->sh_size < sizeof(QPluginMetaData::MagicHeader)) + return error(QLibrary::tr("section .qtmetadata is too small")); - 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 {}; - } - -#if defined(QELFPARSER_DEBUG) - qDebug() << "++++" << i << shnam; -#endif - - 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) }; - } - s += e_shentsize; + return { qsizetype(shdr->sh_offset + sizeof(QPluginMetaData::MagicString)), + qsizetype(shdr->sh_size - sizeof(QPluginMetaData::MagicString)) }; } - return {}; + + // 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(data.data()) }; + + auto header = reinterpret_cast(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)); + } + 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 d91b1d48674..42eb212ee1d 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() 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 1df6dfeb089..a8c6644bfde 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() + return QElfParser::parse({s, s_len}, errMsg); #elif defined(Q_OF_MACH_O) return QMachOParser::parse(s, s_len, errMsg); #endif diff --git a/tests/auto/corelib/plugin/qpluginloader/elftest/corrupt1.elf64.so b/tests/auto/corelib/plugin/qpluginloader/elftest/corrupt1.elf64.so deleted file mode 100644 index 12ce7362dc5..00000000000 Binary files a/tests/auto/corelib/plugin/qpluginloader/elftest/corrupt1.elf64.so and /dev/null differ diff --git a/tests/auto/corelib/plugin/qpluginloader/elftest/corrupt2.elf64.so b/tests/auto/corelib/plugin/qpluginloader/elftest/corrupt2.elf64.so deleted file mode 100644 index 11fdc2c1189..00000000000 Binary files a/tests/auto/corelib/plugin/qpluginloader/elftest/corrupt2.elf64.so and /dev/null differ diff --git a/tests/auto/corelib/plugin/qpluginloader/elftest/corrupt3.elf64.so b/tests/auto/corelib/plugin/qpluginloader/elftest/corrupt3.elf64.so deleted file mode 100644 index 94a2bc35601..00000000000 Binary files a/tests/auto/corelib/plugin/qpluginloader/elftest/corrupt3.elf64.so and /dev/null differ diff --git a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage1.so b/tests/auto/corelib/plugin/qpluginloader/elftest/garbage1.so deleted file mode 100644 index 0c7453077f0..00000000000 --- a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage1.so +++ /dev/null @@ -1,4 +0,0 @@ -pcdL+&&e= -oÒʎI ٝmg]!Z -L')t -N(e P)Y8G 6-y "Zk4?^n5$Y=#y \ No newline at end of file diff --git a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage2.so b/tests/auto/corelib/plugin/qpluginloader/elftest/garbage2.so deleted file mode 100644 index c06338e0c84..00000000000 --- a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage2.so +++ /dev/null @@ -1 +0,0 @@ -v.YtKW3 \ No newline at end of file diff --git a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage3.so b/tests/auto/corelib/plugin/qpluginloader/elftest/garbage3.so deleted file mode 100644 index a24c523a77e..00000000000 --- a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage3.so +++ /dev/null @@ -1 +0,0 @@ -ȂT-ڥ 쾜i8_xI׮x=4@[BKS$ \ No newline at end of file diff --git a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage4.so b/tests/auto/corelib/plugin/qpluginloader/elftest/garbage4.so deleted file mode 100644 index 4f45cf5157a..00000000000 --- a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage4.so +++ /dev/null @@ -1 +0,0 @@ - !\~Uu:9T+91QEǚxng5zh^t'mm*ˈdXH;vw+G 9L0! \ No newline at end of file diff --git a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage5.so b/tests/auto/corelib/plugin/qpluginloader/elftest/garbage5.so deleted file mode 100644 index f8c0a1d5447..00000000000 --- a/tests/auto/corelib/plugin/qpluginloader/elftest/garbage5.so +++ /dev/null @@ -1,2 +0,0 @@ -Q --9 \ No newline at end of file diff --git a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp index 53fc0b80e35..bab35449462 100644 --- a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp +++ b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2018 Intel Corporation. +** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. @@ -31,7 +31,10 @@ #include #include #include +#include #include +#include +#include #include "theplugin/plugininterface.h" #if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O) @@ -91,6 +94,84 @@ # define PREFIX "lib" #endif +#if defined(Q_OF_ELF) +# include +# include +# include + +# ifdef _LP64 +using ElfHeader = Elf64_Ehdr; +using ElfShdr = Elf64_Shdr; +# else +using ElfHeader = Elf32_Ehdr; +using ElfShdr = Elf32_Shdr; +# endif + +struct ElfPatcher +{ + using FullPatcher = void(ElfHeader *, QFile *); + FullPatcher *f; + + ElfPatcher(FullPatcher *f = nullptr) : f(f) {} + + template using IsSingleArg = std::is_invocable; + template static std::enable_if_t::value, ElfPatcher> fromLambda(T &&t) + { + using WithoutQFile = void(*)(ElfHeader *); + static const WithoutQFile f = t; + return { [](ElfHeader *h, QFile *) { f(h);} }; + } + template static std::enable_if_t::value, ElfPatcher> fromLambda(T &&t) + { + return { t }; + } +}; + +Q_DECLARE_METATYPE(ElfPatcher) + +static std::unique_ptr patchElf(const QString &source, ElfPatcher patcher) +{ + std::unique_ptr tmplib; + + bool ok = false; + [&]() { + QFile srclib(source); + QVERIFY2(srclib.open(QIODevice::ReadOnly), qPrintable(srclib.errorString())); + qint64 srcsize = srclib.size(); + const uchar *srcdata = srclib.map(0, srcsize, QFile::MapPrivateOption); + QVERIFY2(srcdata, qPrintable(srclib.errorString())); + + // copy our source plugin so we can modify it + tmplib.reset(new QTemporaryFile(QTest::currentDataTag() + QString(".XXXXXX" SUFFIX))); + QVERIFY2(tmplib->open(), qPrintable(tmplib->errorString())); + + // sanity-check + QByteArray magic = QByteArray::fromRawData(reinterpret_cast(srcdata), SELFMAG); + QCOMPARE(magic, QByteArray(ELFMAG)); + + // copy everything via mmap() + QVERIFY2(tmplib->resize(srcsize), qPrintable(tmplib->errorString())); + uchar *dstdata = tmplib->map(0, srcsize); + memcpy(dstdata, srcdata, srcsize); + + // now patch the file + patcher.f(reinterpret_cast(dstdata), tmplib.get()); + + ok = true; + }(); + if (!ok) + tmplib.reset(); + return tmplib; +} + +// All ELF systems are expected to support GCC expression statements +#define patchElf(source, patcher) __extension__({ \ + auto r = patchElf(source, patcher); \ + if (QTest::currentTestFailed()) return; \ + std::move(r); \ + }) +#endif + static QString sys_qualifiedLibraryName(const QString &fileName) { QString name = QLatin1String("bin/") + QLatin1String(PREFIX) + fileName + QLatin1String(SUFFIX); @@ -111,13 +192,13 @@ private slots: void errorString(); void loadHints(); void deleteinstanceOnUnload(); +#if defined (__ELF__) void loadDebugObj(); + void loadCorruptElf_data(); void loadCorruptElf(); +#endif void loadMachO_data(); void loadMachO(); -#if defined (Q_OS_UNIX) - void loadGarbage(); -#endif void relativePath(); void absolutePath(); void reloadPlugin(); @@ -289,48 +370,253 @@ void tst_QPluginLoader::deleteinstanceOnUnload() } } +#if defined (__ELF__) void tst_QPluginLoader::loadDebugObj() { #if !defined(QT_SHARED) QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds"); #endif -#if defined (__ELF__) QVERIFY(QFile::exists(QFINDTESTDATA("elftest/debugobj.so"))); QPluginLoader lib1(QFINDTESTDATA("elftest/debugobj.so")); QCOMPARE(lib1.load(), false); -#endif } -void tst_QPluginLoader::loadCorruptElf() +void tst_QPluginLoader::loadCorruptElf_data() { #if !defined(QT_SHARED) QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds"); #endif -#if defined (__ELF__) - if (sizeof(void*) == 8) { - QVERIFY(QFile::exists(QFINDTESTDATA("elftest/corrupt1.elf64.so"))); + QTest::addColumn("snippet"); + QTest::addColumn("patcher"); + auto newRow = [](const char *rowname, QString &&snippet, auto patcher) { + QTest::newRow(rowname) << std::move(snippet) << ElfPatcher::fromLambda(patcher); + }; - QPluginLoader lib1(QFINDTESTDATA("elftest/corrupt1.elf64.so")); - QCOMPARE(lib1.load(), false); - QVERIFY2(lib1.errorString().contains("not an ELF object"), qPrintable(lib1.errorString())); + using H = ElfHeader *; // because I'm lazy + newRow("not-elf", "invalid signature", [](H h) { + h->e_ident[EI_MAG0] = 'Q'; + h->e_ident[EI_MAG1] = 't'; + }); - QPluginLoader lib2(QFINDTESTDATA("elftest/corrupt2.elf64.so")); - QCOMPARE(lib2.load(), false); - QVERIFY2(lib2.errorString().contains("invalid"), qPrintable(lib2.errorString())); + newRow("wrong-word-size", "file is for a different word size", [](H h) { + h->e_ident[EI_CLASS] = sizeof(void *) == 8 ? ELFCLASS32 : ELFCLASS64; - QPluginLoader lib3(QFINDTESTDATA("elftest/corrupt3.elf64.so")); - QCOMPARE(lib3.load(), false); - QVERIFY2(lib3.errorString().contains("invalid"), qPrintable(lib3.errorString())); - } else if (sizeof(void*) == 4) { - QPluginLoader libW(QFINDTESTDATA("elftest/corrupt3.elf64.so")); - QCOMPARE(libW.load(), false); - QVERIFY2(libW.errorString().contains("architecture"), qPrintable(libW.errorString())); - } else { - QFAIL("Please port QElfParser to this platform or blacklist this test."); - } -#endif + // unnecessary, but we're doing it anyway +# ifdef _LP64 + Elf32_Ehdr o; + o.e_phentsize = sizeof(Elf32_Phdr); + o.e_shentsize = sizeof(Elf32_Shdr); +# else + Elf64_Ehdr o; + o.e_phentsize = sizeof(Elf64_Phdr); + o.e_shentsize = sizeof(Elf64_Shdr); +# endif + memcpy(o.e_ident, h->e_ident, EI_NIDENT); + o.e_type = h->e_type; + o.e_machine = h->e_machine; + o.e_version = h->e_version; + o.e_entry = h->e_entry; + o.e_phoff = h->e_phoff; + o.e_shoff = h->e_shoff; + o.e_flags = h->e_flags; + o.e_ehsize = sizeof(o); + o.e_phnum = h->e_phnum; + o.e_shnum = h->e_shnum; + o.e_shstrndx = h->e_shstrndx; + memcpy(h, &o, sizeof(o)); + }); + newRow("invalid-word-size", "file is for a different word size", [](H h) { + h->e_ident[EI_CLASS] = ELFCLASSNONE;; + }); + newRow("unknown-word-size", "file is for a different word size", [](H h) { + h->e_ident[EI_CLASS] |= 0x40; + }); + + newRow("wrong-endian", "file is for the wrong endianness", [](H h) { + h->e_ident[EI_DATA] = QSysInfo::ByteOrder == QSysInfo::LittleEndian ? ELFDATA2MSB : ELFDATA2LSB; + + // unnecessary, but we're doing it anyway + h->e_type = qbswap(h->e_type); + h->e_machine = qbswap(h->e_machine); + h->e_version = qbswap(h->e_version); + h->e_entry = qbswap(h->e_entry); + h->e_phoff = qbswap(h->e_phoff); + h->e_shoff = qbswap(h->e_shoff); + h->e_flags = qbswap(h->e_flags); + h->e_ehsize = qbswap(h->e_ehsize); + h->e_phnum = qbswap(h->e_phnum); + h->e_phentsize = qbswap(h->e_phentsize); + h->e_shnum = qbswap(h->e_shnum); + h->e_shentsize = qbswap(h->e_shentsize); + h->e_shstrndx = qbswap(h->e_shstrndx); + }); + newRow("invalid-endian", "file is for the wrong endianness", [](H h) { + h->e_ident[EI_DATA] = ELFDATANONE; + }); + newRow("unknown-endian", "file is for the wrong endianness", [](H h) { + h->e_ident[EI_DATA] |= 0x40; + }); + + newRow("elf-version-0", "file has an unknown ELF version", [](H h) { + --h->e_ident[EI_VERSION]; + }); + newRow("elf-version-2", "file has an unknown ELF version", [](H h) { + ++h->e_ident[EI_VERSION]; + }); + + newRow("executable", "file is not a shared object", [](H h) { + h->e_type = ET_EXEC; + }); + newRow("relocatable", "file is not a shared object", [](H h) { + h->e_type = ET_REL; + }); + newRow("core-file", "file is not a shared object", [](H h) { + h->e_type = ET_CORE; + }); + newRow("invalid-type", "file is not a shared object", [](H h) { + h->e_type |= 0x100; + }); + + newRow("wrong-arch", "file is for a different processor", [](H h) { + // could just ++h->e_machine... +# if defined(Q_PROCESSOR_X86_64) + h->e_machine = EM_AARCH64; +# elif defined(Q_PROCESSOR_ARM_64) + h->e_machine = EM_X86_64; +# elif defined(Q_PROCESSOR_X86_32) + h->e_machine = EM_ARM; +# elif defined(Q_PROCESSOR_ARM) + h->e_machine = EM_386; +# elif defined(Q_PROCESSOR_MIPS_64) + h->e_machine = EM_PPC64; +# elif defined(Q_PROCESSOR_MIPS_32) + h->e_machine = EM_PPC; +# elif defined(Q_PROCESSOR_POWER_64) + h->e_machine = EM_S390; +# elif defined(Q_PROCESSOR_POWER_32) + h->e_machine = EM_MIPS; +# endif + }); + + newRow("file-version-0", "file has an unknown ELF version", [](H h) { + --h->e_version; + }); + newRow("file-version-2", "file has an unknown ELF version", [](H h) { + ++h->e_version; + }); + + newRow("section-entry-size-zero", "unexpected section entry size", [](H h) { + h->e_shentsize = 0; + }); + newRow("section-entry-small", "unexpected section entry size", [](H h) { + h->e_shentsize = alignof(ElfShdr); + }); + newRow("section-entry-misaligned", "unexpected section entry size", [](H h) { + ++h->e_shentsize; + }); + newRow("no-sections", "is not a Qt plugin (.qtmetadata section not found)", [](H h){ + h->e_shnum = h->e_shoff = h->e_shstrndx = 0; + }); + + // section table tests + newRow("section-table-starts-past-eof", "section table extends past the end of the file", + [](H h, QFile *f) { + h->e_shoff = f->size(); + }); + newRow("section-table-ends-past-eof", "section table extends past the end of the file", + [](H h, QFile *f) { + h->e_shoff = f->size() + 1 - h->e_shentsize * h->e_shnum; + }); + + static auto getSection = +[](H h, int index) { + auto sections = reinterpret_cast(h->e_shoff + reinterpret_cast(h)); + return sections + index; + }; + + // arbitrary section bounds checks + // section index = 0 is usually a NULL section, so we try 1 + newRow("section1-starts-past-eof", "a section data extends past the end of the file", + [](H h, QFile *f) { + ElfShdr *s = getSection(h, 1); + s->sh_offset = f->size(); + }); + newRow("section1-ends-past-eof", "a section data extends past the end of the file", + [](H h, QFile *f) { + ElfShdr *s = getSection(h, 1); + s->sh_size = f->size() + 1 - s->sh_offset; + }); + newRow("section1-bounds-overflow", "a section data extends past the end of the file", [](H h) { + ElfShdr *s = getSection(h, 1); + s->sh_size = -sizeof(*s); + }); + + // section header string table tests + newRow("shstrndx-invalid", "e_shstrndx greater than the number of sections", [](H h) { + h->e_shstrndx = h->e_shnum; + }); + newRow("shstrtab-starts-past-eof", "section header string table extends past the end of the file", + [](H h, QFile *f) { + ElfShdr *s = getSection(h, h->e_shstrndx); + s->sh_offset = f->size(); + }); + newRow("shstrtab-ends-past-eof", "section header string table extends past the end of the file", + [](H h, QFile *f) { + ElfShdr *s = getSection(h, h->e_shstrndx); + s->sh_size = f->size() + 1 - s->sh_offset; + }); + newRow("shstrtab-bounds-overflow", "section header string table extends past the end of the file", [](H h) { + ElfShdr *s = getSection(h, h->e_shstrndx); + s->sh_size = -sizeof(*s); + }); + newRow("section-name-past-eof", "section name extends past the end of the file", [](H h, QFile *f) { + ElfShdr *section1 = getSection(h, 1); + ElfShdr *shstrtab = getSection(h, h->e_shstrndx); + section1->sh_name = f->size() - shstrtab->sh_offset; + }); + newRow("section-name-past-end-of-shstrtab", "section name extends past the end of the file", [](H h) { + ElfShdr *section1 = getSection(h, 1); + ElfShdr *shstrtab = getSection(h, h->e_shstrndx); + section1->sh_name = shstrtab->sh_size; + }); + + newRow("debug-symbols", ".qtmetadata section not found", [](H h) { + // attempt to make it look like extracted debug info + for (int i = 1; i < h->e_shnum; ++i) { + ElfShdr *s = getSection(h, i); + if (s->sh_type == SHT_NOBITS) + break; + if (s->sh_type != SHT_NOTE && s->sh_flags & SHF_ALLOC) + s->sh_type = SHT_NOBITS; + } + }); + + // we don't know which section is .qtmetadata, so we just apply to all of them + static auto applyToAllSectionFlags = +[](H h, int flag) { + for (int i = 0; i < h->e_shnum; ++i) + getSection(h, i)->sh_flags |= flag; + }; + newRow("qtmetadata-executable", ".qtmetadata section is executable", [](H h) { + applyToAllSectionFlags(h, SHF_EXECINSTR); + }); + newRow("qtmetadata-writable", ".qtmetadata section is writable", [](H h) { + applyToAllSectionFlags(h, SHF_WRITE); + }); } +void tst_QPluginLoader::loadCorruptElf() +{ + QFETCH(QString, snippet); + QFETCH(ElfPatcher, patcher); + + std::unique_ptr tmplib = + patchElf(sys_qualifiedLibraryName("theplugin"), patcher); + + QPluginLoader lib(tmplib->fileName()); + QVERIFY(!lib.load()); + QVERIFY2(lib.errorString().contains(snippet), qPrintable(lib.errorString())); +} +#endif // __ELF__ + void tst_QPluginLoader::loadMachO_data() { #if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O) @@ -404,21 +690,6 @@ void tst_QPluginLoader::loadMachO() #endif } -#if defined (Q_OS_UNIX) -void tst_QPluginLoader::loadGarbage() -{ -#if !defined(QT_SHARED) - QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds"); -#endif - for (int i=0; i<5; i++) { - const QString name = QLatin1String("elftest/garbage") + QString::number(i + 1) + QLatin1String(".so"); - QPluginLoader lib(QFINDTESTDATA(name)); - QCOMPARE(lib.load(), false); - QVERIFY(lib.errorString() != QString("Unknown error")); - } -} -#endif - void tst_QPluginLoader::relativePath() { #if !defined(QT_SHARED)