]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: check PE section against SizeOfImage
authorLuca Boccassi <luca.boccassi@gmail.com>
Fri, 26 Jun 2026 20:58:57 +0000 (21:58 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 3 Jul 2026 14:19:39 +0000 (15:19 +0100)
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

src/boot/boot.c
src/boot/linux.c
src/boot/pe.c
src/boot/pe.h
src/boot/stub.c

index dbe7f0c8ab60ce98aed66541e1afadcd68c81622..c2f7e9b5008dec1ce2daf79d3b5fb39148f335a8 100644 (file)
@@ -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, &section_table, &n_section_table);
+        size_t n_section_table, size_in_memory;
+        err = pe_section_table_from_file(handle, &section_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, &section_table, &n_section_table);
+        size_t n_section_table, size_in_memory;
+        err = pe_section_table_from_file(handle, &section_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) {
index d1da46ed4afd3b8065da27e397d9cbcd21355c39..584fc051b71bf8b2afcf8b2bd2b80c2644f71913 100644 (file)
@@ -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, &section_alignment);
+        err = pe_kernel_info(kernel->iov_base, kernel->iov_len, &entry_point, &compat_entry_point, &kernel_size_in_memory, &section_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");
 
index 685812a488a3e678941266f92a052a91973a1162..5e4c07e1610c8c12492232d0c7e5ff0fa304cb33 100644 (file)
@@ -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, &section_table, &n_section_table);
+        size_t n_section_table, size_in_memory;
+        err = pe_section_table_from_base(base, base_len, &section_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;
index 5c8dc86fe938914d8da58ae4bfeb34953b0735e9..bc9963799f51935519ec110a480823f99bf33e34 100644 (file)
@@ -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,
index e69faca00b0bf95f39f8cf7d7ce056a6adcfed19..94c4ac49e4bf61b2a7b50bf62dbd9226be8ba134 100644 (file)
@@ -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, &section_table, &n_section_table);
+        size_t n_section_table, size_in_memory;
+        err = pe_section_table_from_base(loaded_image->ImageBase, loaded_image->ImageSize, &section_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");