919 lines
28 KiB
Diff
919 lines
28 KiB
Diff
From: Julian Andres Klode <julian.klode@canonical.com>
|
|
Date: Mon, 24 Jul 2023 15:26:10 +0200
|
|
Subject: efi: Provide a shim for load_image, start_image, unload_image
|
|
|
|
Provide custom implementations of load_image(), start_image(),
|
|
and unload_image() to workaround shim just forwarding those
|
|
calls to the firmware.
|
|
|
|
The code consumes a PE-COFF image loaded into memory. The functions
|
|
|
|
* check validity of header
|
|
* copy the sections
|
|
* relocate the code
|
|
* invalidate the instruction cache
|
|
* execute the image
|
|
* return to caller
|
|
|
|
This was previously in use in Ubuntu on riscv64 and arm64 only,
|
|
exposed as a single function grub_efi_run_image(). It was
|
|
originally written by Heinrich and split up into 3 functions
|
|
by Julian to integrate with the upstream boot loader.
|
|
|
|
Caveats:
|
|
|
|
- We do not always check for over and underflows, but at the
|
|
point we reach this loader, the file has been verified by
|
|
shim already, so this is not much of a concern.
|
|
|
|
Signed-off-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
|
|
Signed-off-by: Julian Andres Klode <julian.klode@canonical.com>
|
|
---
|
|
grub-core/Makefile.core.def | 13 +
|
|
grub-core/loader/efi/peimage.c | 826 +++++++++++++++++++++++++++++++++++++++++
|
|
include/grub/efi/peimage.h | 19 +
|
|
3 files changed, 858 insertions(+)
|
|
create mode 100644 grub-core/loader/efi/peimage.c
|
|
create mode 100644 include/grub/efi/peimage.h
|
|
|
|
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
|
|
index 333d3fe..7e7e360 100644
|
|
--- a/grub-core/Makefile.core.def
|
|
+++ b/grub-core/Makefile.core.def
|
|
@@ -1875,6 +1875,19 @@ module = {
|
|
common = lib/cmdline.c;
|
|
};
|
|
|
|
+module = {
|
|
+ name = peimage;
|
|
+ efi = loader/efi/peimage.c;
|
|
+ enable = arm_efi;
|
|
+ enable = arm64_efi;
|
|
+ enable = i386_efi;
|
|
+ enable = x86_64_efi;
|
|
+ enable = riscv32_efi;
|
|
+ enable = riscv64_efi;
|
|
+ enable = loongarch64_efi;
|
|
+};
|
|
+
|
|
+
|
|
module = {
|
|
name = fdt;
|
|
efi = loader/efi/fdt.c;
|
|
diff --git a/grub-core/loader/efi/peimage.c b/grub-core/loader/efi/peimage.c
|
|
new file mode 100644
|
|
index 0000000..b04fc61
|
|
--- /dev/null
|
|
+++ b/grub-core/loader/efi/peimage.c
|
|
@@ -0,0 +1,826 @@
|
|
+/* peimage.c - load EFI PE binaries (for Secure Boot support) */
|
|
+
|
|
+// SPDX-License-Identifier: GPL-3.0+
|
|
+
|
|
+#include <grub/cache.h>
|
|
+#include <grub/cpu/efi/memory.h>
|
|
+#include <grub/dl.h>
|
|
+#include <grub/efi/efi.h>
|
|
+#include <grub/efi/pe32.h>
|
|
+#include <grub/efi/peimage.h>
|
|
+#include <grub/env.h>
|
|
+#include <grub/misc.h>
|
|
+#include <grub/mm.h>
|
|
+#include <grub/setjmp.h>
|
|
+
|
|
+GRUB_MOD_LICENSE ("GPLv3+");
|
|
+
|
|
+static grub_dl_t my_mod;
|
|
+
|
|
+struct image_info
|
|
+{
|
|
+ void *data;
|
|
+ grub_efi_uint32_t data_size;
|
|
+ grub_efi_device_path_t *file_path;
|
|
+ grub_efi_uint16_t machine;
|
|
+ grub_efi_uint16_t num_sections;
|
|
+ struct grub_pe32_section_table *section;
|
|
+ struct grub_pe32_data_directory *reloc;
|
|
+ grub_uint64_t image_base;
|
|
+ grub_uint32_t section_alignment;
|
|
+ grub_uint32_t image_size;
|
|
+ grub_uint32_t header_size;
|
|
+ void *alloc_addr;
|
|
+ grub_uint32_t alloc_pages;
|
|
+ void *image_addr;
|
|
+ grub_efi_status_t (__grub_efi_api *entry_point) (
|
|
+ grub_efi_handle_t image_handle, grub_efi_system_table_t *system_table);
|
|
+};
|
|
+
|
|
+static struct
|
|
+{
|
|
+ grub_jmp_buf jmp;
|
|
+ grub_efi_handle_t image_handle;
|
|
+ grub_efi_status_t exit_status;
|
|
+ grub_efi_status_t (__grub_efi_api *exit) (grub_efi_handle_t image_handle,
|
|
+ grub_efi_status_t exit_status,
|
|
+ grub_efi_uintn_t exit_data_size,
|
|
+ grub_efi_char16_t *exit_data);
|
|
+} started_image;
|
|
+
|
|
+
|
|
+static grub_uint16_t machines[] = {
|
|
+#if defined(__x86_64__)
|
|
+ GRUB_PE32_MACHINE_X86_64,
|
|
+#elif defined(__i386__)
|
|
+ GRUB_PE32_MACHINE_I386,
|
|
+#elif defined(__aarch64__)
|
|
+ GRUB_PE32_MACHINE_ARM64,
|
|
+#elif defined(__arm__)
|
|
+ GRUB_PE32_MACHINE_ARMTHUMB_MIXED,
|
|
+#elif defined(__riscv) && __riscv_xlen == 32
|
|
+ GRUB_PE32_MACHINE_RISCV32,
|
|
+#elif defined(__riscv) && __riscv_xlen == 64
|
|
+ GRUB_PE32_MACHINE_RISCV64,
|
|
+#elif defined(__loongarch__) && __loongarch_grlen == 64
|
|
+ GRUB_PE32_MACHINE_LOONGARCH64,
|
|
+#endif
|
|
+};
|
|
+
|
|
+/**
|
|
+ * check_machine_type() - check if the machine type matches the architecture
|
|
+ *
|
|
+ * @machine: the value of the Machine field of the COFF file header.
|
|
+ * Return: status code
|
|
+ */
|
|
+static grub_efi_status_t
|
|
+check_machine_type (grub_uint16_t machine)
|
|
+{
|
|
+ for (grub_size_t i = 0; i < sizeof (machines) / sizeof (*machines); ++i)
|
|
+ {
|
|
+ if (machine == machines[i])
|
|
+ return GRUB_EFI_SUCCESS;
|
|
+ }
|
|
+
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * check_pe_header() - check the headers of a PE-COFF image
|
|
+ *
|
|
+ * @info: information about the image
|
|
+ */
|
|
+static grub_efi_status_t
|
|
+check_pe_header (struct image_info *info)
|
|
+{
|
|
+ struct grub_msdos_image_header *dos_stub = info->data;
|
|
+ void *pe_magic;
|
|
+ struct grub_pe32_coff_header *coff_header;
|
|
+ struct grub_pe32_optional_header *pe32_header;
|
|
+ struct grub_pe64_optional_header *pe64_header;
|
|
+
|
|
+ if (info->data_size < sizeof (struct grub_msdos_image_header))
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "truncated image");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+ if (dos_stub->msdos_magic != GRUB_PE32_MAGIC)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "not a PE-COFF file");
|
|
+ return GRUB_EFI_UNSUPPORTED;
|
|
+ }
|
|
+ if (info->data_size < dos_stub->pe_image_header_offset
|
|
+ + GRUB_PE32_SIGNATURE_SIZE
|
|
+ + sizeof (struct grub_pe32_coff_header)
|
|
+ + sizeof (struct grub_pe64_optional_header))
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "truncated image");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+ pe_magic
|
|
+ = (void *)((unsigned long)info->data + dos_stub->pe_image_header_offset);
|
|
+ if (grub_memcmp (pe_magic, GRUB_PE32_SIGNATURE, GRUB_PE32_SIGNATURE_SIZE))
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "not a PE-COFF file");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ coff_header = (void *)((unsigned long)pe_magic + GRUB_PE32_SIGNATURE_SIZE);
|
|
+ info->machine = coff_header->machine;
|
|
+ info->num_sections = coff_header->num_sections;
|
|
+
|
|
+ if (check_machine_type (info->machine) != GRUB_EFI_SUCCESS)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "wrong machine type %u",
|
|
+ coff_header->machine);
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ pe32_header = (void *)((unsigned long)coff_header + sizeof (*coff_header));
|
|
+ pe64_header = (void *)((unsigned long)coff_header + sizeof (*coff_header));
|
|
+
|
|
+ switch (pe32_header->magic)
|
|
+ {
|
|
+ case GRUB_PE32_PE32_MAGIC:
|
|
+ if (pe32_header->subsystem != GRUB_PE32_SUBSYSTEM_EFI_APPLICATION)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "expected EFI application");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+ info->section_alignment = pe32_header->section_alignment;
|
|
+ info->image_base = pe32_header->image_base;
|
|
+ info->image_size = pe32_header->image_size;
|
|
+ info->entry_point = (void *)(unsigned long)pe32_header->entry_addr;
|
|
+ info->header_size = pe32_header->header_size;
|
|
+ if (info->data_size < info->header_size)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "truncated image");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ if (pe32_header->num_data_directories >= 6
|
|
+ && pe32_header->base_relocation_table.size)
|
|
+ info->reloc = &pe32_header->base_relocation_table;
|
|
+
|
|
+ info->section
|
|
+ = (void *)((unsigned long)&pe32_header->export_table
|
|
+ + pe32_header->num_data_directories
|
|
+ * sizeof (struct grub_pe32_data_directory));
|
|
+ break;
|
|
+ case GRUB_PE32_PE64_MAGIC:
|
|
+ if (pe64_header->subsystem != GRUB_PE32_SUBSYSTEM_EFI_APPLICATION)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "expected EFI application");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+ info->section_alignment = pe64_header->section_alignment;
|
|
+ info->image_base = pe64_header->image_base;
|
|
+ info->image_size = pe64_header->image_size;
|
|
+ info->entry_point = (void *)(unsigned long)pe64_header->entry_addr;
|
|
+ info->header_size = pe64_header->header_size;
|
|
+ if (info->data_size < info->header_size)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "truncated image");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ if (pe64_header->num_data_directories >= 6
|
|
+ && pe64_header->base_relocation_table.size)
|
|
+ info->reloc = &pe64_header->base_relocation_table;
|
|
+
|
|
+ info->section
|
|
+ = (void *)((unsigned long)&pe64_header->export_table
|
|
+ + pe64_header->num_data_directories
|
|
+ * sizeof (struct grub_pe32_data_directory));
|
|
+ break;
|
|
+ default:
|
|
+ grub_error (GRUB_ERR_BAD_OS, "not a PE-COFF file");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ if ((unsigned long)info->section
|
|
+ + info->num_sections * sizeof (*info->section)
|
|
+ > (unsigned long)info->data + info->data_size)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "truncated image");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ grub_dprintf ("linux", "PE-COFF header checked\n");
|
|
+
|
|
+ return GRUB_EFI_SUCCESS;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * load_sections() - load image sections into memory
|
|
+ *
|
|
+ * Allocate fresh memory and copy the image sections there.
|
|
+ *
|
|
+ * @info: image information
|
|
+ */
|
|
+static grub_efi_status_t
|
|
+load_sections (struct image_info *info)
|
|
+{
|
|
+ struct grub_pe32_section_table *section;
|
|
+ unsigned long align_mask = 0xfff;
|
|
+
|
|
+ /* Section alignment must be a power of two */
|
|
+ if (info->section_alignment & (info->section_alignment - 1))
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "invalid section alignment");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ if (info->section_alignment > align_mask)
|
|
+ align_mask = info->section_alignment - 1;
|
|
+
|
|
+ info->alloc_pages = GRUB_EFI_BYTES_TO_PAGES (info->image_size + (align_mask & ~0xfffUL));
|
|
+
|
|
+ info->alloc_addr = grub_efi_allocate_pages_real (
|
|
+ GRUB_EFI_MAX_USABLE_ADDRESS, info->alloc_pages,
|
|
+ GRUB_EFI_ALLOCATE_MAX_ADDRESS, GRUB_EFI_LOADER_CODE);
|
|
+ if (!info->alloc_addr)
|
|
+ return GRUB_EFI_OUT_OF_RESOURCES;
|
|
+
|
|
+ info->image_addr
|
|
+ = (void *)(((unsigned long)info->alloc_addr + align_mask) & ~align_mask);
|
|
+
|
|
+ grub_memcpy (info->image_addr, info->data, info->header_size);
|
|
+ for (section = &info->section[0];
|
|
+ section < &info->section[info->num_sections]; ++section)
|
|
+ {
|
|
+ if (section->virtual_address < info->header_size
|
|
+ || (section->raw_data_size
|
|
+ && section->raw_data_offset < info->header_size))
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "section inside header");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+ if (section->raw_data_offset + section->raw_data_size > info->data_size)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "truncated image");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+ if (section->virtual_address + section->virtual_size > info->image_size)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "section outside image");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ grub_memset ((void *)((unsigned long)info->image_addr + section->virtual_address),
|
|
+ 0, section->virtual_size);
|
|
+ grub_memcpy (
|
|
+ (void *)((unsigned long)info->image_addr + section->virtual_address),
|
|
+ (void *)((unsigned long)info->data + section->raw_data_offset),
|
|
+ section->raw_data_size);
|
|
+ }
|
|
+
|
|
+ info->entry_point = (void *)((unsigned long)info->entry_point
|
|
+ + (unsigned long)info->image_addr);
|
|
+
|
|
+ grub_dprintf ("linux", "sections loaded\n");
|
|
+
|
|
+ return GRUB_EFI_SUCCESS;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * lo12i_get() - get the immediate value of a format I instruction
|
|
+ *
|
|
+ * Instruction format I::
|
|
+ *
|
|
+ * +---------------------------------------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0 f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +-----------------------+---------+-----+---------+-------------+
|
|
+ * | imm[11:0] | rs1 |fun3 | rd | opcode |
|
|
+ * +-----------------------+---------+-----+---------+-------------+
|
|
+ *
|
|
+ * @instr: pointer to instruction
|
|
+ * Return: immediate value
|
|
+ */
|
|
+static grub_uint16_t
|
|
+lo12i_get (grub_uint32_t *instr)
|
|
+{
|
|
+ return ((*instr & 0xfff00000) >> 20);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * lo12i_set() - set the immediate value of a format I instruction
|
|
+ *
|
|
+ * Instruction format I::
|
|
+ *
|
|
+ * +---------------------------------------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0 f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +-----------------------+---------+-----+---------+-------------+
|
|
+ * | imm[11:0] | rs1 |fun3 | rd | opcode |
|
|
+ * +-----------------------+---------+-----+---------+-------------+
|
|
+ *
|
|
+ * @instr: pointer to instruction
|
|
+ * @imm: immediate value
|
|
+ */
|
|
+static void
|
|
+lo12i_set (grub_uint32_t *instr, grub_uint32_t imm)
|
|
+{
|
|
+ *instr = (*instr & 0x000fffff) | (imm & 0x00000fff << 20);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * hi20_get() - get the immediate value of a format I instruction
|
|
+ *
|
|
+ * Instruction format U::
|
|
+ *
|
|
+ * +---------------------------------------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0 f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +---------------------------------------+---------+-------------+
|
|
+ * | imm[31:12] | rd | opcode |
|
|
+ * +---------------------------------------+---------+-------------+
|
|
+ *
|
|
+ * @instr: pointer to instruction
|
|
+ * Return: immediate value
|
|
+ */
|
|
+static grub_uint16_t
|
|
+hi20_get (grub_uint32_t *instr)
|
|
+{
|
|
+ return *instr & 0xfffff000;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * hi20_set() - set the immediate value of a format I instruction
|
|
+ *
|
|
+ * Instruction format U::
|
|
+ *
|
|
+ * +---------------------------------------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0 f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +---------------------------------------+---------+-------------+
|
|
+ * | imm[31:12] | rd | opcode |
|
|
+ * +---------------------------------------+---------+-------------+
|
|
+ *
|
|
+ * @instr: pointer to instruction
|
|
+ * @imm: immediate value
|
|
+ */
|
|
+static void
|
|
+hi20_set (grub_uint32_t *instr, grub_uint32_t imm)
|
|
+{
|
|
+ *instr = (*instr & 0x00000fff) | (imm & 0xfffff000);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * lo12s_get() - get the immediate value of a format I instruction
|
|
+ *
|
|
+ * Instruction format S::
|
|
+ *
|
|
+ * +---------------------------------------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0 f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +-------------+---------+---------+-----+----+----+-------------+
|
|
+ * | imm[11:5] | rs2 | rs1 |fun3 |imm[4:0] | opcode |
|
|
+ * +-------------+---------+---------+-----+----+----+-------------+
|
|
+ *
|
|
+ * @instr: pointer to instruction
|
|
+ * Return: immediate value
|
|
+ */
|
|
+static grub_uint16_t
|
|
+lo12s_get (grub_uint32_t *instr)
|
|
+{
|
|
+ return ((*instr & 0x00000f80) >> 7) | ((*instr & 0xfe000000) >> 20);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * lo12s_set() - set the immediate value of a format I instruction
|
|
+ *
|
|
+ * Instruction format S::
|
|
+ *
|
|
+ * +---------------------------------------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0 f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +-------------+---------+---------+-----+----+----+-------------+
|
|
+ * | imm[11:5] | rs2 | rs1 |fun3 |imm[4:0] | opcode |
|
|
+ * +-------------+---------+---------+-----+----+----+-------------+
|
|
+ *
|
|
+ * @instr: pointer to instruction
|
|
+ * @imm: immediate value
|
|
+ */
|
|
+static void
|
|
+lo12s_set (grub_uint32_t *instr, grub_uint32_t imm)
|
|
+{
|
|
+ *instr = (*instr & 0x01fff07f) | (imm & 0x00000fe0 << 20)
|
|
+ | (imm & 0x0000001f << 7);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * movw_get_imm() - get the immediate value of MOVT and MOVW instructions
|
|
+ *
|
|
+ * MOVT::
|
|
+ *
|
|
+ * +-------------------------------+-------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0|f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +---------+-+-----------+-------+-+-----+-------+---------------+
|
|
+ * |1 1 1 1 0|i|1 0 1 1 0 0| imm4 |0| imm3| Rd | imm8 |
|
|
+ * +---------+-+-----------+-------+-+-----+-------+---------------+
|
|
+ *
|
|
+ * MOVW::
|
|
+ *
|
|
+ * +-------------------------------+-------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0|f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +---------+-+-----------+-------+-+-----+-------+---------------+
|
|
+ * |1 1 1 1 0|i|1 0 0 1 0 0| imm4 |0| imm3| Rd | imm8 |
|
|
+ * +---------+-+-----------+-------+-+-----+-------+---------------+
|
|
+ *
|
|
+ * @instr: pointer to instruction
|
|
+ * Return: immediate value
|
|
+ */
|
|
+static grub_uint16_t
|
|
+movw_get_imm (grub_uint16_t *instr)
|
|
+{
|
|
+ /* imm16 = imm4:i:imm3:imm8; */
|
|
+ return (instr[1] & 0x00ff) | ((instr[1] & 0x7000) >> 3)
|
|
+ | ((instr[0] & 0x0400) >> 8) | ((instr[0] & 0x000f) << 12);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * movw_set_imm() - set the immediate value of MOVT and MOVW instructions
|
|
+ *
|
|
+ * MOVT::
|
|
+ *
|
|
+ * +-------------------------------+-------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0|f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +---------+-+-----------+-------+-+-----+-------+---------------+
|
|
+ * |1 1 1 1 0|i|1 0 1 1 0 0| imm4 |0| imm3| Rd | imm8 |
|
|
+ * +---------+-+-----------+-------+-+-----+-------+---------------+
|
|
+ *
|
|
+ * MOVW::
|
|
+ *
|
|
+ * +-------------------------------+-------------------------------+
|
|
+ * |f e d c b a 9 8 7 6 5 4 3 2 1 0|f e d c b a 9 8 7 6 5 4 3 2 1 0|
|
|
+ * +---------+-+-----------+-------+-+-----+-------+---------------+
|
|
+ * |1 1 1 1 0|i|1 0 0 1 0 0| imm4 |0| imm3| Rd | imm8 |
|
|
+ * +---------+-+-----------+-------+-+-----+-------+---------------+
|
|
+ *
|
|
+ * @instr: pointer to instruction
|
|
+ * @imm immediate value
|
|
+ */
|
|
+static void
|
|
+movw_set_imm (grub_uint16_t *instr, grub_uint16_t imm)
|
|
+{
|
|
+ /* imm16 = imm4:i:imm3:imm8; */
|
|
+ instr[0] = (instr[0] & 0xfbf0) | (imm & 0xf000) >> 12 | (imm & 0x0800) << 3;
|
|
+ instr[1] = (instr[0] & 0x8f00) | (imm & 0xff) | (imm & 0x0700) >> 4;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * relocate() - apply relocations to the image
|
|
+ *
|
|
+ * @info: information about the loaded image
|
|
+ */
|
|
+static grub_efi_status_t
|
|
+relocate (struct image_info *info)
|
|
+{
|
|
+ struct grub_pe32_fixup_block *block, *reloc_end;
|
|
+ unsigned long offset;
|
|
+ grub_uint16_t reloc_type;
|
|
+ grub_uint16_t *reloc_entry;
|
|
+ grub_uint32_t *rvhi20_addr = NULL;
|
|
+
|
|
+ if (!info->reloc || !(info->reloc->size))
|
|
+ {
|
|
+ grub_dprintf ("linux", "no relocations\n");
|
|
+ return GRUB_EFI_SUCCESS;
|
|
+ }
|
|
+
|
|
+ if (info->reloc->rva + info->reloc->size > info->image_size)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "relocation block outside image");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * The relocations are based on the difference between
|
|
+ * actual load address and the preferred base address.
|
|
+ */
|
|
+ offset = (unsigned long)info->image_addr - info->image_base;
|
|
+
|
|
+ block = (void *)((unsigned long)info->image_addr + info->reloc->rva);
|
|
+ reloc_end = (void *)((unsigned long)block + info->reloc->size);
|
|
+
|
|
+ for (; block < reloc_end;
|
|
+ block = (void *)((unsigned long)block + block->block_size))
|
|
+ {
|
|
+ reloc_entry = block->entries;
|
|
+ grub_uint16_t *block_end
|
|
+ = (void *)((unsigned long)block + block->block_size);
|
|
+
|
|
+ for (; reloc_entry < block_end; ++reloc_entry)
|
|
+ {
|
|
+ void *addr = (void *)((unsigned long)info->image_addr
|
|
+ + block->page_rva + (*reloc_entry & 0xfff));
|
|
+
|
|
+ reloc_type = *reloc_entry >> 12;
|
|
+
|
|
+ switch (reloc_type)
|
|
+ {
|
|
+ case GRUB_PE32_REL_BASED_ABSOLUTE:
|
|
+ /* skip */
|
|
+ break;
|
|
+ case GRUB_PE32_REL_BASED_HIGH:
|
|
+ *(grub_uint16_t *)addr += offset >> 16;
|
|
+ break;
|
|
+ case GRUB_PE32_REL_BASED_LOW:
|
|
+ *(grub_uint16_t *)addr += offset;
|
|
+ break;
|
|
+ case GRUB_PE32_REL_BASED_HIGHLOW:
|
|
+ *(grub_uint32_t *)addr += offset;
|
|
+ break;
|
|
+ case GRUB_PE32_REL_BASED_RISCV_HI20:
|
|
+ switch (info->machine)
|
|
+ {
|
|
+ case GRUB_PE32_MACHINE_RISCV32:
|
|
+ case GRUB_PE32_MACHINE_RISCV64:
|
|
+ rvhi20_addr = addr;
|
|
+ break;
|
|
+ default:
|
|
+ goto bad_reloc;
|
|
+ }
|
|
+ break;
|
|
+ case GRUB_PE32_REL_BASED_ARM_MOV32T:
|
|
+ /* = GRUB_PE32_REL_BASED_RISCV_LOW12I */
|
|
+ switch (info->machine)
|
|
+ {
|
|
+ case GRUB_PE32_MACHINE_ARMTHUMB_MIXED:
|
|
+ {
|
|
+ grub_uint16_t *instr = addr;
|
|
+ grub_uint32_t val;
|
|
+
|
|
+ val = movw_get_imm (&instr[0])
|
|
+ + (movw_get_imm (&instr[2]) << 16) + offset;
|
|
+ movw_set_imm (&instr[0], val);
|
|
+ movw_set_imm (&instr[2], val >> 16);
|
|
+ break;
|
|
+ }
|
|
+ case GRUB_PE32_MACHINE_RISCV32:
|
|
+ case GRUB_PE32_MACHINE_RISCV64:
|
|
+ if (rvhi20_addr)
|
|
+ {
|
|
+ grub_uint32_t val
|
|
+ = hi20_get (rvhi20_addr) + lo12i_get (addr) + offset;
|
|
+ hi20_set (rvhi20_addr, val);
|
|
+ lo12i_set (addr, val);
|
|
+ rvhi20_addr = NULL;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ goto bad_reloc;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ goto bad_reloc;
|
|
+ }
|
|
+ break;
|
|
+ case GRUB_PE32_REL_BASED_RISCV_LOW12S:
|
|
+ switch (info->machine)
|
|
+ {
|
|
+ case GRUB_PE32_MACHINE_RISCV32:
|
|
+ case GRUB_PE32_MACHINE_RISCV64:
|
|
+ if (rvhi20_addr)
|
|
+ {
|
|
+ grub_uint32_t val
|
|
+ = hi20_get (rvhi20_addr) + lo12s_get (addr) + offset;
|
|
+ hi20_set (rvhi20_addr, val);
|
|
+ lo12s_set (addr, val);
|
|
+ rvhi20_addr = NULL;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ goto bad_reloc;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ goto bad_reloc;
|
|
+ }
|
|
+ break;
|
|
+ case GRUB_PE32_REL_BASED_DIR64:
|
|
+ *(grub_uint64_t *)addr += offset;
|
|
+ break;
|
|
+ default:
|
|
+ goto bad_reloc;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ grub_dprintf ("linux", "image relocated\n");
|
|
+
|
|
+ return GRUB_EFI_SUCCESS;
|
|
+
|
|
+bad_reloc:
|
|
+ grub_error (GRUB_ERR_BAD_OS, "unsupported relocation type %d, rva 0x%08lx\n",
|
|
+ *reloc_entry >> 12,
|
|
+ (unsigned long)reloc_entry - (unsigned long)info->image_addr);
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * efi_exit() - replacement for EFI_BOOT_SERVICES.Exit()
|
|
+ *
|
|
+ * This function is inserted into system table to trap invocations of
|
|
+ * EFI_BOOT_SERVICES.Exit(). If Exit() is called with our handle
|
|
+ * return to our start routine using a long jump.
|
|
+ *
|
|
+ * @image_handle: handle of the application as passed on entry
|
|
+ * @exit_status: the images exit code
|
|
+ * @exit_data_size: size of @exit_data
|
|
+ * @exit_data: null terminated string followed by optional data
|
|
+ */
|
|
+static grub_efi_status_t __grub_efi_api
|
|
+efi_exit (grub_efi_handle_t image_handle, grub_efi_status_t exit_status,
|
|
+ grub_efi_uintn_t exit_data_size, grub_efi_char16_t *exit_data)
|
|
+{
|
|
+ grub_efi_system_table->boot_services->exit = started_image.exit;
|
|
+
|
|
+ if (!image_handle)
|
|
+ return GRUB_EFI_INVALID_PARAMETER;
|
|
+
|
|
+ if (image_handle != started_image.image_handle)
|
|
+ {
|
|
+ grub_dprintf ("linux", "delegating Exit()\n");
|
|
+ return started_image.exit (image_handle, exit_status, exit_data_size,
|
|
+ (grub_efi_char16_t *)exit_data);
|
|
+ }
|
|
+
|
|
+ started_image.exit_status = exit_status;
|
|
+
|
|
+ if (exit_status != GRUB_EFI_SUCCESS)
|
|
+ {
|
|
+ grub_printf ("Application failed, r = %d\n",
|
|
+ (int)exit_status & 0x7fffffff);
|
|
+ if (exit_data_size && exit_data)
|
|
+ {
|
|
+ grub_printf ("exit message: ");
|
|
+ for (grub_efi_uintn_t pos = 0;
|
|
+ exit_data[pos] && pos < exit_data_size / 2; ++pos)
|
|
+ grub_printf ("%C", exit_data[pos]);
|
|
+ grub_printf ("\n");
|
|
+ }
|
|
+ }
|
|
+ if (exit_data_size && exit_data)
|
|
+ {
|
|
+ /* exit data must be freed by the caller */
|
|
+ grub_efi_system_table->boot_services->free_pool (exit_data);
|
|
+ }
|
|
+ grub_longjmp (started_image.jmp, 1);
|
|
+}
|
|
+
|
|
+static grub_efi_status_t __grub_efi_api
|
|
+do_unload_image (grub_efi_handle_t image_handle);
|
|
+
|
|
+/**
|
|
+ * start_image() - our implementation of StartImage()
|
|
+ *
|
|
+ * As we do not load the image via LoadImage() we need our own implementation
|
|
+ * of StartImage() to launch the PE-COFF image.
|
|
+ */
|
|
+static grub_efi_status_t
|
|
+start_image (struct image_info *info)
|
|
+{
|
|
+ int ret;
|
|
+ grub_efi_status_t status;
|
|
+ grub_efi_loaded_image_t *loaded_image;
|
|
+
|
|
+ /*
|
|
+ * NOTE: We cannot easily comply with the UEFI specification and provide the
|
|
+ * child its own handle, otherwise things can go horribly wrong if said custom
|
|
+ * handle is passed to the firmware by child images
|
|
+ */
|
|
+ started_image.image_handle = grub_efi_image_handle;
|
|
+
|
|
+ loaded_image = grub_efi_get_loaded_image (grub_efi_image_handle);
|
|
+ if (loaded_image)
|
|
+ {
|
|
+ loaded_image->image_base = info->image_addr;
|
|
+ loaded_image->image_size = info->image_size;
|
|
+
|
|
+ // Pass just the file path portion to the loaded image
|
|
+ loaded_image->file_path = info->file_path;
|
|
+ while (loaded_image->file_path &&
|
|
+ (loaded_image->file_path->type != GRUB_EFI_MEDIA_DEVICE_PATH_TYPE
|
|
+ || loaded_image->file_path->subtype != GRUB_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE))
|
|
+ loaded_image->file_path = GRUB_EFI_NEXT_DEVICE_PATH (loaded_image->file_path);
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ grub_dprintf ("linux", "Loaded image protocol missing\n");
|
|
+ }
|
|
+
|
|
+ ret = grub_setjmp (started_image.jmp);
|
|
+ if (ret)
|
|
+ {
|
|
+ do_unload_image(started_image.image_handle);
|
|
+ started_image.image_handle = NULL;
|
|
+ return started_image.exit_status;
|
|
+ }
|
|
+
|
|
+ started_image.exit = grub_efi_system_table->boot_services->exit;
|
|
+ grub_efi_system_table->boot_services->exit = efi_exit;
|
|
+
|
|
+ grub_dprintf (
|
|
+ "linux",
|
|
+ "Executing image loaded at 0x%lx\nEntry point 0x%lx\nSize 0x%08x\n",
|
|
+ (unsigned long)info->image_addr, (unsigned long)info->entry_point,
|
|
+ info->image_size);
|
|
+
|
|
+ /* Invalidate the instruction cache */
|
|
+ grub_arch_sync_caches (info->image_addr, info->image_size);
|
|
+
|
|
+ status = info->entry_point (started_image.image_handle, grub_efi_system_table);
|
|
+
|
|
+ grub_dprintf ("linux", "Application returned\n");
|
|
+
|
|
+ return efi_exit (started_image.image_handle, status, 0, NULL);
|
|
+}
|
|
+
|
|
+static struct image_info info;
|
|
+
|
|
+/* TODO: move the creation of the load options here */
|
|
+static grub_efi_status_t __grub_efi_api
|
|
+do_load_image (grub_efi_boolean_t boot_policy __attribute__ ((unused)),
|
|
+ grub_efi_handle_t parent_image_handle __attribute__ ((unused)),
|
|
+ grub_efi_device_path_t *file_path,
|
|
+ void *source_buffer, grub_efi_uintn_t source_size,
|
|
+ grub_efi_handle_t *image_handle)
|
|
+{
|
|
+ grub_efi_status_t ret = GRUB_EFI_SUCCESS;
|
|
+ if (info.data != NULL)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "cannot load multiple images");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+
|
|
+ grub_dl_ref (my_mod);
|
|
+
|
|
+ info = (struct image_info){
|
|
+ .data = source_buffer,
|
|
+ .data_size = source_size,
|
|
+ .file_path = grub_efi_duplicate_device_path(file_path),
|
|
+ };
|
|
+
|
|
+ ret = check_pe_header (&info);
|
|
+ if (ret != GRUB_EFI_SUCCESS)
|
|
+ goto err;
|
|
+
|
|
+ ret = load_sections (&info);
|
|
+ if (ret != GRUB_EFI_SUCCESS)
|
|
+ goto err;
|
|
+
|
|
+ ret = relocate (&info);
|
|
+ if (ret != GRUB_EFI_SUCCESS)
|
|
+ goto err;
|
|
+
|
|
+ // We are hacking this up as we go along
|
|
+ *image_handle = grub_efi_image_handle;
|
|
+err:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static grub_efi_status_t __grub_efi_api
|
|
+do_start_image (grub_efi_handle_t image_handle __attribute__ ((unused)),
|
|
+ grub_efi_uintn_t *exit_data_size __attribute__ ((unused)),
|
|
+ grub_efi_char16_t **exit_data __attribute__ ((unused)))
|
|
+{
|
|
+ if (info.data == NULL)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "image not loaded");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+ return start_image (&info);
|
|
+}
|
|
+
|
|
+static grub_efi_status_t __grub_efi_api
|
|
+do_unload_image (grub_efi_handle_t image_handle __attribute__ ((unused)))
|
|
+{
|
|
+ if (info.data == NULL)
|
|
+ {
|
|
+ grub_error (GRUB_ERR_BAD_OS, "image not loaded");
|
|
+ return GRUB_EFI_LOAD_ERROR;
|
|
+ }
|
|
+ if (info.alloc_addr)
|
|
+ grub_efi_free_pages ((unsigned long)info.alloc_addr, info.alloc_pages);
|
|
+ if (info.file_path)
|
|
+ grub_free(info.file_path);
|
|
+
|
|
+ grub_dl_unref (my_mod);
|
|
+ info = (struct image_info){};
|
|
+
|
|
+ return GRUB_EFI_SUCCESS;
|
|
+}
|
|
+
|
|
+static const grub_efi_loader_t peimage_loader = {
|
|
+ .load_image = do_load_image,
|
|
+ .start_image = do_start_image,
|
|
+ .unload_image = do_unload_image,
|
|
+};
|
|
+
|
|
+GRUB_MOD_INIT (peimage)
|
|
+{
|
|
+ grub_efi_register_loader (&peimage_loader);
|
|
+ my_mod = mod;
|
|
+}
|
|
+
|
|
+GRUB_MOD_FINI (peimage)
|
|
+{
|
|
+ grub_efi_unregister_loader (&peimage_loader);
|
|
+}
|
|
diff --git a/include/grub/efi/peimage.h b/include/grub/efi/peimage.h
|
|
new file mode 100644
|
|
index 0000000..e57d6de
|
|
--- /dev/null
|
|
+++ b/include/grub/efi/peimage.h
|
|
@@ -0,0 +1,19 @@
|
|
+/* SPDX-License-Identifier: GPL-3.0+ */
|
|
+
|
|
+/* Distinguishing our loaded image handles from the firmware's */
|
|
+#define GRUB_PEIMAGE_MARKER_GUID \
|
|
+ { 0xda24567a, 0xf899, 0x4566, \
|
|
+ { 0xb8, 0x27, 0x9f, 0x66, 0x00, 0xc2, 0x14, 0x39 } \
|
|
+ }
|
|
+
|
|
+/* Associates an image handle with the device path it was loaded from */
|
|
+#define GRUB_EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID \
|
|
+ { 0xbc62157e, 0x3e33, 0x4fec, \
|
|
+ { 0x99, 0x20, 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf } \
|
|
+ }
|
|
+
|
|
+/* Revision defined for the EFI_LOADED_IMAGE_PROTOCOL */
|
|
+#define GRUB_EFI_LOADED_IMAGE_REVISION 0x1000
|
|
+
|
|
+/* Value of the signature field of a PE image header */
|
|
+#define GRUB_PE32_SIGNATURE "PE\0"
|