From e097b4e25469aabdceac79c45cca27029824c1b5 Mon Sep 17 00:00:00 2001
From: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
Date: Fri, 27 Mar 2015 08:19:21 -0700
Subject: [PATCH 4/7] efi: chainloader: port shim to grub

Upstream-Status: Inappropriate [embedded specific]

Shim is a thin loader to execute signed binaries under the
chain of trust of UEFI secure boot. Before executing the image,
shim verifies that such image is signed with any of the Machine
Owner Keys (MOKs). If the verification is successful, shim will
load, parse, relocate and execute the image.

Shim is useful in case the user does not want to modify the UEFI
database of valid certificates (DB).

This commit ports Matthew Garret's code from shim to grub in order
to provide to grub the capability of load and execute trusted
binaries. This is useful in case we need to chainload two bootloaders.

Shim can be found here: https://github.com/rhinstaller/shim

Signed-off-by: Ricardo Neri <ricardo.neri-calderon@linux.intel.com>
---
 grub-core/loader/efi/chainloader.c | 534 +++++++++++++++++++++++++++++++++++++
 1 file changed, 534 insertions(+)

diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c
index 3f3e6e3..bd83859 100644
--- a/grub-core/loader/efi/chainloader.c
+++ b/grub-core/loader/efi/chainloader.c
@@ -32,6 +32,7 @@
 #include <grub/efi/api.h>
 #include <grub/efi/efi.h>
 #include <grub/efi/disk.h>
+#include <grub/efi/shim.h>
 #include <grub/command.h>
 #include <grub/i18n.h>
 #include <grub/net.h>
@@ -45,6 +46,539 @@ static grub_efi_uintn_t pages;
 static grub_efi_device_path_t *file_path;
 static grub_efi_handle_t image_handle;
 static grub_efi_char16_t *cmdline;
+static grub_int32_t shim_used;
+static grub_efi_physical_address_t shim_buffer;
+static grub_efi_uintn_t shim_pages;
+static grub_efi_loaded_image_t shim_li_bak;
+static grub_efi_status_t (*shim_entry_point) (grub_efi_handle_t image_handle,
+					      grub_efi_system_table_t *systab);
+
+static const grub_uint16_t
+grub_shim_machine_type =
+#if defined(__x86_64__)
+  GRUB_PE32_MACHINE_X86_64;
+#elif defined(__aarch64__)
+  IMAGE_FILE_MACHINE_ARM64;
+#elif defined(__arm__)
+  IMAGE_FILE_MACHINE_ARMTHUMB_MIXED;
+#elif defined(__i386__) || defined(__i486__) || defined(__i686__)
+  GRUB_PE32_MACHINE_I386;
+#elif defined(__ia64__)
+  GRUB_PE32_MACHINE_IA64;
+#else
+#error this architecture is not supported by shim chainloader
+#endif
+
+static grub_efi_guid_t grub_shim_protocol_guid = GRUB_EFI_SHIM_PROTOCOL_GUID;
+
+static grub_int32_t
+grub_shim_allow_64_bit (void)
+{
+/* TODO: what is the definition for aarch64? */
+#if defined(__x86_64__)
+  return 1;
+#elif defined(__i386__) || defined(__i686__)
+/* TODO: find out what to do with in_protocol */
+  return 0;
+#else /* assuming everything else is 32-bit... */
+  return 0;
+#endif
+}
+
+static grub_int32_t
+grub_shim_allow_32_bit (void)
+{
+/* TODO: what is the definition for aarch64? */
+#if defined(__x86_64__)
+/* TODO: find out what to do with in_protocol */
+  return 0;
+#elif defined(__i386__) || defined(__i686__)
+  return 1;
+#else /* assuming everything else is 32-bit... */
+  return 1;
+#endif
+}
+
+static grub_int32_t
+grub_shim_image_is_64_bit (union grub_shim_optional_header_union *pe_hdr)
+{
+  /* .Magic is the same offset in all cases */
+  if (pe_hdr->pe32plus.opt_hdr.magic == GRUB_PE32_PE64_MAGIC)
+    return 1;
+  return 0;
+}
+
+static grub_int32_t
+grub_shim_image_is_loadable (union grub_shim_optional_header_union *pe_hdr)
+{
+  /* If the machine type doesn't match the binary, bail, unless
+   * we're in an allowed 64-on-32 scenario
+   */
+  if (pe_hdr->pe32.file_hdr.machine != grub_shim_machine_type)
+    {
+      if (!(grub_shim_machine_type == GRUB_PE32_MACHINE_I386
+	  && pe_hdr->pe32.file_hdr.machine == GRUB_PE32_MACHINE_X86_64
+	  && grub_shim_allow_64_bit ()))
+        return 0;
+    }
+
+  /* If it's not a header type we recognize at all, bail */
+  switch (pe_hdr->pe32plus.opt_hdr.magic)
+    {
+      case GRUB_PE32_PE64_MAGIC:
+      case GRUB_PE32_PE32_MAGIC:
+        break;
+      default:
+        return 0;
+    }
+
+  /* and now just check for general 64-vs-32 compatibility */
+  if (grub_shim_image_is_64_bit(pe_hdr))
+    {
+      if (grub_shim_allow_64_bit ())
+        return 1;
+    }
+  else
+    {
+      if (grub_shim_allow_32_bit ())
+        return 1;
+    }
+    return 0;
+}
+
+/*
+ * Perform basic bounds checking of the intra-image pointers
+ */
+static grub_efi_uint64_t
+grub_shim_image_address (grub_addr_t image, grub_uint32_t size, grub_uint32_t addr)
+{
+  if (addr > size)
+    return 0;
+  return image + addr;
+}
+
+/*
+ * Perform the actual relocation
+ */
+static grub_err_t
+grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
+			 void *orig, void *data)
+{
+  struct grub_image_base_relocation *reloc_base, *reloc_base_end;
+  grub_efi_uint64_t adjust;
+  grub_efi_uint16_t *reloc, *reloc_end;
+  grub_uint8_t *fixup, *fixup_base, *fixup_data = NULL;
+  grub_efi_uint16_t *fixup16;
+  grub_efi_uint32_t *fixup32;
+  grub_efi_uint64_t *fixup64;
+  grub_int32_t size = context->image_size;
+  void *image_end = (char *)orig + size;
+
+  if (grub_shim_image_is_64_bit(context->pe_hdr))
+    context->pe_hdr->pe32plus.opt_hdr.image_base = (grub_efi_uint64_t)(unsigned long)data;
+  else
+    context->pe_hdr->pe32.opt_hdr.image_base = (grub_efi_uint32_t)(unsigned long)data;
+
+  reloc_base = (struct grub_image_base_relocation *)
+               grub_shim_image_address ((grub_efi_uint64_t)orig, size,
+					context->reloc_dir->rva);
+  reloc_base_end = (struct grub_image_base_relocation *)
+                   grub_shim_image_address ((grub_efi_uint64_t)orig, size,
+					    context->reloc_dir->rva
+					    + context->reloc_dir->size - 1);
+
+  if (!reloc_base || !reloc_base_end)
+    {
+      grub_printf("Reloc table overflows binary\n");
+      return GRUB_ERR_BAD_FILE_TYPE;
+    }
+
+  adjust = (grub_efi_uintn_t)data - context->image_address;
+
+  if (adjust == 0)
+    return GRUB_EFI_SUCCESS;
+
+  while (reloc_base < reloc_base_end)
+    {
+      reloc = (grub_efi_uint16_t *) ((grub_int8_t *) reloc_base
+	      + sizeof (struct grub_image_base_relocation));
+
+      if ((reloc_base->block_size == 0)
+	  || (reloc_base->block_size > context->reloc_dir->size))
+        {
+          grub_printf("Reloc block size %d is invalid\n", reloc_base->block_size);
+          return GRUB_ERR_FILE_READ_ERROR;
+        }
+
+        reloc_end = (grub_efi_uint16_t *)
+		    ((grub_uint8_t *) reloc_base + reloc_base->block_size);
+        if ((void *)reloc_end < orig || (void *)reloc_end > image_end)
+          {
+            grub_printf("Reloc entry overflows binary\n");
+            return GRUB_ERR_FILE_READ_ERROR;
+          }
+
+        fixup_base = (grub_uint8_t *)
+		     grub_shim_image_address ((grub_efi_uint64_t)data,
+					      size,
+					      reloc_base->virtual_address);
+        if (!fixup_base)
+          {
+            grub_printf("Invalid fixup_base\n");
+            return GRUB_ERR_FILE_READ_ERROR;
+          }
+
+        while (reloc < reloc_end)
+          {
+            fixup = fixup_base + (*reloc & 0xFFF);
+            switch ((*reloc) >> 12)
+              {
+                case EFI_IMAGE_REL_BASED_ABSOLUTE:
+                  break;
+
+                case EFI_IMAGE_REL_BASED_HIGH:
+                  fixup16 = (grub_efi_uint16_t *) fixup;
+                  *fixup16 = (grub_efi_uint16_t)
+			     (*fixup16
+			     + ((grub_efi_uint16_t) ((grub_efi_uint32_t) adjust >> 16)));
+                  if (fixup_data != NULL)
+                    {
+                      *(grub_efi_uint16_t *) fixup_data = *fixup16;
+                      fixup_data = fixup_data + sizeof (grub_efi_uint16_t);
+                    }
+                  break;
+
+                case EFI_IMAGE_REL_BASED_LOW:
+                  fixup16 = (grub_efi_uint16_t *) fixup;
+                  *fixup16 = (grub_efi_uint16_t)
+			     (*fixup16 + (grub_efi_uint16_t) adjust);
+                  if (fixup_data != NULL)
+                    {
+                      *(grub_efi_uint16_t *) fixup_data = *fixup16;
+                      fixup_data = fixup_data + sizeof (grub_efi_uint16_t);
+                    }
+                    break;
+
+                case EFI_IMAGE_REL_BASED_HIGHLOW:
+                  fixup32   = (grub_efi_uint32_t *) fixup;
+                  *fixup32  = *fixup32 + (grub_efi_uint32_t) adjust;
+                  if (fixup_data != NULL)
+                    {
+                      fixup_data = ALIGN_POINTER (fixup_data, sizeof (grub_efi_uint32_t));
+                      *(grub_efi_uint32_t *)fixup_data  = *fixup32;
+                      fixup_data = fixup_data + sizeof (grub_efi_uint32_t);
+                    }
+                  break;
+
+                case EFI_IMAGE_REL_BASED_DIR64:
+		  fixup64 = (grub_efi_uint64_t *) fixup;
+                  *fixup64 = *fixup64 + (grub_efi_uint64_t) adjust;
+                  if (fixup_data != NULL)
+                    {
+                      fixup_data = ALIGN_POINTER (fixup_data, sizeof(grub_efi_uint64_t));
+                      *(grub_efi_uint64_t *)(fixup_data) = *fixup64;
+                      fixup_data = fixup_data + sizeof(grub_efi_uint64_t);
+                    }
+                  break;
+
+                default:
+                  grub_printf("Unknown relocation\n");
+                  return GRUB_ERR_FILE_READ_ERROR;
+              }
+            reloc += 1;
+          }
+        reloc_base = (struct grub_image_base_relocation *) reloc_end;
+    }
+
+  return GRUB_EFI_SUCCESS;
+}
+
+/*
+ * Read the binary header and grab appropriate information from it
+ */
+static grub_err_t
+grub_shim_read_header(grub_efi_physical_address_t data, grub_uint32_t datasize,
+		      struct grub_shim_pe_coff_loader_image_context *context)
+{
+  struct grub_dos_header *dos_hdr = (struct grub_dos_header *)data;
+  union grub_shim_optional_header_union *pe_hdr = (union grub_shim_optional_header_union *)data;
+  grub_uint64_t header_without_data_dir, section_header_offset, opt_hdr_size;
+
+  if (datasize < sizeof (pe_hdr->pe32))
+    {
+      grub_printf("Invalid image\n");
+      return GRUB_ERR_BAD_FILE_TYPE;
+    }
+
+  if (dos_hdr->magic == EFI_IMAGE_DOS_SIGNATURE)
+    pe_hdr = (union grub_shim_optional_header_union  *)((grub_uint8_t *)data
+							+ dos_hdr->lfanew);
+
+  if (!grub_shim_image_is_loadable(pe_hdr))
+    {
+      grub_printf("Platform does not support this image\n");
+      return GRUB_ERR_BAD_FILE_TYPE;
+    }
+
+  if (grub_shim_image_is_64_bit(pe_hdr))
+    {
+      context->number_of_rva_and_sizes = pe_hdr->pe32plus.opt_hdr.num_data_directories;
+      context->header_size = pe_hdr->pe32plus.opt_hdr.header_size;
+      context->image_size = pe_hdr->pe32plus.opt_hdr.image_size;
+      opt_hdr_size = sizeof(struct grub_pe64_optional_header);
+    } else
+    {
+      context->number_of_rva_and_sizes = pe_hdr->pe32.opt_hdr.num_data_directories;
+      context->header_size = pe_hdr->pe32.opt_hdr.header_size;
+      context->image_size = (grub_efi_uint64_t)pe_hdr->pe32.opt_hdr.header_size;
+      opt_hdr_size = sizeof(struct grub_pe32_optional_header);
+    }
+
+  context->num_sections = pe_hdr->pe32.file_hdr.num_sections;
+
+  if (GRUB_PE32_NUM_DATA_DIRECTORIES < context->number_of_rva_and_sizes)
+    {
+      grub_printf("Image header too small\n");
+      return GRUB_ERR_FILE_READ_ERROR;
+    }
+
+  header_without_data_dir = opt_hdr_size
+                            - sizeof (struct grub_pe32_data_directory)
+                            * GRUB_PE32_NUM_DATA_DIRECTORIES;
+  if (((grub_efi_uint32_t)pe_hdr->pe32.file_hdr.optional_header_size
+       - header_without_data_dir) !=
+      context->number_of_rva_and_sizes * sizeof (struct grub_pe32_data_directory))
+    {
+      grub_printf("Image header overflows data directory\n");
+      return GRUB_ERR_FILE_READ_ERROR;
+    }
+
+  section_header_offset = dos_hdr->lfanew
+                          + sizeof (grub_efi_uint32_t)
+                          + sizeof (struct grub_pe32_coff_header)
+                          + pe_hdr->pe32.file_hdr.optional_header_size;
+  if (((grub_efi_uint32_t)context->image_size - section_header_offset)
+      / sizeof (struct grub_pe32_section_table)
+      <= context->num_sections)
+    {
+      grub_printf("Image sections overflow image size\n");
+      return GRUB_ERR_FILE_READ_ERROR;
+    }
+
+  if ((context->header_size - section_header_offset)
+      / sizeof (struct grub_pe32_section_table)
+      < (grub_efi_uint32_t)context->num_sections)
+    {
+      grub_printf("Image sections overflow section headers\n");
+      return GRUB_ERR_FILE_READ_ERROR;
+    }
+
+  if ((((grub_efi_uint8_t *)pe_hdr
+      - (grub_efi_uint8_t *)data)
+      + sizeof(union grub_shim_optional_header_union )) > datasize)
+    {
+      grub_printf("Invalid image\n");
+      return GRUB_ERR_BAD_FILE_TYPE;
+    }
+
+  if (pe_hdr->te.signature != EFI_IMAGE_NT_SIGNATURE)
+    {
+      grub_printf("Unsupported image type\n");
+      return GRUB_ERR_BAD_FILE_TYPE;
+    }
+
+  if (pe_hdr->pe32.file_hdr.characteristics & GRUB_PE32_RELOCS_STRIPPED)
+    {
+      grub_printf("Unsupported image - Relocations have been stripped\n");
+      return GRUB_ERR_BAD_FILE_TYPE;
+    }
+
+  context->pe_hdr = pe_hdr;
+
+  if (grub_shim_image_is_64_bit(pe_hdr))
+    {
+      context->image_address = pe_hdr->pe32plus.opt_hdr.image_base;
+      context->entry_point = pe_hdr->pe32plus.opt_hdr.entry_addr;
+      context->reloc_dir = &pe_hdr->pe32plus.opt_hdr.base_relocation_table;
+      context->sec_dir = &pe_hdr->pe32plus.opt_hdr.certificate_table;
+    } else
+    {
+      context->image_address = pe_hdr->pe32.opt_hdr.image_base;
+      context->entry_point = pe_hdr->pe32.opt_hdr.entry_addr;
+      context->reloc_dir = &pe_hdr->pe32.opt_hdr.base_relocation_table;
+      context->sec_dir = &pe_hdr->pe32.opt_hdr.certificate_table;
+    }
+
+  context->first_section = (struct grub_pe32_section_table *)
+                           ((char *)pe_hdr
+                           + pe_hdr->pe32.file_hdr.optional_header_size
+                           + sizeof(grub_efi_uint32_t)
+                           + sizeof(struct grub_pe32_coff_header));
+
+  if (context->image_size < context->header_size)
+    {
+      grub_printf("Invalid image\n");
+      return GRUB_ERR_BAD_FILE_TYPE;
+    }
+
+  if ((unsigned long)((grub_efi_uint8_t *)context->sec_dir - (grub_efi_uint8_t *)data) >
+      (datasize - sizeof(struct grub_pe32_data_directory)))
+    {
+      grub_printf("Invalid image\n");
+      return GRUB_ERR_BAD_FILE_TYPE;
+    }
+
+  if (context->sec_dir->rva >= datasize)
+    {
+      grub_printf("Malformed security header\n");
+      return GRUB_ERR_BAD_FILE_TYPE;
+    }
+  return GRUB_ERR_NONE;
+}
+
+static grub_efi_status_t
+grub_shim_verify (grub_addr_t addr, grub_ssize_t size)
+{
+  struct grub_shim_lock *shim_lock;
+  shim_lock = grub_efi_locate_protocol (&grub_shim_protocol_guid, 0);
+  if (!shim_lock)
+    {
+      grub_error (GRUB_ERR_BAD_OS, "could not load shim protocol");
+      return GRUB_EFI_UNSUPPORTED;
+    }
+
+  return shim_lock->verify((void *) addr, size);
+}
+
+static grub_err_t
+grub_shim_load_image(grub_addr_t addr, grub_ssize_t size,
+		     struct grub_shim_pe_coff_loader_image_context *context)
+{
+  grub_err_t status;
+  grub_efi_status_t efi_status;
+  grub_uint32_t sect_size;
+  /* TODO: can they be unsigned? */
+  grub_int8_t *base, *end;
+  grub_int32_t i;
+  struct grub_pe32_section_table *section;
+  grub_efi_boot_services_t *b;
+
+  shim_used = 0;
+  shim_buffer = 0;
+
+  status = grub_shim_verify (addr, size);
+  if (status != GRUB_ERR_NONE)
+    {
+      grub_error (GRUB_ERR_BAD_OS, "shim verification failed");
+      return GRUB_ERR_BAD_OS;
+    }
+
+  grub_memset(context, 0, sizeof(*context));
+  status = grub_shim_read_header (addr, size, context);
+  if (status != GRUB_ERR_NONE)
+    {
+      grub_error (GRUB_ERR_BAD_OS, "read header failed");
+      return GRUB_ERR_BAD_OS;
+    }
+
+  /* TODO: do we need to do this with efi_allocate? */
+  shim_pages = (((grub_efi_uintn_t) context->image_size + ((1 << 12) - 1)) >> 12);
+
+  b = grub_efi_system_table->boot_services;
+  efi_status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_ANY_PAGES,
+			   GRUB_EFI_LOADER_CODE, shim_pages, &shim_buffer);
+  if (efi_status != GRUB_EFI_SUCCESS)
+    {
+      grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory for shim buffer"));
+      return GRUB_ERR_OUT_OF_MEMORY;
+    }
+
+  /* TODO: do we need the double cast? */
+  grub_memcpy ((void *) ((grub_efi_physical_address_t) shim_buffer),
+               (void *) ((grub_addr_t) addr), context->header_size);
+  /*
+   * Copy the executable's sections to their desired offsets
+  */
+  section = context->first_section;
+  for (i = 0; i < context->num_sections; i++, section++)
+    {
+      if (section->characteristics & 0x02000000)
+        /* section has EFI_IMAGE_SCN_MEM_DISCARDABLE attr set */
+        continue;
+
+	sect_size = section->virtual_size;
+
+        if (sect_size > section->raw_data_size)
+	  sect_size = section->raw_data_size;
+
+	base = (grub_int8_t *)
+	       grub_shim_image_address (shim_buffer, context->image_size,
+					section->virtual_address);
+	end = (grub_int8_t *)
+	      grub_shim_image_address (shim_buffer, context->image_size,
+				       section->virtual_address
+				       + sect_size - 1);
+	if (!base || !end)
+	  {
+	    grub_printf("Invalid section base\n");
+	    status = GRUB_ERR_BAD_FILE_TYPE;
+	    goto fail;
+	 }
+
+	if (section->virtual_address < context->header_size
+	    || section->raw_data_offset < context->header_size)
+	  {
+	    grub_printf("Section is inside image headers\n");
+	    status = GRUB_ERR_BAD_FILE_TYPE;
+	    goto fail;
+	  }
+
+	if (section->raw_data_size > 0)
+	  /* TODO: do we need the double cast? */
+	  grub_memcpy ((void *)base,
+		       (void *) (((grub_addr_t) addr)
+		       + section->raw_data_offset), sect_size);
+
+	if (sect_size < section->virtual_size)
+	  grub_memset ((void *)(base + sect_size), 0,
+		       section->virtual_size - sect_size);
+    }
+
+  if (context->number_of_rva_and_sizes <= EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC)
+    {
+      grub_printf("Image has no relocation entry\n");
+      status = GRUB_ERR_BAD_FILE_TYPE;
+      goto fail;
+    }
+
+  if (context->reloc_dir->size)
+    {
+      status = grub_shim_relocate_coff (context, (void *) addr,
+					(void *) shim_buffer);
+      if (status != GRUB_ERR_NONE)
+	{
+	  grub_printf("Relocation failed: [%u]\n", status);
+	  status = GRUB_ERR_BAD_FILE_TYPE;
+	  goto fail;
+	}
+    }
+  shim_entry_point = (void *)grub_shim_image_address (shim_buffer,
+						      context->image_size,
+						      context->entry_point);
+  if (!shim_entry_point)
+    {
+      grub_printf("Invalid entry point\n");
+      status = GRUB_ERR_BAD_FILE_TYPE;
+      goto fail;
+    }
+
+  shim_used = 1;
+  return GRUB_ERR_NONE;
+fail:
+  efi_call_2 (b->free_pages, shim_buffer, shim_pages);
+  shim_buffer = 0;
+  return status;
+}
 
 static grub_err_t
 grub_chainloader_unload (void)
-- 
1.9.1

