summaryrefslogtreecommitdiffstats
path: root/src/corelib/plugin
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2021-09-20 23:20:11 -0700
committerThiago Macieira <thiago.macieira@intel.com>2021-10-18 01:53:27 -0700
commit8c2969ea86c4b38cbece5c1c2295e44dd5aa4f71 (patch)
tree6833549f7b6cefa79646420247f175e6b172edb3 /src/corelib/plugin
parentd09306064fb88be5b6c42b1c5b52db31821b748e (diff)
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>
Diffstat (limited to 'src/corelib/plugin')
-rw-r--r--src/corelib/plugin/qelfparser_p.cpp237
-rw-r--r--src/corelib/plugin/qplugin.h29
2 files changed, 249 insertions, 17 deletions
diff --git a/src/corelib/plugin/qelfparser_p.cpp b/src/corelib/plugin/qelfparser_p.cpp
index e50ad6b16c..33af51d59b 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 <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
diff --git a/src/corelib/plugin/qplugin.h b/src/corelib/plugin/qplugin.h
index d70324b560..408e22f37d 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 <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;