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

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