@@ -1815,6 +1815,7 @@ module = {
common = loader/multiboot.c;
common = loader/multiboot_mbi2.c;
+ common = loader/multiboot_pe.c;
enable = x86;
enable = i386_xen_pvh;
enable = mips;
@@ -220,6 +220,13 @@ static grub_uint64_t highest_load;
#include "multiboot_elfxx.c"
#undef MULTIBOOT_LOAD_ELF32
+bool
+GRUB_MULTIBOOT (is_elf) (mbi_load_data_t *mld)
+{
+ return grub_multiboot_is_elf32 (mld->buffer) ||
+ grub_multiboot_is_elf64 (mld->buffer);
+}
+
/* Load ELF32 or ELF64. */
grub_err_t
GRUB_MULTIBOOT (load_elf) (mbi_load_data_t *mld)
@@ -349,7 +349,16 @@ grub_multiboot2_load (grub_file_t file, const char *filename)
mld.file = file;
mld.filename = filename;
mld.avoid_efi_boot_services = keep_bs;
- err = grub_multiboot2_load_elf (&mld);
+ if (grub_multiboot2_is_pe (&mld))
+ err = grub_multiboot2_load_pe (&mld);
+ else if (grub_multiboot2_is_elf (&mld))
+ err = grub_multiboot2_load_elf (&mld);
+ else
+ {
+ grub_free (mld.buffer);
+ return grub_error (GRUB_ERR_UNKNOWN_OS,
+ "Unknown image type and address tag not specified");
+ }
if (err)
{
grub_free (mld.buffer);
new file mode 100644
@@ -0,0 +1,702 @@
+/*
+ * Significant portions of this code are derived from the Fedora GRUB patch
+ * "0007-Add-secureboot-support-on-efi-chainloader.patch" which is in turn
+ * derived from the PE loading code in Shim. The license is reproduced below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Copyright Peter Jones <pjones@redhat.com>
+ *
+ * Modifications:
+ *
+ * Copyright (c) 2024. Cloud Software Group, Inc.
+ */
+
+#include <grub/multiboot2.h>
+#include <grub/i386/relocator.h>
+#include <grub/efi/pe32.h>
+
+static int
+image_is_64_bit (grub_pe_header_t *pe_hdr)
+{
+ /* .Magic is the same offset in all cases */
+ return pe_hdr->pe32plus.optional_header.magic == GRUB_PE32_PE64_MAGIC;
+}
+
+static const grub_uint16_t machine_type __attribute__((__unused__)) =
+#if defined(__x86_64__)
+ GRUB_PE32_MACHINE_X86_64;
+#elif defined(__aarch64__)
+ GRUB_PE32_MACHINE_ARM64;
+#elif defined(__arm__)
+ GRUB_PE32_MACHINE_ARMTHUMB_MIXED;
+#elif defined(__i386__) || defined(__i486__) || defined(__i686__)
+ GRUB_PE32_MACHINE_I386;
+#elif defined(__ia64__)
+ GRUB_PE32_MACHINE_IA64;
+#elif defined(__riscv) && (__riscv_xlen == 32)
+ GRUB_PE32_MACHINE_RISCV32;
+#elif defined(__riscv) && (__riscv_xlen == 64)
+ GRUB_PE32_MACHINE_RISCV64;
+#else
+#error this architecture is not supported by grub2
+#endif
+
+static int
+image_is_loadable(grub_pe_header_t *pe_hdr)
+{
+ /*
+ * Check the machine type matches the binary and that we recognize
+ * the magic number.
+ */
+ return (pe_hdr->pe32.coff_header.machine == machine_type ||
+ (pe_hdr->pe32.coff_header.machine == GRUB_PE32_MACHINE_X86_64 &&
+ machine_type == GRUB_PE32_MACHINE_I386)) &&
+ (pe_hdr->pe32plus.optional_header.magic == GRUB_PE32_PE32_MAGIC ||
+ pe_hdr->pe32plus.optional_header.magic == GRUB_PE32_PE64_MAGIC);
+}
+
+/*
+ * Read the binary header and grab appropriate information from it
+ */
+static grub_err_t
+read_header(void *data, unsigned int datasize,
+ pe_coff_loader_image_context_t *context)
+{
+ grub_pe32_msdos_header_t *dos_hdr = data;
+ grub_pe_header_t *pe_hdr = data;
+ unsigned long header_without_data_dir, section_header_offset, opt_header_size;
+ unsigned long file_alignment = 0;
+
+ if (datasize < sizeof (pe_hdr->pe32))
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "Invalid image");
+ return GRUB_ERR_OUT_OF_RANGE;
+ }
+
+ if (dos_hdr->e_magic == GRUB_PE32_MAGIC)
+ pe_hdr = (grub_pe_header_t *)((char *)data + dos_hdr->e_lfanew);
+
+ if (!image_is_loadable(pe_hdr))
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "Platform does not support this image");
+ return GRUB_ERR_NOT_IMPLEMENTED_YET;
+ }
+
+ if (image_is_64_bit(pe_hdr))
+ {
+ context->number_of_rva_and_sizes = pe_hdr->pe32plus.optional_header.num_data_directories;
+ context->size_of_headers = pe_hdr->pe32plus.optional_header.header_size;
+ context->image_size = pe_hdr->pe32plus.optional_header.image_size;
+ context->section_alignment = pe_hdr->pe32plus.optional_header.section_alignment;
+ file_alignment = pe_hdr->pe32plus.optional_header.file_alignment;
+ opt_header_size = sizeof(struct grub_pe64_optional_header);
+ }
+ else
+ {
+ context->number_of_rva_and_sizes = pe_hdr->pe32.optional_header.num_data_directories;
+ context->size_of_headers = pe_hdr->pe32.optional_header.header_size;
+ context->image_size = (grub_uint64_t)pe_hdr->pe32.optional_header.image_size;
+ context->section_alignment = pe_hdr->pe32.optional_header.section_alignment;
+ file_alignment = pe_hdr->pe32.optional_header.file_alignment;
+ opt_header_size = sizeof(struct grub_pe32_optional_header);
+ }
+
+ if (file_alignment % 2 != 0)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "File Alignment is invalid (%ld)", file_alignment);
+ return GRUB_ERR_OUT_OF_RANGE;
+ }
+ if (file_alignment == 0)
+ file_alignment = 0x200;
+ if (context->section_alignment == 0)
+ context->section_alignment = GRUB_EFI_PAGE_SIZE;
+ if (context->section_alignment < file_alignment)
+ context->section_alignment = file_alignment;
+
+ context->number_of_sections = pe_hdr->pe32.coff_header.num_sections;
+
+ if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < context->number_of_rva_and_sizes)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "Image header too small");
+ return GRUB_ERR_OUT_OF_RANGE;
+ }
+
+ header_without_data_dir = opt_header_size
+ - sizeof (struct grub_pe32_data_directory) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES;
+ if (((grub_uint32_t)pe_hdr->pe32.coff_header.optional_header_size - header_without_data_dir) !=
+ context->number_of_rva_and_sizes * sizeof (struct grub_pe32_data_directory))
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "Image header overflows data directory");
+ return GRUB_ERR_OUT_OF_RANGE;
+ }
+
+ section_header_offset = dos_hdr->e_lfanew
+ + sizeof (grub_uint32_t)
+ + sizeof (struct grub_pe32_coff_header)
+ + pe_hdr->pe32.coff_header.optional_header_size;
+ if (((grub_uint32_t)context->image_size - section_header_offset) / sizeof(struct grub_pe32_section_table)
+ <= context->number_of_sections)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "Image sections overflow image size");
+ return GRUB_ERR_OUT_OF_RANGE;
+ }
+
+ if ((context->size_of_headers - section_header_offset) / sizeof(struct grub_pe32_section_table)
+ < (grub_uint32_t)context->number_of_sections)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "Image sections overflow section headers");
+ return GRUB_ERR_OUT_OF_RANGE;
+ }
+
+ if ((((grub_uint8_t *)pe_hdr - (grub_uint8_t *)data) + sizeof(grub_pe_header_t)) > datasize)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "Invalid image");
+ return GRUB_ERR_OUT_OF_RANGE;
+ }
+
+ if (pe_hdr->pe32.signature[0] != 'P' ||
+ pe_hdr->pe32.signature[1] != 'E' ||
+ pe_hdr->pe32.signature[2] != 0x00 ||
+ pe_hdr->pe32.signature[3] != 0x00)
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "Unsupported image type");
+ return GRUB_ERR_NOT_IMPLEMENTED_YET;
+ }
+
+ context->pe_hdr = pe_hdr;
+
+ if (image_is_64_bit(pe_hdr))
+ {
+ context->image_address = pe_hdr->pe32plus.optional_header.image_base;
+ context->entry_point = pe_hdr->pe32plus.optional_header.entry_addr;
+ context->reloc_dir = &pe_hdr->pe32plus.optional_header.base_relocation_table;
+ context->sec_dir = &pe_hdr->pe32plus.optional_header.certificate_table;
+ }
+ else
+ {
+ context->image_address = pe_hdr->pe32.optional_header.image_base;
+ context->entry_point = pe_hdr->pe32.optional_header.entry_addr;
+ context->reloc_dir = &pe_hdr->pe32.optional_header.base_relocation_table;
+ context->sec_dir = &pe_hdr->pe32.optional_header.certificate_table;
+ }
+
+ context->first_section = (struct grub_pe32_section_table *)((char *)pe_hdr +
+ pe_hdr->pe32.coff_header.optional_header_size +
+ sizeof(grub_uint32_t) + sizeof(struct grub_pe32_coff_header));
+
+ if (context->image_size < context->size_of_headers)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "Invalid image");
+ return GRUB_ERR_OUT_OF_RANGE;
+ }
+
+ if ((unsigned long)((grub_uint8_t *)context->sec_dir - (grub_uint8_t *)data) >
+ (datasize - sizeof(struct grub_pe32_data_directory)))
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "Invalid image");
+ return GRUB_ERR_OUT_OF_RANGE;
+ }
+ return GRUB_ERR_NONE;
+}
+
+static grub_phys_addr_t
+image_address (grub_phys_addr_t image, grub_uint64_t sz, grub_uint64_t addr)
+{
+ if (addr > sz)
+ return 0;
+
+ return image + addr;
+}
+
+static grub_err_t
+relocate_coff (pe_coff_loader_image_context_t *context,
+ struct grub_pe32_section_table *section,
+ grub_phys_addr_t phys_addr, grub_uint8_t *virt_addr,
+ mbi_load_data_t *mld)
+{
+ struct grub_pe32_data_directory *reloc_base, *reloc_base_end;
+ grub_uint8_t *reloc_buffer;
+ grub_uint64_t adjust;
+ struct grub_pe32_fixup_block *reloc, *reloc_end;
+ grub_uint8_t *fixup, *fixup_base;
+ grub_uint16_t *fixup_16;
+ grub_uint32_t *fixup_32;
+ grub_uint64_t *fixup_64;
+ int n = 0;
+ grub_err_t ret = GRUB_ERR_NONE;
+
+ if (image_is_64_bit (context->pe_hdr))
+ context->pe_hdr->pe32plus.optional_header.image_base =
+ (grub_uint64_t)(unsigned long)phys_addr;
+ else
+ context->pe_hdr->pe32.optional_header.image_base =
+ (grub_uint32_t)(unsigned long)phys_addr;
+
+ /* Alright, so here's how this works:
+ *
+ * context->reloc_dir gives us two things:
+ * - the VA the table of base relocation blocks are (maybe) to be
+ * mapped at (reloc_dir->rva)
+ * - the virtual size (reloc_dir->size)
+ *
+ * The .reloc section (section here) gives us some other things:
+ * - the name! kind of. (section->name)
+ * - the virtual size (section->virtual_size), which should be the same
+ * as RelocDir->Size
+ * - the virtual address (section->virtual_address)
+ * - the file section size (section->raw_data_size), which is
+ * a multiple of optional_header->file_alignment. Only useful for image
+ * validation, not really useful for iteration bounds.
+ * - the file address (section->raw_data_offset)
+ * - a bunch of stuff we don't use that's 0 in our binaries usually
+ * - Flags (section->characteristics)
+ *
+ * and then the thing that's actually at the file address is an array
+ * of struct grub_pe32_fixup_block structs with some values packed behind
+ * them. The block_size field of this structure includes the
+ * structure itself, and adding it to that structure's address will
+ * yield the next entry in the array.
+ */
+
+ reloc_buffer = grub_malloc (section->virtual_size);
+ if (!reloc_buffer)
+ return grub_errno;
+
+ if (grub_file_seek (mld->file, section->raw_data_offset) == (grub_off_t) -1)
+ {
+ ret = grub_errno;
+ goto out;
+ }
+
+ if (grub_file_read (mld->file, reloc_buffer, section->virtual_size)
+ != (grub_ssize_t) section->virtual_size)
+ {
+ if (!grub_errno)
+ {
+ grub_error (GRUB_ERR_FILE_READ_ERROR, "premature end of file %s",
+ mld->filename);
+ ret = GRUB_ERR_FILE_READ_ERROR;
+ }
+ else
+ ret = grub_errno;
+ goto out;
+ }
+
+ reloc_base = (struct grub_pe32_data_directory *)reloc_buffer;
+ reloc_base_end = (struct grub_pe32_data_directory *)(reloc_buffer + section->virtual_size);
+
+ grub_dprintf ("multiboot_loader", "relocate_coff(): reloc_base %p reloc_base_end %p\n",
+ reloc_base, reloc_base_end);
+
+ if (!reloc_base && !reloc_base_end)
+ return GRUB_ERR_NONE;
+
+ if (!reloc_base || !reloc_base_end)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "Reloc table overflows binary");
+ return GRUB_ERR_BAD_ARGUMENT;
+ }
+
+ adjust = (grub_uint64_t)(grub_efi_uintn_t)phys_addr - context->image_address;
+ if (adjust == 0)
+ return GRUB_ERR_NONE;
+
+ while (reloc_base < reloc_base_end)
+ {
+ grub_uint16_t *entry;
+ reloc = (struct grub_pe32_fixup_block *)((char*)reloc_base);
+
+ if ((reloc_base->size == 0) ||
+ (reloc_base->size > context->reloc_dir->size))
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Reloc %d block size %d is invalid\n", n,
+ reloc_base->size);
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+
+ entry = &reloc->entries[0];
+ reloc_end = (struct grub_pe32_fixup_block *)
+ ((char *)reloc_base + reloc_base->size);
+
+ if ((grub_uint8_t *)reloc_end < reloc_buffer ||
+ (grub_uint8_t *)reloc_end > (reloc_buffer + section->virtual_size))
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "Reloc entry %d overflows binary",
+ n);
+ return GRUB_ERR_BAD_ARGUMENT;
+ }
+
+ fixup_base = virt_addr + reloc_base->rva;
+ if (!fixup_base)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "Reloc %d Invalid fixupbase", n);
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+
+ while ((void *)entry < (void *)reloc_end)
+ {
+ fixup = fixup_base + (*entry & 0xFFF);
+ switch ((*entry) >> 12)
+ {
+ case GRUB_PE32_REL_BASED_ABSOLUTE:
+ break;
+ case GRUB_PE32_REL_BASED_HIGH:
+ fixup_16 = (grub_uint16_t *)fixup;
+ *fixup_16 = (grub_uint16_t)
+ (*fixup_16 + ((grub_uint16_t)((grub_uint32_t)adjust >> 16)));
+ break;
+ case GRUB_PE32_REL_BASED_LOW:
+ fixup_16 = (grub_uint16_t *)fixup;
+ *fixup_16 = (grub_uint16_t) (*fixup_16 + (grub_uint16_t)adjust);
+ break;
+ case GRUB_PE32_REL_BASED_HIGHLOW:
+ fixup_32 = (grub_uint32_t *)fixup;
+ *fixup_32 = *fixup_32 + (grub_uint32_t)adjust;
+ break;
+ case GRUB_PE32_REL_BASED_DIR64:
+ fixup_64 = (grub_uint64_t *)fixup;
+ *fixup_64 = *fixup_64 + (grub_uint64_t)adjust;
+ break;
+ default:
+ grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Reloc %d unknown relocation type %d",
+ n, (*entry) >> 12);
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+ entry += 1;
+ }
+ reloc_base = (struct grub_pe32_data_directory *)reloc_end;
+ n++;
+ }
+
+out:
+ grub_free(reloc_buffer);
+
+ return ret;
+}
+
+grub_err_t
+grub_multiboot2_load_pe (mbi_load_data_t *mld)
+{
+ int i;
+ grub_uint8_t *virt_addr;
+ grub_phys_addr_t phys_addr, reloc_base, reloc_base_end, base, end;
+ struct grub_pe32_section_table *reloc_section = NULL, fake_reloc_section;
+ struct grub_pe32_section_table *section;
+ grub_relocator_chunk_t ch;
+ pe_coff_loader_image_context_t context;
+ grub_err_t ret = GRUB_ERR_NONE;
+ grub_uint32_t section_alignment;
+ int found_entry_point = 0;
+
+ ret = read_header(mld->buffer, MULTIBOOT_SEARCH, &context);
+ if (ret)
+ return ret;
+
+ /*
+ * The spec says, uselessly, of SectionAlignment:
+ * =====
+ * The alignment (in bytes) of sections when they are loaded into
+ * memory. It must be greater than or equal to FileAlignment. The
+ * default is the page size for the architecture.
+ * =====
+ * Which doesn't tell you whose responsibility it is to enforce the
+ * "default", or when. It implies that the value in the field must
+ * be > FileAlignment (also poorly defined), but it appears visual
+ * studio will happily write 512 for FileAlignment (its default) and
+ * 0 for SectionAlignment, intending to imply PAGE_SIZE.
+ *
+ * We only support one page size, so if it's zero, nerf it to 4096.
+ */
+ section_alignment = context.section_alignment;
+ if (section_alignment == 0)
+ section_alignment = 4096;
+
+ section_alignment = grub_max(section_alignment, mld->align);
+
+ ret = grub_relocator_alloc_chunk_align_safe (grub_multiboot2_relocator, &ch,
+ mld->min_addr, mld->max_addr,
+ context.image_size, section_alignment,
+ mld->preference, mld->avoid_efi_boot_services);
+
+ if (ret)
+ {
+ grub_dprintf ("multiboot_loader", "Cannot allocate memory for OS image\n");
+ return ret;
+ }
+
+ virt_addr = get_virtual_current_address (ch);
+ phys_addr = get_physical_target_address (ch);
+
+ if (grub_file_seek (mld->file, 0) == (grub_off_t) -1) {
+ ret = grub_errno;
+ goto out;
+ }
+
+ if (grub_file_read (mld->file, virt_addr, context.size_of_headers)
+ != (grub_ssize_t) context.size_of_headers)
+ {
+ if (!grub_errno)
+ {
+ grub_error (GRUB_ERR_FILE_READ_ERROR, "premature end of file %s",
+ mld->filename);
+ ret = GRUB_ERR_FILE_READ_ERROR;
+ }
+ else
+ ret = grub_errno;
+ goto out;
+ }
+
+ mld->load_base_addr = phys_addr;
+ mld->link_base_addr = context.image_address;
+
+ grub_dprintf ("multiboot_loader", "load_base_addr: 0x%08x link_base_addr 0x%08x\n",
+ mld->load_base_addr, mld->link_base_addr);
+
+ grub_multiboot2_payload_eip = context.entry_point;
+ grub_dprintf ("multiboot_loader", "entry_point: 0x%08x\n", grub_multiboot2_payload_eip);
+ if (!grub_multiboot2_payload_eip)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid entry point");
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+
+ grub_dprintf ("multiboot_loader", "reloc_dir: %p reloc_size: 0x%08x\n",
+ (void *)(unsigned long)context.reloc_dir->rva,
+ context.reloc_dir->size);
+ reloc_base = image_address (phys_addr, context.image_size,
+ context.reloc_dir->rva);
+ /* RelocBaseEnd here is the address of the last byte of the table */
+ reloc_base_end = image_address (phys_addr, context.image_size,
+ context.reloc_dir->rva
+ + context.reloc_dir->size - 1);
+ grub_dprintf ("multiboot_loader", "reloc_base: 0x%016lx reloc_base_end: 0x%016lx\n",
+ reloc_base, reloc_base_end);
+
+ section = context.first_section;
+ for (i = 0; i < context.number_of_sections; i++, section++)
+ {
+ char name[9];
+
+ base = image_address (phys_addr, context.image_size,
+ section->virtual_address);
+ end = image_address (phys_addr, context.image_size,
+ section->virtual_address + section->virtual_size -1);
+
+ grub_strncpy(name, section->name, 9);
+ name[8] = '\0';
+ grub_dprintf ("multiboot_loader", "Section %d \"%s\" at 0x%016lx..0x%016lx\n", i,
+ name, base, end);
+
+ if (end < base)
+ {
+ grub_dprintf ("multiboot_loader", " base is 0x%016lx but end is 0x%016lx... bad.\n",
+ base, end);
+ grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Image has invalid negative size");
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+
+ if (section->virtual_address <= context.entry_point &&
+ (section->virtual_address + section->raw_data_size - 1)
+ > context.entry_point)
+ {
+ found_entry_point++;
+ grub_dprintf ("multiboot_loader", " section contains entry point\n");
+ }
+
+ /* We do want to process .reloc, but it's often marked
+ * discardable, so we don't want to memcpy it. */
+ if (grub_memcmp (section->name, ".reloc\0\0", 8) == 0)
+ {
+ if (reloc_section)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Image has multiple relocation sections");
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+
+ /* If it has nonzero sizes, and our bounds check
+ * made sense, and the VA and size match RelocDir's
+ * versions, then we believe in this section table. */
+ if (section->raw_data_size && section->virtual_size &&
+ base && end && reloc_base == base)
+ {
+ if (reloc_base_end == end)
+ {
+ grub_dprintf ("multiboot_loader", " section is relocation section\n");
+ reloc_section = section;
+ }
+ else if (reloc_base_end && reloc_base_end < end)
+ {
+ /* Bogus virtual size in the reloc section -- RelocDir
+ * reported a smaller Base Relocation Directory. Decrease
+ * the section's virtual size so that it equal RelocDir's
+ * idea, but only for the purposes of relocate_coff(). */
+ grub_dprintf ("multiboot_loader",
+ " section is (overlong) relocation section\n");
+ grub_memcpy (&fake_reloc_section, section, sizeof *section);
+ fake_reloc_section.virtual_size -= (end - reloc_base_end);
+ reloc_section = &fake_reloc_section;
+ }
+ }
+
+ if (!reloc_section)
+ {
+ grub_dprintf ("multiboot_loader", " section is not reloc section?\n");
+ grub_dprintf ("multiboot_loader", " rds: 0x%08x, vs: %08x\n",
+ section->raw_data_size, section->virtual_size);
+ grub_dprintf ("multiboot_loader", " base: 0x%016lx end: 0x%016lx\n", base, end);
+ grub_dprintf ("multiboot_loader", " reloc_base: 0x%016lx reloc_base_end: 0x%016lx\n",
+ reloc_base, reloc_base_end);
+ }
+ }
+
+ grub_dprintf ("multiboot_loader", " Section characteristics are %08x\n",
+ section->characteristics);
+ grub_dprintf ("multiboot_loader", " Section virtual size: %08x\n",
+ section->virtual_size);
+ grub_dprintf ("multiboot_loader", " Section raw_data size: %08x\n",
+ section->raw_data_size);
+ if (section->characteristics & GRUB_PE32_SCN_MEM_DISCARDABLE)
+ {
+ grub_dprintf ("multiboot_loader", " Discarding section\n");
+ continue;
+ }
+
+ if (!base || !end)
+ {
+ grub_dprintf ("multiboot_loader", " section is invalid\n");
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid section size");
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+
+ if (section->characteristics & GRUB_PE32_SCN_CNT_UNINITIALIZED_DATA)
+ {
+ if (section->raw_data_size != 0)
+ grub_dprintf ("multiboot_loader", " UNINITIALIZED_DATA section has data?\n");
+ }
+ else if (section->virtual_address < context.size_of_headers ||
+ section->raw_data_offset < context.size_of_headers)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Section %d is inside image headers", i);
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+
+ if (section->raw_data_size > 0)
+ {
+ grub_dprintf ("multiboot_loader", " copying 0x%08x bytes to 0x%016lx\n",
+ section->raw_data_size, base);
+
+ if (grub_file_seek (mld->file, section->raw_data_offset) == (grub_off_t) -1)
+ {
+ ret = grub_errno;
+ goto out;
+ }
+
+ if (grub_file_read (mld->file, virt_addr + (base - phys_addr), section->raw_data_size)
+ != (grub_ssize_t) section->raw_data_size)
+ {
+ if (!grub_errno)
+ {
+ grub_error (GRUB_ERR_FILE_READ_ERROR, "premature end of file %s",
+ mld->filename);
+ ret = GRUB_ERR_FILE_READ_ERROR;
+ }
+ else
+ ret = grub_errno;
+ goto out;
+ }
+ }
+
+ if (section->raw_data_size < section->virtual_size)
+ {
+ grub_dprintf ("multiboot_loader", " padding with 0x%08x bytes at 0x%016lx\n",
+ section->virtual_size - section->raw_data_size,
+ base + section->raw_data_size);
+ grub_memset (virt_addr + (base - phys_addr) + section->raw_data_size, 0,
+ section->virtual_size - section->raw_data_size);
+ }
+
+ grub_dprintf ("multiboot_loader", " finished section %s\n", name);
+ }
+
+ /* 5 == EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC */
+ if (context.number_of_rva_and_sizes <= 5)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "image has no relocation entry\n");
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+
+ if (context.reloc_dir->size && reloc_section)
+ {
+ /* run the relocation fixups */
+ ret = relocate_coff (&context, reloc_section, phys_addr, virt_addr, mld);
+
+ if (ret)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "relocation failed");
+ goto out;
+ }
+ }
+
+ if (!found_entry_point)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "entry point is not within sections");
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+ if (found_entry_point > 1)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "%d sections contain entry point",
+ found_entry_point);
+ ret = GRUB_ERR_BAD_ARGUMENT;
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+bool
+grub_multiboot2_is_pe (mbi_load_data_t *mld)
+{
+ grub_pe32_msdos_header_t *dos_hdr = mld->buffer;
+
+ return dos_hdr->e_magic == GRUB_PE32_MAGIC;
+}
@@ -20,6 +20,7 @@
#define GRUB_EFI_PE32_HEADER 1
#include <grub/types.h>
+#include <grub/efi/api.h>
#include <grub/efi/memory.h>
/* The MSDOS compatibility stub. This was copied from the output of
@@ -46,6 +47,28 @@
#define GRUB_PE32_MSDOS_STUB_SIZE 0x80
+typedef struct {
+ grub_uint16_t e_magic; ///< Magic number.
+ grub_uint16_t e_cblp; ///< Bytes on last page of file.
+ grub_uint16_t e_cp; ///< Pages in file.
+ grub_uint16_t e_crlc; ///< Relocations.
+ grub_uint16_t e_cparhdr; ///< Size of header in paragraphs.
+ grub_uint16_t e_minalloc; ///< Minimum extra paragraphs needed.
+ grub_uint16_t e_maxalloc; ///< Maximum extra paragraphs needed.
+ grub_uint16_t e_ss; ///< Initial (relative) SS value.
+ grub_uint16_t e_sp; ///< Initial SP value.
+ grub_uint16_t e_csum; ///< Checksum.
+ grub_uint16_t e_ip; ///< Initial IP value.
+ grub_uint16_t e_cs; ///< Initial (relative) CS value.
+ grub_uint16_t e_lfarlc; ///< File address of relocation table.
+ grub_uint16_t e_ovno; ///< Overlay number.
+ grub_uint16_t e_res[4]; ///< Reserved words.
+ grub_uint16_t e_oemid; ///< OEM identifier (for e_oeminfo).
+ grub_uint16_t e_oeminfo; ///< OEM information; e_oemid specific.
+ grub_uint16_t e_res2[10]; ///< Reserved words.
+ grub_uint32_t e_lfanew; ///< File address of new exe header.
+} grub_pe32_msdos_header_t;
+
#define GRUB_PE32_MAGIC 0x5a4d
struct grub_msdos_image_header
@@ -249,6 +272,7 @@ struct grub_pe32_section_table
#define GRUB_PE32_SCN_CNT_CODE 0x00000020
#define GRUB_PE32_SCN_CNT_INITIALIZED_DATA 0x00000040
+#define GRUB_PE32_SCN_CNT_UNINITIALIZED_DATA 0x00000080
#define GRUB_PE32_SCN_MEM_DISCARDABLE 0x02000000
#define GRUB_PE32_SCN_MEM_EXECUTE 0x20000000
#define GRUB_PE32_SCN_MEM_READ 0x40000000
@@ -349,4 +373,44 @@ struct grub_pe32_reloc
#define GRUB_PE32_REL_I386_DIR32 0x6
#define GRUB_PE32_REL_I386_REL32 0x14
+struct grub_pe32_header_32
+{
+ char signature[GRUB_PE32_SIGNATURE_SIZE];
+ struct grub_pe32_coff_header coff_header;
+ struct grub_pe32_optional_header optional_header;
+};
+
+struct grub_pe32_header_64
+{
+ char signature[GRUB_PE32_SIGNATURE_SIZE];
+ struct grub_pe32_coff_header coff_header;
+ struct grub_pe64_optional_header optional_header;
+};
+
+typedef union
+{
+ struct grub_pe32_header_32 pe32;
+ struct grub_pe32_header_64 pe32plus;
+} grub_pe_header_t;
+
+struct pe_coff_loader_image_context
+{
+ grub_efi_uint64_t image_address;
+ grub_efi_uint64_t image_size;
+ grub_efi_uint64_t entry_point;
+ grub_efi_uintn_t size_of_headers;
+ grub_efi_uint16_t image_type;
+ grub_efi_uint16_t number_of_sections;
+ grub_efi_uint32_t section_alignment;
+ struct grub_pe32_section_table *first_section;
+ struct grub_pe32_data_directory *reloc_dir;
+ struct grub_pe32_data_directory *sec_dir;
+ grub_efi_uint64_t number_of_rva_and_sizes;
+ grub_pe_header_t *pe_hdr;
+};
+
+typedef struct pe_coff_loader_image_context pe_coff_loader_image_context_t;
+
+#define EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES 16
+
#endif /* ! GRUB_EFI_PE32_HEADER */
@@ -100,6 +100,9 @@ struct mbi_load_data
};
typedef struct mbi_load_data mbi_load_data_t;
+bool
+grub_multiboot_is_elf (mbi_load_data_t *mld);
+
/* Load ELF32 or ELF64. */
grub_err_t
grub_multiboot_load_elf (mbi_load_data_t *mld);
@@ -92,10 +92,19 @@ struct mbi_load_data
};
typedef struct mbi_load_data mbi_load_data_t;
+bool
+grub_multiboot2_is_elf (mbi_load_data_t *mld);
+
/* Load ELF32 or ELF64. */
grub_err_t
grub_multiboot2_load_elf (mbi_load_data_t *mld);
+grub_err_t
+grub_multiboot2_load_pe (mbi_load_data_t *mld);
+
+bool
+grub_multiboot2_is_pe (mbi_load_data_t *mld);
+
extern grub_size_t grub_multiboot2_pure_size;
extern grub_size_t grub_multiboot2_alloc_mbi;
extern grub_uint32_t grub_multiboot2_payload_eip;
Add the ability to load multiboot binaries in PE format. This allows the binaries to be signed and verified. Signed-off-by: Ross Lagerwall <ross.lagerwall@citrix.com> --- grub-core/Makefile.core.def | 1 + grub-core/loader/multiboot.c | 7 + grub-core/loader/multiboot_mbi2.c | 11 +- grub-core/loader/multiboot_pe.c | 702 ++++++++++++++++++++++++++++++ include/grub/efi/pe32.h | 64 +++ include/grub/multiboot.h | 3 + include/grub/multiboot2.h | 9 + 7 files changed, 796 insertions(+), 1 deletion(-) create mode 100644 grub-core/loader/multiboot_pe.c