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