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,
