:: commit 7d811beee6d3f5e94f9d79f90a43a93fa9aac888

Mintsuki <mintsuki@protonmail.com> — 2026-01-11 21:07

parents: 2f1bac3274

pe: Validate PE header offsets against file size

diff --git a/common/lib/pe.c b/common/lib/pe.c
index e2c734e1..6bea9f88 100644
--- a/common/lib/pe.c
+++ b/common/lib/pe.c
@@ -154,13 +154,21 @@ typedef struct {
     uint32_t SizeOfBlock;
 } IMAGE_BASE_RELOCATION_BLOCK;
 
-static void pe64_validate(uint8_t *image) {
+static void pe64_validate(uint8_t *image, size_t file_size) {
     IMAGE_DOS_HEADER *dos_hdr = (IMAGE_DOS_HEADER *)image;
 
+    if (file_size < sizeof(IMAGE_DOS_HEADER)) {
+        panic(true, "pe: File too small for DOS header");
+    }
+
     if (dos_hdr->e_magic != IMAGE_DOS_SIGNATURE) {
         panic(true, "pe: Not a valid PE file");
     }
 
+    if (dos_hdr->e_lfanew > file_size - sizeof(IMAGE_NT_HEADERS64)) {
+        panic(true, "pe: e_lfanew offset out of bounds");
+    }
+
     IMAGE_NT_HEADERS64 *nt_hdrs = (IMAGE_NT_HEADERS64 *)(image + dos_hdr->e_lfanew);
 
     if (nt_hdrs->Signature != IMAGE_NT_SIGNATURE) {
@@ -214,11 +222,19 @@ int pe_bits(uint8_t *image) {
     return -1;
 }
 
-bool pe64_load(uint8_t *image, uint64_t *entry_point, uint64_t *_slide, uint32_t alloc_type, bool kaslr, struct mem_range **_ranges, uint64_t *_ranges_count, uint64_t *physical_base, uint64_t *virtual_base, uint64_t *_image_size, uint64_t *image_size_before_bss, bool *_is_reloc) {
-    pe64_validate(image);
+bool pe64_load(uint8_t *image, size_t file_size, uint64_t *entry_point, uint64_t *_slide, uint32_t alloc_type, bool kaslr, struct mem_range **_ranges, uint64_t *_ranges_count, uint64_t *physical_base, uint64_t *virtual_base, uint64_t *_image_size, uint64_t *image_size_before_bss, bool *_is_reloc) {
+    pe64_validate(image, file_size);
 
     IMAGE_DOS_HEADER *dos_hdr = (IMAGE_DOS_HEADER *)image;
     IMAGE_NT_HEADERS64 *nt_hdrs = (IMAGE_NT_HEADERS64 *)(image + dos_hdr->e_lfanew);
+
+    // Validate SizeOfOptionalHeader doesn't cause sections pointer to go out of bounds
+    size_t sections_offset = dos_hdr->e_lfanew + sizeof(uint32_t) + sizeof(IMAGE_FILE_HEADER) + nt_hdrs->FileHeader.SizeOfOptionalHeader;
+    size_t sections_end = sections_offset + (size_t)nt_hdrs->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
+    if (sections_end > file_size) {
+        panic(true, "pe: Section headers extend beyond file bounds");
+    }
+
     IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)((uintptr_t)&nt_hdrs->OptionalHeader + nt_hdrs->FileHeader.SizeOfOptionalHeader);
 
     bool is_reloc = true;
@@ -256,6 +272,11 @@ bool pe64_load(uint8_t *image, uint64_t *entry_point, uint64_t *_slide, uint32_t
     *physical_base = (uintptr_t)ext_mem_alloc_type_aligned(image_size, alloc_type, alignment);
     *virtual_base = image_base;
 
+    // Validate SizeOfHeaders doesn't exceed file size
+    if (nt_hdrs->OptionalHeader.SizeOfHeaders > file_size) {
+        panic(true, "pe: SizeOfHeaders exceeds file size");
+    }
+
     memcpy((void *)(uintptr_t)*physical_base, image, nt_hdrs->OptionalHeader.SizeOfHeaders);
 
     if (_image_size) {
@@ -280,6 +301,11 @@ again:
         uintptr_t section_base = *physical_base + section->VirtualAddress;
         uint32_t section_raw_size = section->VirtualSize < section->SizeOfRawData ? section->VirtualSize : section->SizeOfRawData;
 
+        // Validate section data doesn't exceed file bounds
+        if ((uint64_t)section->PointerToRawData + section_raw_size > file_size) {
+            panic(true, "pe: Section %zu data extends beyond file bounds", i);
+        }
+
         memcpy((void *)section_base, image + section->PointerToRawData, section_raw_size);
     }
 
diff --git a/common/lib/pe.h b/common/lib/pe.h
index 920f48d4..0725efb0 100644
--- a/common/lib/pe.h
+++ b/common/lib/pe.h
@@ -7,6 +7,6 @@
 
 int pe_bits(uint8_t *image);
 
-bool pe64_load(uint8_t *image, uint64_t *entry_point, uint64_t *_slide, uint32_t alloc_type, bool kaslr, struct mem_range **ranges, uint64_t *ranges_count, uint64_t *physical_base, uint64_t *virtual_base, uint64_t *image_size, uint64_t *image_size_before_bss, bool *is_reloc);
+bool pe64_load(uint8_t *image, size_t file_size, uint64_t *entry_point, uint64_t *_slide, uint32_t alloc_type, bool kaslr, struct mem_range **ranges, uint64_t *ranges_count, uint64_t *physical_base, uint64_t *virtual_base, uint64_t *image_size, uint64_t *image_size_before_bss, bool *is_reloc);
 
 #endif
diff --git a/common/protos/limine.c b/common/protos/limine.c
index 19a659d8..8a558dc5 100644
--- a/common/protos/limine.c
+++ b/common/protos/limine.c
@@ -484,7 +484,7 @@ noreturn void limine_load(char *config, char *cmdline) {
             }
             break;
         case EXECUTABLE_FORMAT_PE:
-            if (!pe64_load(kernel, &entry_point, &slide,
+            if (!pe64_load(kernel, kernel_file->size, &entry_point, &slide,
                             MEMMAP_KERNEL_AND_MODULES, kaslr,
                             &ranges, &ranges_count,
                             &physical_base, &virtual_base, NULL,
tab: 248 wrap: offon