1 | // Copyright (C) 2017 The Qt Company Ltd. |
---|---|
2 | // Copyright (C) 2021 Intel Corporation. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include "qelfparser_p.h" |
6 | |
7 | #ifdef Q_OF_ELF |
8 | |
9 | #include "qlibrary_p.h" |
10 | |
11 | #include <qloggingcategory.h> |
12 | #include <qnumeric.h> |
13 | #include <qsysinfo.h> |
14 | |
15 | #if __has_include(<elf.h>) |
16 | # include <elf.h> |
17 | #elif __has_include(<sys/elf.h>) |
18 | # include <sys/elf.h> |
19 | #else |
20 | # error "Need ELF header to parse plugins." |
21 | #endif |
22 | |
23 | QT_BEGIN_NAMESPACE |
24 | |
25 | using namespace Qt::StringLiterals; |
26 | |
27 | // ### Qt7: propagate the constant and eliminate dead code |
28 | static constexpr bool ElfNotesAreMandatory = QT_VERSION >= QT_VERSION_CHECK(7,0,0); |
29 | |
30 | // Whether we include some extra validity checks |
31 | // (checks to ensure we don't read out-of-bounds are always included) |
32 | static constexpr bool IncludeValidityChecks = true; |
33 | |
34 | #ifdef QT_BUILD_INTERNAL |
35 | # define QELFPARSER_DEBUG |
36 | #endif |
37 | #if defined(QELFPARSER_DEBUG) |
38 | static Q_LOGGING_CATEGORY(lcElfParser, "qt.core.plugin.elfparser") |
39 | # define qEDebug qCDebug(lcElfParser) << reinterpret_cast<const char16_t *>(error.errMsg->constData()) << ':' |
40 | #else |
41 | # define qEDebug if (false) {} else QNoDebug() |
42 | #endif |
43 | |
44 | #ifndef PT_GNU_EH_FRAME |
45 | # define PT_GNU_EH_FRAME 0x6474e550 |
46 | #endif |
47 | #ifndef PT_GNU_STACK |
48 | # define PT_GNU_STACK 0x6474e551 |
49 | #endif |
50 | #ifndef PT_GNU_RELRO |
51 | # define PT_GNU_RELRO 0x6474e552 |
52 | #endif |
53 | #ifndef PT_GNU_PROPERTY |
54 | # define PT_GNU_PROPERTY 0x6474e553 |
55 | #endif |
56 | |
57 | #ifndef PN_XNUM |
58 | # define PN_XNUM 0xffff |
59 | #endif |
60 | |
61 | QT_WARNING_PUSH |
62 | QT_WARNING_DISABLE_CLANG("-Wunused-const-variable") |
63 | |
64 | namespace { |
65 | template <QSysInfo::Endian Order> struct ElfEndianTraits |
66 | { |
67 | static constexpr unsigned char DataOrder = ELFDATA2LSB; |
68 | template <typename T> static T fromEndian(T value) { return qFromLittleEndian(value); } |
69 | }; |
70 | template <> struct ElfEndianTraits<QSysInfo::BigEndian> |
71 | { |
72 | static constexpr unsigned char DataOrder = ELFDATA2MSB; |
73 | template <typename T> static T fromEndian(T value) { return qFromBigEndian(value); } |
74 | }; |
75 | |
76 | template <typename EquivalentPointerType> struct ElfTypeTraits |
77 | { |
78 | static constexpr unsigned char Class = ELFCLASS64; |
79 | |
80 | // integer types |
81 | using Half = Elf64_Half; |
82 | using Word = Elf64_Word; |
83 | using Addr = Elf64_Addr; |
84 | using Off = Elf64_Off; |
85 | |
86 | // structure types |
87 | using Ehdr = Elf64_Ehdr; |
88 | using Shdr = Elf64_Shdr; |
89 | using Phdr = Elf64_Phdr; |
90 | using Nhdr = Elf64_Nhdr; |
91 | }; |
92 | template <> struct ElfTypeTraits<quint32> |
93 | { |
94 | static constexpr unsigned char Class = ELFCLASS32; |
95 | |
96 | // integer types |
97 | using Half = Elf32_Half; |
98 | using Word = Elf32_Word; |
99 | using Addr = Elf32_Addr; |
100 | using Off = Elf32_Off; |
101 | |
102 | // structure types |
103 | using Ehdr = Elf32_Ehdr; |
104 | using Shdr = Elf32_Shdr; |
105 | using Phdr = Elf32_Phdr; |
106 | using Nhdr = Elf32_Nhdr; |
107 | }; |
108 | |
109 | struct ElfMachineCheck |
110 | { |
111 | static const Elf32_Half ExpectedMachine = |
112 | #if 0 |
113 | // nothing |
114 | #elif defined(Q_PROCESSOR_ALPHA) |
115 | EM_ALPHA |
116 | #elif defined(Q_PROCESSOR_ARM_32) |
117 | EM_ARM |
118 | #elif defined(Q_PROCESSOR_ARM_64) |
119 | EM_AARCH64 |
120 | #elif defined(Q_PROCESSOR_BLACKFIN) |
121 | EM_BLACKFIN |
122 | #elif defined(Q_PROCESSOR_HPPA) |
123 | EM_PARISC |
124 | #elif defined(Q_PROCESSOR_IA64) |
125 | EM_IA_64 |
126 | #elif defined(Q_PROCESSOR_LOONGARCH) |
127 | EM_LOONGARCH |
128 | #elif defined(Q_PROCESSOR_M68K) |
129 | EM_68K |
130 | #elif defined(Q_PROCESSOR_MIPS) |
131 | EM_MIPS |
132 | #elif defined(Q_PROCESSOR_POWER_32) |
133 | EM_PPC |
134 | #elif defined(Q_PROCESSOR_POWER_64) |
135 | EM_PPC64 |
136 | #elif defined(Q_PROCESSOR_RISCV) |
137 | EM_RISCV |
138 | #elif defined(Q_PROCESSOR_S390) |
139 | EM_S390 |
140 | #elif defined(Q_PROCESSOR_SH) |
141 | EM_SH |
142 | #elif defined(Q_PROCESSOR_SPARC_V9) |
143 | EM_SPARCV9 |
144 | #elif defined(Q_PROCESSOR_SPARC_64) |
145 | EM_SPARCV9 |
146 | #elif defined(Q_PROCESSOR_SPARC) |
147 | EM_SPARC |
148 | #elif defined(Q_PROCESSOR_WASM) |
149 | #elif defined(Q_PROCESSOR_X86_32) |
150 | EM_386 |
151 | #elif defined(Q_PROCESSOR_X86_64) |
152 | EM_X86_64 |
153 | #else |
154 | # error "Unknown Q_PROCESSOR_xxx macro, please update." |
155 | EM_NONE |
156 | #endif |
157 | ; |
158 | }; |
159 | |
160 | struct ElfHeaderCommonCheck |
161 | { |
162 | static_assert(std::is_same_v<decltype(Elf32_Ehdr::e_ident), decltype(Elf64_Ehdr::e_ident)>, |
163 | "e_ident field is not the same in both Elf32_Ehdr and Elf64_Ehdr"); |
164 | |
165 | // bytes 0-3 |
166 | static bool checkElfMagic(const uchar *ident) |
167 | { |
168 | return memcmp(s1: ident, ELFMAG, SELFMAG) == 0; |
169 | } |
170 | |
171 | // byte 6 |
172 | static bool checkElfVersion(const uchar *ident) |
173 | { |
174 | uchar elfversion = ident[EI_VERSION]; |
175 | return elfversion == EV_CURRENT; |
176 | } |
177 | |
178 | struct CommonHeader { |
179 | Elf32_Half type; |
180 | Elf32_Half machine; |
181 | Elf32_Word version; |
182 | }; |
183 | }; |
184 | |
185 | template <typename EquivalentPointerType = quintptr, QSysInfo::Endian Order = QSysInfo::ByteOrder> |
186 | struct ElfHeaderCheck : public ElfHeaderCommonCheck |
187 | { |
188 | using TypeTraits = ElfTypeTraits<EquivalentPointerType>; |
189 | using EndianTraits = ElfEndianTraits<Order>; |
190 | using Ehdr = typename TypeTraits::Ehdr; |
191 | |
192 | // byte 4 |
193 | static bool checkClass(const uchar *ident) |
194 | { |
195 | uchar klass = ident[EI_CLASS]; |
196 | return klass == TypeTraits::Class; |
197 | } |
198 | |
199 | // byte 5 |
200 | static bool checkDataOrder(const uchar *ident) |
201 | { |
202 | uchar data = ident[EI_DATA]; |
203 | return data == EndianTraits::DataOrder; |
204 | } |
205 | |
206 | // byte 7 |
207 | static bool checkOsAbi(const uchar *ident) |
208 | { |
209 | uchar osabi = ident[EI_OSABI]; |
210 | // we don't check |
211 | Q_UNUSED(osabi); |
212 | return true; |
213 | } |
214 | |
215 | // byte 8 |
216 | static bool checkAbiVersion(const uchar *ident) |
217 | { |
218 | uchar abiversion = ident[EI_ABIVERSION]; |
219 | // we don't check (and I don't know anyone who uses this) |
220 | Q_UNUSED(abiversion); |
221 | return true; |
222 | } |
223 | |
224 | // bytes 9-16 |
225 | static bool checkPadding(const uchar *ident) |
226 | { |
227 | // why would we check this? |
228 | Q_UNUSED(ident); |
229 | return true; |
230 | } |
231 | |
232 | static bool checkIdent(const Ehdr &header) |
233 | { |
234 | return checkElfMagic(ident: header.e_ident) |
235 | && checkClass(ident: header.e_ident) |
236 | && checkDataOrder(ident: header.e_ident) |
237 | && checkElfVersion(ident: header.e_ident) |
238 | && checkOsAbi(ident: header.e_ident) |
239 | && checkAbiVersion(ident: header.e_ident) |
240 | && checkPadding(ident: header.e_ident); |
241 | } |
242 | |
243 | static bool checkType(const Ehdr &header) |
244 | { |
245 | return header.e_type == ET_DYN; |
246 | } |
247 | |
248 | static bool checkMachine(const Ehdr &header) |
249 | { |
250 | return header.e_machine == ElfMachineCheck::ExpectedMachine; |
251 | } |
252 | |
253 | static bool checkFileVersion(const Ehdr &header) |
254 | { |
255 | return header.e_version == EV_CURRENT; |
256 | } |
257 | |
258 | static bool checkHeader(const Ehdr &header) |
259 | { |
260 | if (!checkIdent(header)) |
261 | return false; |
262 | if (!IncludeValidityChecks) |
263 | return true; |
264 | return checkType(header) |
265 | && checkMachine(header) |
266 | && checkFileVersion(header); |
267 | } |
268 | |
269 | Q_DECL_COLD_FUNCTION static QString explainCheckFailure(const Ehdr &header) |
270 | { |
271 | if (!checkElfMagic(ident: header.e_ident)) |
272 | return QLibrary::tr(s: "invalid signature"); |
273 | if (!checkClass(ident: header.e_ident)) |
274 | return QLibrary::tr(s: "file is for a different word size"); |
275 | if (!checkDataOrder(ident: header.e_ident)) |
276 | return QLibrary::tr(s: "file is for the wrong endianness"); |
277 | if (!checkElfVersion(ident: header.e_ident) || !checkFileVersion(header)) |
278 | return QLibrary::tr(s: "file has an unknown ELF version"); |
279 | if (!checkOsAbi(ident: header.e_ident) || !checkAbiVersion(ident: header.e_ident)) |
280 | return QLibrary::tr(s: "file has an unexpected ABI"); |
281 | if (!checkType(header)) |
282 | return QLibrary::tr(s: "file is not a shared object"); |
283 | if (!checkMachine(header)) |
284 | return QLibrary::tr(s: "file is for a different processor"); |
285 | return QString(); |
286 | } |
287 | |
288 | static CommonHeader extractCommonHeader(const uchar *data) |
289 | { |
290 | auto header = reinterpret_cast<const Ehdr *>(data); |
291 | CommonHeader r; |
292 | r.type = EndianTraits::fromEndian(header->e_type); |
293 | r.machine = EndianTraits::fromEndian(header->e_machine); |
294 | r.version = EndianTraits::fromEndian(header->e_version); |
295 | return r; |
296 | } |
297 | }; |
298 | |
299 | struct ElfHeaderDebug { const uchar *e_ident; }; |
300 | Q_DECL_UNUSED Q_DECL_COLD_FUNCTION static QDebug &operator<<(QDebug &d, ElfHeaderDebug h) |
301 | { |
302 | const uchar *e_ident = h.e_ident; |
303 | if (!ElfHeaderCommonCheck::checkElfMagic(ident: e_ident)) { |
304 | d << "Not an ELF file (invalid signature)"; |
305 | return d; |
306 | } |
307 | |
308 | QDebugStateSaver saver(d); |
309 | d.nospace(); |
310 | quint8 elfclass = e_ident[EI_CLASS]; |
311 | switch (elfclass) { |
312 | case ELFCLASSNONE: |
313 | default: |
314 | d << "Invalid ELF file (class "<< e_ident[EI_CLASS] << "), "; |
315 | break; |
316 | case ELFCLASS32: |
317 | d << "ELF 32-bit "; |
318 | break; |
319 | case ELFCLASS64: |
320 | d << "ELF 64-bit "; |
321 | break; |
322 | } |
323 | |
324 | quint8 dataorder = e_ident[EI_DATA]; |
325 | switch (dataorder) { |
326 | case ELFDATANONE: |
327 | default: |
328 | d << "invalid endianness ("<< e_ident[EI_DATA] << ')'; |
329 | break; |
330 | case ELFDATA2LSB: |
331 | d << "LSB"; |
332 | break; |
333 | case ELFDATA2MSB: |
334 | d << "MSB"; |
335 | break; |
336 | } |
337 | |
338 | switch (e_ident[EI_OSABI]) { |
339 | case ELFOSABI_SYSV: d << " (SYSV"; break; |
340 | case ELFOSABI_HPUX: d << " (HP-UX"; break; |
341 | case ELFOSABI_NETBSD: d << " (NetBSD"; break; |
342 | case ELFOSABI_LINUX: d << " (GNU/Linux"; break; |
343 | case ELFOSABI_SOLARIS: d << " (Solaris"; break; |
344 | case ELFOSABI_AIX: d << " (AIX"; break; |
345 | case ELFOSABI_IRIX: d << " (IRIX"; break; |
346 | case ELFOSABI_FREEBSD: d << " (FreeBSD"; break; |
347 | case ELFOSABI_OPENBSD: d << " (OpenBSD"; break; |
348 | default: d << " (OS ABI "<< e_ident[EI_VERSION]; break; |
349 | } |
350 | |
351 | if (e_ident[EI_ABIVERSION]) |
352 | d << " v"<< e_ident[EI_ABIVERSION]; |
353 | d << ')'; |
354 | |
355 | if (e_ident[EI_VERSION] != 1) { |
356 | d << ", file version "<< e_ident[EI_VERSION]; |
357 | return d; |
358 | } |
359 | |
360 | ElfHeaderCommonCheck::CommonHeader r; |
361 | if (elfclass == ELFCLASS64 && dataorder == ELFDATA2LSB) |
362 | r = ElfHeaderCheck<quint64, QSysInfo::LittleEndian>::extractCommonHeader(data: e_ident); |
363 | else if (elfclass == ELFCLASS32 && dataorder == ELFDATA2LSB) |
364 | r = ElfHeaderCheck<quint32, QSysInfo::LittleEndian>::extractCommonHeader(data: e_ident); |
365 | else if (elfclass == ELFCLASS64 && dataorder == ELFDATA2MSB) |
366 | r = ElfHeaderCheck<quint64, QSysInfo::BigEndian>::extractCommonHeader(data: e_ident); |
367 | else if (elfclass == ELFCLASS32 && dataorder == ELFDATA2MSB) |
368 | r = ElfHeaderCheck<quint32, QSysInfo::BigEndian>::extractCommonHeader(data: e_ident); |
369 | else |
370 | return d; |
371 | |
372 | d << ", version "<< r.version; |
373 | |
374 | switch (r.type) { |
375 | case ET_NONE: d << ", no type"; break; |
376 | case ET_REL: d << ", relocatable"; break; |
377 | case ET_EXEC: d << ", executable"; break; |
378 | case ET_DYN: d << ", shared library or PIC executable"; break; |
379 | case ET_CORE: d << ", core dump"; break; |
380 | default: d << ", unknown type "<< r.type; break; |
381 | } |
382 | |
383 | switch (r.machine) { |
384 | // list definitely not exhaustive! |
385 | case EM_NONE: d << ", no machine"; break; |
386 | case EM_ALPHA: d << ", Alpha"; break; |
387 | case EM_68K: d << ", MC68000"; break; |
388 | case EM_ARM: d << ", ARM"; break; |
389 | case EM_AARCH64: d << ", AArch64"; break; |
390 | #ifdef EM_BLACKFIN |
391 | case EM_BLACKFIN: d << ", Blackfin"; break; |
392 | #endif |
393 | case EM_IA_64: d << ", IA-64"; break; |
394 | #ifdef EM_LOONGARCH |
395 | case EM_LOONGARCH: d << ", LoongArch"; break; |
396 | #endif |
397 | case EM_MIPS: d << ", MIPS"; break; |
398 | case EM_PARISC: d << ", HPPA"; break; |
399 | case EM_PPC: d << ", PowerPC"; break; |
400 | case EM_PPC64: d << ", PowerPC 64-bit"; break; |
401 | #ifdef EM_RISCV |
402 | case EM_RISCV: d << ", RISC-V"; break; |
403 | #endif |
404 | #ifdef EM_S390 |
405 | case EM_S390: d << ", S/390"; break; |
406 | #endif |
407 | case EM_SH: d << ", SuperH"; break; |
408 | case EM_SPARC: d << ", SPARC"; break; |
409 | case EM_SPARCV9: d << ", SPARCv9"; break; |
410 | case EM_386: d << ", i386"; break; |
411 | case EM_X86_64: d << ", x86-64"; break; |
412 | default: d << ", other machine type "<< r.machine; break; |
413 | } |
414 | |
415 | return d; |
416 | } |
417 | |
418 | struct ElfSectionDebug { const ElfHeaderCheck<>::TypeTraits::Shdr *shdr; }; |
419 | Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, ElfSectionDebug s) |
420 | { |
421 | // not exhaustive, just a few common things |
422 | QDebugStateSaver saver(d); |
423 | d << Qt::hex << Qt::showbase; |
424 | d << "type"; |
425 | switch (s.shdr->sh_type) { |
426 | case SHT_NULL: d << "NULL"; break; |
427 | case SHT_PROGBITS: d << "PROGBITS"; break; |
428 | case SHT_SYMTAB: d << "SYMTAB"; break; |
429 | case SHT_STRTAB: d << "STRTAB"; break; |
430 | case SHT_RELA: d << "RELA"; break; |
431 | case SHT_HASH: d << "HASH"; break; |
432 | case SHT_DYNAMIC: d << "DYNAMIC"; break; |
433 | case SHT_NOTE: d << "NOTE"; break; |
434 | case SHT_NOBITS: d << "NOBITS"; break; |
435 | case SHT_DYNSYM: d << "DYNSYM"; break; |
436 | case SHT_INIT_ARRAY: d << "INIT_ARRAY"; break; |
437 | case SHT_FINI_ARRAY: d << "FINI_ARRAY"; break; |
438 | default: d << s.shdr->sh_type; |
439 | } |
440 | |
441 | d << "flags"; |
442 | d.nospace(); |
443 | if (s.shdr->sh_flags & SHF_WRITE) |
444 | d << 'W'; |
445 | if (s.shdr->sh_flags & SHF_ALLOC) |
446 | d << 'A'; |
447 | if (s.shdr->sh_flags & SHF_EXECINSTR) |
448 | d << 'X'; |
449 | if (s.shdr->sh_flags & SHF_STRINGS) |
450 | d << 'S'; |
451 | if (s.shdr->sh_flags & SHF_TLS) |
452 | d << 'T'; |
453 | |
454 | d.space() << "offset"<< s.shdr->sh_offset << "size"<< s.shdr->sh_size; |
455 | return d; |
456 | } |
457 | |
458 | struct ElfProgramDebug { const ElfHeaderCheck<>::TypeTraits::Phdr *phdr; }; |
459 | Q_DECL_UNUSED static QDebug &operator<<(QDebug &d, ElfProgramDebug p) |
460 | { |
461 | QDebugStateSaver saved(d); |
462 | d << Qt::hex << Qt::showbase << "program"; |
463 | switch (p.phdr->p_type) { |
464 | case PT_NULL: d << "NULL"; break; |
465 | case PT_LOAD: d << "LOAD"; break; |
466 | case PT_DYNAMIC: d << "DYNAMIC"; break; |
467 | case PT_INTERP: d << "INTERP"; break; |
468 | case PT_NOTE: d << "NOTE"; break; |
469 | case PT_PHDR: d << "PHDR"; break; |
470 | case PT_TLS: d << "TLS"; break; |
471 | case PT_GNU_EH_FRAME: d << "GNU_EH_FRAME"; break; |
472 | case PT_GNU_STACK: d << "GNU_STACK"; break; |
473 | case PT_GNU_RELRO: d << "GNU_RELRO"; break; |
474 | case PT_GNU_PROPERTY: d << "GNU_PROPERTY"; break; |
475 | default: d << "type"<< p.phdr->p_type; break; |
476 | } |
477 | |
478 | d << "offset"<< p.phdr->p_offset |
479 | << "virtaddr"<< p.phdr->p_vaddr |
480 | << "filesz"<< p.phdr->p_filesz |
481 | << "memsz"<< p.phdr->p_memsz |
482 | << "align"<< p.phdr->p_align |
483 | << "flags"; |
484 | |
485 | d.nospace(); |
486 | if (p.phdr->p_flags & PF_R) |
487 | d << 'R'; |
488 | if (p.phdr->p_flags & PF_W) |
489 | d << 'W'; |
490 | if (p.phdr->p_flags & PF_X) |
491 | d << 'X'; |
492 | |
493 | return d; |
494 | } |
495 | |
496 | struct ErrorMaker |
497 | { |
498 | QString *errMsg; |
499 | constexpr ErrorMaker(QString *errMsg) : errMsg(errMsg) {} |
500 | |
501 | |
502 | Q_DECL_COLD_FUNCTION QLibraryScanResult operator()(QString &&text) const |
503 | { |
504 | *errMsg = QLibrary::tr(s: "'%1' is not a valid ELF object (%2)").arg(args&: *errMsg, args: std::move(text)); |
505 | return {}; |
506 | } |
507 | |
508 | Q_DECL_COLD_FUNCTION QLibraryScanResult notplugin(QString &&explanation) const |
509 | { |
510 | *errMsg = QLibrary::tr(s: "'%1' is not a Qt plugin (%2)").arg(args&: *errMsg, args&: explanation); |
511 | return {}; |
512 | } |
513 | |
514 | Q_DECL_COLD_FUNCTION QLibraryScanResult notfound() const |
515 | { |
516 | return notplugin(explanation: QLibrary::tr(s: "metadata not found")); |
517 | } |
518 | }; |
519 | } // unnamed namespace |
520 | |
521 | QT_WARNING_POP |
522 | |
523 | using T = ElfHeaderCheck<>::TypeTraits; |
524 | |
525 | template <typename F> |
526 | static bool scanProgramHeaders(QByteArrayView data, const ErrorMaker &error, F f) |
527 | { |
528 | auto header = reinterpret_cast<const T::Ehdr *>(data.data()); |
529 | Q_UNUSED(error); |
530 | |
531 | auto phdr = reinterpret_cast<const T::Phdr *>(data.data() + header->e_phoff); |
532 | auto phdr_end = phdr + header->e_phnum; |
533 | for ( ; phdr != phdr_end; ++phdr) { |
534 | if (!f(phdr)) |
535 | return false; |
536 | } |
537 | return true; |
538 | } |
539 | |
540 | static bool preScanProgramHeaders(QByteArrayView data, const ErrorMaker &error) |
541 | { |
542 | auto header = reinterpret_cast<const T::Ehdr *>(data.data()); |
543 | |
544 | // first, validate the extent of the full program header table |
545 | T::Word e_phnum = header->e_phnum; |
546 | if (e_phnum == PN_XNUM) |
547 | return error(QLibrary::tr(s: "unimplemented: PN_XNUM program headers")), false; |
548 | T::Off offset = e_phnum * sizeof(T::Phdr); // can't overflow due to size of T::Half |
549 | if (qAddOverflow(v1: offset, v2: header->e_phoff, r: &offset) || offset > size_t(data.size())) |
550 | return error(QLibrary::tr(s: "program header table extends past the end of the file")), false; |
551 | |
552 | // confirm validity |
553 | bool hasCode = false; |
554 | auto checker = [&](const T::Phdr *phdr) { |
555 | qEDebug << ElfProgramDebug{.phdr: phdr}; |
556 | |
557 | if (T::Off end; qAddOverflow(v1: phdr->p_offset, v2: phdr->p_filesz, r: &end) |
558 | || end > size_t(data.size())) |
559 | return error(QLibrary::tr(s: "a program header entry extends past the end of the file")), false; |
560 | |
561 | // this is not a validity check, it's to exclude debug symbol files |
562 | if (phdr->p_type == PT_LOAD && phdr->p_filesz != 0 && (phdr->p_flags & PF_X)) |
563 | hasCode = true; |
564 | |
565 | // this probably applies to all segments, but we'll only apply it to notes |
566 | if (phdr->p_type == PT_NOTE && qPopulationCount(v: phdr->p_align) == 1 |
567 | && phdr->p_offset & (phdr->p_align - 1)) { |
568 | return error(QLibrary::tr(s: "a note segment start is not properly aligned " |
569 | "(offset 0x%1, alignment %2)") |
570 | .arg(a: phdr->p_offset, fieldWidth: 6, base: 16, fillChar: QChar(u'0')) |
571 | .arg(a: phdr->p_align)), false; |
572 | } |
573 | |
574 | return true; |
575 | }; |
576 | if (!scanProgramHeaders(data, error, f: checker)) |
577 | return false; |
578 | if (!hasCode) |
579 | return error.notplugin(explanation: QLibrary::tr(s: "file has no code")), false; |
580 | return true; |
581 | } |
582 | |
583 | static QLibraryScanResult scanProgramHeadersForNotes(QByteArrayView data, const ErrorMaker &error) |
584 | { |
585 | // minimum metadata payload is 2 bytes |
586 | constexpr size_t MinPayloadSize = sizeof(QPluginMetaData::Header) + 2; |
587 | constexpr qptrdiff MinNoteSize = sizeof(QPluginMetaData::ElfNoteHeader) + 2; |
588 | constexpr size_t NoteNameSize = sizeof(QPluginMetaData::ElfNoteHeader::name); |
589 | constexpr size_t NoteAlignment = alignof(QPluginMetaData::ElfNoteHeader); |
590 | constexpr qptrdiff PayloadStartDelta = offsetof(QPluginMetaData::ElfNoteHeader, header); |
591 | static_assert(MinNoteSize > PayloadStartDelta); |
592 | static_assert((PayloadStartDelta & (NoteAlignment - 1)) == 0); |
593 | |
594 | QLibraryScanResult r = {}; |
595 | auto noteFinder = [&](const T::Phdr *phdr) { |
596 | if (phdr->p_type != PT_NOTE || phdr->p_align != NoteAlignment) |
597 | return true; |
598 | |
599 | // check for signed integer overflows, to avoid issues with the |
600 | // arithmetic below |
601 | if (qptrdiff(phdr->p_filesz) < 0) { |
602 | auto h = reinterpret_cast<const T::Ehdr *>(data.data()); |
603 | auto segments = reinterpret_cast<const T::Phdr *>(data.data() + h->e_phoff); |
604 | qEDebug << "segment"<< (phdr - segments) << "contains a note with size" |
605 | << Qt::hex << Qt::showbase << phdr->p_filesz |
606 | << "which is larger than half the virtual memory space"; |
607 | return true; |
608 | } |
609 | |
610 | // iterate over the notes in this segment |
611 | T::Off offset = phdr->p_offset; |
612 | const T::Off end_offset = offset + phdr->p_filesz; |
613 | while (qptrdiff(end_offset - offset) >= MinNoteSize) { |
614 | auto nhdr = reinterpret_cast<const T::Nhdr *>(data.data() + offset); |
615 | T::Word n_namesz = nhdr->n_namesz; |
616 | T::Word n_descsz = nhdr->n_descsz; |
617 | T::Word n_type = nhdr->n_type; |
618 | |
619 | // overflow check: calculate where the next note will be, if it exists |
620 | T::Off next_offset = offset; |
621 | next_offset += sizeof(T::Nhdr); // can't overflow (we checked above) |
622 | next_offset += NoteAlignment - 3; // offset is aligned, this can't overflow |
623 | if (qAddOverflow<T::Off>(v1: next_offset, v2: n_namesz, r: &next_offset)) |
624 | break; |
625 | next_offset &= -NoteAlignment; |
626 | |
627 | next_offset += NoteAlignment - 3; // offset is aligned, this can't overflow |
628 | if (qAddOverflow<T::Off>(v1: next_offset, v2: n_descsz, r: &next_offset)) |
629 | break; |
630 | next_offset &= -NoteAlignment; |
631 | if (next_offset > end_offset) |
632 | break; |
633 | |
634 | if (n_namesz == NoteNameSize && n_descsz >= MinPayloadSize |
635 | && n_type == QPluginMetaData::ElfNoteHeader::NoteType |
636 | && memcmp(s1: nhdr + 1, s2: QPluginMetaData::ElfNoteHeader::NoteName, n: NoteNameSize) == 0) { |
637 | // yes, it's our note |
638 | r.pos = offset + PayloadStartDelta; |
639 | r.length = nhdr->n_descsz; |
640 | return false; |
641 | } |
642 | offset = next_offset; |
643 | } |
644 | return true; |
645 | }; |
646 | scanProgramHeaders(data, error, f: noteFinder); |
647 | |
648 | if (!r.length) |
649 | return r; |
650 | |
651 | qEDebug << "found Qt metadata in ELF note at" |
652 | << Qt::hex << Qt::showbase << r.pos << "size"<< Qt::reset << r.length; |
653 | return r; |
654 | } |
655 | |
656 | static QLibraryScanResult scanSections(QByteArrayView data, const ErrorMaker &error) |
657 | { |
658 | auto header = reinterpret_cast<const T::Ehdr *>(data.data()); |
659 | |
660 | // in order to find the .qtmetadata section, we need to: |
661 | // a) find the section table |
662 | // it's located at offset header->e_shoff |
663 | // validate it |
664 | T::Word e_shnum = header->e_shnum; |
665 | T::Off offset = e_shnum * sizeof(T::Shdr); // can't overflow due to size of T::Half |
666 | if (qAddOverflow(v1: offset, v2: header->e_shoff, r: &offset) || offset > size_t(data.size())) |
667 | return error(QLibrary::tr(s: "section table extends past the end of the file")); |
668 | |
669 | // b) find the section entry for the section header string table (shstrab) |
670 | // it's a section whose entry is pointed by e_shstrndx |
671 | auto sections = reinterpret_cast<const T::Shdr *>(data.data() + header->e_shoff); |
672 | auto sections_end = sections + e_shnum; |
673 | auto shdr = sections + header->e_shstrndx; |
674 | |
675 | // validate the shstrtab |
676 | offset = shdr->sh_offset; |
677 | T::Off shstrtab_size = shdr->sh_size; |
678 | qEDebug << "shstrtab section is located at offset"<< offset << "size"<< shstrtab_size; |
679 | if (T::Off end; qAddOverflow<T::Off>(v1: offset, v2: shstrtab_size, r: &end) |
680 | || end > size_t(data.size())) |
681 | return error(QLibrary::tr(s: "section header string table extends past the end of the file")); |
682 | |
683 | // c) iterate over the sections to find .qtmetadata |
684 | const char *shstrtab_start = data.data() + offset; |
685 | shdr = sections; |
686 | for (int section = 0; shdr != sections_end; ++section, ++shdr) { |
687 | QLatin1StringView name; |
688 | if (shdr->sh_name < shstrtab_size) { |
689 | const char *namestart = shstrtab_start + shdr->sh_name; |
690 | size_t len = qstrnlen(str: namestart, maxlen: shstrtab_size - shdr->sh_name); |
691 | name = QLatin1StringView(namestart, len); |
692 | } |
693 | qEDebug << "section"<< section << "name"<< name << ElfSectionDebug{.shdr: shdr}; |
694 | |
695 | // sanity check the section |
696 | if (name.isNull()) |
697 | return error(QLibrary::tr(s: "a section name extends past the end of the file")); |
698 | |
699 | // sections aren't allowed to extend past the end of the file, unless |
700 | // they are NOBITS sections |
701 | if (shdr->sh_type == SHT_NOBITS) |
702 | continue; |
703 | if (T::Off end; qAddOverflow(v1: shdr->sh_offset, v2: shdr->sh_size, r: &end) |
704 | || end > size_t(data.size())) { |
705 | return error(QLibrary::tr(s: "section contents extend past the end of the file")); |
706 | } |
707 | |
708 | if (name != ".qtmetadata"_L1) |
709 | continue; |
710 | qEDebug << "found .qtmetadata section"; |
711 | if (shdr->sh_size < sizeof(QPluginMetaData::MagicHeader)) |
712 | return error(QLibrary::tr(s: ".qtmetadata section is too small")); |
713 | |
714 | if (IncludeValidityChecks) { |
715 | QByteArrayView expectedMagic = QByteArrayView::fromArray(data: QPluginMetaData::MagicString); |
716 | QByteArrayView actualMagic = data.sliced(pos: shdr->sh_offset, n: expectedMagic.size()); |
717 | if (expectedMagic != actualMagic) |
718 | return error(QLibrary::tr(s: ".qtmetadata section has incorrect magic")); |
719 | |
720 | if (shdr->sh_flags & SHF_WRITE) |
721 | return error(QLibrary::tr(s: ".qtmetadata section is writable")); |
722 | if (shdr->sh_flags & SHF_EXECINSTR) |
723 | return error(QLibrary::tr(s: ".qtmetadata section is executable")); |
724 | } |
725 | |
726 | return { .pos: qsizetype(shdr->sh_offset + sizeof(QPluginMetaData::MagicString)), |
727 | .length: qsizetype(shdr->sh_size - sizeof(QPluginMetaData::MagicString)) }; |
728 | } |
729 | |
730 | // section .qtmetadata not found |
731 | return error.notfound(); |
732 | } |
733 | |
734 | QLibraryScanResult QElfParser::parse(QByteArrayView data, QString *errMsg) |
735 | { |
736 | ErrorMaker error(errMsg); |
737 | if (size_t(data.size()) < sizeof(T::Ehdr)) { |
738 | qEDebug << "file too small:"<< size_t(data.size()); |
739 | return error(QLibrary::tr(s: "file too small")); |
740 | } |
741 | |
742 | qEDebug << ElfHeaderDebug{ .e_ident: reinterpret_cast<const uchar *>(data.data()) }; |
743 | |
744 | auto header = reinterpret_cast<const T::Ehdr *>(data.data()); |
745 | if (!ElfHeaderCheck<>::checkHeader(header: *header)) |
746 | return error(ElfHeaderCheck<>::explainCheckFailure(header: *header)); |
747 | |
748 | qEDebug << "contains"<< header->e_phnum << "program headers of" |
749 | << header->e_phentsize << "bytes at offset"<< header->e_phoff; |
750 | qEDebug << "contains"<< header->e_shnum << "sections of"<< header->e_shentsize |
751 | << "bytes at offset"<< header->e_shoff |
752 | << "; section header string table (shstrtab) is entry"<< header->e_shstrndx; |
753 | |
754 | // some sanity checks |
755 | if constexpr (IncludeValidityChecks) { |
756 | if (header->e_phentsize != sizeof(T::Phdr)) |
757 | return error(QLibrary::tr(s: "unexpected program header entry size (%1)") |
758 | .arg(a: header->e_phentsize)); |
759 | } |
760 | |
761 | if (!preScanProgramHeaders(data, error)) |
762 | return {}; |
763 | |
764 | if (QLibraryScanResult r = scanProgramHeadersForNotes(data, error); r.length) |
765 | return r; |
766 | |
767 | if (!ElfNotesAreMandatory) { |
768 | if constexpr (IncludeValidityChecks) { |
769 | if (header->e_shentsize != sizeof(T::Shdr)) |
770 | return error(QLibrary::tr(s: "unexpected section entry size (%1)") |
771 | .arg(a: header->e_shentsize)); |
772 | } |
773 | if (header->e_shoff == 0 || header->e_shnum == 0) { |
774 | // this is still a valid ELF file but we don't have a section table |
775 | qEDebug << "no section table present, not able to find Qt metadata"; |
776 | return error.notfound(); |
777 | } |
778 | |
779 | if (header->e_shnum && header->e_shstrndx >= header->e_shnum) |
780 | return error(QLibrary::tr(s: "e_shstrndx greater than the number of sections e_shnum (%1 >= %2)") |
781 | .arg(a: header->e_shstrndx).arg(a: header->e_shnum)); |
782 | return scanSections(data, error); |
783 | } |
784 | return error.notfound(); |
785 | } |
786 | |
787 | QT_END_NAMESPACE |
788 | |
789 | #endif // Q_OF_ELF |
790 |
Definitions
- ElfNotesAreMandatory
- IncludeValidityChecks
- lcElfParser
- ElfEndianTraits
- DataOrder
- fromEndian
- ElfEndianTraits
- DataOrder
- fromEndian
- ElfTypeTraits
- Class
- ElfTypeTraits
- Class
- ElfMachineCheck
- ElfHeaderCommonCheck
- checkElfMagic
- checkElfVersion
- CommonHeader
- ElfHeaderCheck
- checkClass
- checkDataOrder
- checkOsAbi
- checkAbiVersion
- checkPadding
- checkIdent
- checkType
- checkMachine
- checkFileVersion
- checkHeader
- explainCheckFailure
- extractCommonHeader
- ElfHeaderDebug
- operator<<
- ElfSectionDebug
- operator<<
- ElfProgramDebug
- operator<<
- ErrorMaker
- ErrorMaker
- operator()
- notplugin
- notfound
- scanProgramHeaders
- preScanProgramHeaders
- scanProgramHeadersForNotes
- scanSections
Learn to use CMake with our Intro Training
Find out more