diff options
Diffstat (limited to 'drivers/firmware/efi/libstub/zboot.c')
-rw-r--r-- | drivers/firmware/efi/libstub/zboot.c | 284 |
1 files changed, 74 insertions, 210 deletions
diff --git a/drivers/firmware/efi/libstub/zboot.c b/drivers/firmware/efi/libstub/zboot.c index 5f41a5b17d6e..66be5fdc6b58 100644 --- a/drivers/firmware/efi/libstub/zboot.c +++ b/drivers/firmware/efi/libstub/zboot.c @@ -37,247 +37,111 @@ static void error(char *x) efi_err("EFI decompressor: %s\n", x); } -static efi_status_t __efiapi -load_file(efi_load_file_protocol_t *this, efi_device_path_protocol_t *rem, - bool boot_policy, unsigned long *bufsize, void *buffer) +static unsigned long alloc_preferred_address(unsigned long alloc_size) { - unsigned long compressed_size = _gzdata_end - _gzdata_start; - struct efi_vendor_dev_path *vendor_dp; - bool decompress = false; - unsigned long size; - int ret; - - if (rem == NULL || bufsize == NULL) - return EFI_INVALID_PARAMETER; - - if (boot_policy) - return EFI_UNSUPPORTED; - - // Look for our vendor media device node in the remaining file path - if (rem->type == EFI_DEV_MEDIA && - rem->sub_type == EFI_DEV_MEDIA_VENDOR) { - vendor_dp = container_of(rem, struct efi_vendor_dev_path, header); - if (efi_guidcmp(vendor_dp->vendorguid, LINUX_EFI_ZBOOT_MEDIA_GUID)) - return EFI_NOT_FOUND; - - decompress = true; - rem = (void *)(vendor_dp + 1); - } - - if (rem->type != EFI_DEV_END_PATH || - rem->sub_type != EFI_DEV_END_ENTIRE) - return EFI_NOT_FOUND; - - // The uncompressed size of the payload is appended to the raw bit - // stream, and may therefore appear misaligned in memory - size = decompress ? get_unaligned_le32(_gzdata_end - 4) - : compressed_size; - if (buffer == NULL || *bufsize < size) { - *bufsize = size; - return EFI_BUFFER_TOO_SMALL; - } - - if (decompress) { - ret = __decompress(_gzdata_start, compressed_size, NULL, NULL, - buffer, size, NULL, error); - if (ret < 0) { - error("Decompression failed"); - return EFI_DEVICE_ERROR; - } - } else { - memcpy(buffer, _gzdata_start, compressed_size); - } - - return EFI_SUCCESS; -} - -// Return the length in bytes of the device path up to the first end node. -static int device_path_length(const efi_device_path_protocol_t *dp) -{ - int len = 0; - - while (dp->type != EFI_DEV_END_PATH) { - len += dp->length; - dp = (void *)((u8 *)dp + dp->length); - } - return len; -} - -static void append_rel_offset_node(efi_device_path_protocol_t **dp, - unsigned long start, unsigned long end) -{ - struct efi_rel_offset_dev_path *rodp = (void *)*dp; - - rodp->header.type = EFI_DEV_MEDIA; - rodp->header.sub_type = EFI_DEV_MEDIA_REL_OFFSET; - rodp->header.length = sizeof(struct efi_rel_offset_dev_path); - rodp->reserved = 0; - rodp->starting_offset = start; - rodp->ending_offset = end; - - *dp = (void *)(rodp + 1); -} - -static void append_ven_media_node(efi_device_path_protocol_t **dp, - efi_guid_t *guid) -{ - struct efi_vendor_dev_path *vmdp = (void *)*dp; - - vmdp->header.type = EFI_DEV_MEDIA; - vmdp->header.sub_type = EFI_DEV_MEDIA_VENDOR; - vmdp->header.length = sizeof(struct efi_vendor_dev_path); - vmdp->vendorguid = *guid; +#ifdef EFI_KIMG_PREFERRED_ADDRESS + efi_physical_addr_t efi_addr = EFI_KIMG_PREFERRED_ADDRESS; - *dp = (void *)(vmdp + 1); + if (efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA, + alloc_size / EFI_PAGE_SIZE, &efi_addr) == EFI_SUCCESS) + return efi_addr; +#endif + return ULONG_MAX; } -static void append_end_node(efi_device_path_protocol_t **dp) +void __weak efi_cache_sync_image(unsigned long image_base, + unsigned long alloc_size, + unsigned long code_size) { - (*dp)->type = EFI_DEV_END_PATH; - (*dp)->sub_type = EFI_DEV_END_ENTIRE; - (*dp)->length = sizeof(struct efi_generic_dev_path); - - ++*dp; + // Provided by the arch to perform the cache maintenance necessary for + // executable code loaded into memory to be safe for execution. } asmlinkage efi_status_t __efiapi efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab) { - struct efi_mem_mapped_dev_path mmdp = { - .header.type = EFI_DEV_HW, - .header.sub_type = EFI_DEV_MEM_MAPPED, - .header.length = sizeof(struct efi_mem_mapped_dev_path) - }; - efi_device_path_protocol_t *parent_dp, *dpp, *lf2_dp, *li_dp; - efi_load_file2_protocol_t zboot_load_file2; - efi_loaded_image_t *parent, *child; - unsigned long exit_data_size; - efi_handle_t child_handle; - efi_handle_t zboot_handle; - efi_char16_t *exit_data; + unsigned long compressed_size = _gzdata_end - _gzdata_start; + unsigned long image_base, alloc_size, code_size; + efi_loaded_image_t *image; efi_status_t status; - void *dp_alloc; - int dp_len; + char *cmdline_ptr; + int ret; WRITE_ONCE(efi_system_table, systab); free_mem_ptr = (unsigned long)&zboot_heap; free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap); - exit_data = NULL; - exit_data_size = 0; - status = efi_bs_call(handle_protocol, handle, - &LOADED_IMAGE_PROTOCOL_GUID, (void **)&parent); + &LOADED_IMAGE_PROTOCOL_GUID, (void **)&image); if (status != EFI_SUCCESS) { error("Failed to locate parent's loaded image protocol"); return status; } - status = efi_bs_call(handle_protocol, handle, - &LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID, - (void **)&parent_dp); - if (status != EFI_SUCCESS || parent_dp == NULL) { - // Create a MemoryMapped() device path node to describe - // the parent image if no device path was provided. - mmdp.memory_type = parent->image_code_type; - mmdp.starting_addr = (unsigned long)parent->image_base; - mmdp.ending_addr = (unsigned long)parent->image_base + - parent->image_size - 1; - parent_dp = &mmdp.header; - dp_len = sizeof(mmdp); - } else { - dp_len = device_path_length(parent_dp); - } - - // Allocate some pool memory for device path protocol data - status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, - 2 * (dp_len + sizeof(struct efi_rel_offset_dev_path) + - sizeof(struct efi_generic_dev_path)) + - sizeof(struct efi_vendor_dev_path), - (void **)&dp_alloc); - if (status != EFI_SUCCESS) { - error("Failed to allocate device path pool memory"); + status = efi_handle_cmdline(image, &cmdline_ptr); + if (status != EFI_SUCCESS) return status; - } - - // Create a device path describing the compressed payload in this image - // <...parent_dp...>/Offset(<start>, <end>) - lf2_dp = memcpy(dp_alloc, parent_dp, dp_len); - dpp = (void *)((u8 *)lf2_dp + dp_len); - append_rel_offset_node(&dpp, - (unsigned long)(_gzdata_start - efi_zboot_header), - (unsigned long)(_gzdata_end - efi_zboot_header - 1)); - append_end_node(&dpp); - // Create a device path describing the decompressed payload in this image - // <...parent_dp...>/Offset(<start>, <end>)/VenMedia(ZBOOT_MEDIA_GUID) - dp_len += sizeof(struct efi_rel_offset_dev_path); - li_dp = memcpy(dpp, lf2_dp, dp_len); - dpp = (void *)((u8 *)li_dp + dp_len); - append_ven_media_node(&dpp, &LINUX_EFI_ZBOOT_MEDIA_GUID); - append_end_node(&dpp); - - zboot_handle = NULL; - zboot_load_file2.load_file = load_file; - status = efi_bs_call(install_multiple_protocol_interfaces, - &zboot_handle, - &EFI_DEVICE_PATH_PROTOCOL_GUID, lf2_dp, - &EFI_LOAD_FILE2_PROTOCOL_GUID, &zboot_load_file2, - NULL); - if (status != EFI_SUCCESS) { - error("Failed to install LoadFile2 protocol and device path"); - goto free_dpalloc; - } - - status = efi_bs_call(load_image, false, handle, li_dp, NULL, 0, - &child_handle); - if (status != EFI_SUCCESS) { - error("Failed to load image"); - goto uninstall_lf2; - } + efi_info("Decompressing Linux Kernel...\n"); + + // SizeOfImage from the compressee's PE/COFF header + alloc_size = round_up(get_unaligned_le32(_gzdata_end - 4), + EFI_ALLOC_ALIGN); + + // SizeOfHeaders and SizeOfCode from the compressee's PE/COFF header + code_size = get_unaligned_le32(_gzdata_end - 8) + + get_unaligned_le32(_gzdata_end - 12); + + // If the architecture has a preferred address for the image, + // try that first. + image_base = alloc_preferred_address(alloc_size); + if (image_base == ULONG_MAX) { + unsigned long min_kimg_align = efi_get_kimg_min_align(); + u32 seed = U32_MAX; + + if (!IS_ENABLED(CONFIG_RANDOMIZE_BASE)) { + // Setting the random seed to 0x0 is the same as + // allocating as low as possible + seed = 0; + } else if (efi_nokaslr) { + efi_info("KASLR disabled on kernel command line\n"); + } else { + status = efi_get_random_bytes(sizeof(seed), (u8 *)&seed); + if (status == EFI_NOT_FOUND) { + efi_info("EFI_RNG_PROTOCOL unavailable\n"); + efi_nokaslr = true; + } else if (status != EFI_SUCCESS) { + efi_err("efi_get_random_bytes() failed (0x%lx)\n", + status); + efi_nokaslr = true; + } + } - status = efi_bs_call(handle_protocol, child_handle, - &LOADED_IMAGE_PROTOCOL_GUID, (void **)&child); - if (status != EFI_SUCCESS) { - error("Failed to locate child's loaded image protocol"); - goto unload_image; + status = efi_random_alloc(alloc_size, min_kimg_align, &image_base, + seed, EFI_LOADER_CODE); + if (status != EFI_SUCCESS) { + efi_err("Failed to allocate memory\n"); + goto free_cmdline; + } } - // Copy the kernel command line - child->load_options = parent->load_options; - child->load_options_size = parent->load_options_size; - - status = efi_bs_call(start_image, child_handle, &exit_data_size, - &exit_data); - if (status != EFI_SUCCESS) { - error("StartImage() returned with error:"); - if (exit_data_size > 0) - efi_err("%ls\n", exit_data); - - // If StartImage() returns EFI_SECURITY_VIOLATION, the image is - // not unloaded so we need to do it by hand. - if (status == EFI_SECURITY_VIOLATION) -unload_image: - efi_bs_call(unload_image, child_handle); + // Decompress the payload into the newly allocated buffer. + ret = __decompress(_gzdata_start, compressed_size, NULL, NULL, + (void *)image_base, alloc_size, NULL, error); + if (ret < 0) { + error("Decompression failed"); + status = EFI_DEVICE_ERROR; + goto free_image; } -uninstall_lf2: - efi_bs_call(uninstall_multiple_protocol_interfaces, - zboot_handle, - &EFI_DEVICE_PATH_PROTOCOL_GUID, lf2_dp, - &EFI_LOAD_FILE2_PROTOCOL_GUID, &zboot_load_file2, - NULL); - -free_dpalloc: - efi_bs_call(free_pool, dp_alloc); + efi_cache_sync_image(image_base, alloc_size, code_size); - efi_bs_call(exit, handle, status, exit_data_size, exit_data); + status = efi_stub_common(handle, image, image_base, cmdline_ptr); - // Free ExitData in case Exit() returned with a failure code, - // but return the original status code. - error("Exit() returned with failure code"); - if (exit_data != NULL) - efi_bs_call(free_pool, exit_data); +free_image: + efi_free(alloc_size, image_base); +free_cmdline: + efi_bs_call(free_pool, cmdline_ptr); return status; } |