]> git.proxmox.com Git - efi-boot-shim.git/blob - post-process-pe.c
Merge branch 'fixes20240429' into 'master'
[efi-boot-shim.git] / post-process-pe.c
1 // SPDX-License-Identifier: BSD-2-Clause-Patent
2 /*
3 * post-process-pe.c - fix up timestamps and checksums in broken PE files
4 * Copyright Peter Jones <pjones@redhat.com>
5 */
6
7 #define _GNU_SOURCE 1
8
9 #include <err.h>
10 #include <fcntl.h>
11 #include <getopt.h>
12 #include <inttypes.h>
13 #include <limits.h>
14 #include <stdbool.h>
15 #include <stdint.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/mman.h>
20 #include <sys/param.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24
25 #ifndef PAGE_SIZE
26 #define PAGE_SIZE 4096
27 #endif
28
29 static int verbosity;
30 #define ERROR 0
31 #define WARNING 1
32 #define INFO 2
33 #define NOISE 3
34 #define MIN_VERBOSITY ERROR
35 #define MAX_VERBOSITY NOISE
36 #define debug(level, ...) \
37 ({ \
38 if (verbosity >= (level)) { \
39 printf("%s():%d: ", __func__, __LINE__); \
40 printf(__VA_ARGS__); \
41 } \
42 0; \
43 })
44
45 static bool set_nx_compat = false;
46
47 typedef uint8_t UINT8;
48 typedef uint16_t UINT16;
49 typedef uint32_t UINT32;
50 typedef uint64_t UINT64;
51
52 typedef uint16_t CHAR16;
53
54 typedef unsigned long UINTN;
55
56 typedef struct {
57 UINT32 Data1;
58 UINT16 Data2;
59 UINT16 Data3;
60 UINT8 Data4[8];
61 } EFI_GUID;
62
63 #include "include/peimage.h"
64
65 #if defined(__GNUC__) && defined(__GNUC_MINOR__)
66 #define GNUC_PREREQ(maj, min) \
67 ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
68 #else
69 #define GNUC_PREREQ(maj, min) 0
70 #endif
71
72 #if defined(__clang__) && defined(__clang_major__) && defined(__clang_minor__)
73 #define CLANG_PREREQ(maj, min) \
74 ((__clang_major__ > (maj)) || \
75 (__clang_major__ == (maj) && __clang_minor__ >= (min)))
76 #else
77 #define CLANG_PREREQ(maj, min) 0
78 #endif
79
80 #if GNUC_PREREQ(5, 1) || CLANG_PREREQ(3, 8)
81 #define add(a0, a1, s) __builtin_add_overflow(a0, a1, s)
82 #define sub(s0, s1, d) __builtin_sub_overflow(s0, s1, d)
83 #define mul(f0, f1, p) __builtin_mul_overflow(f0, f1, p)
84 #else
85 #define add(a0, a1, s) \
86 ({ \
87 (*s) = ((a0) + (a1)); \
88 0; \
89 })
90 #define sub(s0, s1, d) \
91 ({ \
92 (*d) = ((s0) - (s1)); \
93 0; \
94 })
95 #define mul(f0, f1, p) \
96 ({ \
97 (*p) = ((f0) * (f1)); \
98 0; \
99 })
100 #endif
101 #define div(d0, d1, q) \
102 ({ \
103 unsigned int ret_ = ((d1) == 0); \
104 if (ret_ == 0) \
105 (*q) = ((d0) / (d1)); \
106 ret_; \
107 })
108
109 static int
110 image_is_64_bit(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr)
111 {
112 /* .Magic is the same offset in all cases */
113 if (PEHdr->Pe32Plus.OptionalHeader.Magic ==
114 EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC)
115 return 1;
116 return 0;
117 }
118
119 static void
120 load_pe(const char *const file, void *const data, const size_t datasize,
121 PE_COFF_LOADER_IMAGE_CONTEXT *ctx)
122 {
123 EFI_IMAGE_DOS_HEADER *DOSHdr = data;
124 EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data;
125 size_t HeaderWithoutDataDir, SectionHeaderOffset, OptHeaderSize;
126 size_t FileAlignment = 0;
127 size_t sz0 = 0, sz1 = 0;
128 uintptr_t loc = 0;
129
130 debug(NOISE, "datasize:%zu sizeof(PEHdr->Pe32):%zu\n", datasize,
131 sizeof(PEHdr->Pe32));
132 if (datasize < sizeof(PEHdr->Pe32))
133 errx(1, "%s: Invalid image size %zu (%zu < %zu)", file,
134 datasize, datasize, sizeof(PEHdr->Pe32));
135
136 debug(NOISE,
137 "DOSHdr->e_magic:0x%02hx EFI_IMAGE_DOS_SIGNATURE:0x%02hx\n",
138 DOSHdr->e_magic, EFI_IMAGE_DOS_SIGNATURE);
139 if (DOSHdr->e_magic != EFI_IMAGE_DOS_SIGNATURE)
140 errx(1,
141 "%s: Invalid DOS header signature 0x%04hx (expected 0x%04hx)",
142 file, DOSHdr->e_magic, EFI_IMAGE_DOS_SIGNATURE);
143
144 debug(NOISE, "DOSHdr->e_lfanew:%u datasize:%zu\n", DOSHdr->e_lfanew,
145 datasize);
146 if (DOSHdr->e_lfanew >= datasize ||
147 add((uintptr_t)data, DOSHdr->e_lfanew, &loc))
148 errx(1, "%s: invalid pe header location", file);
149
150 ctx->PEHdr = PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)loc;
151 debug(NOISE, "PE signature:0x%04x EFI_IMAGE_NT_SIGNATURE:0x%04x\n",
152 PEHdr->Pe32.Signature, EFI_IMAGE_NT_SIGNATURE);
153 if (PEHdr->Pe32.Signature != EFI_IMAGE_NT_SIGNATURE)
154 errx(1, "%s: Unsupported image type", file);
155
156 if (image_is_64_bit(PEHdr)) {
157 debug(NOISE, "image is 64bit\n");
158 ctx->NumberOfRvaAndSizes =
159 PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes;
160 ctx->SizeOfHeaders =
161 PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders;
162 ctx->ImageSize = PEHdr->Pe32Plus.OptionalHeader.SizeOfImage;
163 ctx->SectionAlignment =
164 PEHdr->Pe32Plus.OptionalHeader.SectionAlignment;
165 FileAlignment = PEHdr->Pe32Plus.OptionalHeader.FileAlignment;
166 OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER64);
167 } else {
168 debug(NOISE, "image is 32bit\n");
169 ctx->NumberOfRvaAndSizes =
170 PEHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes;
171 ctx->SizeOfHeaders = PEHdr->Pe32.OptionalHeader.SizeOfHeaders;
172 ctx->ImageSize = (UINT64)PEHdr->Pe32.OptionalHeader.SizeOfImage;
173 ctx->SectionAlignment =
174 PEHdr->Pe32.OptionalHeader.SectionAlignment;
175 FileAlignment = PEHdr->Pe32.OptionalHeader.FileAlignment;
176 OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32);
177 }
178
179 if (FileAlignment % 2 != 0)
180 errx(1, "%s: Invalid file alignment %zu", file, FileAlignment);
181
182 if (FileAlignment == 0)
183 FileAlignment = 0x200;
184 if (ctx->SectionAlignment == 0)
185 ctx->SectionAlignment = PAGE_SIZE;
186 if (ctx->SectionAlignment < FileAlignment)
187 ctx->SectionAlignment = FileAlignment;
188
189 ctx->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections;
190
191 debug(NOISE,
192 "Number of RVAs:%"PRIu64" EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES:%d\n",
193 ctx->NumberOfRvaAndSizes, EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES);
194 if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < ctx->NumberOfRvaAndSizes)
195 errx(1, "%s: invalid number of RVAs (%lu entries, max is %d)",
196 file, (unsigned long)ctx->NumberOfRvaAndSizes,
197 EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES);
198
199 if (mul(sizeof(EFI_IMAGE_DATA_DIRECTORY),
200 EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES, &sz0) ||
201 sub(OptHeaderSize, sz0, &HeaderWithoutDataDir) ||
202 sub(PEHdr->Pe32.FileHeader.SizeOfOptionalHeader,
203 HeaderWithoutDataDir, &sz0) ||
204 mul(ctx->NumberOfRvaAndSizes, sizeof(EFI_IMAGE_DATA_DIRECTORY),
205 &sz1) ||
206 (sz0 != sz1)) {
207 if (mul(sizeof(EFI_IMAGE_DATA_DIRECTORY),
208 EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES, &sz0))
209 debug(ERROR,
210 "sizeof(EFI_IMAGE_DATA_DIRECTORY) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES overflows\n");
211 else
212 debug(ERROR,
213 "sizeof(EFI_IMAGE_DATA_DIRECTORY) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES = %zu\n",
214 sz0);
215 if (sub(OptHeaderSize, sz0, &HeaderWithoutDataDir))
216 debug(ERROR,
217 "OptHeaderSize (%zu) - HeaderWithoutDataDir (%zu) overflows\n",
218 OptHeaderSize, HeaderWithoutDataDir);
219 else
220 debug(ERROR,
221 "OptHeaderSize (%zu) - HeaderWithoutDataDir (%zu) = %zu\n",
222 OptHeaderSize, sz0, HeaderWithoutDataDir);
223
224 if (sub(PEHdr->Pe32.FileHeader.SizeOfOptionalHeader,
225 HeaderWithoutDataDir, &sz0)) {
226 debug(ERROR,
227 "PEHdr->Pe32.FileHeader.SizeOfOptionalHeader (%d) - %zu overflows\n",
228 PEHdr->Pe32.FileHeader.SizeOfOptionalHeader,
229 HeaderWithoutDataDir);
230 } else {
231 debug(ERROR,
232 "PEHdr->Pe32.FileHeader.SizeOfOptionalHeader (%d)- %zu = %zu\n",
233 PEHdr->Pe32.FileHeader.SizeOfOptionalHeader,
234 HeaderWithoutDataDir, sz0);
235 }
236 if (mul(ctx->NumberOfRvaAndSizes,
237 sizeof(EFI_IMAGE_DATA_DIRECTORY), &sz1))
238 debug(ERROR,
239 "ctx->NumberOfRvaAndSizes (%ld) * sizeof(EFI_IMAGE_DATA_DIRECTORY) overflows\n",
240 (unsigned long)ctx->NumberOfRvaAndSizes);
241 else
242 debug(ERROR,
243 "ctx->NumberOfRvaAndSizes (%ld) * sizeof(EFI_IMAGE_DATA_DIRECTORY) = %zu\n",
244 (unsigned long)ctx->NumberOfRvaAndSizes, sz1);
245 debug(ERROR,
246 "space after image header:%zu data directory size:%zu\n",
247 sz0, sz1);
248
249 errx(1, "%s: image header overflows data directory", file);
250 }
251
252 if (add(DOSHdr->e_lfanew, sizeof(UINT32), &SectionHeaderOffset) ||
253 add(SectionHeaderOffset, sizeof(EFI_IMAGE_FILE_HEADER),
254 &SectionHeaderOffset) ||
255 add(SectionHeaderOffset,
256 PEHdr->Pe32.FileHeader.SizeOfOptionalHeader,
257 &SectionHeaderOffset)) {
258 debug(ERROR, "SectionHeaderOffset:%" PRIu32 " + %zu + %zu + %d",
259 DOSHdr->e_lfanew, sizeof(UINT32),
260 sizeof(EFI_IMAGE_FILE_HEADER),
261 PEHdr->Pe32.FileHeader.SizeOfOptionalHeader);
262 errx(1, "%s: SectionHeaderOffset would overflow", file);
263 }
264
265 if (sub(ctx->ImageSize, SectionHeaderOffset, &sz0) ||
266 div(sz0, EFI_IMAGE_SIZEOF_SECTION_HEADER, &sz0) ||
267 (sz0 <= ctx->NumberOfSections)) {
268 debug(ERROR, "(%" PRIu64 " - %zu) / %d > %d\n", ctx->ImageSize,
269 SectionHeaderOffset, EFI_IMAGE_SIZEOF_SECTION_HEADER,
270 ctx->NumberOfSections);
271 errx(1, "%s: image sections overflow image size", file);
272 }
273
274 if (sub(ctx->SizeOfHeaders, SectionHeaderOffset, &sz0) ||
275 div(sz0, EFI_IMAGE_SIZEOF_SECTION_HEADER, &sz0) ||
276 (sz0 < ctx->NumberOfSections)) {
277 debug(ERROR, "(%zu - %zu) / %d >= %d\n", (size_t)ctx->SizeOfHeaders,
278 SectionHeaderOffset, EFI_IMAGE_SIZEOF_SECTION_HEADER,
279 ctx->NumberOfSections);
280 errx(1, "%s: image sections overflow section headers", file);
281 }
282
283 if (sub((uintptr_t)PEHdr, (uintptr_t)data, &sz0) ||
284 add(sz0, sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION), &sz0) ||
285 (sz0 > datasize)) {
286 errx(1, "%s: PE Image size %zu > %zu", file, sz0, datasize);
287 }
288
289 if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED)
290 errx(1, "%s: Unsupported image - Relocations have been stripped", file);
291
292 if (image_is_64_bit(PEHdr)) {
293 ctx->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase;
294 ctx->EntryPoint =
295 PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint;
296 ctx->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory
297 [EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
298 ctx->SecDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory
299 [EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
300 } else {
301 ctx->ImageAddress = PEHdr->Pe32.OptionalHeader.ImageBase;
302 ctx->EntryPoint =
303 PEHdr->Pe32.OptionalHeader.AddressOfEntryPoint;
304 ctx->RelocDir = &PEHdr->Pe32.OptionalHeader.DataDirectory
305 [EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
306 ctx->SecDir = &PEHdr->Pe32.OptionalHeader.DataDirectory
307 [EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
308 }
309
310 if (add((uintptr_t)PEHdr, PEHdr->Pe32.FileHeader.SizeOfOptionalHeader,
311 &loc) ||
312 add(loc, sizeof(UINT32), &loc) ||
313 add(loc, sizeof(EFI_IMAGE_FILE_HEADER), &loc))
314 errx(1, "%s: invalid location for first section", file);
315
316 ctx->FirstSection = (EFI_IMAGE_SECTION_HEADER *)loc;
317
318 if (ctx->ImageSize < ctx->SizeOfHeaders)
319 errx(1,
320 "%s: Image size %"PRIu64" is smaller than header size %lu",
321 file, ctx->ImageSize, ctx->SizeOfHeaders);
322
323 if (sub((uintptr_t)ctx->SecDir, (uintptr_t)data, &sz0) ||
324 sub(datasize, sizeof(EFI_IMAGE_DATA_DIRECTORY), &sz1) ||
325 sz0 > sz1)
326 errx(1,
327 "%s: security direcory offset %zu past data directory at %zu",
328 file, sz0, sz1);
329
330 if (ctx->SecDir->VirtualAddress > datasize ||
331 (ctx->SecDir->VirtualAddress == datasize &&
332 ctx->SecDir->Size > 0))
333 errx(1, "%s: Security directory extends past end", file);
334 }
335
336 static void
337 set_dll_characteristics(PE_COFF_LOADER_IMAGE_CONTEXT *ctx)
338 {
339 uint16_t oldflags, newflags;
340
341 if (image_is_64_bit(ctx->PEHdr)) {
342 oldflags = ctx->PEHdr->Pe32Plus.OptionalHeader.DllCharacteristics;
343 } else {
344 oldflags = ctx->PEHdr->Pe32.OptionalHeader.DllCharacteristics;
345 }
346
347 if (set_nx_compat)
348 newflags = oldflags | EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT;
349 else
350 newflags = oldflags & ~(uint16_t)EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT;
351 if (oldflags == newflags)
352 return;
353
354 debug(INFO, "Updating DLL Characteristics from 0x%04hx to 0x%04hx\n",
355 oldflags, newflags);
356 if (image_is_64_bit(ctx->PEHdr)) {
357 ctx->PEHdr->Pe32Plus.OptionalHeader.DllCharacteristics = newflags;
358 } else {
359 ctx->PEHdr->Pe32.OptionalHeader.DllCharacteristics = newflags;
360 }
361 }
362
363 static void
364 fix_timestamp(PE_COFF_LOADER_IMAGE_CONTEXT *ctx)
365 {
366 uint32_t ts;
367
368 if (image_is_64_bit(ctx->PEHdr)) {
369 ts = ctx->PEHdr->Pe32Plus.FileHeader.TimeDateStamp;
370 } else {
371 ts = ctx->PEHdr->Pe32.FileHeader.TimeDateStamp;
372 }
373
374 if (ts != 0) {
375 debug(INFO, "Updating timestamp from 0x%08x to 0\n", ts);
376 if (image_is_64_bit(ctx->PEHdr)) {
377 ctx->PEHdr->Pe32Plus.FileHeader.TimeDateStamp = 0;
378 } else {
379 ctx->PEHdr->Pe32.FileHeader.TimeDateStamp = 0;
380 }
381 }
382 }
383
384 static void
385 fix_checksum(PE_COFF_LOADER_IMAGE_CONTEXT *ctx, void *map, size_t mapsize)
386 {
387 uint32_t old;
388 uint32_t checksum = 0;
389 uint16_t word;
390 uint8_t *data = map;
391
392 if (image_is_64_bit(ctx->PEHdr)) {
393 old = ctx->PEHdr->Pe32Plus.OptionalHeader.CheckSum;
394 ctx->PEHdr->Pe32Plus.OptionalHeader.CheckSum = 0;
395 } else {
396 old = ctx->PEHdr->Pe32.OptionalHeader.CheckSum;
397 ctx->PEHdr->Pe32.OptionalHeader.CheckSum = 0;
398 }
399 debug(NOISE, "old checksum was 0x%08x\n", old);
400
401 for (size_t i = 0; i < mapsize - 1; i += 2) {
402 word = (data[i + 1] << 8ul) | data[i];
403 checksum += word;
404 checksum = 0xffff & (checksum + (checksum >> 0x10));
405 }
406 debug(NOISE, "checksum = 0x%08x + 0x%08zx = 0x%08zx\n", checksum,
407 mapsize, checksum + mapsize);
408
409 checksum += mapsize;
410
411 if (checksum != old)
412 debug(INFO, "Updating checksum from 0x%08x to 0x%08x\n",
413 old, checksum);
414
415 if (image_is_64_bit(ctx->PEHdr)) {
416 ctx->PEHdr->Pe32Plus.OptionalHeader.CheckSum = checksum;
417 } else {
418 ctx->PEHdr->Pe32.OptionalHeader.CheckSum = checksum;
419 }
420 }
421
422 static void
423 handle_one(char *f)
424 {
425 int fd;
426 int rc;
427 struct stat statbuf;
428 size_t sz;
429 void *map;
430 int failed = 0;
431
432 PE_COFF_LOADER_IMAGE_CONTEXT ctx = { 0, 0 };
433
434 fd = open(f, O_RDWR | O_EXCL);
435 if (fd < 0)
436 err(1, "Could not open \"%s\"", f);
437
438 rc = fstat(fd, &statbuf);
439 if (rc < 0)
440 err(1, "Could not stat \"%s\"", f);
441
442 sz = statbuf.st_size;
443
444 map = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
445 if (map == MAP_FAILED)
446 err(1, "Could not map \"%s\"", f);
447
448 load_pe(f, map, sz, &ctx);
449
450 set_dll_characteristics(&ctx);
451
452 fix_timestamp(&ctx);
453
454 fix_checksum(&ctx, map, sz);
455
456 rc = msync(map, sz, MS_SYNC);
457 if (rc < 0) {
458 warn("msync(%p, %zu, MS_SYNC) failed", map, sz);
459 failed = 1;
460 }
461 rc = munmap(map, sz);
462 if (rc < 0) {
463 warn("munmap(%p, %zu) failed", map, sz);
464 failed = 1;
465 }
466 rc = close(fd);
467 if (rc < 0) {
468 warn("close(%d) failed", fd);
469 failed = 1;
470 }
471 if (failed)
472 exit(1);
473 }
474
475 static void __attribute__((__noreturn__)) usage(int status)
476 {
477 FILE *out = status ? stderr : stdout;
478
479 fprintf(out,
480 "Usage: post-process-pe [OPTIONS] file0 [file1 [.. fileN]]\n");
481 fprintf(out, "Options:\n");
482 fprintf(out, " -q Be more quiet\n");
483 fprintf(out, " -v Be more verbose\n");
484 fprintf(out, " -N Disable the NX compatibility flag\n");
485 fprintf(out, " -n Enable the NX compatibility flag\n");
486 fprintf(out, " -h Print this help text and exit\n");
487
488 exit(status);
489 }
490
491 int main(int argc, char **argv)
492 {
493 int i;
494 struct option options[] = {
495 {.name = "help",
496 .val = '?',
497 },
498 {.name = "usage",
499 .val = '?',
500 },
501 {.name = "disable-nx-compat",
502 .val = 'N',
503 },
504 {.name = "enable-nx-compat",
505 .val = 'n',
506 },
507 {.name = "quiet",
508 .val = 'q',
509 },
510 {.name = "verbose",
511 .val = 'v',
512 },
513 {.name = ""}
514 };
515 int longindex = -1;
516
517 while ((i = getopt_long(argc, argv, "hNnqv", options, &longindex)) != -1) {
518 switch (i) {
519 case 'h':
520 case '?':
521 usage(longindex == -1 ? 1 : 0);
522 break;
523 case 'N':
524 set_nx_compat = false;
525 break;
526 case 'n':
527 set_nx_compat = true;
528 break;
529 case 'q':
530 verbosity = MAX(verbosity - 1, MIN_VERBOSITY);
531 break;
532 case 'v':
533 verbosity = MIN(verbosity + 1, MAX_VERBOSITY);
534 break;
535 }
536 }
537
538 if (optind == argc)
539 usage(1);
540
541 for (i = optind; i < argc; i++)
542 handle_one(argv[i]);
543
544 return 0;
545 }
546
547 // vim:fenc=utf-8:tw=75:noet