Blame SOURCES/0228-efi-chainloader-truncate-overlong-relocation-section.patch

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