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