QPlugin: Move the plugin metadata to a note in ELF platforms

A few systems, like OpenWRT, may strip the section table off the
resulting binaries (see [1]), making it impossible for us to pinpoint
the exact location of the Qt plugin metadata. This commit moves the meta
data to a location that is identifiable even in fully stripped binaries:
an ELF note.

By naming our section ".note.qt.metadata", we instruct the linker to
place it along the other notes and to mark it in the program header
section. Another advantage is that the notes are usually in the very
beginning of the file, as they are used by the dynamic linker itself, so
we'll need to read much less of the full contents.

The unit test is modified not to attempt to strip the plugin of
debugging data. In fact, we add something to the end that would,
otherwise, be matched as (invalid) metadata.

The following was produced with GCC 11 and GNU binutils ld 2.36.1.

Section Headers:
[Nr] Name                 Type         Addr             Off      Size     ES Flags Lk Inf Al
[ 0]                      NULL         0000000000000000 00000000 00000000  0        0   0  0
[ 1] .note.gnu.property   NOTE         00000000000002a8 000002a8 00000030  0 A      0   0  8
[ 2] .note.gnu.build-id   NOTE         00000000000002d8 000002d8 00000024  0 A      0   0  4
[ 3] .note.qt.metadata    NOTE         00000000000002fc 000002fc 000001ac  0 A      0   0  4

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
...
  NOTE           0x0002a8 0x00000000000002a8 0x00000000000002a8 0x000030 0x000030 R   0x8
  NOTE           0x0002d8 0x00000000000002d8 0x00000000000002d8 0x0001d0 0x0001d0 R   0x4

The Qt metadata note is 4-byte aligned and can thus be found in the
second note section, which spans from 0x02d8 to 0x02d8+0xac=0x0384.

GNU readelf -n can even show it:

Displaying notes found in: .note.qt.metadata
  Owner                Data size        Description
  qt-project!          0x0000018f       Unknown note type: (0x74510001)
   description data: 01 06 03 81 bf ...... ff

I chose 0x7451 as the prefix for our notes, even though they're already
namespaced by the owner in the first place, because eu-readelf
mistakenly tries to interpret note 1 as a GNU ABI tag regardless of
owner. The owner name was chosen to be 12 bytes long, so the ELF note
header is 24 bytes in total. There's no space wasted because the payload
needs to be aligned to 32-bit anyway and I didn't want to use only 4
characters (header total size 16 bytes) so we'd skip the "GNU" note on
size, without string comparison. And I couldn't think of a 4-character
representative string ("QtP" ?).

[1] https://github.com/openwrt/video/issues/1

Fixes: QTBUG-96327
Change-Id: I2de1b4dfacd443148279fffd16a3987729346567
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
Thiago Macieira 2021-09-20 23:20:11 -07:00
parent d09306064f
commit 8c2969ea86
8 changed files with 751 additions and 78 deletions

View File

@ -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 <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());
@ -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

View File

@ -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 <auto (&PluginMetaData)> 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 <auto (&PluginMetaData)> 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;

View File

@ -28,6 +28,7 @@
#include <qplugin.h>
// 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', ' ',

View File

@ -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 <QtCore/qplugin.h>
#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 <QtCore/qglobal.h>
#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";

View File

@ -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:<TRUE>:
# 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()

View File

@ -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 <QtCore/QString>
#include <QtCore/qplugin.h>
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)

View File

@ -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 <QObject>
#include <QtPlugin>
#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

View File

@ -33,7 +33,6 @@
#include <qdir.h>
#include <qendian.h>
#include <qpluginloader.h>
#include <qprocess.h>
#include <qtemporaryfile.h>
#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<QTemporaryFile> 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<const char *> 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<QObject*>(0));
QCOMPARE(loader.errorString(), unknown);
QVERIFY(loader.instance() != static_cast<QObject*>(0));
QCOMPARE(loader.errorString(), unknown);
// Make sure that plugin really works
PluginInterface* theplugin = qobject_cast<PluginInterface*>(loader.instance());
QString pluginName = theplugin->pluginName();
QCOMPARE(pluginName, QLatin1String("Plugin ok"));
// Make sure that plugin really works
PluginInterface* theplugin = qobject_cast<PluginInterface*>(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 <typename Lambda>
static void newRow(const char *rowname, QString &&snippet, Lambda &&patcher)
{
QTest::newRow(rowname)
<< std::move(snippet) << ElfPatcher::fromLambda(std::forward<Lambda>(patcher));
}
static ElfPhdr *getProgramEntry(ElfHeader *h, int index)
{
auto phdr = reinterpret_cast<ElfPhdr *>(h->e_phoff + reinterpret_cast<uchar *>(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<QString>("snippet");
QTest::addColumn<ElfPatcher>("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<ElfNhdr *>(static_cast<uchar *>(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<uchar *>(n) - static_cast<uchar *>(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<ElfNhdr *>(reinterpret_cast<uchar *>(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<QTemporaryFile> 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<QTemporaryFile> 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<const char *>(&badHeader), sizeof(badHeader));
} };
std::unique_ptr<QTemporaryFile> 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<PluginInterface*>(loader.instance());
QVERIFY(instance);
QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok"));
QVERIFY(loader.unload());
#endif
}
void tst_QPluginLoader::preloadedPlugin_data()
{
QTest::addColumn<bool>("doLoad");