9d15b4d
From e2b22111a8ec58091603fc785f54b1b998888735 Mon Sep 17 00:00:00 2001
9d15b4d
From: Peter Jones <pjones@redhat.com>
9d15b4d
Date: Thu, 9 Jun 2016 12:22:29 -0400
7ec92ff
Subject: [PATCH 82/88] Re-work some intricacies of PE loading.
9d15b4d
9d15b4d
The PE spec is not a well written document, and awesomely every place
9d15b4d
where there's an ambiguous way to read something, Windows' bootmgfw.efi
9d15b4d
takes a different read than either of them.
9d15b4d
---
9d15b4d
 grub-core/loader/efi/chainloader.c | 156 +++++++++++++++++++++++++++++--------
9d15b4d
 include/grub/efi/pe32.h            |  32 +++++++-
9d15b4d
 2 files changed, 152 insertions(+), 36 deletions(-)
9d15b4d
9d15b4d
diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c
9d15b4d
index c4184fa..323f873 100644
9d15b4d
--- a/grub-core/loader/efi/chainloader.c
9d15b4d
+++ b/grub-core/loader/efi/chainloader.c
9d15b4d
@@ -297,7 +297,7 @@ image_is_64_bit (grub_pe_header_t *pe_hdr)
9d15b4d
   return 0;
9d15b4d
 }
9d15b4d
 
9d15b4d
-static const grub_uint16_t machine_type =
9d15b4d
+static const grub_uint16_t machine_type __attribute__((__unused__)) =
9d15b4d
 #if defined(__x86_64__)
9d15b4d
   GRUB_PE32_MACHINE_X86_64;
9d15b4d
 #elif defined(__aarch64__)
9d15b4d
@@ -363,10 +363,10 @@ relocate_coff (pe_coff_loader_image_context_t *context,
9d15b4d
 
9d15b4d
   reloc_base = image_address (orig, size, section->raw_data_offset);
9d15b4d
   reloc_base_end = image_address (orig, size, section->raw_data_offset
9d15b4d
-				  + section->virtual_size - 1);
9d15b4d
+				  + section->virtual_size);
9d15b4d
 
9d15b4d
-  grub_dprintf ("chain", "reloc_base %p reloc_base_end %p\n", reloc_base,
9d15b4d
-		reloc_base_end);
9d15b4d
+  grub_dprintf ("chain", "relocate_coff(): reloc_base %p reloc_base_end %p\n",
9d15b4d
+		reloc_base, reloc_base_end);
9d15b4d
 
9d15b4d
   if (!reloc_base && !reloc_base_end)
9d15b4d
     return GRUB_EFI_SUCCESS;
9d15b4d
@@ -503,12 +503,13 @@ handle_image (void *data, grub_efi_uint32_t datasize)
9d15b4d
   grub_efi_status_t efi_status;
9d15b4d
   char *buffer = NULL;
9d15b4d
   char *buffer_aligned = NULL;
9d15b4d
-  grub_efi_uint32_t i, size;
9d15b4d
+  grub_efi_uint32_t i;
9d15b4d
   struct grub_pe32_section_table *section;
9d15b4d
   char *base, *end;
9d15b4d
   pe_coff_loader_image_context_t context;
9d15b4d
   grub_uint32_t section_alignment;
9d15b4d
   grub_uint32_t buffer_size;
9d15b4d
+  int found_entry_point = 0;
9d15b4d
 
9d15b4d
   b = grub_efi_system_table->boot_services;
9d15b4d
 
9d15b4d
@@ -522,8 +523,28 @@ handle_image (void *data, grub_efi_uint32_t datasize)
9d15b4d
       goto error_exit;
9d15b4d
     }
9d15b4d
 
9d15b4d
+  /*
9d15b4d
+   * The spec says, uselessly, of SectionAlignment:
9d15b4d
+   * =====
9d15b4d
+   * The alignment (in bytes) of sections when they are loaded into
9d15b4d
+   * memory. It must be greater than or equal to FileAlignment. The
9d15b4d
+   * default is the page size for the architecture.
9d15b4d
+   * =====
9d15b4d
+   * Which doesn't tell you whose responsibility it is to enforce the
9d15b4d
+   * "default", or when.  It implies that the value in the field must
9d15b4d
+   * be > FileAlignment (also poorly defined), but it appears visual
9d15b4d
+   * studio will happily write 512 for FileAlignment (its default) and
9d15b4d
+   * 0 for SectionAlignment, intending to imply PAGE_SIZE.
9d15b4d
+   *
9d15b4d
+   * We only support one page size, so if it's zero, nerf it to 4096.
9d15b4d
+   */
9d15b4d
   section_alignment = context.section_alignment;
9d15b4d
+  if (section_alignment == 0)
9d15b4d
+    section_alignment = 4096;
9d15b4d
+
9d15b4d
   buffer_size = context.image_size + section_alignment;
9d15b4d
+  grub_dprintf ("chain", "image size is %08lx, datasize is %08x\n",
9d15b4d
+	       context.image_size, datasize);
9d15b4d
 
9d15b4d
   efi_status = efi_call_3 (b->allocate_pool, GRUB_EFI_LOADER_DATA,
9d15b4d
 			   buffer_size, &buffer);
9d15b4d
@@ -535,7 +556,6 @@ handle_image (void *data, grub_efi_uint32_t datasize)
9d15b4d
     }
9d15b4d
 
9d15b4d
   buffer_aligned = (char *)ALIGN_UP ((grub_addr_t)buffer, section_alignment);
9d15b4d
-
9d15b4d
   if (!buffer_aligned)
9d15b4d
     {
9d15b4d
       grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
9d15b4d
@@ -544,27 +564,62 @@ handle_image (void *data, grub_efi_uint32_t datasize)
9d15b4d
 
9d15b4d
   grub_memcpy (buffer_aligned, data, context.size_of_headers);
9d15b4d
 
9d15b4d
+  entry_point = image_address (buffer_aligned, context.image_size,
9d15b4d
+			       context.entry_point);
9d15b4d
+
9d15b4d
+  grub_dprintf ("chain", "entry_point: %p\n", entry_point);
9d15b4d
+  if (!entry_point)
9d15b4d
+    {
9d15b4d
+      grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid entry point");
9d15b4d
+      goto error_exit;
9d15b4d
+    }
9d15b4d
+
9d15b4d
   char *reloc_base, *reloc_base_end;
9d15b4d
-  reloc_base = image_address (buffer_aligned, datasize,
9d15b4d
+  grub_dprintf ("chain", "reloc_dir: %p reloc_size: 0x%08x\n",
9d15b4d
+		(void *)(unsigned long long)context.reloc_dir->rva,
9d15b4d
+		context.reloc_dir->size);
9d15b4d
+  reloc_base = image_address (buffer_aligned, context.image_size,
9d15b4d
 			      context.reloc_dir->rva);
9d15b4d
   /* RelocBaseEnd here is the address of the last byte of the table */
9d15b4d
-  reloc_base_end = image_address (buffer_aligned, datasize,
9d15b4d
+  reloc_base_end = image_address (buffer_aligned, context.image_size,
9d15b4d
 				  context.reloc_dir->rva
9d15b4d
 				  + context.reloc_dir->size - 1);
9d15b4d
+  grub_dprintf ("chain", "reloc_base: %p reloc_base_end: %p\n",
9d15b4d
+		reloc_base, reloc_base_end);
9d15b4d
+
9d15b4d
   struct grub_pe32_section_table *reloc_section = NULL;
9d15b4d
 
9d15b4d
   section = context.first_section;
9d15b4d
   for (i = 0; i < context.number_of_sections; i++, section++)
9d15b4d
     {
9d15b4d
-      size = section->virtual_size;
9d15b4d
-      if (size > section->raw_data_size)
9d15b4d
-        size = section->raw_data_size;
9d15b4d
+      char name[9];
9d15b4d
 
9d15b4d
       base = image_address (buffer_aligned, context.image_size,
9d15b4d
 			    section->virtual_address);
9d15b4d
       end = image_address (buffer_aligned, context.image_size,
9d15b4d
-			   section->virtual_address + size - 1);
9d15b4d
+			   section->virtual_address + section->virtual_size -1);
9d15b4d
 
9d15b4d
+      grub_strncpy(name, section->name, 9);
9d15b4d
+      name[8] = '\0';
9d15b4d
+      grub_dprintf ("chain", "Section %d \"%s\" at %p..%p\n", i,
9d15b4d
+		   name, base, end);
9d15b4d
+
9d15b4d
+      if (end < base)
9d15b4d
+	{
9d15b4d
+	  grub_dprintf ("chain", " base is %p but end is %p... bad.\n",
9d15b4d
+		       base, end);
9d15b4d
+	  grub_error (GRUB_ERR_BAD_ARGUMENT,
9d15b4d
+		      "Image has invalid negative size");
9d15b4d
+	  goto error_exit;
9d15b4d
+	}
9d15b4d
+
9d15b4d
+      if (section->virtual_address <= context.entry_point &&
9d15b4d
+	  (section->virtual_address + section->raw_data_size - 1)
9d15b4d
+	  > context.entry_point)
9d15b4d
+	{
9d15b4d
+	  found_entry_point++;
9d15b4d
+	  grub_dprintf ("chain", " section contains entry point\n");
9d15b4d
+	}
9d15b4d
 
9d15b4d
       /* We do want to process .reloc, but it's often marked
9d15b4d
        * discardable, so we don't want to memcpy it. */
9d15b4d
@@ -583,21 +638,46 @@ handle_image (void *data, grub_efi_uint32_t datasize)
9d15b4d
 	  if (section->raw_data_size && section->virtual_size &&
9d15b4d
 	      base && end && reloc_base == base && reloc_base_end == end)
9d15b4d
 	    {
9d15b4d
+	      grub_dprintf ("chain", " section is relocation section\n");
9d15b4d
 	      reloc_section = section;
9d15b4d
 	    }
9d15b4d
+	  else
9d15b4d
+	    {
9d15b4d
+	      grub_dprintf ("chain", " section is not reloc section?\n");
9d15b4d
+	      grub_dprintf ("chain", " rds: 0x%08x, vs: %08x\n",
9d15b4d
+			    section->raw_data_size, section->virtual_size);
9d15b4d
+	      grub_dprintf ("chain", " base: %p end: %p\n", base, end);
9d15b4d
+	      grub_dprintf ("chain", " reloc_base: %p reloc_base_end: %p\n",
9d15b4d
+			    reloc_base, reloc_base_end);
9d15b4d
+	    }
9d15b4d
 	}
9d15b4d
 
9d15b4d
-      if (section->characteristics && GRUB_PE32_SCN_MEM_DISCARDABLE)
9d15b4d
-	continue;
9d15b4d
+      grub_dprintf ("chain", " Section characteristics are %08x\n",
9d15b4d
+		   section->characteristics);
9d15b4d
+      grub_dprintf ("chain", " Section virtual size: %08x\n",
9d15b4d
+		   section->virtual_size);
9d15b4d
+      grub_dprintf ("chain", " Section raw_data size: %08x\n",
9d15b4d
+		   section->raw_data_size);
9d15b4d
+      if (section->characteristics & GRUB_PE32_SCN_MEM_DISCARDABLE)
9d15b4d
+	{
9d15b4d
+	  grub_dprintf ("chain", " Discarding section\n");
9d15b4d
+	  continue;
9d15b4d
+	}
9d15b4d
 
9d15b4d
       if (!base || !end)
9d15b4d
         {
9d15b4d
+	  grub_dprintf ("chain", " section is invalid\n");
9d15b4d
           grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid section size");
9d15b4d
           goto error_exit;
9d15b4d
         }
9d15b4d
 
9d15b4d
-      if (section->virtual_address < context.size_of_headers ||
9d15b4d
-	  section->raw_data_offset < context.size_of_headers)
9d15b4d
+      if (section->characteristics & GRUB_PE32_SCN_CNT_UNINITIALIZED_DATA)
9d15b4d
+	{
9d15b4d
+	  if (section->raw_data_size != 0)
9d15b4d
+	    grub_dprintf ("chain", " UNINITIALIZED_DATA section has data?\n");
9d15b4d
+	}
9d15b4d
+      else if (section->virtual_address < context.size_of_headers ||
9d15b4d
+	       section->raw_data_offset < context.size_of_headers)
9d15b4d
 	{
9d15b4d
 	  grub_error (GRUB_ERR_BAD_ARGUMENT,
9d15b4d
 		      "Section %d is inside image headers", i);
9d15b4d
@@ -605,13 +685,24 @@ handle_image (void *data, grub_efi_uint32_t datasize)
9d15b4d
 	}
9d15b4d
 
9d15b4d
       if (section->raw_data_size > 0)
9d15b4d
-        grub_memcpy (base, (grub_efi_uint8_t*)data + section->raw_data_offset,
9d15b4d
-		     size);
9d15b4d
+	{
9d15b4d
+	  grub_dprintf ("chain", " copying 0x%08x bytes to %p\n",
9d15b4d
+			section->raw_data_size, base);
9d15b4d
+	  grub_memcpy (base,
9d15b4d
+		       (grub_efi_uint8_t*)data + section->raw_data_offset,
9d15b4d
+		       section->raw_data_size);
9d15b4d
+	}
9d15b4d
 
9d15b4d
-      if (size < section->virtual_size)
9d15b4d
-        grub_memset (base + size, 0, section->virtual_size - size);
9d15b4d
+      if (section->raw_data_size < section->virtual_size)
9d15b4d
+	{
9d15b4d
+	  grub_dprintf ("chain", " padding with 0x%08x bytes at %p\n",
9d15b4d
+			section->virtual_size - section->raw_data_size,
9d15b4d
+			base + section->raw_data_size);
9d15b4d
+	  grub_memset (base + section->raw_data_size, 0,
9d15b4d
+		       section->virtual_size - section->raw_data_size);
9d15b4d
+	}
9d15b4d
 
9d15b4d
-      grub_dprintf ("chain", "copied section %s\n", section->name);
9d15b4d
+      grub_dprintf ("chain", " finished section %s\n", name);
9d15b4d
     }
9d15b4d
 
9d15b4d
   /* 5 == EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC */
9d15b4d
@@ -634,12 +725,15 @@ handle_image (void *data, grub_efi_uint32_t datasize)
9d15b4d
 	}
9d15b4d
     }
9d15b4d
 
9d15b4d
-  entry_point = image_address (buffer_aligned, context.image_size,
9d15b4d
-			       context.entry_point);
9d15b4d
-
9d15b4d
-  if (!entry_point)
9d15b4d
+  if (!found_entry_point)
9d15b4d
     {
9d15b4d
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid entry point");
9d15b4d
+      grub_error (GRUB_ERR_BAD_ARGUMENT, "entry point is not within sections");
9d15b4d
+      goto error_exit;
9d15b4d
+    }
9d15b4d
+  if (found_entry_point > 1)
9d15b4d
+    {
9d15b4d
+      grub_error (GRUB_ERR_BAD_ARGUMENT, "%d sections contain entry point",
9d15b4d
+		  found_entry_point);
9d15b4d
       goto error_exit;
9d15b4d
     }
9d15b4d
 
9d15b4d
@@ -657,26 +751,24 @@ handle_image (void *data, grub_efi_uint32_t datasize)
9d15b4d
   li->load_options_size = cmdline_len;
9d15b4d
   li->file_path = grub_efi_get_media_file_path (file_path);
9d15b4d
   li->device_handle = dev_handle;
9d15b4d
-  if (li->file_path)
9d15b4d
-    {
9d15b4d
-      grub_printf ("file path: ");
9d15b4d
-      grub_efi_print_device_path (li->file_path);
9d15b4d
-    }
9d15b4d
-  else
9d15b4d
+  if (!li->file_path)
9d15b4d
     {
9d15b4d
       grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no matching file path found");
9d15b4d
       goto error_exit;
9d15b4d
     }
9d15b4d
 
9d15b4d
+  grub_dprintf ("chain", "booting via entry point\n");
9d15b4d
   efi_status = efi_call_2 (entry_point, grub_efi_image_handle,
9d15b4d
 			   grub_efi_system_table);
9d15b4d
 
9d15b4d
+  grub_dprintf ("chain", "entry_point returned %ld\n", efi_status);
9d15b4d
   grub_memcpy (li, &li_bak, sizeof (grub_efi_loaded_image_t));
9d15b4d
   efi_status = efi_call_1 (b->free_pool, buffer);
9d15b4d
 
9d15b4d
   return 1;
9d15b4d
 
9d15b4d
 error_exit:
9d15b4d
+  grub_dprintf ("chain", "error_exit: grub_errno: %d\n", grub_errno);
9d15b4d
   if (buffer)
9d15b4d
       efi_call_1 (b->free_pool, buffer);
9d15b4d
 
9d15b4d
diff --git a/include/grub/efi/pe32.h b/include/grub/efi/pe32.h
9d15b4d
index f79782e..8396bde 100644
9d15b4d
--- a/include/grub/efi/pe32.h
9d15b4d
+++ b/include/grub/efi/pe32.h
9d15b4d
@@ -227,12 +227,18 @@ struct grub_pe32_section_table
9d15b4d
   grub_uint32_t characteristics;
9d15b4d
 };
9d15b4d
 
9d15b4d
+#define GRUB_PE32_SCN_TYPE_NO_PAD		0x00000008
9d15b4d
 #define GRUB_PE32_SCN_CNT_CODE			0x00000020
9d15b4d
 #define GRUB_PE32_SCN_CNT_INITIALIZED_DATA	0x00000040
9d15b4d
-#define GRUB_PE32_SCN_MEM_DISCARDABLE		0x02000000
9d15b4d
-#define GRUB_PE32_SCN_MEM_EXECUTE		0x20000000
9d15b4d
-#define GRUB_PE32_SCN_MEM_READ			0x40000000
9d15b4d
-#define GRUB_PE32_SCN_MEM_WRITE			0x80000000
9d15b4d
+#define GRUB_PE32_SCN_CNT_UNINITIALIZED_DATA	0x00000080
9d15b4d
+#define GRUB_PE32_SCN_LNK_OTHER			0x00000100
9d15b4d
+#define GRUB_PE32_SCN_LNK_INFO			0x00000200
9d15b4d
+#define GRUB_PE32_SCN_LNK_REMOVE		0x00000800
9d15b4d
+#define GRUB_PE32_SCN_LNK_COMDAT		0x00001000
9d15b4d
+#define GRUB_PE32_SCN_GPREL			0x00008000
9d15b4d
+#define GRUB_PE32_SCN_MEM_16BIT			0x00020000
9d15b4d
+#define GRUB_PE32_SCN_MEM_LOCKED		0x00040000
9d15b4d
+#define GRUB_PE32_SCN_MEM_PRELOAD		0x00080000
9d15b4d
 
9d15b4d
 #define GRUB_PE32_SCN_ALIGN_1BYTES		0x00100000
9d15b4d
 #define GRUB_PE32_SCN_ALIGN_2BYTES		0x00200000
9d15b4d
@@ -241,10 +247,28 @@ struct grub_pe32_section_table
9d15b4d
 #define GRUB_PE32_SCN_ALIGN_16BYTES		0x00500000
9d15b4d
 #define GRUB_PE32_SCN_ALIGN_32BYTES		0x00600000
9d15b4d
 #define GRUB_PE32_SCN_ALIGN_64BYTES		0x00700000
9d15b4d
+#define GRUB_PE32_SCN_ALIGN_128BYTES		0x00800000
9d15b4d
+#define GRUB_PE32_SCN_ALIGN_256BYTES		0x00900000
9d15b4d
+#define GRUB_PE32_SCN_ALIGN_512BYTES		0x00A00000
9d15b4d
+#define GRUB_PE32_SCN_ALIGN_1024BYTES		0x00B00000
9d15b4d
+#define GRUB_PE32_SCN_ALIGN_2048BYTES		0x00C00000
9d15b4d
+#define GRUB_PE32_SCN_ALIGN_4096BYTES		0x00D00000
9d15b4d
+#define GRUB_PE32_SCN_ALIGN_8192BYTES		0x00E00000
9d15b4d
 
9d15b4d
 #define GRUB_PE32_SCN_ALIGN_SHIFT		20
9d15b4d
 #define GRUB_PE32_SCN_ALIGN_MASK		7
9d15b4d
 
9d15b4d
+#define GRUB_PE32_SCN_LNK_NRELOC_OVFL		0x01000000
9d15b4d
+#define GRUB_PE32_SCN_MEM_DISCARDABLE		0x02000000
9d15b4d
+#define GRUB_PE32_SCN_MEM_NOT_CACHED		0x04000000
9d15b4d
+#define GRUB_PE32_SCN_MEM_NOT_PAGED		0x08000000
9d15b4d
+#define GRUB_PE32_SCN_MEM_SHARED		0x10000000
9d15b4d
+#define GRUB_PE32_SCN_MEM_EXECUTE		0x20000000
9d15b4d
+#define GRUB_PE32_SCN_MEM_READ			0x40000000
9d15b4d
+#define GRUB_PE32_SCN_MEM_WRITE			0x80000000
9d15b4d
+
9d15b4d
+
9d15b4d
+
9d15b4d
 #define GRUB_PE32_SIGNATURE_SIZE 4
9d15b4d
 
9d15b4d
 struct grub_pe32_header
9d15b4d
-- 
7ec92ff
2.9.3
9d15b4d