diff --git a/src/corelib/plugin/qelfparser_p.cpp b/src/corelib/plugin/qelfparser_p.cpp index e50ad6b16c3..33af51d59be 100644 --- a/src/corelib/plugin/qelfparser_p.cpp +++ b/src/corelib/plugin/qelfparser_p.cpp @@ -52,6 +52,9 @@ QT_BEGIN_NAMESPACE +// ### 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; @@ -66,6 +69,19 @@ static Q_LOGGING_CATEGORY(lcElfParser, "qt.core.plugin.elfparser") # 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") @@ -445,24 +461,66 @@ Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, ElfSectionDebug s) 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)); + *errMsg = QLibrary::tr("'%1' is not a valid ELF object (%2)").arg(*errMsg, std::move(text)); return {}; } - QLibraryScanResult notfound() const + Q_DECL_COLD_FUNCTION QLibraryScanResult notplugin(QString &&explanation) const { - *errMsg = QLibrary::tr("'%1' is not a Qt plugin (.qtmetadata section not found)") - .arg(*errMsg); + *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 @@ -470,6 +528,135 @@ QT_WARNING_POP using T = ElfHeaderCheck<>::TypeTraits; +template +static bool scanProgramHeaders(QByteArrayView data, const ErrorMaker &error, F f) +{ + auto header = reinterpret_cast(data.data()); + Q_UNUSED(error); + + auto phdr = reinterpret_cast(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(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(data.data()); + auto segments = reinterpret_cast(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(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(next_offset, n_namesz, &next_offset)) + break; + next_offset &= -NoteAlignment; + + next_offset += NoteAlignment - 3; // offset is aligned, this can't overflow + if (qAddOverflow(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(data.data()); @@ -555,27 +742,43 @@ QLibraryScanResult QElfParser::parse(QByteArrayView data, QString *errMsg) 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_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_phentsize != sizeof(T::Phdr)) + return error(QLibrary::tr("unexpected program header entry size (%1)") + .arg(header->e_phentsize)); } - 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)); + if (!preScanProgramHeaders(data, error)) + return {}; - return scanSections(data, error); + 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 diff --git a/src/corelib/plugin/qplugin.h b/src/corelib/plugin/qplugin.h index d70324b5607..408e22f37df 100644 --- a/src/corelib/plugin/qplugin.h +++ b/src/corelib/plugin/qplugin.h @@ -113,6 +113,25 @@ struct QPluginMetaData }; static_assert(alignof(MagicHeader) == 1, "Alignment of header incorrect with this compiler"); + struct ElfNoteHeader { + static constexpr quint32 NoteType = 0x74510001; + static constexpr char NoteName[] = "qt-project!"; + + // ELF note header + quint32 n_namesz = sizeof(name); + quint32 n_descsz; + quint32 n_type = NoteType; + char name[sizeof(NoteName)] = {}; + + // payload + alignas(void *) // mandatory alignment as per ELF note requirements + Header header = {}; + constexpr ElfNoteHeader(quint32 payloadSize) : n_descsz(sizeof(header) + payloadSize) + { QPluginMetaData::copy(name, NoteName); } + }; + static_assert(alignof(ElfNoteHeader) == alignof(void*), "Alignment of header incorrect with this compiler"); + static_assert((sizeof(ElfNoteHeader::name) % 4) == 0, "ELF note name length not a multiple of 4"); + const void *data; size_t size; }; @@ -155,6 +174,13 @@ void Q_CORE_EXPORT qRegisterStaticPluginFunction(QStaticPlugin staticPlugin); // Since Qt 6.3 template class QPluginMetaDataV2 { + struct ElfNotePayload : QPluginMetaData::ElfNoteHeader { + static constexpr size_t HeaderOffset = offsetof(QPluginMetaData::ElfNoteHeader, header); + quint8 payload[sizeof(PluginMetaData)] = {}; + constexpr ElfNotePayload() : ElfNoteHeader(sizeof(PluginMetaData)) + { QPluginMetaData::copy(payload, PluginMetaData); } + }; + struct RegularPayload : QPluginMetaData::MagicHeader { static constexpr size_t HeaderOffset = offsetof(QPluginMetaData::MagicHeader, header); quint8 payload[sizeof(PluginMetaData)] = {}; @@ -171,6 +197,9 @@ template class QPluginMetaDataV2 #if defined(QT_STATICPLUGIN) # define QT_PLUGIN_METADATAV2_SECTION using Payload = StaticPayload; +#elif defined(Q_OF_ELF) +# define QT_PLUGIN_METADATAV2_SECTION __attribute__((section(".note.qt.metadata"), used, aligned(alignof(void*)))) + using Payload = ElfNotePayload; #else # define QT_PLUGIN_METADATAV2_SECTION QT_PLUGIN_METADATA_SECTION using Payload = RegularPayload; diff --git a/tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp b/tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp index e6603ec89fb..50340467b5f 100644 --- a/tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp +++ b/tests/auto/corelib/plugin/qplugin/invalidplugin/main.cpp @@ -28,6 +28,7 @@ #include +// be careful when updating to V2, the header is different on ELF systems QT_PLUGIN_METADATA_SECTION static const char pluginMetaData[512] = { 'q', 'p', 'l', 'u', 'g', 'i', 'n', ' ', diff --git a/tests/auto/corelib/plugin/qpluginloader/fakeplugin.cpp b/tests/auto/corelib/plugin/qpluginloader/fakeplugin.cpp index a6d53f350fc..6d758f23c96 100644 --- a/tests/auto/corelib/plugin/qpluginloader/fakeplugin.cpp +++ b/tests/auto/corelib/plugin/qpluginloader/fakeplugin.cpp @@ -1,9 +1,9 @@ /**************************************************************************** ** -** Copyright (C) 2016 Intel Corporation. +** Copyright (C) 2021 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** -** This file is part of the QtCore module of the Qt Toolkit. +** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage @@ -25,12 +25,56 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ - -#include - -#if QT_POINTER_SIZE == 8 -QT_PLUGIN_METADATA_SECTION const uintptr_t pluginSection = 0xc0ffeec0ffeeULL; -#else -QT_PLUGIN_METADATA_SECTION const uintptr_t pluginSection = 0xc0ffee; +#ifndef QT_VERSION_MAJOR +# include +#endif + +extern "C" void *qt_plugin_instance() +{ + return nullptr; +} + +#ifdef QT_DEBUG +static constexpr bool IsDebug = true; +#else +static constexpr bool IsDebug = false; +#endif + +#ifndef PLUGIN_VERSION +# define PLUGIN_VERSION (QT_VERSION_MAJOR >= 7 ? 1 : 0) +#endif +#if PLUGIN_VERSION == 1 +# define PLUGIN_HEADER 1, QT_VERSION_MAJOR, 0, IsDebug ? 0x80 : 0 +#else +# define PLUGIN_HEADER 0, QT_VERSION_MAJOR, 0, IsDebug +#endif + +#if defined(__ELF__) && PLUGIN_VERSION >= 1 +__attribute__((section(".note.qt.metadata"), used, aligned(4))) +static const struct { + unsigned n_namesz = sizeof(name); + unsigned n_descsz = sizeof(payload); + unsigned n_type = 0x74510001; + char name[12] = "qt-project!"; + alignas(unsigned) unsigned char payload[2 + 4] = { + PLUGIN_HEADER, + 0xbf, + 0xff, + }; +} qtnotemetadata; +#elif PLUGIN_VERSION >= 0 +# ifdef _MSC_VER +# pragma section(".qtmetadata",read,shared) +__declspec(allocate(".qtmetadata")) +# elif defined(__APPLE__) +__attribute__ ((section ("__TEXT,qtmetadata"), used)) +# else +__attribute__ ((section(".qtmetadata"), used)) +# endif +static const unsigned char qtmetadata[] = { + 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!', + PLUGIN_HEADER, + 0xbf, + 0xff, +}; #endif -QT_PLUGIN_METADATA_SECTION const char message[] = "QTMETADATA"; diff --git a/tests/auto/corelib/plugin/qpluginloader/theplugin/CMakeLists.txt b/tests/auto/corelib/plugin/qpluginloader/theplugin/CMakeLists.txt index 5a8bf518c5a..1e7754a1676 100644 --- a/tests/auto/corelib/plugin/qpluginloader/theplugin/CMakeLists.txt +++ b/tests/auto/corelib/plugin/qpluginloader/theplugin/CMakeLists.txt @@ -1,9 +1,3 @@ -# Generated from theplugin.pro. - -##################################################################### -## theplugin Generic Library: -##################################################################### - qt_internal_add_cmake_library(theplugin MODULE INSTALL_DIRECTORY "${INSTALL_TESTSDIR}/tst_qpluginloader/bin" @@ -13,10 +7,23 @@ qt_internal_add_cmake_library(theplugin PUBLIC_LIBRARIES Qt::Core ) - -#### Keys ignored in scope 1:.:.:theplugin.pro:: -# INSTALLS = "target" -# TEMPLATE = "lib" -# target.path = "$$[QT_INSTALL_TESTS]/tst_qpluginloader/bin" - qt_autogen_tools_initial_setup(theplugin) + +if (UNIX AND NOT APPLE) + qt_internal_add_cmake_library(theoldplugin + MODULE + INSTALL_DIRECTORY "${INSTALL_TESTSDIR}/tst_qpluginloader/bin" + OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../bin" + SOURCES + theoldplugin.cpp theoldplugin.h + PUBLIC_LIBRARIES + Qt::Core + ) + qt_autogen_tools_initial_setup(theoldplugin) + + # Force unoptimized builds with debugging information so some "QTMETADATA !" + # strings appear elsewhere in the binary. + target_compile_options(theplugin PRIVATE -O0 -g3) + target_compile_options(theoldplugin PRIVATE -O0 -g3) +endif() + diff --git a/tests/auto/corelib/plugin/qpluginloader/theplugin/theoldplugin.cpp b/tests/auto/corelib/plugin/qpluginloader/theplugin/theoldplugin.cpp new file mode 100644 index 00000000000..fd7a6e16d3d --- /dev/null +++ b/tests/auto/corelib/plugin/qpluginloader/theplugin/theoldplugin.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2021 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "theoldplugin.h" +#include +#include + +QString TheOldPlugin::pluginName() const +{ + return QLatin1String("Plugin ok"); +} + +static int pluginVariable = 0xc0ffee; +extern "C" Q_DECL_EXPORT int *pointerAddress() +{ + return &pluginVariable; +} + +// This hardcodes the old plugin metadata from before Qt 6.2 +QT_PLUGIN_METADATA_SECTION +static constexpr unsigned char qt_pluginMetaData_ThePlugin[] = { + 'Q', 'T', 'M', 'E', 'T', 'A', 'D', 'A', 'T', 'A', ' ', '!', + // metadata version, Qt version, architectural requirements + 0, QT_VERSION_MAJOR, QT_VERSION_MINOR, qPluginArchRequirements(), + 0xbf, + // "IID" + 0x02, 0x78, 0x2b, 'o', 'r', 'g', '.', 'q', + 't', '-', 'p', 'r', 'o', 'j', 'e', 'c', + 't', '.', 'Q', 't', '.', 'a', 'u', 't', + 'o', 't', 'e', 's', 't', 's', '.', 'p', + 'l', 'u', 'g', 'i', 'n', 'i', 'n', 't', + 'e', 'r', 'f', 'a', 'c', 'e', + // "className" + 0x03, 0x69, 'T', 'h', 'e', 'P', 'l', 'u', + 'g', 'i', 'n', + // "MetaData" + 0x04, 0xa2, 0x67, 'K', 'P', 'l', 'u', 'g', + 'i', 'n', 0xa8, 0x64, 'N', 'a', 'm', 'e', + 0x6e, 'W', 'i', 'n', 'd', 'o', 'w', 'G', + 'e', 'o', 'm', 'e', 't', 'r', 'y', 0x68, + 'N', 'a', 'm', 'e', '[', 'm', 'r', ']', + 0x78, 0x1f, uchar('\xe0'), uchar('\xa4'), uchar('\x9a'), uchar('\xe0'), uchar('\xa5'), uchar('\x8c'), + uchar('\xe0'), uchar('\xa4'), uchar('\x95'), uchar('\xe0'), uchar('\xa4'), uchar('\x9f'), ' ', uchar('\xe0'), + uchar('\xa4'), uchar('\xad'), uchar('\xe0'), uchar('\xa5'), uchar('\x82'), uchar('\xe0'), uchar('\xa4'), uchar('\xae'), + uchar('\xe0'), uchar('\xa4'), uchar('\xbf'), uchar('\xe0'), uchar('\xa4'), uchar('\xa4'), uchar('\xe0'), uchar('\xa5'), + uchar('\x80'), 0x68, 'N', 'a', 'm', 'e', '[', 'p', + 'a', ']', 0x78, 0x24, uchar('\xe0'), uchar('\xa8'), uchar('\xb5'), uchar('\xe0'), + uchar('\xa8'), uchar('\xbf'), uchar('\xe0'), uchar('\xa9'), uchar('\xb0'), uchar('\xe0'), uchar('\xa8'), uchar('\xa1'), + uchar('\xe0'), uchar('\xa9'), uchar('\x8b'), uchar('\xe0'), uchar('\xa8'), uchar('\x9c'), uchar('\xe0'), uchar('\xa9'), + uchar('\x81'), uchar('\xe0'), uchar('\xa8'), uchar('\xae'), uchar('\xe0'), uchar('\xa9'), uchar('\x88'), uchar('\xe0'), + uchar('\xa8'), uchar('\x9f'), uchar('\xe0'), uchar('\xa8'), uchar('\xb0'), uchar('\xe0'), uchar('\xa9'), uchar('\x80'), + 0x68, 'N', 'a', 'm', 'e', '[', 't', 'h', + ']', 0x78, 0x39, uchar('\xe0'), uchar('\xb8'), uchar('\xa1'), uchar('\xe0'), uchar('\xb8'), + uchar('\xb4'), uchar('\xe0'), uchar('\xb8'), uchar('\x95'), uchar('\xe0'), uchar('\xb8'), uchar('\xb4'), uchar('\xe0'), + uchar('\xb8'), uchar('\x82'), uchar('\xe0'), uchar('\xb8'), uchar('\x99'), uchar('\xe0'), uchar('\xb8'), uchar('\xb2'), + uchar('\xe0'), uchar('\xb8'), uchar('\x94'), uchar('\xe0'), uchar('\xb8'), uchar('\x82'), uchar('\xe0'), uchar('\xb8'), + uchar('\xad'), uchar('\xe0'), uchar('\xb8'), uchar('\x87'), uchar('\xe0'), uchar('\xb8'), uchar('\xab'), uchar('\xe0'), + uchar('\xb8'), uchar('\x99'), uchar('\xe0'), uchar('\xb9'), uchar('\x89'), uchar('\xe0'), uchar('\xb8'), uchar('\xb2'), + uchar('\xe0'), uchar('\xb8'), uchar('\x95'), uchar('\xe0'), uchar('\xb9'), uchar('\x88'), uchar('\xe0'), uchar('\xb8'), + uchar('\xb2'), uchar('\xe0'), uchar('\xb8'), uchar('\x87'), 0x68, 'N', 'a', 'm', + 'e', '[', 'u', 'k', ']', 0x78, 0x19, uchar('\xd0'), + uchar('\xa0'), uchar('\xd0'), uchar('\xbe'), uchar('\xd0'), uchar('\xb7'), uchar('\xd0'), uchar('\xbc'), uchar('\xd1'), + uchar('\x96'), uchar('\xd1'), uchar('\x80'), uchar('\xd0'), uchar('\xb8'), ' ', uchar('\xd0'), uchar('\xb2'), + uchar('\xd1'), uchar('\x96'), uchar('\xd0'), uchar('\xba'), uchar('\xd0'), uchar('\xbd'), uchar('\xd0'), uchar('\xb0'), + 0x6b, 'N', 'a', 'm', 'e', '[', 'z', 'h', + '_', 'C', 'N', ']', 0x6c, uchar('\xe7'), uchar('\xaa'), uchar('\x97'), + uchar('\xe5'), uchar('\x8f'), uchar('\xa3'), uchar('\xe5'), uchar('\xbd'), uchar('\xa2'), uchar('\xe7'), uchar('\x8a'), + uchar('\xb6'), 0x6b, 'N', 'a', 'm', 'e', '[', 'z', + 'h', '_', 'T', 'W', ']', 0x6c, uchar('\xe8'), uchar('\xa6'), + uchar('\x96'), uchar('\xe7'), uchar('\xaa'), uchar('\x97'), uchar('\xe4'), uchar('\xbd'), uchar('\x8d'), uchar('\xe7'), + uchar('\xbd'), uchar('\xae'), 0x6c, 'S', 'e', 'r', 'v', 'i', + 'c', 'e', 'T', 'y', 'p', 'e', 's', 0x81, + 0x68, 'K', 'C', 'M', 'o', 'd', 'u', 'l', + 'e', 0x76, 'X', '-', 'K', 'D', 'E', '-', + 'P', 'a', 'r', 'e', 'n', 't', 'C', 'o', + 'm', 'p', 'o', 'n', 'e', 'n', 't', 's', + 0x81, 0x6e, 'w', 'i', 'n', 'd', 'o', 'w', + 'g', 'e', 'o', 'm', 'e', 't', 'r', 'y', + 0xff, +}; +QT_MOC_EXPORT_PLUGIN(TheOldPlugin, ThePlugin) diff --git a/tests/auto/corelib/plugin/qpluginloader/theplugin/theoldplugin.h b/tests/auto/corelib/plugin/qpluginloader/theplugin/theoldplugin.h new file mode 100644 index 00000000000..57fd5a41c93 --- /dev/null +++ b/tests/auto/corelib/plugin/qpluginloader/theplugin/theoldplugin.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2021 Intel Corportaion. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef THEOLDPLUGIN_H +#define THEOLDPLUGIN_H + +#include +#include +#include "plugininterface.h" + +class TheOldPlugin : public QObject, public PluginInterface +{ + Q_OBJECT + // Q_PLUGIN_METADATA intentionally missing + Q_INTERFACES(PluginInterface) + +public: + virtual QString pluginName() const override; +}; + +#endif // THEOLDPLUGIN_H + diff --git a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp index bab35449462..d09a6967e5f 100644 --- a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp +++ b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include "theplugin/plugininterface.h" @@ -101,9 +100,13 @@ # ifdef _LP64 using ElfHeader = Elf64_Ehdr; +using ElfPhdr = Elf64_Phdr; +using ElfNhdr = Elf64_Nhdr; using ElfShdr = Elf64_Shdr; # else using ElfHeader = Elf32_Ehdr; +using ElfPhdr = Elf32_Phdr; +using ElfNhdr = Elf32_Nhdr; using ElfShdr = Elf32_Shdr; # endif @@ -142,7 +145,10 @@ static std::unique_ptr patchElf(const QString &source, ElfPatche QVERIFY2(srcdata, qPrintable(srclib.errorString())); // copy our source plugin so we can modify it - tmplib.reset(new QTemporaryFile(QTest::currentDataTag() + QString(".XXXXXX" SUFFIX))); + const char *basename = QTest::currentDataTag(); + if (!basename) + basename = QTest::currentTestFunction(); + tmplib.reset(new QTemporaryFile(basename + QString(".XXXXXX" SUFFIX))); QVERIFY2(tmplib->open(), qPrintable(tmplib->errorString())); // sanity-check @@ -196,12 +202,17 @@ private slots: void loadDebugObj(); void loadCorruptElf_data(); void loadCorruptElf(); +# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + void loadCorruptElfOldPlugin_data(); + void loadCorruptElfOldPlugin(); +# endif #endif void loadMachO_data(); void loadMachO(); void relativePath(); void absolutePath(); void reloadPlugin(); + void loadSectionTableStrippedElf(); void preloadedPlugin_data(); void preloadedPlugin(); void staticPlugins(); @@ -294,29 +305,36 @@ void tst_QPluginLoader::errorString() } #endif - { - QPluginLoader loader( sys_qualifiedLibraryName("theplugin")); //a plugin + static constexpr std::initializer_list validplugins = { + "theplugin", +#if defined(Q_OF_ELF) && QT_VERSION < QT_VERSION_CHECK(7, 0, 0) + "theoldplugin" +#endif + }; + for (const char *basename : validplugins) { + QPluginLoader loader( sys_qualifiedLibraryName(basename)); //a plugin - // Check metadata - const QJsonObject metaData = loader.metaData(); - QCOMPARE(metaData.value("IID").toString(), QStringLiteral("org.qt-project.Qt.autotests.plugininterface")); - const QJsonObject kpluginObject = metaData.value("MetaData").toObject().value("KPlugin").toObject(); - QCOMPARE(kpluginObject.value("Name[mr]").toString(), QString::fromUtf8("चौकट भूमिती")); + // Check metadata + const QJsonObject metaData = loader.metaData(); + QVERIFY2(!metaData.isEmpty(), "No metadata from " + loader.fileName().toLocal8Bit()); + QCOMPARE(metaData.value("IID").toString(), QStringLiteral("org.qt-project.Qt.autotests.plugininterface")); + const QJsonObject kpluginObject = metaData.value("MetaData").toObject().value("KPlugin").toObject(); + QCOMPARE(kpluginObject.value("Name[mr]").toString(), QString::fromUtf8("चौकट भूमिती")); - // Load - QCOMPARE(loader.load(), true); - QCOMPARE(loader.errorString(), unknown); + // Load + QVERIFY2(loader.load(), qPrintable(loader.errorString())); + QCOMPARE(loader.errorString(), unknown); - QVERIFY(loader.instance() != static_cast(0)); - QCOMPARE(loader.errorString(), unknown); + QVERIFY(loader.instance() != static_cast(0)); + QCOMPARE(loader.errorString(), unknown); - // Make sure that plugin really works - PluginInterface* theplugin = qobject_cast(loader.instance()); - QString pluginName = theplugin->pluginName(); - QCOMPARE(pluginName, QLatin1String("Plugin ok")); + // Make sure that plugin really works + PluginInterface* theplugin = qobject_cast(loader.instance()); + QString pluginName = theplugin->pluginName(); + QCOMPARE(pluginName, QLatin1String("Plugin ok")); - QCOMPARE(loader.unload(), true); - QCOMPARE(loader.errorString(), unknown); + QCOMPARE(loader.unload(), true); + QCOMPARE(loader.errorString(), unknown); } } @@ -381,16 +399,23 @@ void tst_QPluginLoader::loadDebugObj() QCOMPARE(lib1.load(), false); } -void tst_QPluginLoader::loadCorruptElf_data() +template +static void newRow(const char *rowname, QString &&snippet, Lambda &&patcher) +{ + QTest::newRow(rowname) + << std::move(snippet) << ElfPatcher::fromLambda(std::forward(patcher)); +} + +static ElfPhdr *getProgramEntry(ElfHeader *h, int index) +{ + auto phdr = reinterpret_cast(h->e_phoff + reinterpret_cast(h)); + return phdr + index; +} + +static void loadCorruptElfCommonRows() { -#if !defined(QT_SHARED) - QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds"); -#endif QTest::addColumn("snippet"); QTest::addColumn("patcher"); - auto newRow = [](const char *rowname, QString &&snippet, auto patcher) { - QTest::newRow(rowname) << std::move(snippet) << ElfPatcher::fromLambda(patcher); - }; using H = ElfHeader *; // because I'm lazy newRow("not-elf", "invalid signature", [](H h) { @@ -505,6 +530,195 @@ void tst_QPluginLoader::loadCorruptElf_data() ++h->e_version; }); + newRow("program-entry-size-zero", "unexpected program header entry size", [](H h) { + h->e_phentsize = 0; + }); + newRow("program-entry-small", "unexpected program header entry size", [](H h) { + h->e_phentsize = alignof(ElfPhdr); + }); + + newRow("program-table-starts-past-eof", "program header table extends past the end of the file", + [](H h, QFile *f) { + h->e_phoff = f->size(); + }); + newRow("program-table-ends-past-eof", "program header table extends past the end of the file", + [](H h, QFile *f) { + h->e_phoff = f->size() + 1- h->e_phentsize * h->e_phnum; + }); + + newRow("segment-starts-past-eof", "a program header entry extends past the end of the file", + [](H h, QFile *f) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_LOAD) + continue; + p->p_offset = f->size(); + break; + } + }); + newRow("segment-ends-past-eof", "a program header entry extends past the end of the file", + [](H h, QFile *f) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_LOAD) + continue; + p->p_filesz = f->size() + 1 - p->p_offset; + break; + } + }); + newRow("segment-bounds-overflow", "a program header entry extends past the end of the file", + [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_LOAD) + continue; + p->p_filesz = ~size_t(0); // -1 + break; + } + }); + + newRow("no-code", "file has no code", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type == PT_LOAD) + p->p_flags &= ~PF_X; + } + }); +} + +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 + loadCorruptElfCommonRows(); + using H = ElfHeader *; // because I'm lazy + + // PT_NOTE tests + // general validity is tested in the common rows, for all segments + + newRow("misaligned-note-segment", "note segment start is not properly aligned", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type == PT_NOTE) + ++p->p_offset; + } + }); + + static const auto getFirstNote = [](void *header, ElfPhdr *phdr) { + return reinterpret_cast(static_cast(header) + phdr->p_offset); + }; + static const auto getNextNote = [](void *header, ElfPhdr *phdr, ElfNhdr *n) { + // how far into the segment are we? + size_t offset = reinterpret_cast(n) - static_cast(header) - phdr->p_offset; + + size_t delta = sizeof(*n) + n->n_namesz + phdr->p_align - 1; + delta &= -phdr->p_align; + delta += n->n_descsz + phdr->p_align - 1; + delta &= -phdr->p_align; + + offset += delta; + if (offset < phdr->p_filesz) + n = reinterpret_cast(reinterpret_cast(n) + delta); + else + n = nullptr; + return n; + }; + + // all the intra-note errors cause the notes simply to be skipped + auto newNoteRow = [](const char *rowname, auto &&lambda) { + newRow(rowname, "is not a Qt plugin (metadata not found)", std::move(lambda)); + }; + newNoteRow("no-notes", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type == PT_NOTE) + p->p_type = PT_NULL; + } + }); + + newNoteRow("note-larger-than-segment-nonqt", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_NOTE) + continue; + ElfNhdr *n = getFirstNote(h, p); + n->n_descsz = p->p_filesz; + n->n_type = 0; // ensure it's not the Qt note + } + }); + newNoteRow("note-larger-than-segment-qt", [](H h) { + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_NOTE || p->p_align != alignof(QPluginMetaData::ElfNoteHeader)) + continue; + + // find the Qt metadata note + constexpr QPluginMetaData::ElfNoteHeader header(0); + ElfNhdr *n = getFirstNote(h, p); + for ( ; n; n = getNextNote(h, p, n)) { + if (n->n_type == header.n_type && n->n_namesz == header.n_namesz) { + if (memcmp(n + 1, header.name, sizeof(header.name)) == 0) + break; + } + } + + if (!n) + break; + n->n_descsz = p->p_filesz; + return; + } + qWarning("Could not find the Qt metadata note in this file. Test will fail."); + }); + newNoteRow("note-size-overflow1", [](H h) { + // due to limited range, this will not overflow on 64-bit + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_NOTE) + continue; + ElfNhdr *n = getFirstNote(h, p); + n->n_namesz = ~decltype(n->n_namesz)(0); + } + }); + newNoteRow("note-size-overflow2", [](H h) { + // due to limited range, this will not overflow on 64-bit + for (int i = 0; i < h->e_phnum; ++i) { + ElfPhdr *p = getProgramEntry(h, i); + if (p->p_type != PT_NOTE) + continue; + ElfNhdr *n = getFirstNote(h, p); + n->n_namesz = ~decltype(n->n_namesz)(0) / 2; + n->n_descsz = ~decltype(n->n_descsz)(0) / 2; + } + }); +} + +static void loadCorruptElf_helper(const QString &origLibrary) +{ + QFETCH(QString, snippet); + QFETCH(ElfPatcher, patcher); + + std::unique_ptr tmplib = patchElf(origLibrary, patcher); + + QPluginLoader lib(tmplib->fileName()); + QVERIFY(!lib.load()); + QVERIFY2(lib.errorString().contains(snippet), qPrintable(lib.errorString())); +} + +void tst_QPluginLoader::loadCorruptElf() +{ + loadCorruptElf_helper(sys_qualifiedLibraryName("theplugin")); +} + +# if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +void tst_QPluginLoader::loadCorruptElfOldPlugin_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 + loadCorruptElfCommonRows(); + using H = ElfHeader *; // because I'm lazy + newRow("section-entry-size-zero", "unexpected section entry size", [](H h) { h->e_shentsize = 0; }); @@ -514,7 +728,7 @@ void tst_QPluginLoader::loadCorruptElf_data() 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){ + newRow("no-sections", "is not a Qt plugin (metadata not found)", [](H h){ h->e_shnum = h->e_shoff = h->e_shstrndx = 0; }); @@ -579,7 +793,7 @@ void tst_QPluginLoader::loadCorruptElf_data() section1->sh_name = shstrtab->sh_size; }); - newRow("debug-symbols", ".qtmetadata section not found", [](H h) { + newRow("debug-symbols", "metadata 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); @@ -603,18 +817,12 @@ void tst_QPluginLoader::loadCorruptElf_data() }); } -void tst_QPluginLoader::loadCorruptElf() +void tst_QPluginLoader::loadCorruptElfOldPlugin() { - 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())); + // ### Qt7: don't forget to remove theoldplugin from the build + loadCorruptElf_helper(sys_qualifiedLibraryName("theoldplugin")); } +# endif // Qt 7 #endif // __ELF__ void tst_QPluginLoader::loadMachO_data() @@ -752,6 +960,36 @@ void tst_QPluginLoader::reloadPlugin() QVERIFY(loader.unload()); } +void tst_QPluginLoader::loadSectionTableStrippedElf() +{ +#if !defined(QT_SHARED) + QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds"); +#elif !defined(__ELF__) + QSKIP("Test specific to the ELF file format"); +#else + ElfPatcher patcher { [](ElfHeader *header, QFile *f) { + // modify the header to make it look like the section table was stripped + header->e_shoff = header->e_shnum = header->e_shstrndx = 0; + + // and append a bad header at the end + QPluginMetaData::MagicHeader badHeader = {}; + --badHeader.header.qt_major_version; + f->seek(f->size()); + f->write(reinterpret_cast(&badHeader), sizeof(badHeader)); + } }; + std::unique_ptr tmplib = + patchElf(sys_qualifiedLibraryName("theplugin"), patcher); + + // now attempt to load it + QPluginLoader loader(tmplib->fileName()); + QVERIFY2(loader.load(), qPrintable(loader.errorString())); + PluginInterface *instance = qobject_cast(loader.instance()); + QVERIFY(instance); + QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok")); + QVERIFY(loader.unload()); +#endif +} + void tst_QPluginLoader::preloadedPlugin_data() { QTest::addColumn("doLoad");