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

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