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

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