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