From f6c412a240312a2be28b85905a0866288db9ffc8 Mon Sep 17 00:00:00 2001
From: Lans Zhang <jia.zhang@windriver.com>
Date: Sun, 24 Apr 2016 19:02:28 +0800
Subject: [PATCH] chainloader: Actually find the relocations correctly and
 process them that way.

Upstream-Status: Pending

Refer to a846aedd0e9dfe26ca6afaf6a1db8a54c20363c1 in shim.

Find the relocations based on the *file* address in the old binary,
because it's only the same as the virtual address some of the time.

Also perform some extra validation before processing it, and don't bail
out in /error/ if both ReloceBase and RelocEnd are null - that condition
is fine.

Signed-off-by: Lans Zhang <jia.zhang@windriver.com>
---
 grub-core/loader/efi/chainloader.c | 97 +++++++++++++++++++++++++++++++-------
 1 file changed, 81 insertions(+), 16 deletions(-)

diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c
index 0e84100..83769a2 100644
--- a/grub-core/loader/efi/chainloader.c
+++ b/grub-core/loader/efi/chainloader.c
@@ -162,6 +162,7 @@ grub_shim_image_address (grub_addr_t image, grub_uint32_t size, grub_uint32_t ad
  */
 static grub_err_t
 grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
+			 struct grub_pe32_section_table *section,
 			 void *orig, void *data)
 {
   struct grub_image_base_relocation *reloc_base, *reloc_base_end;
@@ -173,19 +174,53 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
   grub_efi_uint64_t *fixup64;
   grub_int32_t size = context->image_size;
   void *image_end = (char *)orig + size;
+  int n = 0;
 
   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;
 
+
+  /* Alright, so here's how this works:
+   *
+   * context->RelocDir gives us two things:
+   * - the VA the table of base relocation blocks are (maybe) to be
+   *   mapped at (RelocDir->VirtualAddress)
+   * - the virtual size (RelocDir->Size)
+   *
+   * The .reloc section (Section here) gives us some other things:
+   * - the name! kind of. (Section->Name)
+   * - the virtual size (Section->VirtualSize), which should be the same
+   *   as RelocDir->Size
+   * - the virtual address (Section->VirtualAddress)
+   * - the file section size (Section->SizeOfRawData), which is
+   *   a multiple of OptHdr->FileAlignment.  Only useful for image
+   *   validation, not really useful for iteration bounds.
+   * - the file address (Section->PointerToRawData)
+   * - 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 EFI_IMAGE_BASE_RELOCATION structs with some values packed behind
+   * them.  The SizeOfBlock 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_base = (struct grub_image_base_relocation *)
                grub_shim_image_address ((grub_efi_uint64_t)orig, size,
-					context->reloc_dir->rva);
+					section->raw_data_offset);
+  /* reloc_base_end is the address of the first entry /past/ the
+   * table.  */
   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);
+					    section->raw_data_offset
+					    + section->virtual_size - 1);
+
+  if (!reloc_base && !reloc_base_end)
+    {
+      return GRUB_EFI_SUCCESS; 
+    }
 
   if (!reloc_base || !reloc_base_end)
     {
@@ -206,7 +241,7 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
       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);
+          grub_printf("Reloc %d block size %d is invalid\n", n, reloc_base->block_size);
           return GRUB_ERR_FILE_READ_ERROR;
         }
 
@@ -214,7 +249,7 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
 		    ((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");
+            grub_printf("Reloc %d entry overflows binary\n", n);
             return GRUB_ERR_FILE_READ_ERROR;
           }
 
@@ -224,7 +259,7 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
 					      reloc_base->virtual_address);
         if (!fixup_base)
           {
-            grub_printf("Invalid fixup_base\n");
+            grub_printf("Reloc %d invalid fixup_base\n", n);
             return GRUB_ERR_FILE_READ_ERROR;
           }
 
@@ -282,12 +317,13 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
                   break;
 
                 default:
-                  grub_printf("Unknown relocation\n");
+                  grub_printf("Reloc %d unknown relocation\n", n);
                   return GRUB_ERR_FILE_READ_ERROR;
               }
             reloc += 1;
           }
         reloc_base = (struct grub_image_base_relocation *) reloc_end;
+        n++;
     }
 
   return GRUB_EFI_SUCCESS;
@@ -458,9 +494,9 @@ grub_shim_load_image(grub_addr_t addr, grub_ssize_t size,
   grub_efi_status_t efi_status;
   grub_uint32_t sect_size;
   /* TODO: can they be unsigned? */
-  grub_int8_t *base, *end;
+  grub_int8_t *base, *end, *reloc_base, *reloc_base_end;
   grub_int32_t i;
-  struct grub_pe32_section_table *section;
+  struct grub_pe32_section_table *section, *reloc_section;
   grub_efi_boot_services_t *b;
 
   shim_used = 0;
@@ -496,16 +532,21 @@ grub_shim_load_image(grub_addr_t addr, grub_ssize_t size,
   /* 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);
+
+  reloc_base = (grub_int8_t *) grub_shim_image_address (shim_buffer, size,
+							context->reloc_dir->rva);
+  /* reloc_base_end here is the address of the last byte of the table */
+  reloc_base_end = (grub_int8_t *) grub_shim_image_address (shim_buffer, size,
+							    context->reloc_dir->rva +
+							    context->reloc_dir->size - 1);
+  reloc_section = NULL;
+
   /*
    * 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)
@@ -518,6 +559,30 @@ grub_shim_load_image(grub_addr_t addr, grub_ssize_t size,
 	      grub_shim_image_address (shim_buffer, context->image_size,
 				       section->virtual_address
 				       + sect_size - 1);
+
+	/* 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_printf("Image has multiple relocation sections\n");
+			status = GRUB_ERR_BAD_FILE_TYPE;
+			goto fail;
+		}
+		/* 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 &&
+				reloc_base_end == end) {
+			reloc_section = section;
+		}
+	}
+        
+	if (section->characteristics & 0x02000000)
+          /* section has EFI_IMAGE_SCN_MEM_DISCARDABLE attr set */
+          continue;
+
 	if (!base || !end)
 	  {
 	    grub_printf("Invalid section base\n");
@@ -551,10 +616,10 @@ grub_shim_load_image(grub_addr_t addr, grub_ssize_t size,
       goto fail;
     }
 
-  if (context->reloc_dir->size)
+  if (context->reloc_dir->size && reloc_section)
     {
-      status = grub_shim_relocate_coff (context, (void *) addr,
-					(void *) shim_buffer);
+      status = grub_shim_relocate_coff (context, reloc_section,
+					(void *) addr, (void *) shim_buffer);
       if (status != GRUB_ERR_NONE)
 	{
 	  grub_printf("Relocation failed: [%u]\n", status);
-- 
1.9.1

