Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcoffpeparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 Intel Corporation.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <qendian.h>
7#include <qloggingcategory.h>
8#include <qnumeric.h>
9
10#include <optional>
11
12// Include minimal set of headers
13#define WIN32_LEAN_AND_MEAN
14#define NOGDI
15#include <qt_windows.h>
16
17QT_BEGIN_NAMESPACE
18
19using namespace Qt::StringLiterals;
20
21// Whether we include some extra validity checks
22// (checks to ensure we don't read out-of-bounds are always included)
23static constexpr bool IncludeValidityChecks = true;
24
25static constexpr inline auto metadataSectionName() noexcept { return ".qtmetadata"_L1; }
27 metadataSectionName().left(sizeof(IMAGE_SECTION_HEADER::Name));
28
29#ifdef QT_BUILD_INTERNAL
30# define QCOFFPEPARSER_DEBUG
31#endif
32#if defined(QCOFFPEPARSER_DEBUG)
33Q_STATIC_LOGGING_CATEGORY(lcCoffPeParser, "qt.core.plugin.coffpeparser")
34# define peDebug qCDebug(lcCoffPeParser) << reinterpret_cast<const char16_t *>(error.errMsg->constData()) << ':'
35#else
36# define peDebug if (false) {} else QNoDebug()
37#endif
38
40QT_WARNING_DISABLE_CLANG("-Wunused-const-variable")
41
42static const WORD ExpectedMachine =
43#if 0
44 // nothing, just so everything is #elf
45#elif defined(Q_PROCESSOR_ARM_32)
46 IMAGE_FILE_MACHINE_ARMNT
47#elif defined(Q_PROCESSOR_ARM_64)
48 IMAGE_FILE_MACHINE_ARM64
49#elif defined(Q_PROCESSOR_IA64)
50 IMAGE_FILE_MACHINE_IA64
51#elif defined(Q_PROCESSOR_RISCV_32)
52 0x5032 // IMAGE_FILE_MACHINE_RISCV32
53#elif defined(Q_PROCESSOR_RISCV_64)
54 0x5064 // IMAGE_FILE_MACHINE_RISCV64
55#elif defined(Q_PROCESSOR_X86_32)
56 IMAGE_FILE_MACHINE_I386
57#elif defined(Q_PROCESSOR_X86_64)
58 IMAGE_FILE_MACHINE_AMD64
59#else
60# error "Unknown Q_PROCESSOR_xxx macro, please update."
61 IMAGE_FILE_MACHINE_UNKNOWN
62#endif
63 ;
64
66 sizeof(void*) == sizeof(quint64) ? IMAGE_NT_OPTIONAL_HDR64_MAGIC :
67 IMAGE_NT_OPTIONAL_HDR32_MAGIC;
68
69namespace {
70struct ErrorMaker
71{
72 QString *errMsg;
73 constexpr ErrorMaker(QString *errMsg) : errMsg(errMsg) {}
74
75 Q_DECL_COLD_FUNCTION QLibraryScanResult operator()(QString &&text) const
76 {
77 *errMsg = QLibrary::tr("'%1' is not a valid Windows DLL (%2)").arg(*errMsg, std::move(text));
78 return {};
79 }
80
81 Q_DECL_COLD_FUNCTION QLibraryScanResult toosmall() const
82 {
83 *errMsg = QLibrary::tr("'%1' is too small").arg(*errMsg);
84 return {};
85 }
86
87 Q_DECL_COLD_FUNCTION QLibraryScanResult notplugin(QString &&explanation) const
88 {
89 *errMsg = QLibrary::tr("'%1' is not a Qt plugin (%2)").arg(*errMsg, explanation);
90 return {};
91 }
92
93 Q_DECL_COLD_FUNCTION QLibraryScanResult notfound() const
94 {
95 return notplugin(QLibrary::tr("metadata not found"));
96 }
97};
98
99struct HeaderDebug { const IMAGE_NT_HEADERS *h; };
100Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, HeaderDebug h)
101{
102 switch (h.h->Signature & 0xffff) {
103 case IMAGE_OS2_SIGNATURE: return d << "NE executable";
104 case IMAGE_VXD_SIGNATURE: return d << "LE executable";
105 default: return d << "Unknown file type";
106 case IMAGE_NT_SIGNATURE: break;
107 }
108
109 // the FileHeader and the starting portion of OptionalHeader are the same
110 // for 32- and 64-bit
111 switch (h.h->OptionalHeader.Magic) {
112 case IMAGE_NT_OPTIONAL_HDR32_MAGIC: d << "COFF PE"; break;
113 case IMAGE_NT_OPTIONAL_HDR64_MAGIC: d << "COFF PE+"; break;
114 default: return d << "Unknown COFF PE type";
115 }
116
117 QDebugStateSaver saver(d);
118 d.nospace() << Qt::hex << Qt::showbase;
119
120 switch (h.h->FileHeader.Machine) {
121 case IMAGE_FILE_MACHINE_I386: d << "i386"; break;
122 case IMAGE_FILE_MACHINE_ARM: d << "ARM"; break;
123 case IMAGE_FILE_MACHINE_ARMNT: d << "ARM Thumb-2"; break;
124 case IMAGE_FILE_MACHINE_THUMB: d << "Thumb"; break;
125 case IMAGE_FILE_MACHINE_IA64: d << "IA-64"; break;
126 case IMAGE_FILE_MACHINE_MIPS16: d << "MIPS16"; break;
127 case IMAGE_FILE_MACHINE_MIPSFPU: d << "MIPS with FPU"; break;
128 case IMAGE_FILE_MACHINE_MIPSFPU16: d << "MIPS16 with FPU"; break;
129 case IMAGE_FILE_MACHINE_AMD64: d << "x86-64"; break;
130 case 0xaa64: d << "AArch64"; break;
131 default: d << h.h->FileHeader.Machine; break;
132 }
133
134 // this usually prints "executable DLL"
135 if (h.h->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE)
136 d << " executable";
137 if (h.h->FileHeader.Characteristics & IMAGE_FILE_DLL)
138 d << " DLL";
139 if (h.h->FileHeader.Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE)
140 d << " large-address aware";
141
142 d << ", " << Qt::dec << h.h->FileHeader.NumberOfSections << " sections, ";
143 if (h.h->FileHeader.SizeOfOptionalHeader < sizeof(IMAGE_OPTIONAL_HEADER32))
144 return d;
145
146 auto optDebug = [&d](const auto *hdr) {
147 d << "(Windows " << hdr->MajorSubsystemVersion
148 << '.' << hdr->MinorSubsystemVersion;
149 switch (hdr->Subsystem) {
150 case IMAGE_SUBSYSTEM_NATIVE: d << " native)"; break;
151 case IMAGE_SUBSYSTEM_WINDOWS_GUI: d << " GUI)"; break;
152 case IMAGE_SUBSYSTEM_WINDOWS_CUI: d << " CUI)"; break;
153 default: d << " subsystem " << hdr->Subsystem << ')'; break;
154 }
155
156 d.space() << Qt::hex << hdr->SizeOfHeaders << "header bytes,"
157 << Qt::dec << hdr->NumberOfRvaAndSizes << "RVA entries";
158 };
159
160 if (h.h->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
161 optDebug(reinterpret_cast<const IMAGE_OPTIONAL_HEADER64 *>(&h.h->OptionalHeader));
162 else
163 optDebug(reinterpret_cast<const IMAGE_OPTIONAL_HEADER32 *>(&h.h->OptionalHeader));
164 return d;
165}
166
167struct SectionDebug { const IMAGE_SECTION_HEADER *s; };
168Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, SectionDebug s)
169{
170 QDebugStateSaver saver(d);
171 d << Qt::hex << Qt::showbase;
172 d << "contains";
173 if (s.s->Characteristics & IMAGE_SCN_CNT_CODE)
174 d << "CODE";
175 if (s.s->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA)
176 d << "DATA";
177 if (s.s->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA)
178 d << "BSS";
179
180 d << "flags";
181 d.nospace();
182 if (s.s->Characteristics & IMAGE_SCN_MEM_READ)
183 d << 'R';
184 if (s.s->Characteristics & IMAGE_SCN_MEM_WRITE)
185 d << 'W';
186 if (s.s->Characteristics & IMAGE_SCN_MEM_EXECUTE)
187 d << 'X';
188 if (s.s->Characteristics & IMAGE_SCN_MEM_SHARED)
189 d << 'S';
190 if (s.s->Characteristics & IMAGE_SCN_MEM_DISCARDABLE)
191 d << 'D';
192
193 d.space() << "offset" << s.s->PointerToRawData << "size" << s.s->SizeOfRawData;
194 return d;
195}
196} // unnamed namespace
197
198QT_WARNING_POP
199
200const IMAGE_NT_HEADERS *checkNtHeaders(QByteArrayView data, const ErrorMaker &error)
201{
202 if (size_t(data.size()) < qMax(sizeof(IMAGE_DOS_HEADER), sizeof(IMAGE_NT_HEADERS))) {
203 peDebug << "file too small:" << size_t(data.size());
204 return error.toosmall(), nullptr;
205 }
206
207 // check if there's a DOS image header (almost everything does)
208 size_t off = 0;
209 auto dosHeader = reinterpret_cast<const IMAGE_DOS_HEADER *>(data.data());
210 if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) {
211 off = dosHeader->e_lfanew;
212 // peDebug << "DOS file header redirects to offset" << Qt::hex << Qt::showbase << off;
213 if (size_t end; qAddOverflow<sizeof(IMAGE_NT_HEADERS)>(off, &end)
214 || end > size_t(data.size())) {
215 peDebug << "file too small:" << size_t(data.size());
216 return error.toosmall(), nullptr;
217 }
218 }
219
220 // now check the NT headers
221 auto ntHeader = reinterpret_cast<const IMAGE_NT_HEADERS *>(data.data() + off);
222 peDebug << HeaderDebug{ntHeader};
223 if (ntHeader->Signature != IMAGE_NT_SIGNATURE) // "PE\0\0"
224 return error(QLibrary::tr("invalid signature")), nullptr;
225 if (ntHeader->FileHeader.Machine != ExpectedMachine)
226 return error(QLibrary::tr("file is for a different processor")), nullptr;
227 if (ntHeader->FileHeader.NumberOfSections == 0)
228 return error(QLibrary::tr("file has no sections")), nullptr;
229
230 WORD requiredCharacteristics =
231 IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DLL;
232 if ((ntHeader->FileHeader.Characteristics & requiredCharacteristics) != requiredCharacteristics)
233 return error(QLibrary::tr("wrong characteristics")), nullptr;
234
235 // the optional header is not optional
236 if (ntHeader->OptionalHeader.Magic != ExpectedOptionalHeaderSignature)
237 return error(QLibrary::tr("file is for a different word size")), nullptr;
238 if (ntHeader->OptionalHeader.SizeOfCode == 0)
239 return error.notplugin(QLibrary::tr("file has no code")), nullptr;
240
241 return ntHeader;
242}
243
244static const IMAGE_SECTION_HEADER *
245findSectionTable(QByteArrayView data, const IMAGE_NT_HEADERS *ntHeader, const ErrorMaker &error)
246{
247 // macro IMAGE_FIRST_SECTION can't overflow due to limited range
248 // of type, but adding to the offset from the DOS header could
249 // overflow on 32-bit
250 static_assert(sizeof(ntHeader->FileHeader.SizeOfOptionalHeader) < sizeof(size_t));
251 static_assert(sizeof(ntHeader->FileHeader.NumberOfSections) < sizeof(size_t));
252
253 size_t off = offsetof(IMAGE_NT_HEADERS, OptionalHeader);
254 off += ntHeader->FileHeader.SizeOfOptionalHeader;
255 if (qAddOverflow<size_t>(off, reinterpret_cast<const char *>(ntHeader) - data.data(), &off))
256 return error.toosmall(), nullptr;
257
258 size_t end = ntHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
259
260 // validate that the file is big enough for all sections we're
261 // supposed to have
262 if (qAddOverflow(end, off, &end) || end > size_t(data.size()))
263 return error.toosmall(), nullptr;
264
265 peDebug << "contains" << ntHeader->FileHeader.NumberOfSections << "sections at offset" << off;
266 return reinterpret_cast<const IMAGE_SECTION_HEADER *>(data.data() + off);
267}
268
270findStringTable(QByteArrayView data, const IMAGE_NT_HEADERS *ntHeader, const ErrorMaker &error)
271{
272 // first, find the symbol table
273 size_t off = ntHeader->FileHeader.PointerToSymbolTable;
274 if (off == 0)
275 return QByteArrayView();
276
277 // skip the symbol table to find the string table after it
278 constexpr size_t SymbolEntrySize = 18;
279 size_t size = ntHeader->FileHeader.NumberOfSymbols;
280 if (qMulOverflow<SymbolEntrySize>(size, &size)
281 || qAddOverflow(off, size, &off)
282 || qAddOverflow(off, sizeof(DWORD), &off)
283 || off > size_t(data.size()))
284 return error.toosmall(), std::nullopt;
285
286 off -= sizeof(DWORD);
287
288 // we've found the string table, ensure it's big enough
289 size = qFromUnaligned<DWORD>(data.data() + off);
290 if (size_t end; qAddOverflow(off, size, &end) || end > size_t(data.size()))
291 return error.toosmall(), std::nullopt;
292
293 // the conversion to signed is fine because we checked above it wasn't
294 // bigger than data.size()
295 return data.sliced(off, size);
296}
297
298static QLatin1StringView findSectionName(const IMAGE_SECTION_HEADER *section, QByteArrayView stringTable)
299{
300 auto ptr = reinterpret_cast<const char *>(section->Name);
301 qsizetype n = qstrnlen(ptr, sizeof(section->Name));
302 if (ptr[0] == '/' && !stringTable.isEmpty()) {
303 // long section name
304 // Microsoft's link.exe does not use these and will truncate the
305 // section name to fit section->Name. GNU binutils' ld does use long
306 // section names on executable image files by default (can be disabled
307 // using --disable-long-section-names). LLVM's lld does generate a
308 // string table in MinGW mode, but does not use it for our section.
309
310 static_assert(sizeof(section->Name) - 1 < std::numeric_limits<uint>::digits10);
311 bool ok;
312 qsizetype offset = QByteArrayView(ptr + 1, n - 1).toUInt(&ok);
313 if (!ok || offset >= stringTable.size())
314 return {};
315
316 ptr = stringTable.data() + offset;
317 n = qstrnlen(ptr, stringTable.size() - offset);
318 }
319
320 return {ptr, n};
321}
322
323QLibraryScanResult QCoffPeParser::parse(QByteArrayView data, QString *errMsg)
324{
325 ErrorMaker error(errMsg);
326 auto ntHeaders = checkNtHeaders(data, error);
327 if (!ntHeaders)
328 return {};
329
330 auto section = findSectionTable(data, ntHeaders, error);
331 if (!ntHeaders)
332 return {};
333
334 QByteArrayView stringTable;
335 if (auto optional = findStringTable(data, ntHeaders, error))
336 stringTable = *optional;
337 else
338 return {};
339
340 // scan the sections now
341 const auto sectionTableEnd = section + ntHeaders->FileHeader.NumberOfSections;
342 for ( ; section < sectionTableEnd; ++section) {
343 QLatin1StringView sectionName = findSectionName(section, stringTable);
344 peDebug << "section" << sectionName << SectionDebug{section};
345 if (IncludeValidityChecks && sectionName.isEmpty())
346 return error(QLibrary::tr("a section name is empty or extends past the end of the file"));
347
348 size_t offset = section->PointerToRawData;
349 if (size_t end; qAddOverflow<size_t>(offset, section->SizeOfRawData, &end)
350 || end > size_t(data.size()))
351 return error(QLibrary::tr("section contents extend past the end of the file"));
352
353 DWORD type = section->Characteristics
354 & (IMAGE_SCN_CNT_CODE | IMAGE_SCN_CNT_INITIALIZED_DATA
355 | IMAGE_SCN_CNT_UNINITIALIZED_DATA);
356 if (type != IMAGE_SCN_CNT_INITIALIZED_DATA)
357 continue;
358
359 // if we do have a string table, the name may be complete
360 if (sectionName != truncatedSectionName && sectionName != metadataSectionName())
361 continue;
362 peDebug << "found .qtmetadata section";
363
364 size_t size = qMin(section->SizeOfRawData, section->Misc.VirtualSize);
365 if (size < sizeof(QPluginMetaData::MagicHeader))
366 return error(QLibrary::tr(".qtmetadata section is too small"));
367 if (IncludeValidityChecks) {
368 QByteArrayView expectedMagic = QByteArrayView::fromArray(QPluginMetaData::MagicString);
369 QByteArrayView actualMagic = data.sliced(offset, expectedMagic.size());
370 if (expectedMagic != actualMagic)
371 return error(QLibrary::tr(".qtmetadata section has incorrect magic"));
372
373 if (section->Characteristics & IMAGE_SCN_MEM_WRITE)
374 return error(QLibrary::tr(".qtmetadata section is writable"));
375 if (section->Characteristics & IMAGE_SCN_MEM_EXECUTE)
376 return error(QLibrary::tr(".qtmetadata section is executable"));
377 }
378
379 return { qsizetype(offset + sizeof(QPluginMetaData::MagicString)),
380 qsizetype(size - sizeof(QPluginMetaData::MagicString)) };
381 }
382
383 return error.notfound();
384}
385
386QT_END_NAMESPACE
static QT_WARNING_PUSH const WORD ExpectedMachine
static QLatin1StringView findSectionName(const IMAGE_SECTION_HEADER *section, QByteArrayView stringTable)
static constexpr auto metadataSectionName() noexcept
#define peDebug
static constexpr bool IncludeValidityChecks
static std::optional< QByteArrayView > findStringTable(QByteArrayView data, const IMAGE_NT_HEADERS *ntHeader, const ErrorMaker &error)
static constexpr QLatin1StringView truncatedSectionName
static const WORD ExpectedOptionalHeaderSignature
static const IMAGE_SECTION_HEADER * findSectionTable(QByteArrayView data, const IMAGE_NT_HEADERS *ntHeader, const ErrorMaker &error)