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