From 06ff1079788fedac5e3f1f12ed7bbe69228a7ae0 Mon Sep 17 00:00:00 2001 From: Michael Chang Date: Tue, 18 Dec 2012 16:54:03 +0800 Subject: [PATCH] Add secureboot support on efi chainloader References: fate#314485 Patch-Mainline: no Expand the chainloader to be able to verify the image by means of shim lock protocol. The PE/COFF image is loaded and relocated by the chainloader instead of calling LoadImage and StartImage UEFI boot Service as they require positive verification result from keys enrolled in KEK or DB. The shim will use MOK in addition to firmware enrolled keys to verify the image. The chainloader module could be used to load other UEFI bootloaders, such as xen.efi, and could be signed by any of MOK, KEK or DB. v1: Use grub_efi_get_secureboot to get secure boot status Signed-off-by: Michael Chang --- grub-core/loader/efi/chainloader.c | 538 +++++++++++++++++++++++++++++++++-- 1 files changed, 507 insertions(+), 31 deletions(-) Index: grub-2.04/grub-core/loader/efi/chainloader.c =================================================================== --- grub-2.04.orig/grub-core/loader/efi/chainloader.c +++ grub-2.04/grub-core/loader/efi/chainloader.c @@ -40,15 +40,32 @@ #include #endif +#ifdef __x86_64__ +#define SUPPORT_SECURE_BOOT +#endif + +#ifdef SUPPORT_SECURE_BOOT +#include +#include +#endif + 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_ssize_t fsize; static grub_efi_device_path_t *file_path; static grub_efi_handle_t image_handle; static grub_efi_char16_t *cmdline; +static grub_ssize_t cmdline_len; +static grub_efi_handle_t dev_handle; + +#ifdef SUPPORT_SECURE_BOOT +static grub_efi_boolean_t debug_secureboot = 0; +static grub_efi_status_t (*entry_point) (grub_efi_handle_t image_handle, grub_efi_system_table_t *system_table); +#endif static grub_err_t grub_chainloader_unload (void) @@ -63,6 +80,7 @@ grub_chainloader_unload (void) grub_free (cmdline); cmdline = 0; file_path = 0; + dev_handle = 0; grub_dl_unref (my_mod); return GRUB_ERR_NONE; @@ -197,12 +215,409 @@ make_file_path (grub_efi_device_path_t * return file_path; } +#ifdef SUPPORT_SECURE_BOOT +#define SHIM_LOCK_GUID \ + { 0x605dab50, 0xe046, 0x4300, {0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23} } + +struct grub_pe32_header_no_msdos_stub +{ + char signature[GRUB_PE32_SIGNATURE_SIZE]; + struct grub_pe32_coff_header coff_header; + struct grub_pe64_optional_header optional_header; +}; + +struct pe_coff_loader_image_context +{ + grub_efi_uint64_t image_address; + grub_efi_uint64_t image_size; + grub_efi_uint64_t entry_point; + grub_efi_uintn_t size_of_headers; + grub_efi_uint16_t image_type; + grub_efi_uint16_t number_of_sections; + struct grub_pe32_section_table *first_section; + struct grub_pe32_data_directory *reloc_dir; + struct grub_pe32_data_directory *sec_dir; + grub_efi_uint64_t number_of_rva_and_sizes; + struct grub_pe32_header_no_msdos_stub *pe_hdr; +}; + +typedef struct pe_coff_loader_image_context pe_coff_loader_image_context_t; + +struct grub_efi_shim_lock +{ + grub_efi_status_t (*verify)(void *buffer, + grub_efi_uint32_t size); + grub_efi_status_t (*hash)(void *data, + grub_efi_int32_t datasize, + pe_coff_loader_image_context_t *context, + grub_efi_uint8_t *sha256hash, + grub_efi_uint8_t *sha1hash); + grub_efi_status_t (*context)(void *data, + grub_efi_uint32_t size, + pe_coff_loader_image_context_t *context); +}; + +typedef struct grub_efi_shim_lock grub_efi_shim_lock_t; + +static grub_efi_boolean_t +grub_secure_validate (void *data, grub_efi_uint32_t size) +{ + grub_efi_guid_t guid = SHIM_LOCK_GUID; + grub_efi_shim_lock_t *shim_lock; + + shim_lock = grub_efi_locate_protocol (&guid, NULL); + + if (!shim_lock) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no shim lock protocol"); + return 0; + } + + if (shim_lock->verify (data, size) == GRUB_EFI_SUCCESS) + { + grub_dprintf ("chain", "verify success\n"); + return 1; + } + + grub_error (GRUB_ERR_BAD_ARGUMENT, "verify failed"); + return 0; +} + +static grub_efi_boolean_t +read_header (void *data, grub_efi_uint32_t size, pe_coff_loader_image_context_t *context) +{ + grub_efi_guid_t guid = SHIM_LOCK_GUID; + grub_efi_shim_lock_t *shim_lock; + grub_efi_status_t status; + + shim_lock = grub_efi_locate_protocol (&guid, NULL); + + if (!shim_lock) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no shim lock protocol"); + return 0; + } + + status = shim_lock->context (data, size, context); + + if (status == GRUB_EFI_SUCCESS) + { + grub_dprintf ("chain", "context success\n"); + return 1; + } + + switch (status) + { + case GRUB_EFI_UNSUPPORTED: + grub_error (GRUB_ERR_BAD_ARGUMENT, "context error unsupported"); + break; + case GRUB_EFI_INVALID_PARAMETER: + grub_error (GRUB_ERR_BAD_ARGUMENT, "context error invalid parameter"); + break; + default: + grub_error (GRUB_ERR_BAD_ARGUMENT, "context error code"); + break; + } + + return 0; +} + +static void* +image_address (void *image, grub_efi_uint64_t sz, grub_efi_uint64_t adr) +{ + if (adr > sz) + return NULL; + + return ((grub_uint8_t*)image + adr); +} + +static grub_efi_status_t +relocate_coff (pe_coff_loader_image_context_t *context, void *data) +{ + struct grub_pe32_data_directory *reloc_base, *reloc_base_end; + grub_efi_uint64_t adjust; + grub_efi_uint16_t *reloc, *reloc_end; + char *fixup, *fixup_base, *fixup_data = NULL; + grub_efi_uint16_t *fixup_16; + grub_efi_uint32_t *fixup_32; + grub_efi_uint64_t *fixup_64; + + grub_efi_uint64_t size = context->image_size; + void *image_end = (char *)data + size; + + context->pe_hdr->optional_header.image_base = (grub_uint64_t)data; + + if (context->number_of_rva_and_sizes <= 5 || context->reloc_dir->size == 0) + { + grub_dprintf ("chain", "no need to reloc, we are done\n"); + return GRUB_EFI_SUCCESS; + } + + reloc_base = image_address (data, size, context->reloc_dir->rva); + reloc_base_end = image_address (data, size, context->reloc_dir->rva + context->reloc_dir->size -1); + + grub_dprintf ("chain", "reloc_base %p reloc_base_end %p\n", reloc_base, reloc_base_end); + + if (!reloc_base || !reloc_base_end) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "Reloc table overflows binary"); + return GRUB_EFI_UNSUPPORTED; + } + + adjust = (grub_uint64_t)data - context->image_address; + + while (reloc_base < reloc_base_end) + { + reloc = (grub_uint16_t *)((char*)reloc_base + sizeof (struct grub_pe32_data_directory)); + reloc_end = (grub_uint16_t *)((char*)reloc_base + reloc_base->size); + + if ((void *)reloc_end < data || (void *)reloc_end > image_end) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "Reloc table overflows binary"); + return GRUB_EFI_UNSUPPORTED; + } + + fixup_base = image_address(data, size, reloc_base->rva); + + if (!fixup_base) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid fixupbase"); + return GRUB_EFI_UNSUPPORTED; + } + + while (reloc < reloc_end) + { + fixup = fixup_base + (*reloc & 0xFFF); + switch ((*reloc) >> 12) + { + case GRUB_PE32_REL_BASED_ABSOLUTE: + break; + case GRUB_PE32_REL_BASED_HIGH: + fixup_16 = (grub_uint16_t *)fixup; + *fixup_16 = (grub_uint16_t) (*fixup_16 + ((grub_uint16_t)((grub_uint32_t)adjust >> 16))); + if (fixup_data != NULL) + { + *(grub_uint16_t *) fixup_data = *fixup_16; + fixup_data = fixup_data + sizeof (grub_uint16_t); + } + break; + case GRUB_PE32_REL_BASED_LOW: + fixup_16 = (grub_uint16_t *)fixup; + *fixup_16 = (grub_uint16_t) (*fixup_16 + (grub_uint16_t)adjust ); + if (fixup_data != NULL) + { + *(grub_uint16_t *) fixup_data = *fixup_16; + fixup_data = fixup_data + sizeof (grub_uint16_t); + } + break; + case GRUB_PE32_REL_BASED_HIGHLOW: + fixup_32 = (grub_uint32_t *)fixup; + *fixup_32 = *fixup_32 + (grub_uint32_t)adjust; + if (fixup_data != NULL) + { + fixup_data = (char *)ALIGN_UP ((grub_addr_t)fixup_data, sizeof (grub_uint32_t)); + *(grub_uint32_t *) fixup_data = *fixup_32; + fixup_data += sizeof (grub_uint32_t); + } + break; + case GRUB_PE32_REL_BASED_DIR64: + fixup_64 = (grub_uint64_t *)fixup; + *fixup_64 = *fixup_64 + (grub_uint64_t)adjust; + if (fixup_data != NULL) + { + fixup_data = (char *)ALIGN_UP ((grub_addr_t)fixup_data, sizeof (grub_uint64_t)); + *(grub_uint64_t *) fixup_data = *fixup_64; + fixup_data += sizeof (grub_uint64_t); + } + break; + default: + grub_error (GRUB_ERR_BAD_ARGUMENT, "unknown relocation"); + return GRUB_EFI_UNSUPPORTED; + } + reloc += 1; + } + reloc_base = (struct grub_pe32_data_directory *)reloc_end; + } + + return GRUB_EFI_SUCCESS; +} + +static grub_efi_device_path_t * +grub_efi_get_media_file_path (grub_efi_device_path_t *dp) +{ + while (1) + { + grub_efi_uint8_t type = GRUB_EFI_DEVICE_PATH_TYPE (dp); + grub_efi_uint8_t subtype = GRUB_EFI_DEVICE_PATH_SUBTYPE (dp); + + if (type == GRUB_EFI_END_DEVICE_PATH_TYPE) + break; + else if (type == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE + && subtype == GRUB_EFI_FILE_PATH_DEVICE_PATH_SUBTYPE) + return dp; + + dp = GRUB_EFI_NEXT_DEVICE_PATH (dp); + } + + return NULL; +} + +static grub_efi_boolean_t +handle_image (void *data, grub_efi_uint32_t datasize) +{ + grub_efi_boot_services_t *b; + grub_efi_loaded_image_t *li, li_bak; + grub_efi_status_t efi_status; + char *buffer = NULL; + char *buffer_aligned = NULL; + grub_efi_uint32_t i, size; + struct grub_pe32_section_table *section; + char *base, *end; + pe_coff_loader_image_context_t context; + grub_uint32_t section_alignment; + grub_uint32_t buffer_size; + + b = grub_efi_system_table->boot_services; + + if (read_header (data, datasize, &context)) + { + grub_dprintf ("chain", "Succeed to read header\n"); + } + else + { + grub_dprintf ("chain", "Failed to read header\n"); + goto error_exit; + } + + section_alignment = context.pe_hdr->optional_header.section_alignment; + buffer_size = context.image_size + section_alignment; + + efi_status = efi_call_3 (b->allocate_pool, GRUB_EFI_LOADER_DATA, + buffer_size, &buffer); + + if (efi_status != GRUB_EFI_SUCCESS) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory")); + goto error_exit; + } + + buffer_aligned = (char *)ALIGN_UP ((grub_addr_t)buffer, section_alignment); + + grub_memcpy (buffer_aligned, data, context.size_of_headers); + + section = context.first_section; + for (i = 0; i < context.number_of_sections; i++) + { + size = section->virtual_size; + if (size > section->raw_data_size) + size = section->raw_data_size; + + base = image_address (buffer_aligned, context.image_size, section->virtual_address); + end = image_address (buffer_aligned, context.image_size, section->virtual_address + size - 1); + + if (!base || !end) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid section size"); + goto error_exit; + } + + if (section->raw_data_size > 0) + grub_memcpy (base, (grub_efi_uint8_t*)data + section->raw_data_offset, size); + + if (size < section->virtual_size) + grub_memset (base + size, 0, section->virtual_size - size); + + grub_dprintf ("chain", "copied section %s\n", section->name); + section += 1; + } + + efi_status = relocate_coff (&context, buffer_aligned); + + if (efi_status != GRUB_EFI_SUCCESS) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "relocation failed"); + goto error_exit; + } + + entry_point = image_address (buffer_aligned, context.image_size, context.entry_point); + + if (!entry_point) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid entry point"); + goto error_exit; + } + + li = grub_efi_get_loaded_image (grub_efi_image_handle); + if (!li) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "no loaded image available"); + goto error_exit; + } + + grub_memcpy (&li_bak, li, sizeof (grub_efi_loaded_image_t)); + li->image_base = buffer_aligned; + li->image_size = context.image_size; + li->load_options = cmdline; + li->load_options_size = cmdline_len; + li->file_path = grub_efi_get_media_file_path (file_path); + li->device_handle = dev_handle; + if (li->file_path) + { + grub_printf ("file path: "); + grub_efi_print_device_path (li->file_path); + } + else + { + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no matching file path found"); + goto error_exit; + } + + efi_status = efi_call_2 (entry_point, grub_efi_image_handle, grub_efi_system_table); + + grub_memcpy (li, &li_bak, sizeof (grub_efi_loaded_image_t)); + efi_status = efi_call_1 (b->free_pool, buffer); + + return 1; + +error_exit: + if (buffer) + efi_call_1 (b->free_pool, buffer); + + return 0; + +} + +static grub_err_t +grub_secureboot_chainloader_unload (void) +{ + grub_efi_boot_services_t *b; + + b = grub_efi_system_table->boot_services; + efi_call_2 (b->free_pages, address, pages); + grub_free (file_path); + grub_free (cmdline); + cmdline = 0; + file_path = 0; + dev_handle = 0; + + grub_dl_unref (my_mod); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_secureboot_chainloader_boot (void) +{ + handle_image ((void *)address, fsize); + grub_loader_unset (); + return grub_errno; +} +#endif + static grub_err_t grub_cmd_chainloader (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { grub_file_t file = 0; - grub_ssize_t size; grub_efi_status_t status; grub_efi_boot_services_t *b; grub_device_t dev = 0; @@ -210,7 +625,6 @@ grub_cmd_chainloader (grub_command_t cmd grub_efi_loaded_image_t *loaded_image; char *filename; void *boot_image = 0; - grub_efi_handle_t dev_handle = 0; if (argc == 0) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); @@ -222,9 +636,36 @@ grub_cmd_chainloader (grub_command_t cmd address = 0; image_handle = 0; file_path = 0; + dev_handle = 0; b = grub_efi_system_table->boot_services; + if (argc > 1) + { + int i; + grub_efi_char16_t *p16; + + for (i = 1, cmdline_len = 0; i < argc; i++) + cmdline_len += grub_strlen (argv[i]) + 1; + + cmdline_len *= sizeof (grub_efi_char16_t); + cmdline = p16 = grub_malloc (cmdline_len); + if (! cmdline) + goto fail; + + for (i = 1; i < argc; i++) + { + char *p8; + + p8 = argv[i]; + while (*p8) + *(p16++) = *(p8++); + + *(p16++) = ' '; + } + *(--p16) = 0; + } + file = grub_file_open (filename, GRUB_FILE_TYPE_EFI_CHAINLOADED_IMAGE); if (! file) goto fail; @@ -270,14 +711,14 @@ grub_cmd_chainloader (grub_command_t cmd grub_printf ("file path: "); grub_efi_print_device_path (file_path); - size = grub_file_size (file); - if (!size) + fsize = grub_file_size (file); + if (!fsize) { grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), filename); goto fail; } - pages = (((grub_efi_uintn_t) size + ((1 << 12) - 1)) >> 12); + pages = (((grub_efi_uintn_t) fsize + ((1 << 12) - 1)) >> 12); status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_ANY_PAGES, GRUB_EFI_LOADER_CODE, @@ -291,7 +732,7 @@ grub_cmd_chainloader (grub_command_t cmd } boot_image = (void *) ((grub_addr_t) address); - if (grub_file_read (file, boot_image, size) != size) + if (grub_file_read (file, boot_image, fsize) != fsize) { if (grub_errno == GRUB_ERR_NONE) grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), @@ -301,7 +742,7 @@ grub_cmd_chainloader (grub_command_t cmd } #if defined (__i386__) || defined (__x86_64__) - if (size >= (grub_ssize_t) sizeof (struct grub_macho_fat_header)) + if (fsize >= (grub_ssize_t) sizeof (struct grub_macho_fat_header)) { struct grub_macho_fat_header *head = boot_image; if (head->magic @@ -324,20 +765,30 @@ grub_cmd_chainloader (grub_command_t cmd > ~grub_cpu_to_le32 (archs[i].size) || grub_cpu_to_le32 (archs[i].offset) + grub_cpu_to_le32 (archs[i].size) - > (grub_size_t) size) + > (grub_size_t) fsize) { grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), filename); goto fail; } boot_image = (char *) boot_image + grub_cpu_to_le32 (archs[i].offset); - size = grub_cpu_to_le32 (archs[i].size); + fsize = grub_cpu_to_le32 (archs[i].size); } } #endif +#ifdef SUPPORT_SECURE_BOOT + /* FIXME is secure boot possible also with universal binaries? */ + if (debug_secureboot || (grub_efi_get_secureboot () == GRUB_EFI_SECUREBOOT_MODE_ENABLED && grub_secure_validate ((void *)address, fsize))) + { + grub_file_close (file); + grub_loader_set (grub_secureboot_chainloader_boot, grub_secureboot_chainloader_unload, 0); + return 0; + } +#endif + status = efi_call_6 (b->load_image, 0, grub_efi_image_handle, file_path, - boot_image, size, + boot_image, fsize, &image_handle); if (status != GRUB_EFI_SUCCESS) { @@ -360,33 +811,10 @@ grub_cmd_chainloader (grub_command_t cmd } loaded_image->device_handle = dev_handle; - if (argc > 1) + if (cmdline) { - int i, len; - grub_efi_char16_t *p16; - - for (i = 1, len = 0; i < argc; i++) - len += grub_strlen (argv[i]) + 1; - - len *= sizeof (grub_efi_char16_t); - cmdline = p16 = grub_malloc (len); - if (! cmdline) - goto fail; - - for (i = 1; i < argc; i++) - { - char *p8; - - p8 = argv[i]; - while (*p8) - *(p16++) = *(p8++); - - *(p16++) = ' '; - } - *(--p16) = 0; - loaded_image->load_options = cmdline; - loaded_image->load_options_size = len; + loaded_image->load_options_size = cmdline_len; } grub_file_close (file); @@ -408,6 +836,9 @@ grub_cmd_chainloader (grub_command_t cmd if (address) efi_call_2 (b->free_pages, address, pages); + if (cmdline) + grub_free (cmdline); + grub_dl_unref (my_mod); return grub_errno;