// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include "efistub.h" static unsigned char zboot_heap[SZ_256K] __aligned(64); static unsigned long free_mem_ptr, free_mem_end_ptr; #define STATIC static #if defined(CONFIG_KERNEL_GZIP) #include "../../../../lib/decompress_inflate.c" #elif defined(CONFIG_KERNEL_LZ4) #include "../../../../lib/decompress_unlz4.c" #elif defined(CONFIG_KERNEL_LZMA) #include "../../../../lib/decompress_unlzma.c" #elif defined(CONFIG_KERNEL_LZO) #include "../../../../lib/decompress_unlzo.c" #elif defined(CONFIG_KERNEL_XZ) #undef memcpy #define memcpy memcpy #undef memmove #define memmove memmove #include "../../../../lib/decompress_unxz.c" #elif defined(CONFIG_KERNEL_ZSTD) #include "../../../../lib/decompress_unzstd.c" #endif extern char efi_zboot_header[]; extern char _gzdata_start[], _gzdata_end[]; static void log(efi_char16_t str[]) { efi_call_proto(efi_table_attr(efi_system_table, con_out), output_string, L"EFI decompressor: "); efi_call_proto(efi_table_attr(efi_system_table, con_out), output_string, str); efi_call_proto(efi_table_attr(efi_system_table, con_out), output_string, L"\n"); } static void error(char *x) { log(L"error() called from decompressor library\n"); } // Local version to avoid pulling in memcmp() static bool guids_eq(const efi_guid_t *a, const efi_guid_t *b) { const u32 *l = (u32 *)a; const u32 *r = (u32 *)b; return l[0] == r[0] && l[1] == r[1] && l[2] == r[2] && l[3] == r[3]; } 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) { 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 (!guids_eq(&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) { log(L"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; *dp = (void *)(vmdp + 1); } static void append_end_node(efi_device_path_protocol_t **dp) { (*dp)->type = EFI_DEV_END_PATH; (*dp)->sub_type = EFI_DEV_END_ENTIRE; (*dp)->length = sizeof(struct efi_generic_dev_path); ++*dp; } 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; efi_status_t status; void *dp_alloc; int dp_len; 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); if (status != EFI_SUCCESS) { log(L"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) { log(L"Failed to allocate device path pool memory"); return status; } // Create a device path describing the compressed payload in this image // <...parent_dp...>/Offset(, ) 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(, )/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) { log(L"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) { log(L"Failed to load image"); goto uninstall_lf2; } status = efi_bs_call(handle_protocol, child_handle, &LOADED_IMAGE_PROTOCOL_GUID, (void **)&child); if (status != EFI_SUCCESS) { log(L"Failed to locate child's loaded image protocol"); goto unload_image; } // 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) { log(L"StartImage() returned with error"); if (exit_data_size > 0) log(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); } 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_bs_call(exit, handle, status, exit_data_size, exit_data); // Free ExitData in case Exit() returned with a failure code, // but return the original status code. log(L"Exit() returned with failure code"); if (exit_data != NULL) efi_bs_call(free_pool, exit_data); return status; }