From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Alexey Makhalov Date: Wed, 8 Jul 2020 01:44:38 +0000 Subject: [PATCH] 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 Reviewed-by: Daniel Kiper Upstream-commit-id: 10498c8ba17 --- grub-core/lib/i386/relocator.c | 28 +++++++++++----------------- grub-core/lib/mips/relocator.c | 6 ++---- grub-core/lib/powerpc/relocator.c | 6 ++---- grub-core/lib/x86_64/efi/relocator.c | 7 +++---- grub-core/loader/i386/linux.c | 5 ++--- grub-core/loader/i386/multiboot_mbi.c | 7 +++---- grub-core/loader/i386/pc/linux.c | 6 ++---- grub-core/loader/mips/linux.c | 9 +++------ grub-core/loader/multiboot.c | 2 +- grub-core/loader/multiboot_elfxx.c | 10 +++++----- grub-core/loader/multiboot_mbi2.c | 10 +++++----- grub-core/loader/xnu_resume.c | 2 +- include/grub/relocator.h | 29 +++++++++++++++++++++++++++++ 13 files changed, 69 insertions(+), 58 deletions(-) diff --git a/grub-core/lib/i386/relocator.c b/grub-core/lib/i386/relocator.c index 71dd4f0ab..34cbe834f 100644 --- a/grub-core/lib/i386/relocator.c +++ b/grub-core/lib/i386/relocator.c @@ -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; diff --git a/grub-core/lib/mips/relocator.c b/grub-core/lib/mips/relocator.c index 9d5f49cb9..743b213e6 100644 --- a/grub-core/lib/mips/relocator.c +++ b/grub-core/lib/mips/relocator.c @@ -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; diff --git a/grub-core/lib/powerpc/relocator.c b/grub-core/lib/powerpc/relocator.c index bdf2b111b..8ffb8b686 100644 --- a/grub-core/lib/powerpc/relocator.c +++ b/grub-core/lib/powerpc/relocator.c @@ -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; diff --git a/grub-core/lib/x86_64/efi/relocator.c b/grub-core/lib/x86_64/efi/relocator.c index 3caef7a40..7d200a125 100644 --- a/grub-core/lib/x86_64/efi/relocator.c +++ b/grub-core/lib/x86_64/efi/relocator.c @@ -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; diff --git a/grub-core/loader/i386/linux.c b/grub-core/loader/i386/linux.c index b4a30f607..191f1631e 100644 --- a/grub-core/loader/i386/linux.c +++ b/grub-core/loader/i386/linux.c @@ -231,9 +231,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); diff --git a/grub-core/loader/i386/multiboot_mbi.c b/grub-core/loader/i386/multiboot_mbi.c index ca85358f7..9d3466d6a 100644 --- a/grub-core/loader/i386/multiboot_mbi.c +++ b/grub-core/loader/i386/multiboot_mbi.c @@ -470,10 +470,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); diff --git a/grub-core/loader/i386/pc/linux.c b/grub-core/loader/i386/pc/linux.c index 540891371..63736fae9 100644 --- a/grub-core/loader/i386/pc/linux.c +++ b/grub-core/loader/i386/pc/linux.c @@ -460,10 +460,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); diff --git a/grub-core/loader/mips/linux.c b/grub-core/loader/mips/linux.c index 5f383be3d..27c1db84a 100644 --- a/grub-core/loader/mips/linux.c +++ b/grub-core/loader/mips/linux.c @@ -434,12 +434,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; diff --git a/grub-core/loader/multiboot.c b/grub-core/loader/multiboot.c index 9a8dae556..f455e8039 100644 --- a/grub-core/loader/multiboot.c +++ b/grub-core/loader/multiboot.c @@ -407,7 +407,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) diff --git a/grub-core/loader/multiboot_elfxx.c b/grub-core/loader/multiboot_elfxx.c index cc6853692..f2318e0d1 100644 --- a/grub-core/loader/multiboot_elfxx.c +++ b/grub-core/loader/multiboot_elfxx.c @@ -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); diff --git a/grub-core/loader/multiboot_mbi2.c b/grub-core/loader/multiboot_mbi2.c index 872dcd42e..3cfb47650 100644 --- a/grub-core/loader/multiboot_mbi2.c +++ b/grub-core/loader/multiboot_mbi2.c @@ -298,10 +298,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, @@ -747,7 +747,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, - 0, 0xffffffff - bufsize, + 0, UP_TO_TOP32 (bufsize), bufsize, MULTIBOOT_TAG_ALIGN, GRUB_RELOCATOR_PREFERENCE_NONE, 1); if (err) diff --git a/grub-core/loader/xnu_resume.c b/grub-core/loader/xnu_resume.c index 534a74438..99119558d 100644 --- a/grub-core/loader/xnu_resume.c +++ b/grub-core/loader/xnu_resume.c @@ -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); diff --git a/include/grub/relocator.h b/include/grub/relocator.h index 24d8672d2..1b3bdd92a 100644 --- a/include/grub/relocator.h +++ b/include/grub/relocator.h @@ -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