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

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