]> git.proxmox.com Git - grub2.git/commitdiff
relocator: Protect grub_relocator_alloc_chunk_align() max_addr against integer underflow
authorAlexey Makhalov <amakhalov@vmware.com>
Wed, 8 Jul 2020 01:44:38 +0000 (01:44 +0000)
committerDaniel Kiper <daniel.kiper@oracle.com>
Wed, 29 Jul 2020 14:55:48 +0000 (16:55 +0200)
This commit introduces integer underflow mitigation in max_addr calculation
in grub_relocator_alloc_chunk_align() invocation.

It consists of 2 fixes:
  1. Introduced grub_relocator_alloc_chunk_align_safe() wrapper function to perform
     sanity check for min/max and size values, and to make safe invocation of
     grub_relocator_alloc_chunk_align() with validated max_addr value. Replace all
     invocations such as grub_relocator_alloc_chunk_align(..., min_addr, max_addr - size, size, ...)
     by grub_relocator_alloc_chunk_align_safe(..., min_addr, max_addr, size, ...).
  2. Introduced UP_TO_TOP32(s) macro for the cases where max_addr is 32-bit top
     address (0xffffffff - size + 1) or similar.

Signed-off-by: Alexey Makhalov <amakhalov@vmware.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
13 files changed:
grub-core/lib/i386/relocator.c
grub-core/lib/mips/relocator.c
grub-core/lib/powerpc/relocator.c
grub-core/lib/x86_64/efi/relocator.c
grub-core/loader/i386/linux.c
grub-core/loader/i386/multiboot_mbi.c
grub-core/loader/i386/pc/linux.c
grub-core/loader/mips/linux.c
grub-core/loader/multiboot.c
grub-core/loader/multiboot_elfxx.c
grub-core/loader/multiboot_mbi2.c
grub-core/loader/xnu_resume.c
include/grub/relocator.h

index 71dd4f0ab0c993656490c59e20973b61ad35ee99..34cbe834fa30b11e013ba82938dcb3fa97a40f9e 100644 (file)
@@ -83,11 +83,10 @@ grub_relocator32_boot (struct grub_relocator *rel,
   /* Specific memory range due to Global Descriptor Table for use by payload
      that we will store in returned chunk.  The address range and preference
      are based on "THE LINUX/x86 BOOT PROTOCOL" specification.  */
-  err = grub_relocator_alloc_chunk_align (rel, &ch, 0x1000,
-                                         0x9a000 - RELOCATOR_SIZEOF (32),
-                                         RELOCATOR_SIZEOF (32), 16,
-                                         GRUB_RELOCATOR_PREFERENCE_LOW,
-                                         avoid_efi_bootservices);
+  err = grub_relocator_alloc_chunk_align_safe (rel, &ch, 0x1000, 0x9a000,
+                                              RELOCATOR_SIZEOF (32), 16,
+                                              GRUB_RELOCATOR_PREFERENCE_LOW,
+                                              avoid_efi_bootservices);
   if (err)
     return err;
 
@@ -125,13 +124,10 @@ grub_relocator16_boot (struct grub_relocator *rel,
   grub_relocator_chunk_t ch;
 
   /* Put it higher than the byte it checks for A20 check.  */
-  err = grub_relocator_alloc_chunk_align (rel, &ch, 0x8010,
-                                         0xa0000 - RELOCATOR_SIZEOF (16)
-                                         - GRUB_RELOCATOR16_STACK_SIZE,
-                                         RELOCATOR_SIZEOF (16)
-                                         + GRUB_RELOCATOR16_STACK_SIZE, 16,
-                                         GRUB_RELOCATOR_PREFERENCE_NONE,
-                                         0);
+  err = grub_relocator_alloc_chunk_align_safe (rel, &ch, 0x8010, 0xa0000,
+                                              RELOCATOR_SIZEOF (16) +
+                                              GRUB_RELOCATOR16_STACK_SIZE, 16,
+                                              GRUB_RELOCATOR_PREFERENCE_NONE, 0);
   if (err)
     return err;
 
@@ -183,11 +179,9 @@ grub_relocator64_boot (struct grub_relocator *rel,
   void *relst;
   grub_relocator_chunk_t ch;
 
-  err = grub_relocator_alloc_chunk_align (rel, &ch, min_addr,
-                                         max_addr - RELOCATOR_SIZEOF (64),
-                                         RELOCATOR_SIZEOF (64), 16,
-                                         GRUB_RELOCATOR_PREFERENCE_NONE,
-                                         0);
+  err = grub_relocator_alloc_chunk_align_safe (rel, &ch, min_addr, max_addr,
+                                              RELOCATOR_SIZEOF (64), 16,
+                                              GRUB_RELOCATOR_PREFERENCE_NONE, 0);
   if (err)
     return err;
 
index 9d5f49cb93a7fc471f33654e2fccbd08895b67aa..743b213e695250ab597f951be7ded98ad9a62948 100644 (file)
@@ -120,10 +120,8 @@ grub_relocator32_boot (struct grub_relocator *rel,
   unsigned i;
   grub_addr_t vtarget;
 
-  err = grub_relocator_alloc_chunk_align (rel, &ch, 0,
-                                         (0xffffffff - stateset_size)
-                                         + 1, stateset_size,
-                                         sizeof (grub_uint32_t),
+  err = grub_relocator_alloc_chunk_align (rel, &ch, 0, UP_TO_TOP32 (stateset_size),
+                                         stateset_size, sizeof (grub_uint32_t),
                                          GRUB_RELOCATOR_PREFERENCE_NONE, 0);
   if (err)
     return err;
index bdf2b111be78cff513e3b02b1b3e3b98edb6ad30..8ffb8b68683e32ecb0d26fff3105686fd7a7f1bb 100644 (file)
@@ -115,10 +115,8 @@ grub_relocator32_boot (struct grub_relocator *rel,
   unsigned i;
   grub_relocator_chunk_t ch;
 
-  err = grub_relocator_alloc_chunk_align (rel, &ch, 0,
-                                         (0xffffffff - stateset_size)
-                                         + 1, stateset_size,
-                                         sizeof (grub_uint32_t),
+  err = grub_relocator_alloc_chunk_align (rel, &ch, 0, UP_TO_TOP32 (stateset_size),
+                                         stateset_size, sizeof (grub_uint32_t),
                                          GRUB_RELOCATOR_PREFERENCE_NONE, 0);
   if (err)
     return err;
index 3caef7a40217ba15b728f3e8641331f82a178988..7d200a125eeef6871cb9d9ce09fc81b5a400dcd5 100644 (file)
@@ -50,10 +50,9 @@ grub_relocator64_efi_boot (struct grub_relocator *rel,
    * 64-bit relocator code may live above 4 GiB quite well.
    * However, I do not want ask for problems. Just in case.
    */
-  err = grub_relocator_alloc_chunk_align (rel, &ch, 0,
-                                         0x100000000 - RELOCATOR_SIZEOF (64_efi),
-                                         RELOCATOR_SIZEOF (64_efi), 16,
-                                         GRUB_RELOCATOR_PREFERENCE_NONE, 1);
+  err = grub_relocator_alloc_chunk_align_safe (rel, &ch, 0, 0x100000000,
+                                              RELOCATOR_SIZEOF (64_efi), 16,
+                                              GRUB_RELOCATOR_PREFERENCE_NONE, 1);
   if (err)
     return err;
 
index 938ba536e62d50502741cb8706bd86135c2de4ac..976af3fae873c0f26501eb402e45bce16ad305c6 100644 (file)
@@ -181,9 +181,8 @@ allocate_pages (grub_size_t prot_size, grub_size_t *align,
        for (; err && *align + 1 > min_align; (*align)--)
          {
            grub_errno = GRUB_ERR_NONE;
-           err = grub_relocator_alloc_chunk_align (relocator, &ch,
-                                                   0x1000000,
-                                                   0xffffffff & ~prot_size,
+           err = grub_relocator_alloc_chunk_align (relocator, &ch, 0x1000000,
+                                                   UP_TO_TOP32 (prot_size),
                                                    prot_size, 1 << *align,
                                                    GRUB_RELOCATOR_PREFERENCE_LOW,
                                                    1);
index ad3cc292fd189eab55ff576db899bebe131978b5..a67d9d0a8088e80bdd1269a429c1bb4f50237546 100644 (file)
@@ -466,10 +466,9 @@ grub_multiboot_make_mbi (grub_uint32_t *target)
 
   bufsize = grub_multiboot_get_mbi_size ();
 
-  err = grub_relocator_alloc_chunk_align (grub_multiboot_relocator, &ch,
-                                         0x10000, 0xa0000 - bufsize,
-                                         bufsize, 4,
-                                         GRUB_RELOCATOR_PREFERENCE_NONE, 0);
+  err = grub_relocator_alloc_chunk_align_safe (grub_multiboot_relocator, &ch,
+                                              0x10000, 0xa0000, bufsize, 4,
+                                              GRUB_RELOCATOR_PREFERENCE_NONE, 0);
   if (err)
     return err;
   ptrorig = get_virtual_current_address (ch);
index 8be763d1976cc4ad7e00d8f51d14ab5f62017005..814988ab96e12ca0527693e4ebbcc0357975ec21 100644 (file)
@@ -453,10 +453,8 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
 
   {
     grub_relocator_chunk_t ch;
-    err = grub_relocator_alloc_chunk_align (relocator, &ch,
-                                           addr_min, addr_max - size,
-                                           size, 0x1000,
-                                           GRUB_RELOCATOR_PREFERENCE_HIGH, 0);
+    err = grub_relocator_alloc_chunk_align_safe (relocator, &ch, addr_min, addr_max, size,
+                                                0x1000, GRUB_RELOCATOR_PREFERENCE_HIGH, 0);
     if (err)
       return err;
     initrd_chunk = get_virtual_current_address (ch);
index 7b723bf189d8bf0c2d72e94badb46f6807b03b2d..e4ed95921dfab1db310de2f42841b52d78dd50a9 100644 (file)
@@ -442,12 +442,9 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
   {
     grub_relocator_chunk_t ch;
 
-    err = grub_relocator_alloc_chunk_align (relocator, &ch,
-                                           (target_addr & 0x1fffffff)
-                                           + linux_size + 0x10000,
-                                           (0x10000000 - size),
-                                           size, 0x10000,
-                                           GRUB_RELOCATOR_PREFERENCE_NONE, 0);
+    err = grub_relocator_alloc_chunk_align_safe (relocator, &ch, (target_addr & 0x1fffffff) +
+                                                linux_size + 0x10000, 0x10000000, size,
+                                                0x10000, GRUB_RELOCATOR_PREFERENCE_NONE, 0);
 
     if (err)
       goto fail;
index 4a98d7082598204aea3ccf0cecb7b907615d30bd..facb13f3d36e0910f18d1f1c677e032c1aa1c6fb 100644 (file)
@@ -403,7 +403,7 @@ grub_cmd_module (grub_command_t cmd __attribute__ ((unused)),
   {
     grub_relocator_chunk_t ch;
     err = grub_relocator_alloc_chunk_align (GRUB_MULTIBOOT (relocator), &ch,
-                                           lowest_addr, (0xffffffff - size) + 1,
+                                           lowest_addr, UP_TO_TOP32 (size),
                                            size, MULTIBOOT_MOD_ALIGN,
                                            GRUB_RELOCATOR_PREFERENCE_NONE, 1);
     if (err)
index cc6853692a8a24c50633ea364681823fa8350b2d..f2318e0d165d03e5c82a274f339c471e769382a5 100644 (file)
@@ -109,10 +109,10 @@ CONCAT(grub_multiboot_load_elf, XX) (mbi_load_data_t *mld)
       if (load_size > mld->max_addr || mld->min_addr > mld->max_addr - load_size)
        return grub_error (GRUB_ERR_BAD_OS, "invalid min/max address and/or load size");
 
-      err = grub_relocator_alloc_chunk_align (GRUB_MULTIBOOT (relocator), &ch,
-                                             mld->min_addr, mld->max_addr - load_size,
-                                             load_size, mld->align ? mld->align : 1,
-                                             mld->preference, mld->avoid_efi_boot_services);
+      err = grub_relocator_alloc_chunk_align_safe (GRUB_MULTIBOOT (relocator), &ch,
+                                                  mld->min_addr, mld->max_addr,
+                                                  load_size, mld->align ? mld->align : 1,
+                                                  mld->preference, mld->avoid_efi_boot_services);
 
       if (err)
         {
@@ -256,7 +256,7 @@ CONCAT(grub_multiboot_load_elf, XX) (mbi_load_data_t *mld)
            continue;
 
          err = grub_relocator_alloc_chunk_align (GRUB_MULTIBOOT (relocator), &ch, 0,
-                                                 (0xffffffff - sh->sh_size) + 1,
+                                                 UP_TO_TOP32 (sh->sh_size),
                                                  sh->sh_size, sh->sh_addralign,
                                                  GRUB_RELOCATOR_PREFERENCE_NONE,
                                                  mld->avoid_efi_boot_services);
index e88c9f4884f48338c19350db0a27975cde284ecf..9a943d7bdd7c22a34c1a094f84232e3597c3d194 100644 (file)
@@ -301,10 +301,10 @@ grub_multiboot2_load (grub_file_t file, const char *filename)
              return grub_error (GRUB_ERR_BAD_OS, "invalid min/max address and/or load size");
            }
 
-         err = grub_relocator_alloc_chunk_align (grub_multiboot2_relocator, &ch,
-                                                 mld.min_addr, mld.max_addr - code_size,
-                                                 code_size, mld.align ? mld.align : 1,
-                                                 mld.preference, keep_bs);
+         err = grub_relocator_alloc_chunk_align_safe (grub_multiboot2_relocator, &ch,
+                                                      mld.min_addr, mld.max_addr,
+                                                      code_size, mld.align ? mld.align : 1,
+                                                      mld.preference, keep_bs);
        }
       else
        err = grub_relocator_alloc_chunk_addr (grub_multiboot2_relocator,
@@ -714,7 +714,7 @@ grub_multiboot2_make_mbi (grub_uint32_t *target)
   COMPILE_TIME_ASSERT (MULTIBOOT_TAG_ALIGN % sizeof (grub_properly_aligned_t) == 0);
 
   err = grub_relocator_alloc_chunk_align (grub_multiboot2_relocator, &ch,
-                                         MBI_MIN_ADDR, 0xffffffff - bufsize,
+                                         MBI_MIN_ADDR, UP_TO_TOP32 (bufsize),
                                          bufsize, MULTIBOOT_TAG_ALIGN,
                                          GRUB_RELOCATOR_PREFERENCE_NONE, 1);
   if (err)
index 8089804d4822a00a1d8b4ab331069386f0db8c97..d648ef0cd3a7d3f5bc137a25768dbf4efa9b8432 100644 (file)
@@ -129,7 +129,7 @@ grub_xnu_resume (char *imagename)
   {
     grub_relocator_chunk_t ch;
     err = grub_relocator_alloc_chunk_align (grub_xnu_relocator, &ch, 0,
-                                           (0xffffffff - hibhead.image_size) + 1,
+                                           UP_TO_TOP32 (hibhead.image_size),
                                            hibhead.image_size,
                                            GRUB_XNU_PAGESIZE,
                                            GRUB_RELOCATOR_PREFERENCE_NONE, 0);
index 24d8672d22c4c539a127633fd182adeb6c9b0b9d..1b3bdd92ac661613a23d76fc4dc5d72cb995db83 100644 (file)
@@ -49,6 +49,35 @@ grub_relocator_alloc_chunk_align (struct grub_relocator *rel,
                                  int preference,
                                  int avoid_efi_boot_services);
 
+/*
+ * Wrapper for grub_relocator_alloc_chunk_align() with purpose of
+ * protecting against integer underflow.
+ *
+ * Compare to its callee, max_addr has different meaning here.
+ * It covers entire chunk and not just start address of the chunk.
+ */
+static inline grub_err_t
+grub_relocator_alloc_chunk_align_safe (struct grub_relocator *rel,
+                                      grub_relocator_chunk_t *out,
+                                      grub_phys_addr_t min_addr,
+                                      grub_phys_addr_t max_addr,
+                                      grub_size_t size, grub_size_t align,
+                                      int preference,
+                                      int avoid_efi_boot_services)
+{
+  /* Sanity check and ensure following equation (max_addr - size) is safe. */
+  if (max_addr < size || (max_addr - size) < min_addr)
+    return GRUB_ERR_OUT_OF_RANGE;
+
+  return grub_relocator_alloc_chunk_align (rel, out, min_addr,
+                                          max_addr - size,
+                                          size, align, preference,
+                                          avoid_efi_boot_services);
+}
+
+/* Top 32-bit address minus s bytes and plus 1 byte. */
+#define UP_TO_TOP32(s) ((~(s) & 0xffffffff) + 1)
+
 #define GRUB_RELOCATOR_PREFERENCE_NONE 0
 #define GRUB_RELOCATOR_PREFERENCE_LOW 1
 #define GRUB_RELOCATOR_PREFERENCE_HIGH 2