relocator: Protect grub_relocator_alloc_chunk_align() max_addr against integer underflow

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>
This commit is contained in:
Alexey Makhalov 2020-07-08 01:44:38 +00:00 committed by Daniel Kiper
parent caea56d1f8
commit 61ff5602fe
13 changed files with 69 additions and 58 deletions

View file

@ -83,8 +83,7 @@ grub_relocator32_boot (struct grub_relocator *rel,
/* Specific memory range due to Global Descriptor Table for use by payload /* Specific memory range due to Global Descriptor Table for use by payload
that we will store in returned chunk. The address range and preference that we will store in returned chunk. The address range and preference
are based on "THE LINUX/x86 BOOT PROTOCOL" specification. */ are based on "THE LINUX/x86 BOOT PROTOCOL" specification. */
err = grub_relocator_alloc_chunk_align (rel, &ch, 0x1000, err = grub_relocator_alloc_chunk_align_safe (rel, &ch, 0x1000, 0x9a000,
0x9a000 - RELOCATOR_SIZEOF (32),
RELOCATOR_SIZEOF (32), 16, RELOCATOR_SIZEOF (32), 16,
GRUB_RELOCATOR_PREFERENCE_LOW, GRUB_RELOCATOR_PREFERENCE_LOW,
avoid_efi_bootservices); avoid_efi_bootservices);
@ -125,13 +124,10 @@ grub_relocator16_boot (struct grub_relocator *rel,
grub_relocator_chunk_t ch; grub_relocator_chunk_t ch;
/* Put it higher than the byte it checks for A20 check. */ /* Put it higher than the byte it checks for A20 check. */
err = grub_relocator_alloc_chunk_align (rel, &ch, 0x8010, err = grub_relocator_alloc_chunk_align_safe (rel, &ch, 0x8010, 0xa0000,
0xa0000 - RELOCATOR_SIZEOF (16) RELOCATOR_SIZEOF (16) +
- GRUB_RELOCATOR16_STACK_SIZE, GRUB_RELOCATOR16_STACK_SIZE, 16,
RELOCATOR_SIZEOF (16) GRUB_RELOCATOR_PREFERENCE_NONE, 0);
+ GRUB_RELOCATOR16_STACK_SIZE, 16,
GRUB_RELOCATOR_PREFERENCE_NONE,
0);
if (err) if (err)
return err; return err;
@ -183,11 +179,9 @@ grub_relocator64_boot (struct grub_relocator *rel,
void *relst; void *relst;
grub_relocator_chunk_t ch; grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (rel, &ch, min_addr, err = grub_relocator_alloc_chunk_align_safe (rel, &ch, min_addr, max_addr,
max_addr - RELOCATOR_SIZEOF (64),
RELOCATOR_SIZEOF (64), 16, RELOCATOR_SIZEOF (64), 16,
GRUB_RELOCATOR_PREFERENCE_NONE, GRUB_RELOCATOR_PREFERENCE_NONE, 0);
0);
if (err) if (err)
return err; return err;

View file

@ -120,10 +120,8 @@ grub_relocator32_boot (struct grub_relocator *rel,
unsigned i; unsigned i;
grub_addr_t vtarget; grub_addr_t vtarget;
err = grub_relocator_alloc_chunk_align (rel, &ch, 0, err = grub_relocator_alloc_chunk_align (rel, &ch, 0, UP_TO_TOP32 (stateset_size),
(0xffffffff - stateset_size) stateset_size, sizeof (grub_uint32_t),
+ 1, stateset_size,
sizeof (grub_uint32_t),
GRUB_RELOCATOR_PREFERENCE_NONE, 0); GRUB_RELOCATOR_PREFERENCE_NONE, 0);
if (err) if (err)
return err; return err;

View file

@ -115,10 +115,8 @@ grub_relocator32_boot (struct grub_relocator *rel,
unsigned i; unsigned i;
grub_relocator_chunk_t ch; grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (rel, &ch, 0, err = grub_relocator_alloc_chunk_align (rel, &ch, 0, UP_TO_TOP32 (stateset_size),
(0xffffffff - stateset_size) stateset_size, sizeof (grub_uint32_t),
+ 1, stateset_size,
sizeof (grub_uint32_t),
GRUB_RELOCATOR_PREFERENCE_NONE, 0); GRUB_RELOCATOR_PREFERENCE_NONE, 0);
if (err) if (err)
return err; return err;

View file

@ -50,8 +50,7 @@ grub_relocator64_efi_boot (struct grub_relocator *rel,
* 64-bit relocator code may live above 4 GiB quite well. * 64-bit relocator code may live above 4 GiB quite well.
* However, I do not want ask for problems. Just in case. * However, I do not want ask for problems. Just in case.
*/ */
err = grub_relocator_alloc_chunk_align (rel, &ch, 0, err = grub_relocator_alloc_chunk_align_safe (rel, &ch, 0, 0x100000000,
0x100000000 - RELOCATOR_SIZEOF (64_efi),
RELOCATOR_SIZEOF (64_efi), 16, RELOCATOR_SIZEOF (64_efi), 16,
GRUB_RELOCATOR_PREFERENCE_NONE, 1); GRUB_RELOCATOR_PREFERENCE_NONE, 1);
if (err) if (err)

View file

@ -181,9 +181,8 @@ allocate_pages (grub_size_t prot_size, grub_size_t *align,
for (; err && *align + 1 > min_align; (*align)--) for (; err && *align + 1 > min_align; (*align)--)
{ {
grub_errno = GRUB_ERR_NONE; grub_errno = GRUB_ERR_NONE;
err = grub_relocator_alloc_chunk_align (relocator, &ch, err = grub_relocator_alloc_chunk_align (relocator, &ch, 0x1000000,
0x1000000, UP_TO_TOP32 (prot_size),
0xffffffff & ~prot_size,
prot_size, 1 << *align, prot_size, 1 << *align,
GRUB_RELOCATOR_PREFERENCE_LOW, GRUB_RELOCATOR_PREFERENCE_LOW,
1); 1);

View file

@ -466,9 +466,8 @@ grub_multiboot_make_mbi (grub_uint32_t *target)
bufsize = grub_multiboot_get_mbi_size (); bufsize = grub_multiboot_get_mbi_size ();
err = grub_relocator_alloc_chunk_align (grub_multiboot_relocator, &ch, err = grub_relocator_alloc_chunk_align_safe (grub_multiboot_relocator, &ch,
0x10000, 0xa0000 - bufsize, 0x10000, 0xa0000, bufsize, 4,
bufsize, 4,
GRUB_RELOCATOR_PREFERENCE_NONE, 0); GRUB_RELOCATOR_PREFERENCE_NONE, 0);
if (err) if (err)
return err; return err;

View file

@ -453,10 +453,8 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
{ {
grub_relocator_chunk_t ch; grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (relocator, &ch, err = grub_relocator_alloc_chunk_align_safe (relocator, &ch, addr_min, addr_max, size,
addr_min, addr_max - size, 0x1000, GRUB_RELOCATOR_PREFERENCE_HIGH, 0);
size, 0x1000,
GRUB_RELOCATOR_PREFERENCE_HIGH, 0);
if (err) if (err)
return err; return err;
initrd_chunk = get_virtual_current_address (ch); initrd_chunk = get_virtual_current_address (ch);

View file

@ -442,12 +442,9 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
{ {
grub_relocator_chunk_t ch; grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (relocator, &ch, err = grub_relocator_alloc_chunk_align_safe (relocator, &ch, (target_addr & 0x1fffffff) +
(target_addr & 0x1fffffff) linux_size + 0x10000, 0x10000000, size,
+ linux_size + 0x10000, 0x10000, GRUB_RELOCATOR_PREFERENCE_NONE, 0);
(0x10000000 - size),
size, 0x10000,
GRUB_RELOCATOR_PREFERENCE_NONE, 0);
if (err) if (err)
goto fail; goto fail;

View file

@ -403,7 +403,7 @@ grub_cmd_module (grub_command_t cmd __attribute__ ((unused)),
{ {
grub_relocator_chunk_t ch; grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (GRUB_MULTIBOOT (relocator), &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, size, MULTIBOOT_MOD_ALIGN,
GRUB_RELOCATOR_PREFERENCE_NONE, 1); GRUB_RELOCATOR_PREFERENCE_NONE, 1);
if (err) if (err)

View file

@ -109,8 +109,8 @@ 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) 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"); 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, err = grub_relocator_alloc_chunk_align_safe (GRUB_MULTIBOOT (relocator), &ch,
mld->min_addr, mld->max_addr - load_size, mld->min_addr, mld->max_addr,
load_size, mld->align ? mld->align : 1, load_size, mld->align ? mld->align : 1,
mld->preference, mld->avoid_efi_boot_services); mld->preference, mld->avoid_efi_boot_services);
@ -256,7 +256,7 @@ CONCAT(grub_multiboot_load_elf, XX) (mbi_load_data_t *mld)
continue; continue;
err = grub_relocator_alloc_chunk_align (GRUB_MULTIBOOT (relocator), &ch, 0, 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, sh->sh_size, sh->sh_addralign,
GRUB_RELOCATOR_PREFERENCE_NONE, GRUB_RELOCATOR_PREFERENCE_NONE,
mld->avoid_efi_boot_services); mld->avoid_efi_boot_services);

View file

@ -301,8 +301,8 @@ 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"); 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, err = grub_relocator_alloc_chunk_align_safe (grub_multiboot2_relocator, &ch,
mld.min_addr, mld.max_addr - code_size, mld.min_addr, mld.max_addr,
code_size, mld.align ? mld.align : 1, code_size, mld.align ? mld.align : 1,
mld.preference, keep_bs); mld.preference, keep_bs);
} }
@ -714,7 +714,7 @@ grub_multiboot2_make_mbi (grub_uint32_t *target)
COMPILE_TIME_ASSERT (MULTIBOOT_TAG_ALIGN % sizeof (grub_properly_aligned_t) == 0); COMPILE_TIME_ASSERT (MULTIBOOT_TAG_ALIGN % sizeof (grub_properly_aligned_t) == 0);
err = grub_relocator_alloc_chunk_align (grub_multiboot2_relocator, &ch, 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, bufsize, MULTIBOOT_TAG_ALIGN,
GRUB_RELOCATOR_PREFERENCE_NONE, 1); GRUB_RELOCATOR_PREFERENCE_NONE, 1);
if (err) if (err)

View file

@ -129,7 +129,7 @@ grub_xnu_resume (char *imagename)
{ {
grub_relocator_chunk_t ch; grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (grub_xnu_relocator, &ch, 0, 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, hibhead.image_size,
GRUB_XNU_PAGESIZE, GRUB_XNU_PAGESIZE,
GRUB_RELOCATOR_PREFERENCE_NONE, 0); GRUB_RELOCATOR_PREFERENCE_NONE, 0);

View file

@ -49,6 +49,35 @@ grub_relocator_alloc_chunk_align (struct grub_relocator *rel,
int preference, int preference,
int avoid_efi_boot_services); 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_NONE 0
#define GRUB_RELOCATOR_PREFERENCE_LOW 1 #define GRUB_RELOCATOR_PREFERENCE_LOW 1
#define GRUB_RELOCATOR_PREFERENCE_HIGH 2 #define GRUB_RELOCATOR_PREFERENCE_HIGH 2