zbyszek / rpms / grub2

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