]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/pe.c
Merge pull request #24628 from medhefgo/boot-sections
[thirdparty/systemd.git] / src / boot / efi / pe.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <efi.h>
4 #include <efilib.h>
5
6 #include "missing_efi.h"
7 #include "pe.h"
8 #include "util.h"
9
10 #define DOS_FILE_MAGIC "MZ"
11 #define PE_FILE_MAGIC "PE\0\0"
12 #define MAX_SECTIONS 96
13
14 #if defined(__i386__)
15 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_IA32
16 # define TARGET_MACHINE_TYPE_COMPATIBILITY EFI_IMAGE_MACHINE_X64
17 #elif defined(__x86_64__)
18 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_X64
19 #elif defined(__aarch64__)
20 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_AARCH64
21 #elif defined(__arm__)
22 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_ARMTHUMB_MIXED
23 #elif defined(__riscv) && __riscv_xlen == 64
24 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_RISCV64
25 #else
26 # error Unknown EFI arch
27 #endif
28
29 #ifndef TARGET_MACHINE_TYPE_COMPATIBILITY
30 # define TARGET_MACHINE_TYPE_COMPATIBILITY 0
31 #endif
32
33 typedef struct DosFileHeader {
34 uint8_t Magic[2];
35 uint16_t LastSize;
36 uint16_t nBlocks;
37 uint16_t nReloc;
38 uint16_t HdrSize;
39 uint16_t MinAlloc;
40 uint16_t MaxAlloc;
41 uint16_t ss;
42 uint16_t sp;
43 uint16_t Checksum;
44 uint16_t ip;
45 uint16_t cs;
46 uint16_t RelocPos;
47 uint16_t nOverlay;
48 uint16_t reserved[4];
49 uint16_t OEMId;
50 uint16_t OEMInfo;
51 uint16_t reserved2[10];
52 uint32_t ExeHeader;
53 } _packed_ DosFileHeader;
54
55 typedef struct CoffFileHeader {
56 uint16_t Machine;
57 uint16_t NumberOfSections;
58 uint32_t TimeDateStamp;
59 uint32_t PointerToSymbolTable;
60 uint32_t NumberOfSymbols;
61 uint16_t SizeOfOptionalHeader;
62 uint16_t Characteristics;
63 } _packed_ CoffFileHeader;
64
65 #define OPTHDR32_MAGIC 0x10B /* PE32 OptionalHeader */
66 #define OPTHDR64_MAGIC 0x20B /* PE32+ OptionalHeader */
67
68 typedef struct PeOptionalHeader {
69 uint16_t Magic;
70 uint8_t LinkerMajor;
71 uint8_t LinkerMinor;
72 uint32_t SizeOfCode;
73 uint32_t SizeOfInitializedData;
74 uint32_t SizeOfUninitializeData;
75 uint32_t AddressOfEntryPoint;
76 uint32_t BaseOfCode;
77 union {
78 struct { /* PE32 */
79 uint32_t BaseOfData;
80 uint32_t ImageBase32;
81 };
82 uint64_t ImageBase64; /* PE32+ */
83 };
84 uint32_t SectionAlignment;
85 uint32_t FileAlignment;
86 uint16_t MajorOperatingSystemVersion;
87 uint16_t MinorOperatingSystemVersion;
88 uint16_t MajorImageVersion;
89 uint16_t MinorImageVersion;
90 uint16_t MajorSubsystemVersion;
91 uint16_t MinorSubsystemVersion;
92 uint32_t Win32VersionValue;
93 uint32_t SizeOfImage;
94 uint32_t SizeOfHeaders;
95 uint32_t CheckSum;
96 uint16_t Subsystem;
97 uint16_t DllCharacteristics;
98 /* fields with different sizes for 32/64 omitted */
99 } _packed_ PeOptionalHeader;
100
101 typedef struct PeFileHeader {
102 uint8_t Magic[4];
103 CoffFileHeader FileHeader;
104 PeOptionalHeader OptionalHeader;
105 } _packed_ PeFileHeader;
106
107 typedef struct PeSectionHeader {
108 uint8_t Name[8];
109 uint32_t VirtualSize;
110 uint32_t VirtualAddress;
111 uint32_t SizeOfRawData;
112 uint32_t PointerToRawData;
113 uint32_t PointerToRelocations;
114 uint32_t PointerToLinenumbers;
115 uint16_t NumberOfRelocations;
116 uint16_t NumberOfLinenumbers;
117 uint32_t Characteristics;
118 } _packed_ PeSectionHeader;
119
120 static inline bool verify_dos(const DosFileHeader *dos) {
121 assert(dos);
122 return memcmp(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0;
123 }
124
125 static inline bool verify_pe(const PeFileHeader *pe, bool allow_compatibility) {
126 assert(pe);
127 return memcmp(pe->Magic, PE_FILE_MAGIC, STRLEN(PE_FILE_MAGIC)) == 0 &&
128 (pe->FileHeader.Machine == TARGET_MACHINE_TYPE ||
129 (allow_compatibility && pe->FileHeader.Machine == TARGET_MACHINE_TYPE_COMPATIBILITY)) &&
130 pe->FileHeader.NumberOfSections > 0 &&
131 pe->FileHeader.NumberOfSections <= MAX_SECTIONS &&
132 IN_SET(pe->OptionalHeader.Magic, OPTHDR32_MAGIC, OPTHDR64_MAGIC);
133 }
134
135 static inline UINTN section_table_offset(const DosFileHeader *dos, const PeFileHeader *pe) {
136 assert(dos);
137 assert(pe);
138 return dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader;
139 }
140
141 static void locate_sections(
142 const PeSectionHeader section_table[],
143 UINTN n_table,
144 const char * const sections[],
145 UINTN *offsets,
146 UINTN *sizes,
147 bool in_memory) {
148
149 assert(section_table);
150 assert(sections);
151 assert(offsets);
152 assert(sizes);
153
154 size_t prev_section_addr = 0;
155
156 for (UINTN i = 0; i < n_table; i++) {
157 const PeSectionHeader *sect = section_table + i;
158
159 if (in_memory) {
160 if (prev_section_addr > sect->VirtualAddress)
161 log_error_stall(u"Overlapping PE sections detected. Boot may fail due to image memory corruption!");
162 prev_section_addr = sect->VirtualAddress + sect->VirtualSize;
163 }
164
165 for (UINTN j = 0; sections[j]; j++) {
166 if (memcmp(sect->Name, sections[j], strlen8(sections[j])) != 0)
167 continue;
168
169 offsets[j] = in_memory ? sect->VirtualAddress : sect->PointerToRawData;
170 sizes[j] = sect->VirtualSize;
171 }
172 }
173 }
174
175 static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) {
176 UINTN addr = 0, size = 0;
177 static const char *sections[] = { ".compat", NULL };
178
179 /* The kernel may provide alternative PE entry points for different PE architectures. This allows
180 * booting a 64bit kernel on 32bit EFI that is otherwise running on a 64bit CPU. The locations of any
181 * such compat entry points are located in a special PE section. */
182
183 locate_sections((const PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)),
184 pe->FileHeader.NumberOfSections,
185 sections,
186 &addr,
187 &size,
188 /*in_memory=*/true);
189
190 if (size == 0)
191 return 0;
192
193 typedef struct {
194 uint8_t type;
195 uint8_t size;
196 uint16_t machine_type;
197 uint32_t entry_point;
198 } _packed_ LinuxPeCompat1;
199
200 while (size >= sizeof(LinuxPeCompat1) && addr % __alignof__(LinuxPeCompat1) == 0) {
201 LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((uint8_t *) dos + addr);
202
203 if (compat->type == 0 || compat->size == 0 || compat->size > size)
204 break;
205
206 if (compat->type == 1 &&
207 compat->size >= sizeof(LinuxPeCompat1) &&
208 compat->machine_type == TARGET_MACHINE_TYPE)
209 return compat->entry_point;
210
211 addr += compat->size;
212 size -= compat->size;
213 }
214
215 return 0;
216 }
217
218 EFI_STATUS pe_kernel_info(
219 const void *base,
220 uint32_t *ret_entry_point_address,
221 uint32_t *ret_size_of_image,
222 uint32_t *ret_section_alignment) {
223
224 const DosFileHeader *dos;
225 const PeFileHeader *pe;
226
227 assert(base);
228 assert(ret_entry_point_address);
229
230 dos = (const DosFileHeader *) base;
231 if (!verify_dos(dos))
232 return EFI_LOAD_ERROR;
233
234 pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader);
235 if (!verify_pe(pe, /* allow_compatibility= */ true))
236 return EFI_LOAD_ERROR;
237
238 /* Support for LINUX_INITRD_MEDIA_GUID was added in kernel stub 1.0. */
239 if (pe->OptionalHeader.MajorImageVersion < 1)
240 return EFI_UNSUPPORTED;
241
242 uint32_t entry_address = pe->OptionalHeader.AddressOfEntryPoint;
243
244 /* Look for a compat entry point. */
245 if (pe->FileHeader.Machine != TARGET_MACHINE_TYPE) {
246 entry_address = get_compatibility_entry_address(dos, pe);
247 if (entry_address == 0)
248 /* Image type not supported and no compat entry found. */
249 return EFI_UNSUPPORTED;
250 }
251
252 *ret_entry_point_address = entry_address;
253 if (ret_size_of_image)
254 *ret_size_of_image = pe->OptionalHeader.SizeOfImage;
255 if (ret_section_alignment)
256 *ret_section_alignment = pe->OptionalHeader.SectionAlignment;
257 return EFI_SUCCESS;
258 }
259
260 EFI_STATUS pe_memory_locate_sections(const void *base, const char * const sections[], UINTN *addrs, UINTN *sizes) {
261 const DosFileHeader *dos;
262 const PeFileHeader *pe;
263 UINTN offset;
264
265 assert(base);
266 assert(sections);
267 assert(addrs);
268 assert(sizes);
269
270 dos = (const DosFileHeader *) base;
271 if (!verify_dos(dos))
272 return EFI_LOAD_ERROR;
273
274 pe = (const PeFileHeader *) ((uint8_t *) base + dos->ExeHeader);
275 if (!verify_pe(pe, /* allow_compatibility= */ false))
276 return EFI_LOAD_ERROR;
277
278 offset = section_table_offset(dos, pe);
279 locate_sections((PeSectionHeader *) ((uint8_t *) base + offset),
280 pe->FileHeader.NumberOfSections,
281 sections,
282 addrs,
283 sizes,
284 /*in_memory=*/true);
285
286 return EFI_SUCCESS;
287 }
288
289 EFI_STATUS pe_file_locate_sections(
290 EFI_FILE *dir,
291 const char16_t *path,
292 const char * const sections[],
293 UINTN *offsets,
294 UINTN *sizes) {
295 _cleanup_free_ PeSectionHeader *section_table = NULL;
296 _cleanup_(file_closep) EFI_FILE *handle = NULL;
297 DosFileHeader dos;
298 PeFileHeader pe;
299 UINTN len, section_table_len;
300 EFI_STATUS err;
301
302 assert(dir);
303 assert(path);
304 assert(sections);
305 assert(offsets);
306 assert(sizes);
307
308 err = dir->Open(dir, &handle, (char16_t *) path, EFI_FILE_MODE_READ, 0ULL);
309 if (err != EFI_SUCCESS)
310 return err;
311
312 len = sizeof(dos);
313 err = handle->Read(handle, &len, &dos);
314 if (err != EFI_SUCCESS)
315 return err;
316 if (len != sizeof(dos) || !verify_dos(&dos))
317 return EFI_LOAD_ERROR;
318
319 err = handle->SetPosition(handle, dos.ExeHeader);
320 if (err != EFI_SUCCESS)
321 return err;
322
323 len = sizeof(pe);
324 err = handle->Read(handle, &len, &pe);
325 if (err != EFI_SUCCESS)
326 return err;
327 if (len != sizeof(pe) || !verify_pe(&pe, /* allow_compatibility= */ false))
328 return EFI_LOAD_ERROR;
329
330 section_table_len = pe.FileHeader.NumberOfSections * sizeof(PeSectionHeader);
331 section_table = xmalloc(section_table_len);
332 if (!section_table)
333 return EFI_OUT_OF_RESOURCES;
334
335 err = handle->SetPosition(handle, section_table_offset(&dos, &pe));
336 if (err != EFI_SUCCESS)
337 return err;
338
339 len = section_table_len;
340 err = handle->Read(handle, &len, section_table);
341 if (err != EFI_SUCCESS)
342 return err;
343 if (len != section_table_len)
344 return EFI_LOAD_ERROR;
345
346 locate_sections(section_table, pe.FileHeader.NumberOfSections,
347 sections, offsets, sizes, /*in_memory=*/false);
348
349 return EFI_SUCCESS;
350 }