From: Luca Boccassi Date: Fri, 26 Jun 2026 20:58:57 +0000 (+0100) Subject: boot: check PE section against SizeOfImage X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=dc55e4a5a00539f39befca4b0350c5e70b067702;p=thirdparty%2Fsystemd.git boot: check PE section against SizeOfImage pe_locate_sections_internal() stores each matching section's VirtualSize and VirtualAddress into PeSectionVector.memory_size/memory_offset with only SIZE_MAX overflow guards, never checking them against the image's SizeOfImage. Wire up the image's SizeOfImage down to pe_locate_sections_internal() and skip any section whose in-memory section does not fit within it. Follow-up for fb974ac485c90f9887d5d21ac25d6d26d452eb3c --- diff --git a/src/boot/boot.c b/src/boot/boot.c index dbe7f0c8ab6..c2f7e9b5008 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2046,8 +2046,8 @@ static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) { return false; _cleanup_free_ PeSectionHeader *section_table = NULL; - size_t n_section_table; - err = pe_section_table_from_file(handle, §ion_table, &n_section_table); + size_t n_section_table, size_in_memory; + err = pe_section_table_from_file(handle, §ion_table, &n_section_table, &size_in_memory); if (err != EFI_SUCCESS) return false; @@ -2058,6 +2058,7 @@ static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) { section_names, /* profile= */ UINT_MAX, /* validate_base= */ 0, + size_in_memory, vector); if (vector[0].memory_size != STRLEN(SD_MAGIC)) return false; @@ -2320,8 +2321,8 @@ static void boot_entry_add_type2( /* Load section table once */ _cleanup_free_ PeSectionHeader *section_table = NULL; - size_t n_section_table; - err = pe_section_table_from_file(handle, §ion_table, &n_section_table); + size_t n_section_table, size_in_memory; + err = pe_section_table_from_file(handle, §ion_table, &n_section_table, &size_in_memory); if (err != EFI_SUCCESS) return; @@ -2333,6 +2334,7 @@ static void boot_entry_add_type2( section_names, /* profile= */ UINT_MAX, /* validate_base= */ 0, + size_in_memory, base_sections); /* and now iterate through possible profiles, and create a menu item for each profile we find */ @@ -2348,6 +2350,7 @@ static void boot_entry_add_type2( section_names, profile, /* validate_base= */ 0, + size_in_memory, sections); if (err != EFI_SUCCESS && profile > 0) /* It's fine if there's no .profile for the first profile */ @@ -3100,7 +3103,7 @@ static EFI_STATUS call_image_start( if (err == EFI_UNSUPPORTED && entry->type == LOADER_LINUX) { uint32_t compat_address; - err = pe_kernel_info(loaded_image->ImageBase, /* ret_entry_point= */ NULL, &compat_address, + err = pe_kernel_info(loaded_image->ImageBase, loaded_image->ImageSize, /* ret_entry_point= */ NULL, &compat_address, /* ret_size_in_memory= */ NULL, /* ret_section_alignment= */ NULL); if (err != EFI_SUCCESS) { diff --git a/src/boot/linux.c b/src/boot/linux.c index d1da46ed4af..584fc051b71 100644 --- a/src/boot/linux.c +++ b/src/boot/linux.c @@ -167,7 +167,7 @@ EFI_STATUS linux_exec( assert(iovec_is_set(kernel)); assert(iovec_is_valid(initrd)); - err = pe_kernel_info(kernel->iov_base, &entry_point, &compat_entry_point, &kernel_size_in_memory, §ion_alignment); + err = pe_kernel_info(kernel->iov_base, kernel->iov_len, &entry_point, &compat_entry_point, &kernel_size_in_memory, §ion_alignment); #if defined(__i386__) || defined(__x86_64__) if (err == EFI_UNSUPPORTED) /* Kernel is too old to support LINUX_INITRD_MEDIA_GUID, try the deprecated EFI handover @@ -259,8 +259,7 @@ EFI_STATUS linux_exec( const PeSectionHeader *headers; size_t n_headers; - /* Do we need to validate anything here? the len? */ - err = pe_section_table_from_base(kernel->iov_base, &headers, &n_headers); + err = pe_section_table_from_base(kernel->iov_base, kernel->iov_len, &headers, &n_headers, /* ret_size_in_memory= */ NULL); if (err != EFI_SUCCESS) return log_error_status(err, "Cannot read sections: %m"); diff --git a/src/boot/pe.c b/src/boot/pe.c index 685812a488a..5e4c07e1610 100644 --- a/src/boot/pe.c +++ b/src/boot/pe.c @@ -173,6 +173,54 @@ static size_t section_table_offset(const DosFileHeader *dos, const PeFileHeader return dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader; } +static EFI_STATUS pe_headers_from_base( + const void *base, + size_t base_len, + bool allow_compatibility, + const DosFileHeader **ret_dos, + const PeFileHeader **ret_pe) { + + assert(base); + assert(ret_dos); + assert(ret_pe); + + /* Validate the DOS and PE headers against the buffer length */ + + if (base_len < sizeof(DosFileHeader)) + return EFI_LOAD_ERROR; + + const DosFileHeader *dos = (const DosFileHeader*) base; + if (!verify_dos(dos)) + return EFI_LOAD_ERROR; + + if (dos->ExeHeader > base_len || base_len - dos->ExeHeader < sizeof(PeFileHeader)) + return EFI_LOAD_ERROR; + + const PeFileHeader *pe = (const PeFileHeader*) ((const uint8_t*) base + dos->ExeHeader); + if (!verify_pe(dos, pe, allow_compatibility)) + return EFI_LOAD_ERROR; + + *ret_dos = dos; + *ret_pe = pe; + return EFI_SUCCESS; +} + +static bool pe_section_table_in_bounds( + const DosFileHeader *dos, + const PeFileHeader *pe, + size_t base_len) { + + assert(dos); + assert(pe); + + /* verify_pe() already bounded SizeOfOptionalHeader so this offset cannot overflow, and + * NumberOfSections is a uint16_t so the byte count cannot either. */ + size_t offset = section_table_offset(dos, pe); + size_t bytes = (size_t) pe->FileHeader.NumberOfSections * sizeof(PeSectionHeader); + + return offset <= base_len && base_len - offset >= bytes; +} + static bool pe_section_name_equal(const char *a, const char *b) { if (a == b) @@ -258,6 +306,7 @@ static void pe_locate_sections_internal( size_t n_section_table, const char *const section_names[], size_t validate_base, + size_t size_in_memory, const void *device_table, const Device *device, PeSectionVector sections[]) { @@ -288,6 +337,11 @@ static void pe_locate_sections_internal( if ((size_t) j->VirtualSize > size_max) continue; + /* The section's in-memory range must lie within the image, otherwise consumers + * reading it via memory_offset/memory_size would read past the loaded image. */ + if ((size_t) j->VirtualAddress + (size_t) j->VirtualSize > size_in_memory) + continue; + /* 2nd overflow check: ignore sections that are impossibly large also taking the * loaded base into account. */ if (validate_base != 0) { @@ -360,6 +414,7 @@ static void pe_locate_sections( size_t n_section_table, const char *const section_names[], size_t validate_base, + size_t size_in_memory, PeSectionVector sections[]) { if (!looking_for_dtbauto_or_efifw(section_names)) @@ -368,6 +423,7 @@ static void pe_locate_sections( n_section_table, section_names, validate_base, + size_in_memory, /* device_table= */ NULL, /* device= */ NULL, sections); @@ -387,6 +443,7 @@ static void pe_locate_sections( n_section_table, hwid_section_names, validate_base, + size_in_memory, /* device_table= */ NULL, /* device= */ NULL, hwids_section); @@ -403,6 +460,7 @@ static void pe_locate_sections( n_section_table, section_names, validate_base, + size_in_memory, hwids, device, sections); @@ -418,6 +476,7 @@ static void pe_locate_sections( n_section_table, section_names, validate_base, + size_in_memory, hwids, device, sections); @@ -433,12 +492,13 @@ static void pe_locate_sections( n_section_table, section_names, validate_base, + size_in_memory, hwids, device, sections); } -static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) { +static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, size_t base_len, const PeFileHeader *pe) { /* The kernel may provide alternative PE entry points for different PE architectures. This allows * booting a 64-bit kernel on 32-bit EFI that is otherwise running on a 64-bit CPU. The locations of any * such compat entry points are located in a special PE section. */ @@ -448,16 +508,29 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const static const char *const section_names[] = { ".compat", NULL }; PeSectionVector vector[1] = {}; + + /* Make sure the section table lies within the buffer before pe_locate_sections() iterates it. */ + if (!pe_section_table_in_bounds(dos, pe, base_len)) + return 0; + pe_locate_sections( (const PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)), pe->FileHeader.NumberOfSections, section_names, PTR_TO_SIZE(dos), + pe->OptionalHeader.SizeOfImage, vector); if (!PE_SECTION_VECTOR_IS_SET(vector)) /* not found */ return 0; + /* pe_locate_sections() bounded the section against SizeOfImage, the in-memory size. Here we read the + * section data straight from the file buffer 'dos', which may be smaller than SizeOfImage, so also + * require the section's data range to lie within base_len before scanning it. */ + if (vector[0].memory_offset > base_len || + vector[0].memory_size > base_len - vector[0].memory_offset) + return 0; + typedef struct { uint8_t type; uint8_t size; @@ -487,19 +560,18 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const EFI_STATUS pe_kernel_info( const void *base, + size_t base_len, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point, size_t *ret_size_in_memory, uint32_t *ret_section_alignment) { assert(base); - const DosFileHeader *dos = (const DosFileHeader *) base; - if (!verify_dos(dos)) - return EFI_LOAD_ERROR; - - const PeFileHeader *pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader); - if (!verify_pe(dos, pe, /* allow_compatibility= */ true)) - return EFI_LOAD_ERROR; + const DosFileHeader *dos; + const PeFileHeader *pe; + EFI_STATUS err = pe_headers_from_base(base, base_len, /* allow_compatibility= */ true, &dos, &pe); + if (err != EFI_SUCCESS) + return err; /* When allocating we need to also consider the virtual/uninitialized data sections, so parse it out * of the SizeOfImage field in the PE header and return it */ @@ -535,7 +607,7 @@ EFI_STATUS pe_kernel_info( return EFI_SUCCESS; } - uint32_t compat_entry_point = get_compatibility_entry_address(dos, pe); + uint32_t compat_entry_point = get_compatibility_entry_address(dos, base_len, pe); if (compat_entry_point == 0) /* Image type not supported and no compat entry found. */ return EFI_UNSUPPORTED; @@ -606,20 +678,20 @@ bool pe_kernel_check_nx_compat(const void *base) { EFI_STATUS pe_section_table_from_base( const void *base, + size_t base_len, const PeSectionHeader **ret_section_table, - size_t *ret_n_section_table) { + size_t *ret_n_section_table, + size_t *ret_size_in_memory) { assert(base); assert(ret_section_table); assert(ret_n_section_table); - const DosFileHeader *dos = (const DosFileHeader*) base; - if (!verify_dos(dos)) - return EFI_LOAD_ERROR; - - const PeFileHeader *pe = (const PeFileHeader*) ((const uint8_t*) base + dos->ExeHeader); - if (!verify_pe(dos, pe, /* allow_compatibility= */ false)) - return EFI_LOAD_ERROR; + const DosFileHeader *dos; + const PeFileHeader *pe; + EFI_STATUS err = pe_headers_from_base(base, base_len, /* allow_compatibility= */ false, &dos, &pe); + if (err != EFI_SUCCESS) + return err; assert_cc(sizeof(pe->FileHeader.NumberOfSections) == sizeof(uint16_t)); /* multiplication below cannot overflow */ @@ -627,14 +699,22 @@ EFI_STATUS pe_section_table_from_base( if (n_section_table * sizeof(PeSectionHeader) > SECTION_TABLE_BYTES_MAX) return EFI_OUT_OF_RESOURCES; + /* Make sure the section table lies within the buffer, so consumers iterating it don't read off the + * end. */ + if (!pe_section_table_in_bounds(dos, pe, base_len)) + return EFI_LOAD_ERROR; + *ret_section_table = (const PeSectionHeader*) ((const uint8_t*) base + section_table_offset(dos, pe)); *ret_n_section_table = n_section_table; + if (ret_size_in_memory) + *ret_size_in_memory = pe->OptionalHeader.SizeOfImage; return EFI_SUCCESS; } EFI_STATUS pe_memory_locate_sections( const void *base, + size_t base_len, const char *const section_names[], PeSectionVector sections[]) { @@ -645,8 +725,8 @@ EFI_STATUS pe_memory_locate_sections( assert(sections); const PeSectionHeader *section_table; - size_t n_section_table; - err = pe_section_table_from_base(base, §ion_table, &n_section_table); + size_t n_section_table, size_in_memory; + err = pe_section_table_from_base(base, base_len, §ion_table, &n_section_table, &size_in_memory); if (err != EFI_SUCCESS) return err; @@ -655,6 +735,7 @@ EFI_STATUS pe_memory_locate_sections( n_section_table, section_names, PTR_TO_SIZE(base), + size_in_memory, sections); return EFI_SUCCESS; @@ -663,7 +744,8 @@ EFI_STATUS pe_memory_locate_sections( EFI_STATUS pe_section_table_from_file( EFI_FILE *handle, PeSectionHeader **ret_section_table, - size_t *ret_n_section_table) { + size_t *ret_n_section_table, + size_t *ret_size_in_memory) { EFI_STATUS err; size_t len; @@ -717,6 +799,8 @@ EFI_STATUS pe_section_table_from_file( *ret_section_table = TAKE_PTR(section_table); *ret_n_section_table = n_section_table; + if (ret_size_in_memory) + *ret_size_in_memory = pe.OptionalHeader.SizeOfImage; return EFI_SUCCESS; } @@ -784,6 +868,7 @@ EFI_STATUS pe_locate_profile_sections( const char* const section_names[], unsigned profile, size_t validate_base, + size_t size_in_memory, PeSectionVector sections[]) { assert(section_table || n_section_table == 0); @@ -805,6 +890,7 @@ EFI_STATUS pe_locate_profile_sections( n, section_names, validate_base, + size_in_memory, sections); return EFI_SUCCESS; diff --git a/src/boot/pe.h b/src/boot/pe.h index 5c8dc86fe93..bc9963799f5 100644 --- a/src/boot/pe.h +++ b/src/boot/pe.h @@ -36,13 +36,16 @@ static inline bool PE_SECTION_VECTOR_IS_SET(const PeSectionVector *v) { EFI_STATUS pe_section_table_from_base( const void *base, + size_t base_len, const PeSectionHeader **ret_section_table, - size_t *ret_n_section_table); + size_t *ret_n_section_table, + size_t *ret_size_in_memory); EFI_STATUS pe_section_table_from_file( EFI_FILE *handle, PeSectionHeader **ret_section_table, - size_t *ret_n_section_table); + size_t *ret_n_section_table, + size_t *ret_size_in_memory); EFI_STATUS pe_locate_profile_sections( const PeSectionHeader section_table[], @@ -50,15 +53,18 @@ EFI_STATUS pe_locate_profile_sections( const char* const section_names[], unsigned profile, size_t validate_base, + size_t size_in_memory, PeSectionVector sections[]); EFI_STATUS pe_memory_locate_sections( const void *base, + size_t base_len, const char *const section_names[], PeSectionVector sections[]); EFI_STATUS pe_kernel_info( const void *base, + size_t base_len, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point, size_t *ret_size_in_memory, diff --git a/src/boot/stub.c b/src/boot/stub.c index e69faca00b0..94c4ac49e4b 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -596,7 +596,7 @@ static EFI_STATUS load_addons( if (err != EFI_SUCCESS) return log_error_status(err, "Failed to find protocol in %ls: %m", items[i]); - err = pe_memory_locate_sections(loaded_addon->ImageBase, unified_sections, sections); + err = pe_memory_locate_sections(loaded_addon->ImageBase, loaded_addon->ImageSize, unified_sections, sections); if (err != EFI_SUCCESS) { log_error_status(err, "Unable to locate embedded .cmdline/.dtb/.dtbauto/.efifw/.initrd/.ucode sections in %ls, ignoring: %m", @@ -1099,8 +1099,8 @@ static EFI_STATUS find_sections( assert(sections); const PeSectionHeader *section_table; - size_t n_section_table; - err = pe_section_table_from_base(loaded_image->ImageBase, §ion_table, &n_section_table); + size_t n_section_table, size_in_memory; + err = pe_section_table_from_base(loaded_image->ImageBase, loaded_image->ImageSize, §ion_table, &n_section_table, &size_in_memory); if (err != EFI_SUCCESS) return log_error_status(err, "Unable to locate PE section table: %m"); @@ -1111,6 +1111,7 @@ static EFI_STATUS find_sections( unified_sections, /* profile= */ UINT_MAX, /* validate_base= */ PTR_TO_SIZE(loaded_image->ImageBase), + size_in_memory, sections); if (err != EFI_SUCCESS) return log_error_status(err, "Unable to locate embedded base PE sections: %m"); @@ -1123,6 +1124,7 @@ static EFI_STATUS find_sections( unified_sections, profile, /* validate_base= */ PTR_TO_SIZE(loaded_image->ImageBase), + size_in_memory, sections); if (err != EFI_SUCCESS && !(err == EFI_NOT_FOUND && profile == 0)) /* the first profile is implied if it doesn't exist */ return log_error_status(err, "Unable to locate embedded per-profile PE sections: %m");