c789522
From d081bd5f47235e3bfd256fd262a6f98fe8b0d0ff Mon Sep 17 00:00:00 2001
377f5fc
From: Laszlo Ersek <lersek@redhat.com>
377f5fc
Date: Wed, 23 Nov 2016 06:27:09 +0100
63f1a98
Subject: [PATCH 163/250] efi/chainloader: truncate overlong relocation section
377f5fc
377f5fc
The UEFI Windows 7 boot loader ("EFI/Microsoft/Boot/bootmgfw.efi", SHA1
377f5fc
31b410e029bba87d2068c65a80b88882f9f8ea25) has inconsistent headers.
377f5fc
377f5fc
Compare:
377f5fc
377f5fc
> The Data Directory
377f5fc
> ...
377f5fc
> Entry 5 00000000000d9000 00000574 Base Relocation Directory [.reloc]
377f5fc
377f5fc
Versus:
377f5fc
377f5fc
> Sections:
377f5fc
> Idx Name      Size      VMA               LMA               File off ...
377f5fc
> ...
377f5fc
>  10 .reloc    00000e22  00000000100d9000  00000000100d9000  000a1800 ...
377f5fc
377f5fc
That is, the size reported by the RelocDir entry (0x574) is smaller than
377f5fc
the virtual size of the .reloc section (0xe22).
377f5fc
377f5fc
Quoting the grub2 debug log for the same:
377f5fc
377f5fc
> chainloader.c:595: reloc_dir: 0xd9000 reloc_size: 0x00000574
377f5fc
> chainloader.c:603: reloc_base: 0x7d208000 reloc_base_end: 0x7d208573
377f5fc
> ...
377f5fc
> chainloader.c:620: Section 10 ".reloc" at 0x7d208000..0x7d208e21
377f5fc
> chainloader.c:661:  section is not reloc section?
377f5fc
> chainloader.c:663:  rds: 0x00001000, vs: 00000e22
377f5fc
> chainloader.c:664:  base: 0x7d208000 end: 0x7d208e21
377f5fc
> chainloader.c:666:  reloc_base: 0x7d208000 reloc_base_end: 0x7d208573
377f5fc
> chainloader.c:671:  Section characteristics are 42000040
377f5fc
> chainloader.c:673:  Section virtual size: 00000e22
377f5fc
> chainloader.c:675:  Section raw_data size: 00001000
377f5fc
> chainloader.c:678:  Discarding section
377f5fc
377f5fc
After hexdumping "bootmgfw.efi" and manually walking its relocation blocks
377f5fc
(yes, really), I determined that the (smaller) RelocDir value is correct.
377f5fc
The remaining area that extends up to the .reloc section size (== 0xe22 -
377f5fc
0x574 == 0x8ae bytes) exists as zero padding in the file.
377f5fc
377f5fc
This zero padding shouldn't be passed to relocate_coff() for parsing. In
377f5fc
order to cope with it, split the handling of .reloc sections into the
377f5fc
following branches:
377f5fc
377f5fc
- original case (equal size): original behavior (--> relocation
377f5fc
  attempted),
377f5fc
377f5fc
- overlong .reloc section (longer than reported by RelocDir): truncate the
377f5fc
  section to the RelocDir size for the purposes of relocate_coff(), and
377f5fc
  attempt relocation,
377f5fc
377f5fc
- .reloc section is too short, or other checks fail: original behavior
377f5fc
  (--> relocation not attempted).
377f5fc
377f5fc
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1347291
377f5fc
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
377f5fc
---
377f5fc
 grub-core/loader/efi/chainloader.c | 26 +++++++++++++++++++++-----
377f5fc
 1 file changed, 21 insertions(+), 5 deletions(-)
377f5fc
377f5fc
diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c
ec4acbb
index d5ab21d09c3..7826e794ad9 100644
377f5fc
--- a/grub-core/loader/efi/chainloader.c
377f5fc
+++ b/grub-core/loader/efi/chainloader.c
6f1e3d5
@@ -596,7 +596,7 @@ handle_image (void *data, grub_efi_uint32_t datasize)
377f5fc
   grub_dprintf ("chain", "reloc_base: %p reloc_base_end: %p\n",
377f5fc
 		reloc_base, reloc_base_end);
377f5fc
 
377f5fc
-  struct grub_pe32_section_table *reloc_section = NULL;
377f5fc
+  struct grub_pe32_section_table *reloc_section = NULL, fake_reloc_section;
377f5fc
 
377f5fc
   section = context.first_section;
377f5fc
   for (i = 0; i < context.number_of_sections; i++, section++)
6f1e3d5
@@ -645,12 +645,28 @@ handle_image (void *data, grub_efi_uint32_t datasize)
377f5fc
 	   * made sense, and the VA and size match RelocDir's
377f5fc
 	   * versions, then we believe in this section table. */
377f5fc
 	  if (section->raw_data_size && section->virtual_size &&
377f5fc
-	      base && end && reloc_base == base && reloc_base_end == end)
377f5fc
+	      base && end && reloc_base == base)
377f5fc
 	    {
377f5fc
-	      grub_dprintf ("chain", " section is relocation section\n");
377f5fc
-	      reloc_section = section;
377f5fc
+	      if (reloc_base_end == end)
377f5fc
+		{
377f5fc
+		  grub_dprintf ("chain", " section is relocation section\n");
377f5fc
+		  reloc_section = section;
377f5fc
+		}
377f5fc
+	      else if (reloc_base_end && reloc_base_end < end)
377f5fc
+	        {
377f5fc
+		  /* Bogus virtual size in the reloc section -- RelocDir
377f5fc
+		   * reported a smaller Base Relocation Directory. Decrease
377f5fc
+		   * the section's virtual size so that it equal RelocDir's
377f5fc
+		   * idea, but only for the purposes of relocate_coff(). */
377f5fc
+		  grub_dprintf ("chain",
377f5fc
+				" section is (overlong) relocation section\n");
377f5fc
+		  grub_memcpy (&fake_reloc_section, section, sizeof *section);
377f5fc
+		  fake_reloc_section.virtual_size -= (end - reloc_base_end);
377f5fc
+		  reloc_section = &fake_reloc_section;
377f5fc
+		}
377f5fc
 	    }
377f5fc
-	  else
377f5fc
+
377f5fc
+	  if (!reloc_section)
377f5fc
 	    {
377f5fc
 	      grub_dprintf ("chain", " section is not reloc section?\n");
377f5fc
 	      grub_dprintf ("chain", " rds: 0x%08x, vs: %08x\n",
377f5fc
-- 
63f1a98
2.14.3
377f5fc