From 9c8e1c85ec9971f2c6bb9a8a7530e94f7dbec14a Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 5 Apr 2022 10:02:04 +0100 Subject: [PATCH 01/30] loader/efi/chainloader: Simplify the loader state The chainloader command retains the source buffer and device path passed to LoadImage(), requiring the unload hook passed to grub_loader_set() to free them. It isn't required to retain this state though - they aren't required by StartImage() or anything else in the boot hook, so clean them up before grub_cmd_chainloader() finishes. Signed-off-by: Chris Coulson Reviewed-by: Daniel Kiper --- grub-core/loader/efi/chainloader.c | 38 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c index 2bd80f4db..d1602c89b 100644 --- a/grub-core/loader/efi/chainloader.c +++ b/grub-core/loader/efi/chainloader.c @@ -44,25 +44,20 @@ GRUB_MOD_LICENSE ("GPLv3+"); static grub_dl_t my_mod; -static grub_efi_physical_address_t address; -static grub_efi_uintn_t pages; -static grub_efi_device_path_t *file_path; static grub_efi_handle_t image_handle; -static grub_efi_char16_t *cmdline; static grub_err_t grub_chainloader_unload (void) { + grub_efi_loaded_image_t *loaded_image; grub_efi_boot_services_t *b; + loaded_image = grub_efi_get_loaded_image (image_handle); + if (loaded_image != NULL) + grub_free (loaded_image->load_options); + b = grub_efi_system_table->boot_services; efi_call_1 (b->unload_image, image_handle); - efi_call_2 (b->free_pages, address, pages); - - grub_free (file_path); - grub_free (cmdline); - cmdline = 0; - file_path = 0; grub_dl_unref (my_mod); return GRUB_ERR_NONE; @@ -140,7 +135,7 @@ make_file_path (grub_efi_device_path_t *dp, const char *filename) char *dir_start; char *dir_end; grub_size_t size; - grub_efi_device_path_t *d; + grub_efi_device_path_t *d, *file_path; dir_start = grub_strchr (filename, ')'); if (! dir_start) @@ -222,11 +217,14 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), grub_efi_status_t status; grub_efi_boot_services_t *b; grub_device_t dev = 0; - grub_efi_device_path_t *dp = 0; + grub_efi_device_path_t *dp = NULL, *file_path = NULL; grub_efi_loaded_image_t *loaded_image; char *filename; void *boot_image = 0; grub_efi_handle_t dev_handle = 0; + grub_efi_physical_address_t address = 0; + grub_efi_uintn_t pages = 0; + grub_efi_char16_t *cmdline = NULL; if (argc == 0) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); @@ -234,11 +232,6 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), grub_dl_ref (my_mod); - /* Initialize some global variables. */ - address = 0; - image_handle = 0; - file_path = 0; - b = grub_efi_system_table->boot_services; file = grub_file_open (filename, GRUB_FILE_TYPE_EFI_CHAINLOADED_IMAGE); @@ -408,6 +401,10 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), grub_file_close (file); grub_device_close (dev); + /* We're finished with the source image buffer and file path now. */ + efi_call_2 (b->free_pages, address, pages); + grub_free (file_path); + grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 0); return 0; @@ -419,11 +416,18 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), if (file) grub_file_close (file); + grub_free (cmdline); grub_free (file_path); if (address) efi_call_2 (b->free_pages, address, pages); + if (image_handle != NULL) + { + efi_call_1 (b->unload_image, image_handle); + image_handle = NULL; + } + grub_dl_unref (my_mod); return grub_errno; From bb35dbb9bf213bc9dde9a5a653e93b4c4d2bbf22 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Fri, 29 Apr 2022 21:16:02 +0100 Subject: [PATCH 02/30] commands/boot: Add API to pass context to loader Loaders rely on global variables for saving context which is consumed in the boot hook and freed in the unload hook. In the case where a loader command is executed twice, calling grub_loader_set a second time executes the unload hook, but in some cases this runs when the loader's global context has already been updated, resulting in the updated context being freed and potential use-after-free bugs when the boot hook is subsequently called. This adds a new API (grub_loader_set_ex) which allows a loader to specify context that is passed to its boot and unload hooks. This is an alternative to requiring that loaders call grub_loader_unset before mutating their global context. Signed-off-by: Chris Coulson (cherry picked from commit 4322a64dde7e8fedb58e50b79408667129d45dd3) --- grub-core/commands/boot.c | 66 ++++++++++++++++++++++++++++++++++----- include/grub/loader.h | 5 +++ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/grub-core/commands/boot.c b/grub-core/commands/boot.c index bbca81e94..53691a62d 100644 --- a/grub-core/commands/boot.c +++ b/grub-core/commands/boot.c @@ -27,10 +27,20 @@ GRUB_MOD_LICENSE ("GPLv3+"); -static grub_err_t (*grub_loader_boot_func) (void); -static grub_err_t (*grub_loader_unload_func) (void); +static grub_err_t (*grub_loader_boot_func) (void *); +static grub_err_t (*grub_loader_unload_func) (void *); +static void *grub_loader_context; static int grub_loader_flags; +struct grub_simple_loader_hooks +{ + grub_err_t (*boot) (void); + grub_err_t (*unload) (void); +}; + +/* Don't heap allocate this to avoid making grub_loader_set fallible. */ +static struct grub_simple_loader_hooks simple_loader_hooks; + struct grub_preboot { grub_err_t (*preboot_func) (int); @@ -44,6 +54,29 @@ static int grub_loader_loaded; static struct grub_preboot *preboots_head = 0, *preboots_tail = 0; +static grub_err_t +grub_simple_boot_hook (void *context) +{ + struct grub_simple_loader_hooks *hooks; + + hooks = (struct grub_simple_loader_hooks *) context; + return hooks->boot (); +} + +static grub_err_t +grub_simple_unload_hook (void *context) +{ + struct grub_simple_loader_hooks *hooks; + grub_err_t ret; + + hooks = (struct grub_simple_loader_hooks *) context; + + ret = hooks->unload (); + grub_memset (hooks, 0, sizeof (*hooks)); + + return ret; +} + int grub_loader_is_loaded (void) { @@ -110,28 +143,45 @@ grub_loader_unregister_preboot_hook (struct grub_preboot *hnd) } void -grub_loader_set (grub_err_t (*boot) (void), - grub_err_t (*unload) (void), - int flags) +grub_loader_set_ex (grub_err_t (*boot) (void *), + grub_err_t (*unload) (void *), + void *context, + int flags) { if (grub_loader_loaded && grub_loader_unload_func) - grub_loader_unload_func (); + grub_loader_unload_func (grub_loader_context); grub_loader_boot_func = boot; grub_loader_unload_func = unload; + grub_loader_context = context; grub_loader_flags = flags; grub_loader_loaded = 1; } +void +grub_loader_set (grub_err_t (*boot) (void), + grub_err_t (*unload) (void), + int flags) +{ + grub_loader_set_ex (grub_simple_boot_hook, + grub_simple_unload_hook, + &simple_loader_hooks, + flags); + + simple_loader_hooks.boot = boot; + simple_loader_hooks.unload = unload; +} + void grub_loader_unset(void) { if (grub_loader_loaded && grub_loader_unload_func) - grub_loader_unload_func (); + grub_loader_unload_func (grub_loader_context); grub_loader_boot_func = 0; grub_loader_unload_func = 0; + grub_loader_context = 0; grub_loader_loaded = 0; } @@ -158,7 +208,7 @@ grub_loader_boot (void) return err; } } - err = (grub_loader_boot_func) (); + err = (grub_loader_boot_func) (grub_loader_context); for (cur = preboots_tail; cur; cur = cur->prev) if (! err) diff --git a/include/grub/loader.h b/include/grub/loader.h index b20864282..1846fa6c5 100644 --- a/include/grub/loader.h +++ b/include/grub/loader.h @@ -40,6 +40,11 @@ void EXPORT_FUNC (grub_loader_set) (grub_err_t (*boot) (void), grub_err_t (*unload) (void), int flags); +void EXPORT_FUNC (grub_loader_set_ex) (grub_err_t (*boot) (void *), + grub_err_t (*unload) (void *), + void *context, + int flags); + /* Unset current loader, if any. */ void EXPORT_FUNC (grub_loader_unset) (void); From 19b4f19cfea30655032c978e228d78e056f55f1a Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 5 Apr 2022 11:48:58 +0100 Subject: [PATCH 03/30] loader/efi/chainloader: Use grub_loader_set_ex() This ports the EFI chainloader to use grub_loader_set_ex() in order to fix a use-after-free bug that occurs when grub_cmd_chainloader() is executed more than once before a boot attempt is performed. Fixes: CVE-2022-28736 Signed-off-by: Chris Coulson Reviewed-by: Daniel Kiper --- grub-core/loader/efi/chainloader.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c index d1602c89b..7557eb269 100644 --- a/grub-core/loader/efi/chainloader.c +++ b/grub-core/loader/efi/chainloader.c @@ -44,11 +44,10 @@ GRUB_MOD_LICENSE ("GPLv3+"); static grub_dl_t my_mod; -static grub_efi_handle_t image_handle; - static grub_err_t -grub_chainloader_unload (void) +grub_chainloader_unload (void *context) { + grub_efi_handle_t image_handle = (grub_efi_handle_t) context; grub_efi_loaded_image_t *loaded_image; grub_efi_boot_services_t *b; @@ -64,8 +63,9 @@ grub_chainloader_unload (void) } static grub_err_t -grub_chainloader_boot (void) +grub_chainloader_boot (void *context) { + grub_efi_handle_t image_handle = (grub_efi_handle_t) context; grub_efi_boot_services_t *b; grub_efi_status_t status; grub_efi_uintn_t exit_data_size; @@ -225,6 +225,7 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), grub_efi_physical_address_t address = 0; grub_efi_uintn_t pages = 0; grub_efi_char16_t *cmdline = NULL; + grub_efi_handle_t image_handle = NULL; if (argc == 0) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); @@ -405,7 +406,7 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), efi_call_2 (b->free_pages, address, pages); grub_free (file_path); - grub_loader_set (grub_chainloader_boot, grub_chainloader_unload, 0); + grub_loader_set_ex (grub_chainloader_boot, grub_chainloader_unload, image_handle, 0); return 0; fail: @@ -423,10 +424,7 @@ grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), efi_call_2 (b->free_pages, address, pages); if (image_handle != NULL) - { - efi_call_1 (b->unload_image, image_handle); - image_handle = NULL; - } + efi_call_1 (b->unload_image, image_handle); grub_dl_unref (my_mod); From 24e6d59ac676791507ff5267bf3bef6cbaff6aef Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 2 Dec 2021 15:03:53 +0100 Subject: [PATCH 04/30] kern/efi/sb: Reject non-kernel files in the shim_lock verifier We must not allow other verifiers to pass things like the GRUB modules. Instead of maintaining a blocklist, maintain an allowlist of things that we do not care about. This allowlist really should be made reusable, and shared by the lockdown verifier, but this is the minimal patch addressing security concerns where the TPM verifier was able to mark modules as verified (or the OpenPGP verifier for that matter), when it should not do so on shim-powered secure boot systems. Fixes: CVE-2022-28735 Signed-off-by: Julian Andres Klode Reviewed-by: Daniel Kiper --- grub-core/kern/efi/sb.c | 43 ++++++++++++++++++++++++++++++++++++----- include/grub/verify.h | 1 + 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/grub-core/kern/efi/sb.c b/grub-core/kern/efi/sb.c index c52ec6226..89c4bb3fd 100644 --- a/grub-core/kern/efi/sb.c +++ b/grub-core/kern/efi/sb.c @@ -119,10 +119,11 @@ shim_lock_verifier_init (grub_file_t io __attribute__ ((unused)), void **context __attribute__ ((unused)), enum grub_verify_flags *flags) { - *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION; + *flags = GRUB_VERIFY_FLAGS_NONE; switch (type & GRUB_FILE_TYPE_MASK) { + /* Files we check. */ case GRUB_FILE_TYPE_LINUX_KERNEL: case GRUB_FILE_TYPE_MULTIBOOT_KERNEL: case GRUB_FILE_TYPE_BSD_KERNEL: @@ -130,11 +131,43 @@ shim_lock_verifier_init (grub_file_t io __attribute__ ((unused)), case GRUB_FILE_TYPE_PLAN9_KERNEL: case GRUB_FILE_TYPE_EFI_CHAINLOADED_IMAGE: *flags = GRUB_VERIFY_FLAGS_SINGLE_CHUNK; - - /* Fall through. */ - - default: return GRUB_ERR_NONE; + + /* Files that do not affect secureboot state. */ + case GRUB_FILE_TYPE_NONE: + case GRUB_FILE_TYPE_LOOPBACK: + case GRUB_FILE_TYPE_LINUX_INITRD: + case GRUB_FILE_TYPE_OPENBSD_RAMDISK: + case GRUB_FILE_TYPE_XNU_RAMDISK: + case GRUB_FILE_TYPE_SIGNATURE: + case GRUB_FILE_TYPE_PUBLIC_KEY: + case GRUB_FILE_TYPE_PUBLIC_KEY_TRUST: + case GRUB_FILE_TYPE_PRINT_BLOCKLIST: + case GRUB_FILE_TYPE_TESTLOAD: + case GRUB_FILE_TYPE_GET_SIZE: + case GRUB_FILE_TYPE_FONT: + case GRUB_FILE_TYPE_ZFS_ENCRYPTION_KEY: + case GRUB_FILE_TYPE_CAT: + case GRUB_FILE_TYPE_HEXCAT: + case GRUB_FILE_TYPE_CMP: + case GRUB_FILE_TYPE_HASHLIST: + case GRUB_FILE_TYPE_TO_HASH: + case GRUB_FILE_TYPE_KEYBOARD_LAYOUT: + case GRUB_FILE_TYPE_PIXMAP: + case GRUB_FILE_TYPE_GRUB_MODULE_LIST: + case GRUB_FILE_TYPE_CONFIG: + case GRUB_FILE_TYPE_THEME: + case GRUB_FILE_TYPE_GETTEXT_CATALOG: + case GRUB_FILE_TYPE_FS_SEARCH: + case GRUB_FILE_TYPE_LOADENV: + case GRUB_FILE_TYPE_SAVEENV: + case GRUB_FILE_TYPE_VERIFY_SIGNATURE: + *flags = GRUB_VERIFY_FLAGS_SKIP_VERIFICATION; + return GRUB_ERR_NONE; + + /* Other files. */ + default: + return grub_error (GRUB_ERR_ACCESS_DENIED, N_("prohibited by secure boot policy")); } } diff --git a/include/grub/verify.h b/include/grub/verify.h index 6fde244fc..67448165f 100644 --- a/include/grub/verify.h +++ b/include/grub/verify.h @@ -24,6 +24,7 @@ enum grub_verify_flags { + GRUB_VERIFY_FLAGS_NONE = 0, GRUB_VERIFY_FLAGS_SKIP_VERIFICATION = 1, GRUB_VERIFY_FLAGS_SINGLE_CHUNK = 2, /* Defer verification to another authority. */ From c15fa5fb039cd5062dfa02a03efd924422c4a8ed Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Fri, 25 Jun 2021 02:19:05 +1000 Subject: [PATCH 05/30] kern/file: Do not leak device_name on error in grub_file_open() If we have an error in grub_file_open() before we free device_name, we will leak it. Free device_name in the error path and null out the pointer in the good path once we free it there. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/kern/file.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grub-core/kern/file.c b/grub-core/kern/file.c index 58454458c..ffdcaba05 100644 --- a/grub-core/kern/file.c +++ b/grub-core/kern/file.c @@ -79,6 +79,7 @@ grub_file_open (const char *name, enum grub_file_type type) device = grub_device_open (device_name); grub_free (device_name); + device_name = NULL; if (! device) goto fail; @@ -131,6 +132,7 @@ grub_file_open (const char *name, enum grub_file_type type) return file; fail: + grub_free (device_name); if (device) grub_device_close (device); From 907f100c841f39e37e4801f726f6b47c2aa9191f Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 6 Jul 2021 14:02:55 +1000 Subject: [PATCH 06/30] video/readers/png: Abort sooner if a read operation fails Fuzzing revealed some inputs that were taking a long time, potentially forever, because they did not bail quickly upon encountering an I/O error. Try to catch I/O errors sooner and bail out. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/video/readers/png.c | 55 ++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/grub-core/video/readers/png.c b/grub-core/video/readers/png.c index 0157ff742..e2a6b1cf3 100644 --- a/grub-core/video/readers/png.c +++ b/grub-core/video/readers/png.c @@ -142,6 +142,7 @@ static grub_uint8_t grub_png_get_byte (struct grub_png_data *data) { grub_uint8_t r; + grub_ssize_t bytes_read = 0; if ((data->inside_idat) && (data->idat_remain == 0)) { @@ -175,7 +176,14 @@ grub_png_get_byte (struct grub_png_data *data) } r = 0; - grub_file_read (data->file, &r, 1); + bytes_read = grub_file_read (data->file, &r, 1); + + if (bytes_read != 1) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, + "png: unexpected end of data"); + return 0; + } if (data->inside_idat) data->idat_remain--; @@ -231,15 +239,16 @@ grub_png_decode_image_palette (struct grub_png_data *data, if (len == 0) return GRUB_ERR_NONE; - for (i = 0; 3 * i < len && i < 256; i++) + grub_errno = GRUB_ERR_NONE; + for (i = 0; 3 * i < len && i < 256 && grub_errno == GRUB_ERR_NONE; i++) for (j = 0; j < 3; j++) data->palette[i][j] = grub_png_get_byte (data); - for (i *= 3; i < len; i++) + for (i *= 3; i < len && grub_errno == GRUB_ERR_NONE; i++) grub_png_get_byte (data); grub_png_get_dword (data); - return GRUB_ERR_NONE; + return grub_errno; } static grub_err_t @@ -256,9 +265,13 @@ grub_png_decode_image_header (struct grub_png_data *data) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: invalid image size"); color_bits = grub_png_get_byte (data); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; data->is_16bit = (color_bits == 16); color_type = grub_png_get_byte (data); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; /* According to PNG spec, no other types are valid. */ if ((color_type & ~(PNG_COLOR_MASK_ALPHA | PNG_COLOR_MASK_COLOR)) @@ -340,14 +353,20 @@ grub_png_decode_image_header (struct grub_png_data *data) if (grub_png_get_byte (data) != PNG_COMPRESSION_BASE) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: compression method not supported"); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; if (grub_png_get_byte (data) != PNG_FILTER_TYPE_BASE) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: filter method not supported"); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; if (grub_png_get_byte (data) != PNG_INTERLACE_NONE) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: interlace method not supported"); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; /* Skip crc checksum. */ grub_png_get_dword (data); @@ -449,7 +468,7 @@ grub_png_get_huff_code (struct grub_png_data *data, struct huff_table *ht) int code, i; code = 0; - for (i = 0; i < ht->max_length; i++) + for (i = 0; i < ht->max_length && grub_errno == GRUB_ERR_NONE; i++) { code = (code << 1) + grub_png_get_bits (data, 1); if (code < ht->maxval[i]) @@ -504,8 +523,14 @@ grub_png_init_dynamic_block (struct grub_png_data *data) grub_uint8_t lens[DEFLATE_HCLEN_MAX]; nl = DEFLATE_HLIT_BASE + grub_png_get_bits (data, 5); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; nd = DEFLATE_HDIST_BASE + grub_png_get_bits (data, 5); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; nb = DEFLATE_HCLEN_BASE + grub_png_get_bits (data, 4); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; if ((nl > DEFLATE_HLIT_MAX) || (nd > DEFLATE_HDIST_MAX) || (nb > DEFLATE_HCLEN_MAX)) @@ -533,7 +558,7 @@ grub_png_init_dynamic_block (struct grub_png_data *data) data->dist_offset); prev = 0; - for (i = 0; i < nl + nd; i++) + for (i = 0; i < nl + nd && grub_errno == GRUB_ERR_NONE; i++) { int n, code; struct huff_table *ht; @@ -721,17 +746,21 @@ grub_png_read_dynamic_block (struct grub_png_data *data) len = cplens[n]; if (cplext[n]) len += grub_png_get_bits (data, cplext[n]); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; n = grub_png_get_huff_code (data, &data->dist_table); dist = cpdist[n]; if (cpdext[n]) dist += grub_png_get_bits (data, cpdext[n]); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; pos = data->wp - dist; if (pos < 0) pos += WSIZE; - while (len > 0) + while (len > 0 && grub_errno == GRUB_ERR_NONE) { data->slide[data->wp] = data->slide[pos]; grub_png_output_byte (data, data->slide[data->wp]); @@ -759,7 +788,11 @@ grub_png_decode_image_data (struct grub_png_data *data) int final; cmf = grub_png_get_byte (data); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; flg = grub_png_get_byte (data); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; if ((cmf & 0xF) != Z_DEFLATED) return grub_error (GRUB_ERR_BAD_FILE_TYPE, @@ -774,7 +807,11 @@ grub_png_decode_image_data (struct grub_png_data *data) int block_type; final = grub_png_get_bits (data, 1); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; block_type = grub_png_get_bits (data, 2); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; switch (block_type) { @@ -790,7 +827,7 @@ grub_png_decode_image_data (struct grub_png_data *data) grub_png_get_byte (data); grub_png_get_byte (data); - for (i = 0; i < len; i++) + for (i = 0; i < len && grub_errno == GRUB_ERR_NONE; i++) grub_png_output_byte (data, grub_png_get_byte (data)); break; @@ -1045,6 +1082,8 @@ grub_png_decode_png (struct grub_png_data *data) len = grub_png_get_dword (data); type = grub_png_get_dword (data); + if (grub_errno != GRUB_ERR_NONE) + break; data->next_offset = data->file->offset + len + 4; switch (type) From 5e496e28b3c76666c98b737153f9b0c2bedf489d Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 6 Jul 2021 14:13:40 +1000 Subject: [PATCH 07/30] video/readers/png: Refuse to handle multiple image headers This causes the bitmap to be leaked. Do not permit multiple image headers. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/video/readers/png.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/grub-core/video/readers/png.c b/grub-core/video/readers/png.c index e2a6b1cf3..8955b8ecf 100644 --- a/grub-core/video/readers/png.c +++ b/grub-core/video/readers/png.c @@ -258,6 +258,9 @@ grub_png_decode_image_header (struct grub_png_data *data) int color_bits; enum grub_video_blit_format blt; + if (data->image_width || data->image_height) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: two image headers found"); + data->image_width = grub_png_get_dword (data); data->image_height = grub_png_get_dword (data); From 558c69b5d36d14d55bff21e6570205fe73a02ca2 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 6 Jul 2021 18:51:35 +1000 Subject: [PATCH 08/30] video/readers/png: Drop greyscale support to fix heap out-of-bounds write A 16-bit greyscale PNG without alpha is processed in the following loop: for (i = 0; i < (data->image_width * data->image_height); i++, d1 += 4, d2 += 2) { d1[R3] = d2[1]; d1[G3] = d2[1]; d1[B3] = d2[1]; } The increment of d1 is wrong. d1 is incremented by 4 bytes per iteration, but there are only 3 bytes allocated for storage. This means that image data will overwrite somewhat-attacker-controlled parts of memory - 3 bytes out of every 4 following the end of the image. This has existed since greyscale support was added in 2013 in commit 3ccf16dff98f (grub-core/video/readers/png.c: Support grayscale). Saving starfield.png as a 16-bit greyscale image without alpha in the gimp and attempting to load it causes grub-emu to crash - I don't think this code has ever worked. Delete all PNG greyscale support. Fixes: CVE-2021-3695 Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/video/readers/png.c | 85 +++-------------------------------- 1 file changed, 6 insertions(+), 79 deletions(-) diff --git a/grub-core/video/readers/png.c b/grub-core/video/readers/png.c index 8955b8ecf..a3161e25b 100644 --- a/grub-core/video/readers/png.c +++ b/grub-core/video/readers/png.c @@ -100,7 +100,7 @@ struct grub_png_data unsigned image_width, image_height; int bpp, is_16bit; - int raw_bytes, is_gray, is_alpha, is_palette; + int raw_bytes, is_alpha, is_palette; int row_bytes, color_bits; grub_uint8_t *image_data; @@ -296,13 +296,13 @@ grub_png_decode_image_header (struct grub_png_data *data) data->bpp = 3; else { - data->is_gray = 1; - data->bpp = 1; + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "png: color type not supported"); } if ((color_bits != 8) && (color_bits != 16) && (color_bits != 4 - || !(data->is_gray || data->is_palette))) + || !data->is_palette)) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: bit depth must be 8 or 16"); @@ -331,7 +331,7 @@ grub_png_decode_image_header (struct grub_png_data *data) } #ifndef GRUB_CPU_WORDS_BIGENDIAN - if (data->is_16bit || data->is_gray || data->is_palette) + if (data->is_16bit || data->is_palette) #endif { data->image_data = grub_calloc (data->image_height, data->row_bytes); @@ -899,27 +899,8 @@ grub_png_convert_image (struct grub_png_data *data) int shift; int mask = (1 << data->color_bits) - 1; unsigned j; - if (data->is_gray) - { - /* Generic formula is - (0xff * i) / ((1U << data->color_bits) - 1) - but for allowed bit depth of 1, 2 and for it's - equivalent to - (0xff / ((1U << data->color_bits) - 1)) * i - Precompute the multipliers to avoid division. - */ - const grub_uint8_t multipliers[5] = { 0xff, 0xff, 0x55, 0x24, 0x11 }; - for (i = 0; i < (1U << data->color_bits); i++) - { - grub_uint8_t col = multipliers[data->color_bits] * i; - palette[i][0] = col; - palette[i][1] = col; - palette[i][2] = col; - } - } - else - grub_memcpy (palette, data->palette, 3 << data->color_bits); + grub_memcpy (palette, data->palette, 3 << data->color_bits); d1c = d1; d2c = d2; for (j = 0; j < data->image_height; j++, d1c += data->image_width * 3, @@ -956,60 +937,6 @@ grub_png_convert_image (struct grub_png_data *data) } return; } - - if (data->is_gray) - { - switch (data->bpp) - { - case 4: - /* 16-bit gray with alpha. */ - for (i = 0; i < (data->image_width * data->image_height); - i++, d1 += 4, d2 += 4) - { - d1[R4] = d2[3]; - d1[G4] = d2[3]; - d1[B4] = d2[3]; - d1[A4] = d2[1]; - } - break; - case 2: - if (data->is_16bit) - /* 16-bit gray without alpha. */ - { - for (i = 0; i < (data->image_width * data->image_height); - i++, d1 += 4, d2 += 2) - { - d1[R3] = d2[1]; - d1[G3] = d2[1]; - d1[B3] = d2[1]; - } - } - else - /* 8-bit gray with alpha. */ - { - for (i = 0; i < (data->image_width * data->image_height); - i++, d1 += 4, d2 += 2) - { - d1[R4] = d2[1]; - d1[G4] = d2[1]; - d1[B4] = d2[1]; - d1[A4] = d2[0]; - } - } - break; - /* 8-bit gray without alpha. */ - case 1: - for (i = 0; i < (data->image_width * data->image_height); - i++, d1 += 3, d2++) - { - d1[R3] = d2[0]; - d1[G3] = d2[0]; - d1[B3] = d2[0]; - } - break; - } - return; - } { /* Only copy the upper 8 bit. */ From 21e3b255f91d9b7711f8346f1e4acf8cc19bf4fb Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 6 Jul 2021 23:25:07 +1000 Subject: [PATCH 09/30] video/readers/png: Avoid heap OOB R/W inserting huff table items In fuzzing we observed crashes where a code would attempt to be inserted into a huffman table before the start, leading to a set of heap OOB reads and writes as table entries with negative indices were shifted around and the new code written in. Catch the case where we would underflow the array and bail. Fixes: CVE-2021-3696 Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/video/readers/png.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/grub-core/video/readers/png.c b/grub-core/video/readers/png.c index a3161e25b..d7ed5aa6c 100644 --- a/grub-core/video/readers/png.c +++ b/grub-core/video/readers/png.c @@ -438,6 +438,13 @@ grub_png_insert_huff_item (struct huff_table *ht, int code, int len) for (i = len; i < ht->max_length; i++) n += ht->maxval[i]; + if (n > ht->num_values) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, + "png: out of range inserting huffman table item"); + return; + } + for (i = 0; i < n; i++) ht->values[ht->num_values - i] = ht->values[ht->num_values - i - 1]; From e1c0a986e39ab93954436bcf6e6a9a7ea465e4e7 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 6 Jul 2021 19:19:11 +1000 Subject: [PATCH 10/30] video/readers/png: Sanity check some huffman codes ASAN picked up two OOB global reads: we weren't checking if some code values fit within the cplens or cpdext arrays. Check and throw an error if not. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/video/readers/png.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/grub-core/video/readers/png.c b/grub-core/video/readers/png.c index d7ed5aa6c..7f2ba7849 100644 --- a/grub-core/video/readers/png.c +++ b/grub-core/video/readers/png.c @@ -753,6 +753,9 @@ grub_png_read_dynamic_block (struct grub_png_data *data) int len, dist, pos; n -= 257; + if (((unsigned int) n) >= ARRAY_SIZE (cplens)) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "png: invalid huff code"); len = cplens[n]; if (cplext[n]) len += grub_png_get_bits (data, cplext[n]); @@ -760,6 +763,9 @@ grub_png_read_dynamic_block (struct grub_png_data *data) return grub_errno; n = grub_png_get_huff_code (data, &data->dist_table); + if (((unsigned int) n) >= ARRAY_SIZE (cpdist)) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "png: invalid huff code"); dist = cpdist[n]; if (cpdext[n]) dist += grub_png_get_bits (data, cpdext[n]); From 40be99c5f8162887d1922fb9428b39de4cdad3af Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Mon, 28 Jun 2021 14:16:14 +1000 Subject: [PATCH 11/30] video/readers/jpeg: Abort sooner if a read operation fails Fuzzing revealed some inputs that were taking a long time, potentially forever, because they did not bail quickly upon encountering an I/O error. Try to catch I/O errors sooner and bail out. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/video/readers/jpeg.c | 86 +++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/grub-core/video/readers/jpeg.c b/grub-core/video/readers/jpeg.c index e31602f76..10225abd5 100644 --- a/grub-core/video/readers/jpeg.c +++ b/grub-core/video/readers/jpeg.c @@ -109,9 +109,17 @@ static grub_uint8_t grub_jpeg_get_byte (struct grub_jpeg_data *data) { grub_uint8_t r; + grub_ssize_t bytes_read; r = 0; - grub_file_read (data->file, &r, 1); + bytes_read = grub_file_read (data->file, &r, 1); + + if (bytes_read != 1) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, + "jpeg: unexpected end of data"); + return 0; + } return r; } @@ -120,9 +128,17 @@ static grub_uint16_t grub_jpeg_get_word (struct grub_jpeg_data *data) { grub_uint16_t r; + grub_ssize_t bytes_read; r = 0; - grub_file_read (data->file, &r, sizeof (grub_uint16_t)); + bytes_read = grub_file_read (data->file, &r, sizeof (grub_uint16_t)); + + if (bytes_read != sizeof (grub_uint16_t)) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, + "jpeg: unexpected end of data"); + return 0; + } return grub_be_to_cpu16 (r); } @@ -135,6 +151,11 @@ grub_jpeg_get_bit (struct grub_jpeg_data *data) if (data->bit_mask == 0) { data->bit_save = grub_jpeg_get_byte (data); + if (grub_errno != GRUB_ERR_NONE) { + grub_error (GRUB_ERR_BAD_FILE_TYPE, + "jpeg: file read error"); + return 0; + } if (data->bit_save == JPEG_ESC_CHAR) { if (grub_jpeg_get_byte (data) != 0) @@ -143,6 +164,11 @@ grub_jpeg_get_bit (struct grub_jpeg_data *data) "jpeg: invalid 0xFF in data stream"); return 0; } + if (grub_errno != GRUB_ERR_NONE) + { + grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: file read error"); + return 0; + } } data->bit_mask = 0x80; } @@ -161,7 +187,7 @@ grub_jpeg_get_number (struct grub_jpeg_data *data, int num) return 0; msb = value = grub_jpeg_get_bit (data); - for (i = 1; i < num; i++) + for (i = 1; i < num && grub_errno == GRUB_ERR_NONE; i++) value = (value << 1) + (grub_jpeg_get_bit (data) != 0); if (!msb) value += 1 - (1 << num); @@ -202,6 +228,8 @@ grub_jpeg_decode_huff_table (struct grub_jpeg_data *data) while (data->file->offset + sizeof (count) + 1 <= next_marker) { id = grub_jpeg_get_byte (data); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; ac = (id >> 4) & 1; id &= 0xF; if (id > 1) @@ -252,6 +280,8 @@ grub_jpeg_decode_quan_table (struct grub_jpeg_data *data) next_marker = data->file->offset; next_marker += grub_jpeg_get_word (data); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; if (next_marker > data->file->size) { @@ -263,6 +293,8 @@ grub_jpeg_decode_quan_table (struct grub_jpeg_data *data) <= next_marker) { id = grub_jpeg_get_byte (data); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; if (id >= 0x10) /* Upper 4-bit is precision. */ return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: only 8-bit precision is supported"); @@ -294,6 +326,9 @@ grub_jpeg_decode_sof (struct grub_jpeg_data *data) next_marker = data->file->offset; next_marker += grub_jpeg_get_word (data); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; + if (grub_jpeg_get_byte (data) != 8) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: only 8-bit precision is supported"); @@ -319,6 +354,8 @@ grub_jpeg_decode_sof (struct grub_jpeg_data *data) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: invalid index"); ss = grub_jpeg_get_byte (data); /* Sampling factor. */ + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; if (!id) { grub_uint8_t vs, hs; @@ -498,7 +535,7 @@ grub_jpeg_idct_transform (jpeg_data_unit_t du) } } -static void +static grub_err_t grub_jpeg_decode_du (struct grub_jpeg_data *data, int id, jpeg_data_unit_t du) { int h1, h2, qt; @@ -513,6 +550,9 @@ grub_jpeg_decode_du (struct grub_jpeg_data *data, int id, jpeg_data_unit_t du) data->dc_value[id] += grub_jpeg_get_number (data, grub_jpeg_get_huff_code (data, h1)); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; + du[0] = data->dc_value[id] * (int) data->quan_table[qt][0]; pos = 1; while (pos < ARRAY_SIZE (data->quan_table[qt])) @@ -527,11 +567,13 @@ grub_jpeg_decode_du (struct grub_jpeg_data *data, int id, jpeg_data_unit_t du) num >>= 4; pos += num; + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; + if (pos >= ARRAY_SIZE (jpeg_zigzag_order)) { - grub_error (GRUB_ERR_BAD_FILE_TYPE, - "jpeg: invalid position in zigzag order!?"); - return; + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "jpeg: invalid position in zigzag order!?"); } du[jpeg_zigzag_order[pos]] = val * (int) data->quan_table[qt][pos]; @@ -539,6 +581,7 @@ grub_jpeg_decode_du (struct grub_jpeg_data *data, int id, jpeg_data_unit_t du) } grub_jpeg_idct_transform (du); + return GRUB_ERR_NONE; } static void @@ -597,7 +640,8 @@ grub_jpeg_decode_sos (struct grub_jpeg_data *data) data_offset += grub_jpeg_get_word (data); cc = grub_jpeg_get_byte (data); - + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; if (cc != 3 && cc != 1) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: component count must be 1 or 3"); @@ -610,7 +654,8 @@ grub_jpeg_decode_sos (struct grub_jpeg_data *data) id = grub_jpeg_get_byte (data) - 1; if ((id < 0) || (id >= 3)) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: invalid index"); - + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; ht = grub_jpeg_get_byte (data); data->comp_index[id][1] = (ht >> 4); data->comp_index[id][2] = (ht & 0xF) + 2; @@ -618,11 +663,14 @@ grub_jpeg_decode_sos (struct grub_jpeg_data *data) if ((data->comp_index[id][1] < 0) || (data->comp_index[id][1] > 3) || (data->comp_index[id][2] < 0) || (data->comp_index[id][2] > 3)) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: invalid hufftable index"); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; } grub_jpeg_get_byte (data); /* Skip 3 unused bytes. */ grub_jpeg_get_word (data); - + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; if (data->file->offset != data_offset) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: extra byte in sos"); @@ -640,6 +688,7 @@ grub_jpeg_decode_data (struct grub_jpeg_data *data) { unsigned c1, vb, hb, nr1, nc1; int rst = data->dri; + grub_err_t err = GRUB_ERR_NONE; vb = 8 << data->log_vs; hb = 8 << data->log_hs; @@ -660,17 +709,22 @@ grub_jpeg_decode_data (struct grub_jpeg_data *data) for (r2 = 0; r2 < (1U << data->log_vs); r2++) for (c2 = 0; c2 < (1U << data->log_hs); c2++) - grub_jpeg_decode_du (data, 0, data->ydu[r2 * 2 + c2]); + { + err = grub_jpeg_decode_du (data, 0, data->ydu[r2 * 2 + c2]); + if (err != GRUB_ERR_NONE) + return err; + } if (data->color_components >= 3) { - grub_jpeg_decode_du (data, 1, data->cbdu); - grub_jpeg_decode_du (data, 2, data->crdu); + err = grub_jpeg_decode_du (data, 1, data->cbdu); + if (err != GRUB_ERR_NONE) + return err; + err = grub_jpeg_decode_du (data, 2, data->crdu); + if (err != GRUB_ERR_NONE) + return err; } - if (grub_errno) - return grub_errno; - nr2 = (data->r1 == nr1 - 1) ? (data->image_height - data->r1 * vb) : vb; nc2 = (c1 == nc1 - 1) ? (data->image_width - c1 * hb) : hb; From 610c5986058312cfc0375fc04f88fcc116bdd043 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Mon, 28 Jun 2021 14:16:58 +1000 Subject: [PATCH 12/30] video/readers/jpeg: Do not reallocate a given huff table Fix a memory leak where an invalid file could cause us to reallocate memory for a huffman table we had already allocated memory for. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/video/readers/jpeg.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/grub-core/video/readers/jpeg.c b/grub-core/video/readers/jpeg.c index 10225abd5..caa211f06 100644 --- a/grub-core/video/readers/jpeg.c +++ b/grub-core/video/readers/jpeg.c @@ -245,6 +245,9 @@ grub_jpeg_decode_huff_table (struct grub_jpeg_data *data) n += count[i]; id += ac * 2; + if (data->huff_value[id] != NULL) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "jpeg: attempt to reallocate huffman table"); data->huff_value[id] = grub_malloc (n); if (grub_errno) return grub_errno; From 9286f0009b922571c247012e699c3ed5f6e918bc Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Mon, 28 Jun 2021 14:25:17 +1000 Subject: [PATCH 13/30] video/readers/jpeg: Refuse to handle multiple start of streams An invalid file could contain multiple start of stream blocks, which would cause us to reallocate and leak our bitmap. Refuse to handle multiple start of streams. Additionally, fix a grub_error() call formatting. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/video/readers/jpeg.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/grub-core/video/readers/jpeg.c b/grub-core/video/readers/jpeg.c index caa211f06..1df1171d7 100644 --- a/grub-core/video/readers/jpeg.c +++ b/grub-core/video/readers/jpeg.c @@ -677,6 +677,9 @@ grub_jpeg_decode_sos (struct grub_jpeg_data *data) if (data->file->offset != data_offset) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: extra byte in sos"); + if (*data->bitmap) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: too many start of scan blocks"); + if (grub_video_bitmap_create (data->bitmap, data->image_width, data->image_height, GRUB_VIDEO_BLIT_FORMAT_RGB_888)) @@ -699,8 +702,8 @@ grub_jpeg_decode_data (struct grub_jpeg_data *data) nc1 = (data->image_width + hb - 1) >> (3 + data->log_hs); if (data->bitmap_ptr == NULL) - return grub_error(GRUB_ERR_BAD_FILE_TYPE, - "jpeg: attempted to decode data before start of stream"); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "jpeg: attempted to decode data before start of stream"); for (; data->r1 < nr1 && (!data->dri || rst); data->r1++, data->bitmap_ptr += (vb * data->image_width - hb * nc1) * 3) From a10c2350a766f9b315735931a49499a7e2c77bf3 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Wed, 7 Jul 2021 15:38:19 +1000 Subject: [PATCH 14/30] video/readers/jpeg: Block int underflow -> wild pointer write Certain 1 px wide images caused a wild pointer write in grub_jpeg_ycrcb_to_rgb(). This was caused because in grub_jpeg_decode_data(), we have the following loop: for (; data->r1 < nr1 && (!data->dri || rst); data->r1++, data->bitmap_ptr += (vb * data->image_width - hb * nc1) * 3) We did not check if vb * width >= hb * nc1. On a 64-bit platform, if that turns out to be negative, it will underflow, be interpreted as unsigned 64-bit, then be added to the 64-bit pointer, so we see data->bitmap_ptr jump, e.g.: 0x6180_0000_0480 to 0x6181_0000_0498 ^ ~--- carry has occurred and this pointer is now far away from any object. On a 32-bit platform, it will decrement the pointer, creating a pointer that won't crash but will overwrite random data. Catch the underflow and error out. Fixes: CVE-2021-3697 Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/video/readers/jpeg.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/grub-core/video/readers/jpeg.c b/grub-core/video/readers/jpeg.c index 1df1171d7..97a533b24 100644 --- a/grub-core/video/readers/jpeg.c +++ b/grub-core/video/readers/jpeg.c @@ -23,6 +23,7 @@ #include #include #include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -693,6 +694,7 @@ static grub_err_t grub_jpeg_decode_data (struct grub_jpeg_data *data) { unsigned c1, vb, hb, nr1, nc1; + unsigned stride_a, stride_b, stride; int rst = data->dri; grub_err_t err = GRUB_ERR_NONE; @@ -705,8 +707,14 @@ grub_jpeg_decode_data (struct grub_jpeg_data *data) return grub_error (GRUB_ERR_BAD_FILE_TYPE, "jpeg: attempted to decode data before start of stream"); + if (grub_mul(vb, data->image_width, &stride_a) || + grub_mul(hb, nc1, &stride_b) || + grub_sub(stride_a, stride_b, &stride)) + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "jpeg: cannot decode image with these dimensions"); + for (; data->r1 < nr1 && (!data->dri || rst); - data->r1++, data->bitmap_ptr += (vb * data->image_width - hb * nc1) * 3) + data->r1++, data->bitmap_ptr += stride * 3) for (c1 = 0; c1 < nc1 && (!data->dri || rst); c1++, rst--, data->bitmap_ptr += hb * 3) { From 557370849b914110a9efbd7256dc3942a8af8b99 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 13 Jul 2021 13:24:38 +1000 Subject: [PATCH 15/30] normal/charset: Fix array out-of-bounds formatting unicode for display In some cases attempting to display arbitrary binary strings leads to ASAN splats reading the widthspec array out of bounds. Check the index. If it would be out of bounds, return a width of 1. I don't know if that's strictly correct, but we're not really expecting great display of arbitrary binary data, and it's certainly not worse than an OOB read. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/normal/charset.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grub-core/normal/charset.c b/grub-core/normal/charset.c index 4dfcc3107..7a5a7c153 100644 --- a/grub-core/normal/charset.c +++ b/grub-core/normal/charset.c @@ -395,6 +395,8 @@ grub_unicode_estimate_width (const struct grub_unicode_glyph *c) { if (grub_unicode_get_comb_type (c->base)) return 0; + if (((unsigned long) (c->base >> 3)) >= ARRAY_SIZE (widthspec)) + return 1; if (widthspec[c->base >> 3] & (1 << (c->base & 7))) return 2; else From 4ea64c827f8bc57180772fd5671ddd010cb7b2ed Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 8 Mar 2022 23:47:46 +1100 Subject: [PATCH 16/30] net/netbuff: Block overly large netbuff allocs A netbuff shouldn't be too huge. It's bounded by MTU and TCP segment reassembly. This helps avoid some bugs (and provides a spot to instrument to catch them at their source). Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/netbuff.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/grub-core/net/netbuff.c b/grub-core/net/netbuff.c index dbeeefe47..d5e9e9a0d 100644 --- a/grub-core/net/netbuff.c +++ b/grub-core/net/netbuff.c @@ -79,10 +79,23 @@ grub_netbuff_alloc (grub_size_t len) COMPILE_TIME_ASSERT (NETBUFF_ALIGN % sizeof (grub_properly_aligned_t) == 0); + /* + * The largest size of a TCP packet is 64 KiB, and everything else + * should be a lot smaller - most MTUs are 1500 or less. Cap data + * size at 64 KiB + a buffer. + */ + if (len > 0xffffUL + 0x1000UL) + { + grub_error (GRUB_ERR_BUG, + "attempted to allocate a packet that is too big"); + return NULL; + } + if (len < NETBUFFMINLEN) len = NETBUFFMINLEN; len = ALIGN_UP (len, NETBUFF_ALIGN); + #ifdef GRUB_MACHINE_EMU data = grub_malloc (len + sizeof (*nb)); #else From 2a4f87df650fd2ef639b48b43fc834b97b6b2bfa Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Mon, 20 Dec 2021 19:41:21 +1100 Subject: [PATCH 17/30] net/ip: Do IP fragment maths safely This avoids an underflow and subsequent unpleasantness. Fixes: CVE-2022-28733 Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/ip.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/grub-core/net/ip.c b/grub-core/net/ip.c index 01410798b..937be8767 100644 --- a/grub-core/net/ip.c +++ b/grub-core/net/ip.c @@ -25,6 +25,7 @@ #include #include #include +#include #include struct iphdr { @@ -551,7 +552,14 @@ grub_net_recv_ip4_packets (struct grub_net_buff *nb, { rsm->total_len = (8 * (grub_be_to_cpu16 (iph->frags) & OFFSET_MASK) + (nb->tail - nb->data)); - rsm->total_len -= ((iph->verhdrlen & 0xf) * sizeof (grub_uint32_t)); + + if (grub_sub (rsm->total_len, (iph->verhdrlen & 0xf) * sizeof (grub_uint32_t), + &rsm->total_len)) + { + grub_dprintf ("net", "IP reassembly size underflow\n"); + return GRUB_ERR_NONE; + } + rsm->asm_netbuff = grub_netbuff_alloc (rsm->total_len); if (!rsm->asm_netbuff) { From 21158c5dfb5e0c5015277346128903397d498da4 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Thu, 16 Sep 2021 01:29:54 +1000 Subject: [PATCH 18/30] net/dns: Fix double-free addresses on corrupt DNS response grub_net_dns_lookup() takes as inputs a pointer to an array of addresses ("addresses") for the given name, and pointer to a number of addresses ("naddresses"). grub_net_dns_lookup() is responsible for allocating "addresses", and the caller is responsible for freeing it if "naddresses" > 0. The DNS recv_hook will sometimes set and free the addresses array, for example if the packet is too short: if (ptr + 10 >= nb->tail) { if (!*data->naddresses) grub_free (*data->addresses); grub_netbuff_free (nb); return GRUB_ERR_NONE; } Later on the nslookup command code unconditionally frees the "addresses" array. Normally this is fine: the array is either populated with valid data or is NULL. But in these sorts of error cases it is neither NULL nor valid and we get a double-free. Only free "addresses" if "naddresses" > 0. It looks like the other use of grub_net_dns_lookup() is not affected. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/dns.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/grub-core/net/dns.c b/grub-core/net/dns.c index 906ec7d67..135faac03 100644 --- a/grub-core/net/dns.c +++ b/grub-core/net/dns.c @@ -667,9 +667,11 @@ grub_cmd_nslookup (struct grub_command *cmd __attribute__ ((unused)), grub_net_addr_to_str (&addresses[i], buf); grub_printf ("%s\n", buf); } - grub_free (addresses); if (naddresses) - return GRUB_ERR_NONE; + { + grub_free (addresses); + return GRUB_ERR_NONE; + } return grub_error (GRUB_ERR_NET_NO_DOMAIN, N_("no DNS record found")); } From 968febf3a4de5df0f91cc13bc6b6053fc22575e1 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Mon, 20 Dec 2021 21:55:43 +1100 Subject: [PATCH 19/30] net/dns: Don't read past the end of the string we're checking against I don't really understand what's going on here but fuzzing found a bug where we read past the end of check_with. That's a C string, so use grub_strlen() to make sure we don't overread it. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/dns.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/grub-core/net/dns.c b/grub-core/net/dns.c index 135faac03..17961a9f1 100644 --- a/grub-core/net/dns.c +++ b/grub-core/net/dns.c @@ -146,11 +146,18 @@ check_name_real (const grub_uint8_t *name_at, const grub_uint8_t *head, int *length, char *set) { const char *readable_ptr = check_with; + int readable_len; const grub_uint8_t *ptr; char *optr = set; int bytes_processed = 0; if (length) *length = 0; + + if (readable_ptr != NULL) + readable_len = grub_strlen (readable_ptr); + else + readable_len = 0; + for (ptr = name_at; ptr < tail && bytes_processed < tail - head + 2; ) { /* End marker. */ @@ -172,13 +179,16 @@ check_name_real (const grub_uint8_t *name_at, const grub_uint8_t *head, ptr = head + (((ptr[0] & 0x3f) << 8) | ptr[1]); continue; } - if (readable_ptr && grub_memcmp (ptr + 1, readable_ptr, *ptr) != 0) + if (readable_ptr != NULL && (*ptr > readable_len || grub_memcmp (ptr + 1, readable_ptr, *ptr) != 0)) return 0; if (grub_memchr (ptr + 1, 0, *ptr) || grub_memchr (ptr + 1, '.', *ptr)) return 0; if (readable_ptr) - readable_ptr += *ptr; + { + readable_ptr += *ptr; + readable_len -= *ptr; + } if (readable_ptr && *readable_ptr != '.' && *readable_ptr != 0) return 0; bytes_processed += *ptr + 1; @@ -192,7 +202,10 @@ check_name_real (const grub_uint8_t *name_at, const grub_uint8_t *head, if (optr) *optr++ = '.'; if (readable_ptr && *readable_ptr) - readable_ptr++; + { + readable_ptr++; + readable_len--; + } ptr += *ptr + 1; } return 0; From e7573be61b3cf005cdf0a068652153437daca4b3 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Mon, 20 Sep 2021 01:12:24 +1000 Subject: [PATCH 20/30] net/tftp: Prevent a UAF and double-free from a failed seek A malicious tftp server can cause UAFs and a double free. An attempt to read from a network file is handled by grub_net_fs_read(). If the read is at an offset other than the current offset, grub_net_seek_real() is invoked. In grub_net_seek_real(), if a backwards seek cannot be satisfied from the currently received packets, and the underlying transport does not provide a seek method, then grub_net_seek_real() will close and reopen the network protocol layer. For tftp, the ->close() call goes to tftp_close() and frees the tftp_data_t file->data. The file->data pointer is not nulled out after the free. If the ->open() call fails, the file->data will not be reallocated and will continue point to a freed memory block. This could happen from a server refusing to send the requisite ack to the new tftp request, for example. The seek and the read will then fail, but the grub_file continues to exist: the failed seek does not necessarily cause the entire file to be thrown away (e.g. where the file is checked to see if it is gzipped/lzio/xz/etc., a read failure is interpreted as a decompressor passing on the file, not as an invalidation of the entire grub_file_t structure). This means subsequent attempts to read or seek the file will use the old file->data after free. Eventually, the file will be close()d again and file->data will be freed again. Mark a net_fs file that doesn't reopen as broken. Do not permit read() or close() on a broken file (seek is not exposed directly to the file API - it is only called as part of read, so this blocks seeks as well). As an additional defence, null out the ->data pointer if tftp_open() fails. That would have lead to a simple null pointer dereference rather than a mess of UAFs. This may affect other protocols, I haven't checked. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/net.c | 11 +++++++++-- grub-core/net/tftp.c | 1 + include/grub/net.h | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/grub-core/net/net.c b/grub-core/net/net.c index 15a2f29a9..af7440776 100644 --- a/grub-core/net/net.c +++ b/grub-core/net/net.c @@ -1548,7 +1548,8 @@ grub_net_fs_close (grub_file_t file) grub_netbuff_free (file->device->net->packs.first->nb); grub_net_remove_packet (file->device->net->packs.first); } - file->device->net->protocol->close (file); + if (!file->device->net->broken) + file->device->net->protocol->close (file); grub_free (file->device->net->name); return GRUB_ERR_NONE; } @@ -1770,7 +1771,10 @@ grub_net_seek_real (struct grub_file *file, grub_off_t offset) file->device->net->stall = 0; err = file->device->net->protocol->open (file, file->device->net->name); if (err) - return err; + { + file->device->net->broken = 1; + return err; + } grub_net_fs_read_real (file, NULL, offset); return grub_errno; } @@ -1779,6 +1783,9 @@ grub_net_seek_real (struct grub_file *file, grub_off_t offset) static grub_ssize_t grub_net_fs_read (grub_file_t file, char *buf, grub_size_t len) { + if (file->device->net->broken) + return -1; + if (file->offset != file->device->net->offset) { grub_err_t err; diff --git a/grub-core/net/tftp.c b/grub-core/net/tftp.c index f3e787938..d1afa2535 100644 --- a/grub-core/net/tftp.c +++ b/grub-core/net/tftp.c @@ -404,6 +404,7 @@ tftp_open (struct grub_file *file, const char *filename) { grub_net_udp_close (data->sock); grub_free (data); + file->data = NULL; return grub_errno; } diff --git a/include/grub/net.h b/include/grub/net.h index cbcae79b1..8d71ca6cc 100644 --- a/include/grub/net.h +++ b/include/grub/net.h @@ -277,6 +277,7 @@ typedef struct grub_net grub_fs_t fs; int eof; int stall; + int broken; } *grub_net_t; extern grub_net_t (*EXPORT_VAR (grub_net_open)) (const char *name); From fb66f40ba67b88408a43cb38492053985bfe4968 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 18 Jan 2022 14:29:20 +1100 Subject: [PATCH 21/30] net/tftp: Avoid a trivial UAF Under tftp errors, we print a tftp error message from the tftp header. However, the tftph pointer is a pointer inside nb, the netbuff. Previously, we were freeing the nb and then dereferencing it. Don't do that, use it and then free it later. This isn't really _bad_ per se, especially as we're single-threaded, but it trips up fuzzers. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/tftp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grub-core/net/tftp.c b/grub-core/net/tftp.c index d1afa2535..4222d93b6 100644 --- a/grub-core/net/tftp.c +++ b/grub-core/net/tftp.c @@ -251,9 +251,9 @@ tftp_receive (grub_net_udp_socket_t sock __attribute__ ((unused)), return GRUB_ERR_NONE; case TFTP_ERROR: data->have_oack = 1; - grub_netbuff_free (nb); grub_error (GRUB_ERR_IO, "%s", tftph->u.err.errmsg); grub_error_save (&data->save_err); + grub_netbuff_free (nb); return GRUB_ERR_NONE; default: grub_netbuff_free (nb); From 6df718714dea5043243e367750b5c6abebcf79fe Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 1 Mar 2022 23:14:15 +1100 Subject: [PATCH 22/30] net/http: Do not tear down socket if it's already been torn down It's possible for data->sock to get torn down in tcp error handling. If we unconditionally tear it down again we will end up doing writes to an offset of the NULL pointer when we go to tear it down again. Detect if it has been torn down and don't do it again. Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/http.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/grub-core/net/http.c b/grub-core/net/http.c index 3fe155f1b..ef6eaff0d 100644 --- a/grub-core/net/http.c +++ b/grub-core/net/http.c @@ -422,7 +422,7 @@ http_establish (struct grub_file *file, grub_off_t offset, int initial) return err; } - for (i = 0; !data->headers_recv && i < 100; i++) + for (i = 0; data->sock && !data->headers_recv && i < 100; i++) { grub_net_tcp_retransmit (); grub_net_poll_cards (300, &data->headers_recv); @@ -430,7 +430,8 @@ http_establish (struct grub_file *file, grub_off_t offset, int initial) if (!data->headers_recv) { - grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); + if (data->sock) + grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT); if (data->err) { char *str = data->errmsg; From f407a45bd3483f6bbf58c5e9386a44caa14287e3 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 8 Mar 2022 18:17:03 +1100 Subject: [PATCH 23/30] net/http: Fix OOB write for split http headers GRUB has special code for handling an http header that is split across two packets. The code tracks the end of line by looking for a "\n" byte. The code for split headers has always advanced the pointer just past the end of the line, whereas the code that handles unsplit headers does not advance the pointer. This extra advance causes the length to be one greater, which breaks an assumption in parse_line(), leading to it writing a NUL byte one byte past the end of the buffer where we reconstruct the line from the two packets. It's conceivable that an attacker controlled set of packets could cause this to zero out the first byte of the "next" pointer of the grub_mm_region structure following the current_line buffer. Do not advance the pointer in the split header case. Fixes: CVE-2022-28734 Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/http.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/grub-core/net/http.c b/grub-core/net/http.c index ef6eaff0d..9f45ad4e8 100644 --- a/grub-core/net/http.c +++ b/grub-core/net/http.c @@ -190,9 +190,7 @@ http_receive (grub_net_tcp_socket_t sock __attribute__ ((unused)), int have_line = 1; char *t; ptr = grub_memchr (nb->data, '\n', nb->tail - nb->data); - if (ptr) - ptr++; - else + if (ptr == NULL) { have_line = 0; ptr = (char *) nb->tail; From 870b94755b6a341d21632293677b346ff033e5f0 Mon Sep 17 00:00:00 2001 From: Daniel Axtens Date: Tue, 8 Mar 2022 19:04:40 +1100 Subject: [PATCH 24/30] net/http: Error out on headers with LF without CR In a similar vein to the previous patch, parse_line() would write a NUL byte past the end of the buffer if there was an HTTP header with a LF rather than a CRLF. RFC-2616 says: Many HTTP/1.1 header field values consist of words separated by LWS or special characters. These special characters MUST be in a quoted string to be used within a parameter value (as defined in section 3.6). We don't support quoted sections or continuation lines, etc. If we see an LF that's not part of a CRLF, bail out. Fixes: CVE-2022-28734 Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/net/http.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/grub-core/net/http.c b/grub-core/net/http.c index 9f45ad4e8..6988d38fb 100644 --- a/grub-core/net/http.c +++ b/grub-core/net/http.c @@ -68,7 +68,15 @@ parse_line (grub_file_t file, http_data_t data, char *ptr, grub_size_t len) char *end = ptr + len; while (end > ptr && *(end - 1) == '\r') end--; + + /* LF without CR. */ + if (end == ptr + len) + { + data->errmsg = grub_strdup (_("invalid HTTP header - LF without CR")); + return GRUB_ERR_NONE; + } *end = 0; + /* Trailing CRLF. */ if (data->in_chunk_len == 1) { From 2d014248d540c7e087934a94b6e7a2aa7fc2c704 Mon Sep 17 00:00:00 2001 From: Sudhakar Kuppusamy Date: Wed, 6 Apr 2022 18:03:37 +0530 Subject: [PATCH 25/30] fs/f2fs: Do not read past the end of nat journal entries A corrupt f2fs file system could specify a nat journal entry count that is beyond the maximum NAT_JOURNAL_ENTRIES. Check if the specified nat journal entry count before accessing the array, and throw an error if it is too large. Signed-off-by: Sudhakar Kuppusamy Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/fs/f2fs.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/grub-core/fs/f2fs.c b/grub-core/fs/f2fs.c index 8a9992ca9..63702214b 100644 --- a/grub-core/fs/f2fs.c +++ b/grub-core/fs/f2fs.c @@ -632,23 +632,27 @@ get_nat_journal (struct grub_f2fs_data *data) return err; } -static grub_uint32_t -get_blkaddr_from_nat_journal (struct grub_f2fs_data *data, grub_uint32_t nid) +static grub_err_t +get_blkaddr_from_nat_journal (struct grub_f2fs_data *data, grub_uint32_t nid, + grub_uint32_t *blkaddr) { grub_uint16_t n = grub_le_to_cpu16 (data->nat_j.n_nats); - grub_uint32_t blkaddr = 0; grub_uint16_t i; + if (n >= NAT_JOURNAL_ENTRIES) + return grub_error (GRUB_ERR_BAD_FS, + "invalid number of nat journal entries"); + for (i = 0; i < n; i++) { if (grub_le_to_cpu32 (data->nat_j.entries[i].nid) == nid) { - blkaddr = grub_le_to_cpu32 (data->nat_j.entries[i].ne.block_addr); + *blkaddr = grub_le_to_cpu32 (data->nat_j.entries[i].ne.block_addr); break; } } - return blkaddr; + return GRUB_ERR_NONE; } static grub_uint32_t @@ -656,10 +660,13 @@ get_node_blkaddr (struct grub_f2fs_data *data, grub_uint32_t nid) { struct grub_f2fs_nat_block *nat_block; grub_uint32_t seg_off, block_off, entry_off, block_addr; - grub_uint32_t blkaddr; + grub_uint32_t blkaddr = 0; grub_err_t err; - blkaddr = get_blkaddr_from_nat_journal (data, nid); + err = get_blkaddr_from_nat_journal (data, nid, &blkaddr); + if (err != GRUB_ERR_NONE) + return 0; + if (blkaddr) return blkaddr; From 9561d7ef621e5e68f12bcd916252ef1c11e60366 Mon Sep 17 00:00:00 2001 From: Sudhakar Kuppusamy Date: Wed, 6 Apr 2022 18:49:09 +0530 Subject: [PATCH 26/30] fs/f2fs: Do not read past the end of nat bitmap A corrupt f2fs filesystem could have a block offset or a bitmap offset that would cause us to read beyond the bounds of the nat bitmap. Introduce the nat_bitmap_size member in grub_f2fs_data which holds the size of nat bitmap. Set the size when loading the nat bitmap in nat_bitmap_ptr(), and catch when an invalid offset would create a pointer past the end of the allocated space. Check against the bitmap size in grub_f2fs_test_bit() test bit to avoid reading past the end of the nat bitmap. Signed-off-by: Sudhakar Kuppusamy Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/fs/f2fs.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/grub-core/fs/f2fs.c b/grub-core/fs/f2fs.c index 63702214b..8898b235e 100644 --- a/grub-core/fs/f2fs.c +++ b/grub-core/fs/f2fs.c @@ -122,6 +122,7 @@ GRUB_MOD_LICENSE ("GPLv3+"); #define F2FS_INLINE_DOTS 0x10 /* File having implicit dot dentries. */ #define MAX_VOLUME_NAME 512 +#define MAX_NAT_BITMAP_SIZE 3900 enum FILE_TYPE { @@ -183,7 +184,7 @@ struct grub_f2fs_checkpoint grub_uint32_t checksum_offset; grub_uint64_t elapsed_time; grub_uint8_t alloc_type[MAX_ACTIVE_LOGS]; - grub_uint8_t sit_nat_version_bitmap[3900]; + grub_uint8_t sit_nat_version_bitmap[MAX_NAT_BITMAP_SIZE]; grub_uint32_t checksum; } GRUB_PACKED; @@ -302,6 +303,7 @@ struct grub_f2fs_data struct grub_f2fs_nat_journal nat_j; char *nat_bitmap; + grub_uint32_t nat_bitmap_size; grub_disk_t disk; struct grub_f2fs_node *inode; @@ -377,15 +379,20 @@ sum_blk_addr (struct grub_f2fs_data *data, int base, int type) } static void * -nat_bitmap_ptr (struct grub_f2fs_data *data) +nat_bitmap_ptr (struct grub_f2fs_data *data, grub_uint32_t *nat_bitmap_size) { struct grub_f2fs_checkpoint *ckpt = &data->ckpt; grub_uint32_t offset; + *nat_bitmap_size = MAX_NAT_BITMAP_SIZE; if (grub_le_to_cpu32 (data->sblock.cp_payload) > 0) return ckpt->sit_nat_version_bitmap; offset = grub_le_to_cpu32 (ckpt->sit_ver_bitmap_bytesize); + if (offset >= MAX_NAT_BITMAP_SIZE) + return NULL; + + *nat_bitmap_size = *nat_bitmap_size - offset; return ckpt->sit_nat_version_bitmap + offset; } @@ -438,11 +445,15 @@ grub_f2fs_crc_valid (grub_uint32_t blk_crc, void *buf, const grub_uint32_t len) } static int -grub_f2fs_test_bit (grub_uint32_t nr, const char *p) +grub_f2fs_test_bit (grub_uint32_t nr, const char *p, grub_uint32_t len) { int mask; + grub_uint32_t shifted_nr = (nr >> 3); - p += (nr >> 3); + if (shifted_nr >= len) + return -1; + + p += shifted_nr; mask = 1 << (7 - (nr & 0x07)); return mask & *p; @@ -662,6 +673,7 @@ get_node_blkaddr (struct grub_f2fs_data *data, grub_uint32_t nid) grub_uint32_t seg_off, block_off, entry_off, block_addr; grub_uint32_t blkaddr = 0; grub_err_t err; + int result_bit; err = get_blkaddr_from_nat_journal (data, nid, &blkaddr); if (err != GRUB_ERR_NONE) @@ -682,8 +694,15 @@ get_node_blkaddr (struct grub_f2fs_data *data, grub_uint32_t nid) ((seg_off * data->blocks_per_seg) << 1) + (block_off & (data->blocks_per_seg - 1)); - if (grub_f2fs_test_bit (block_off, data->nat_bitmap)) + result_bit = grub_f2fs_test_bit (block_off, data->nat_bitmap, + data->nat_bitmap_size); + if (result_bit > 0) block_addr += data->blocks_per_seg; + else if (result_bit == -1) + { + grub_free (nat_block); + return 0; + } err = grub_f2fs_block_read (data, block_addr, nat_block); if (err) @@ -833,7 +852,9 @@ grub_f2fs_mount (grub_disk_t disk) if (err) goto fail; - data->nat_bitmap = nat_bitmap_ptr (data); + data->nat_bitmap = nat_bitmap_ptr (data, &data->nat_bitmap_size); + if (data->nat_bitmap == NULL) + goto fail; err = get_nat_journal (data); if (err) From 998bd74c11c0e00f69fe2f37a8200381faf51061 Mon Sep 17 00:00:00 2001 From: Sudhakar Kuppusamy Date: Wed, 6 Apr 2022 18:17:43 +0530 Subject: [PATCH 27/30] fs/f2fs: Do not copy file names that are too long A corrupt f2fs file system might specify a name length which is greater than the maximum name length supported by the GRUB f2fs driver. We will allocate enough memory to store the overly long name, but there are only F2FS_NAME_LEN bytes in the source, so we would read past the end of the source. While checking directory entries, do not copy a file name with an invalid length. Signed-off-by: Sudhakar Kuppusamy Signed-off-by: Daniel Axtens Reviewed-by: Daniel Kiper --- grub-core/fs/f2fs.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/grub-core/fs/f2fs.c b/grub-core/fs/f2fs.c index 8898b235e..df6beb544 100644 --- a/grub-core/fs/f2fs.c +++ b/grub-core/fs/f2fs.c @@ -1003,6 +1003,10 @@ grub_f2fs_check_dentries (struct grub_f2fs_dir_iter_ctx *ctx) ftype = ctx->dentry[i].file_type; name_len = grub_le_to_cpu16 (ctx->dentry[i].name_len); + + if (name_len >= F2FS_NAME_LEN) + return 0; + filename = grub_malloc (name_len + 1); if (!filename) return 0; From 23c785c3e965731ac440f9c150fdfeec6dd433e7 Mon Sep 17 00:00:00 2001 From: Darren Kenny Date: Tue, 29 Mar 2022 10:49:56 +0000 Subject: [PATCH 28/30] fs/btrfs: Fix several fuzz issues with invalid dir item sizing According to the btrfs code in Linux, the structure of a directory item leaf should be of the form: |struct btrfs_dir_item|name|data| in GRUB the name len and data len are in the grub_btrfs_dir_item structure's n and m fields respectively. The combined size of the structure, name and data should be less than the allocated memory, a difference to the Linux kernel's struct btrfs_dir_item is that the grub_btrfs_dir_item has an extra field for where the name is stored, so we adjust for that too. Signed-off-by: Darren Kenny Reviewed-by: Daniel Kiper --- grub-core/fs/btrfs.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c index 63203034d..eb9857d74 100644 --- a/grub-core/fs/btrfs.c +++ b/grub-core/fs/btrfs.c @@ -1961,6 +1961,7 @@ grub_btrfs_dir (grub_device_t device, const char *path, int r = 0; grub_uint64_t tree; grub_uint8_t type; + grub_size_t est_size = 0; if (!data) return grub_errno; @@ -2019,6 +2020,18 @@ grub_btrfs_dir (grub_device_t device, const char *path, break; } + if (direl == NULL || + grub_add (grub_le_to_cpu16 (direl->n), + grub_le_to_cpu16 (direl->m), &est_size) || + grub_add (est_size, sizeof (*direl), &est_size) || + grub_sub (est_size, sizeof (direl->name), &est_size) || + est_size > allocated) + { + grub_errno = GRUB_ERR_OUT_OF_RANGE; + r = -grub_errno; + goto out; + } + for (cdirel = direl; (grub_uint8_t *) cdirel - (grub_uint8_t *) direl < (grub_ssize_t) elemsize; @@ -2029,6 +2042,19 @@ grub_btrfs_dir (grub_device_t device, const char *path, char c; struct grub_btrfs_inode inode; struct grub_dirhook_info info; + + if (cdirel == NULL || + grub_add (grub_le_to_cpu16 (cdirel->n), + grub_le_to_cpu16 (cdirel->m), &est_size) || + grub_add (est_size, sizeof (*cdirel), &est_size) || + grub_sub (est_size, sizeof (cdirel->name), &est_size) || + est_size > allocated) + { + grub_errno = GRUB_ERR_OUT_OF_RANGE; + r = -grub_errno; + goto out; + } + err = grub_btrfs_read_inode (data, &inode, cdirel->key.object_id, tree); grub_memset (&info, 0, sizeof (info)); From 22976cf1b9864455173e1bfc617bc63f13fbecf7 Mon Sep 17 00:00:00 2001 From: Darren Kenny Date: Tue, 29 Mar 2022 15:52:46 +0000 Subject: [PATCH 29/30] fs/btrfs: Fix more ASAN and SEGV issues found with fuzzing The fuzzer is generating btrfs file systems that have chunks with invalid combinations of stripes and substripes for the given RAID configurations. After examining the Linux kernel fs/btrfs/tree-checker.c code, it appears that sub-stripes should only be applied to RAID10, and in that case there should only ever be 2 of them. Similarly, RAID single should only have 1 stripe, and RAID1/1C3/1C4 should have 2. 3 or 4 stripes respectively, which is what redundancy corresponds. Some of the chunks ended up with a size of 0, which grub_malloc() still returned memory for and in turn generated ASAN errors later when accessed. While it would be possible to specifically limit the number of stripes, a more correct test was on the combination of the chunk item, and the number of stripes by the size of the chunk stripe structure in comparison to the size of the chunk itself. Signed-off-by: Darren Kenny Reviewed-by: Daniel Kiper --- grub-core/fs/btrfs.c | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c index eb9857d74..b3d71cd9e 100644 --- a/grub-core/fs/btrfs.c +++ b/grub-core/fs/btrfs.c @@ -912,6 +912,12 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, return grub_error (GRUB_ERR_BAD_FS, "couldn't find the chunk descriptor"); + if (!chsize) + { + grub_dprintf ("btrfs", "zero-size chunk\n"); + return grub_error (GRUB_ERR_BAD_FS, + "got an invalid zero-size chunk"); + } chunk = grub_malloc (chsize); if (!chunk) return grub_errno; @@ -970,6 +976,16 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, stripe_length = grub_divmod64 (grub_le_to_cpu64 (chunk->size), nstripes, NULL); + + /* For single, there should be exactly 1 stripe. */ + if (grub_le_to_cpu16 (chunk->nstripes) != 1) + { + grub_dprintf ("btrfs", "invalid RAID_SINGLE: nstripes != 1 (%u)\n", + grub_le_to_cpu16 (chunk->nstripes)); + return grub_error (GRUB_ERR_BAD_FS, + "invalid RAID_SINGLE: nstripes != 1 (%u)", + grub_le_to_cpu16 (chunk->nstripes)); + } if (stripe_length == 0) stripe_length = 512; stripen = grub_divmod64 (off, stripe_length, &stripe_offset); @@ -989,6 +1005,19 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, stripen = 0; stripe_offset = off; csize = grub_le_to_cpu64 (chunk->size) - off; + + /* + * Redundancy, and substripes only apply to RAID10, and there + * should be exactly 2 sub-stripes. + */ + if (grub_le_to_cpu16 (chunk->nstripes) != redundancy) + { + grub_dprintf ("btrfs", "invalid RAID1: nstripes != %u (%u)\n", + redundancy, grub_le_to_cpu16 (chunk->nstripes)); + return grub_error (GRUB_ERR_BAD_FS, + "invalid RAID1: nstripes != %u (%u)", + redundancy, grub_le_to_cpu16 (chunk->nstripes)); + } break; } case GRUB_BTRFS_CHUNK_TYPE_RAID0: @@ -1025,6 +1054,20 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, stripe_offset = low + chunk_stripe_length * high; csize = chunk_stripe_length - low; + + /* + * Substripes only apply to RAID10, and there + * should be exactly 2 sub-stripes. + */ + if (grub_le_to_cpu16 (chunk->nsubstripes) != 2) + { + grub_dprintf ("btrfs", "invalid RAID10: nsubstripes != 2 (%u)", + grub_le_to_cpu16 (chunk->nsubstripes)); + return grub_error (GRUB_ERR_BAD_FS, + "invalid RAID10: nsubstripes != 2 (%u)", + grub_le_to_cpu16 (chunk->nsubstripes)); + } + break; } case GRUB_BTRFS_CHUNK_TYPE_RAID5: @@ -1124,6 +1167,8 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, for (j = 0; j < 2; j++) { + grub_size_t est_chunk_alloc = 0; + grub_dprintf ("btrfs", "chunk 0x%" PRIxGRUB_UINT64_T "+0x%" PRIxGRUB_UINT64_T " (%d stripes (%d substripes) of %" @@ -1136,6 +1181,16 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, grub_dprintf ("btrfs", "reading laddr 0x%" PRIxGRUB_UINT64_T "\n", addr); + if (grub_mul (sizeof (struct grub_btrfs_chunk_stripe), + grub_le_to_cpu16 (chunk->nstripes), &est_chunk_alloc) || + grub_add (est_chunk_alloc, + sizeof (struct grub_btrfs_chunk_item), &est_chunk_alloc) || + est_chunk_alloc > chunk->size) + { + err = GRUB_ERR_BAD_FS; + break; + } + if (is_raid56) { err = btrfs_read_from_chunk (data, chunk, stripen, From 589500ad3777d1335c8e5cb139f7c0c6089112a8 Mon Sep 17 00:00:00 2001 From: Darren Kenny Date: Thu, 7 Apr 2022 15:18:12 +0000 Subject: [PATCH 30/30] fs/btrfs: Fix more fuzz issues related to chunks The corpus was generating issues in grub_btrfs_read_logical() when attempting to iterate over stripe entries in the superblock's bootmapping. In most cases the reason for the failure was that the number of stripes in chunk->nstripes exceeded the possible space statically allocated in superblock bootmapping space. Each stripe entry in the bootmapping block consists of a grub_btrfs_key followed by a grub_btrfs_chunk_stripe. Another issue that came up was that while calculating the chunk size, in an earlier piece of code in that function, depending on the data provided in the btrfs file system, it would end up calculating a size that was too small to contain even 1 grub_btrfs_chunk_item, which is obviously invalid too. Signed-off-by: Darren Kenny Reviewed-by: Daniel Kiper --- grub-core/fs/btrfs.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c index b3d71cd9e..54a46b8f8 100644 --- a/grub-core/fs/btrfs.c +++ b/grub-core/fs/btrfs.c @@ -918,6 +918,17 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, return grub_error (GRUB_ERR_BAD_FS, "got an invalid zero-size chunk"); } + + /* + * The space being allocated for a chunk should at least be able to + * contain one chunk item. + */ + if (chsize < sizeof (struct grub_btrfs_chunk_item)) + { + grub_dprintf ("btrfs", "chunk-size too small\n"); + return grub_error (GRUB_ERR_BAD_FS, + "got an invalid chunk size"); + } chunk = grub_malloc (chsize); if (!chunk) return grub_errno; @@ -1165,6 +1176,13 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, if (csize > (grub_uint64_t) size) csize = size; + /* + * The space for a chunk stripe is limited to the space provide in the super-block's + * bootstrap mapping with an initial btrfs key at the start of each chunk. + */ + grub_size_t avail_stripes = sizeof (data->sblock.bootstrap_mapping) / + (sizeof (struct grub_btrfs_key) + sizeof (struct grub_btrfs_chunk_stripe)); + for (j = 0; j < 2; j++) { grub_size_t est_chunk_alloc = 0; @@ -1191,6 +1209,12 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr, break; } + if (grub_le_to_cpu16 (chunk->nstripes) > avail_stripes) + { + err = GRUB_ERR_BAD_FS; + break; + } + if (is_raid56) { err = btrfs_read_from_chunk (data, chunk, stripen,