]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/pe.c
32d0c6650351a40f99b5bbeaf32079cc0862fea0
[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 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_;
54
55 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_;
64
65 #define OPTHDR32_MAGIC 0x10B /* PE32 OptionalHeader */
66 #define OPTHDR64_MAGIC 0x20B /* PE32+ OptionalHeader */
67
68 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_;
100
101 struct PeFileHeader {
102 uint8_t Magic[4];
103 struct CoffFileHeader FileHeader;
104 struct PeOptionalHeader OptionalHeader;
105 } _packed_;
106
107 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_;
119
120 static inline BOOLEAN verify_dos(const struct DosFileHeader *dos) {
121 assert(dos);
122 return memcmp(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0;
123 }
124
125 static inline BOOLEAN verify_pe(const struct PeFileHeader *pe, BOOLEAN 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 struct DosFileHeader *dos, const struct PeFileHeader *pe) {
136 assert(dos);
137 assert(pe);
138 return dos->ExeHeader + offsetof(struct PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader;
139 }
140
141 static void locate_sections(
142 const struct PeSectionHeader section_table[],
143 UINTN n_table,
144 const CHAR8 **sections,
145 UINTN *addrs,
146 UINTN *offsets,
147 UINTN *sizes) {
148
149 assert(section_table);
150 assert(sections);
151 assert(sizes);
152
153 for (UINTN i = 0; i < n_table; i++) {
154 const struct PeSectionHeader *sect = section_table + i;
155
156 for (UINTN j = 0; sections[j]; j++) {
157 if (memcmp(sect->Name, sections[j], strlen8((const char *) sections[j])) != 0)
158 continue;
159
160 if (addrs)
161 addrs[j] = sect->VirtualAddress;
162 if (offsets)
163 offsets[j] = sect->PointerToRawData;
164 sizes[j] = sect->VirtualSize;
165 }
166 }
167 }
168
169 static uint32_t get_compatibility_entry_address(const struct DosFileHeader *dos, const struct PeFileHeader *pe) {
170 UINTN addr = 0, size = 0;
171 static const CHAR8 *sections[] = { (CHAR8 *) ".compat", NULL };
172
173 /* The kernel may provide alternative PE entry points for different PE architectures. This allows
174 * booting a 64bit kernel on 32bit EFI that is otherwise running on a 64bit CPU. The locations of any
175 * such compat entry points are located in a special PE section. */
176
177 locate_sections((const struct PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)),
178 pe->FileHeader.NumberOfSections,
179 sections,
180 &addr,
181 NULL,
182 &size);
183
184 if (size == 0)
185 return 0;
186
187 typedef struct {
188 uint8_t type;
189 uint8_t size;
190 uint16_t machine_type;
191 uint32_t entry_point;
192 } _packed_ LinuxPeCompat1;
193
194 while (size >= sizeof(LinuxPeCompat1) && addr % __alignof__(LinuxPeCompat1) == 0) {
195 LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((uint8_t *) dos + addr);
196
197 if (compat->type == 0 || compat->size == 0 || compat->size > size)
198 break;
199
200 if (compat->type == 1 &&
201 compat->size >= sizeof(LinuxPeCompat1) &&
202 compat->machine_type == TARGET_MACHINE_TYPE)
203 return compat->entry_point;
204
205 addr += compat->size;
206 size -= compat->size;
207 }
208
209 return 0;
210 }
211
212 EFI_STATUS pe_alignment_info(
213 const void *base,
214 uint32_t *ret_entry_point_address,
215 uint32_t *ret_size_of_image,
216 uint32_t *ret_section_alignment) {
217
218 const struct DosFileHeader *dos;
219 const struct PeFileHeader *pe;
220
221 assert(base);
222 assert(ret_entry_point_address);
223
224 dos = (const struct DosFileHeader *) base;
225 if (!verify_dos(dos))
226 return EFI_LOAD_ERROR;
227
228 pe = (const struct PeFileHeader*) ((const uint8_t *)base + dos->ExeHeader);
229 if (!verify_pe(pe, /* allow_compatibility= */ TRUE))
230 return EFI_LOAD_ERROR;
231
232 uint32_t entry_address = pe->OptionalHeader.AddressOfEntryPoint;
233
234 /* Look for a compat entry point. */
235 if (pe->FileHeader.Machine != TARGET_MACHINE_TYPE) {
236 entry_address = get_compatibility_entry_address(dos, pe);
237 if (entry_address == 0)
238 /* Image type not supported and no compat entry found. */
239 return EFI_UNSUPPORTED;
240 }
241
242 *ret_entry_point_address = entry_address;
243 if (ret_size_of_image)
244 *ret_size_of_image = pe->OptionalHeader.SizeOfImage;
245 if (ret_section_alignment)
246 *ret_section_alignment = pe->OptionalHeader.SectionAlignment;
247 return EFI_SUCCESS;
248 }
249
250 EFI_STATUS pe_memory_locate_sections(
251 const CHAR8 *base,
252 const CHAR8 **sections,
253 UINTN *addrs,
254 UINTN *sizes) {
255 const struct DosFileHeader *dos;
256 const struct PeFileHeader *pe;
257 UINTN offset;
258
259 assert(base);
260 assert(sections);
261 assert(addrs);
262 assert(sizes);
263
264 dos = (const struct DosFileHeader*)base;
265 if (!verify_dos(dos))
266 return EFI_LOAD_ERROR;
267
268 pe = (const struct PeFileHeader*)&base[dos->ExeHeader];
269 if (!verify_pe(pe, /* allow_compatibility= */ FALSE))
270 return EFI_LOAD_ERROR;
271
272 offset = section_table_offset(dos, pe);
273 locate_sections((struct PeSectionHeader*)&base[offset], pe->FileHeader.NumberOfSections,
274 sections, addrs, NULL, sizes);
275
276 return EFI_SUCCESS;
277 }
278
279 EFI_STATUS pe_file_locate_sections(
280 EFI_FILE *dir,
281 const char16_t *path,
282 const CHAR8 **sections,
283 UINTN *offsets,
284 UINTN *sizes) {
285 _cleanup_freepool_ struct PeSectionHeader *section_table = NULL;
286 _cleanup_(file_closep) EFI_FILE *handle = NULL;
287 struct DosFileHeader dos;
288 struct PeFileHeader pe;
289 UINTN len, section_table_len;
290 EFI_STATUS err;
291
292 assert(dir);
293 assert(path);
294 assert(sections);
295 assert(offsets);
296 assert(sizes);
297
298 err = dir->Open(dir, &handle, (char16_t *) path, EFI_FILE_MODE_READ, 0ULL);
299 if (err != EFI_SUCCESS)
300 return err;
301
302 len = sizeof(dos);
303 err = handle->Read(handle, &len, &dos);
304 if (err != EFI_SUCCESS)
305 return err;
306 if (len != sizeof(dos) || !verify_dos(&dos))
307 return EFI_LOAD_ERROR;
308
309 err = handle->SetPosition(handle, dos.ExeHeader);
310 if (err != EFI_SUCCESS)
311 return err;
312
313 len = sizeof(pe);
314 err = handle->Read(handle, &len, &pe);
315 if (err != EFI_SUCCESS)
316 return err;
317 if (len != sizeof(pe) || !verify_pe(&pe, /* allow_compatibility= */ FALSE))
318 return EFI_LOAD_ERROR;
319
320 section_table_len = pe.FileHeader.NumberOfSections * sizeof(struct PeSectionHeader);
321 section_table = xmalloc(section_table_len);
322 if (!section_table)
323 return EFI_OUT_OF_RESOURCES;
324
325 err = handle->SetPosition(handle, section_table_offset(&dos, &pe));
326 if (err != EFI_SUCCESS)
327 return err;
328
329 len = section_table_len;
330 err = handle->Read(handle, &len, section_table);
331 if (err != EFI_SUCCESS)
332 return err;
333 if (len != section_table_len)
334 return EFI_LOAD_ERROR;
335
336 locate_sections(section_table, pe.FileHeader.NumberOfSections,
337 sections, NULL, offsets, sizes);
338
339 return EFI_SUCCESS;
340 }