diff --git a/man/bootctl.xml b/man/bootctl.xml index 1e6d537d0b..8c88238383 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -341,6 +341,11 @@ token when adding a second installation. + + + Install binaries for all supported EFI architectures (this implies ). + + diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 60e80484a3..1dd46f7f20 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -29,10 +29,6 @@ static inline char* strstr_ptr(const char *haystack, const char *needle) { return strstr(haystack, needle); } -static inline const char* strempty(const char *s) { - return s ?: ""; -} - static inline const char* strnull(const char *s) { return s ?: "(null)"; } @@ -181,13 +177,6 @@ int free_and_strndup(char **p, const char *s, size_t l); bool string_is_safe(const char *p) _pure_; -static inline size_t strlen_ptr(const char *s) { - if (!s) - return 0; - - return strlen(s); -} - DISABLE_WARNING_STRINGOP_TRUNCATION; static inline void strncpy_exact(char *buf, const char *src, size_t buf_len) { strncpy(buf, src, buf_len); diff --git a/src/basic/strv.h b/src/basic/strv.h index c567785cdb..072739df35 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -140,12 +140,6 @@ static inline int strv_from_nulstr(char ***a, const char *nulstr) { bool strv_overlap(char * const *a, char * const *b) _pure_; -#define _STRV_FOREACH(s, l, i) \ - for (typeof(*(l)) *s, *i = (l); (s = i) && *i; i++) - -#define STRV_FOREACH(s, l) \ - _STRV_FOREACH(s, l, UNIQ_T(i, UNIQ)) - #define _STRV_FOREACH_BACKWARDS(s, l, h, i) \ for (typeof(*(l)) *s, *h = (l), *i = ({ \ size_t _len = strv_length(h); \ diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index b6bd1dfd65..652667e330 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -74,6 +74,7 @@ static enum { } arg_entry_token_type = ARG_ENTRY_TOKEN_AUTO; static char *arg_entry_token = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; +static bool arg_arch_all = false; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -413,6 +414,36 @@ finish: return r; } +static const char *get_efi_arch(void) { + /* Detect EFI firmware architecture of the running system. On mixed mode systems, it could be 32bit + * while the kernel is running in 64bit. */ + +#ifdef __x86_64__ + _cleanup_free_ char *platform_size = NULL; + int r; + + r = read_one_line_file("/sys/firmware/efi/fw_platform_size", &platform_size); + if (r == -ENOENT) + return EFI_MACHINE_TYPE_NAME; + if (r < 0) { + log_warning_errno(r, "Error reading EFI firmware word size, assuming '%u': %m", __WORDSIZE); + return EFI_MACHINE_TYPE_NAME; + } + + if (streq(platform_size, "64")) + return EFI_MACHINE_TYPE_NAME; + if (streq(platform_size, "32")) + return "ia32"; + + log_warning( + "Unknown EFI firmware word size '%s', using default word size '%u' instead.", + platform_size, + __WORDSIZE); +#endif + + return EFI_MACHINE_TYPE_NAME; +} + static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) { _cleanup_closedir_ DIR *d = NULL; const char *p; @@ -829,7 +860,7 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) { return r; } -static int install_binaries(const char *esp_path, bool force) { +static int install_binaries(const char *esp_path, const char *arch, bool force) { _cleanup_closedir_ DIR *d = NULL; int r = 0; @@ -837,10 +868,13 @@ static int install_binaries(const char *esp_path, bool force) { if (!d) return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m"); + const char *suffix = strjoina(arch, ".efi"); + const char *suffix_signed = strjoina(arch, ".efi.signed"); + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \""BOOTLIBDIR"\": %m")) { int k; - if (!endswith_no_case(de->d_name, ".efi") && !endswith_no_case(de->d_name, ".efi.signed")) + if (!endswith_no_case(de->d_name, suffix) && !endswith_no_case(de->d_name, suffix_signed)) continue; /* skip the .efi file, if there's a .signed version of it */ @@ -1327,6 +1361,8 @@ static int help(int argc, char *argv[], void *userdata) { " Entry token to use for this installation\n" " --json=pretty|short|off\n" " Generate JSON output\n" + " --all-architectures\n" + " Install all supported EFI architectures\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -1349,6 +1385,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_MAKE_ENTRY_DIRECTORY, ARG_ENTRY_TOKEN, ARG_JSON, + ARG_ARCH_ALL, }; static const struct option options[] = { @@ -1368,6 +1405,7 @@ static int parse_argv(int argc, char *argv[]) { { "make-machine-id-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, /* Compatibility alias */ { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, { "json", required_argument, NULL, ARG_JSON }, + { "all-architectures", no_argument, NULL, ARG_ARCH_ALL }, {} }; @@ -1470,7 +1508,10 @@ static int parse_argv(int argc, char *argv[]) { r = parse_json_argument(optarg, &arg_json_format_flags); if (r <= 0) return r; + break; + case ARG_ARCH_ALL: + arg_arch_all = true; break; case '?': @@ -1601,13 +1642,14 @@ static int verb_status(int argc, char *argv[], void *userdata) { SecureBootMode secure = efi_get_secure_boot_mode(); printf("System:\n"); - printf(" Firmware: %s%s (%s)%s\n", ansi_highlight(), strna(fw_type), strna(fw_info), ansi_normal()); - printf(" Secure Boot: %sd (%s)\n", + printf(" Firmware: %s%s (%s)%s\n", ansi_highlight(), strna(fw_type), strna(fw_info), ansi_normal()); + printf(" Firmware Arch: %s\n", get_efi_arch()); + printf(" Secure Boot: %sd (%s)\n", enable_disable(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)), secure_boot_mode_to_string(secure)); s = tpm2_support(); - printf(" TPM2 Support: %s%s%s\n", + printf(" TPM2 Support: %s%s%s\n", FLAGS_SET(s, TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER) ? ansi_highlight_green() : (s & (TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER)) != 0 ? ansi_highlight_red() : ansi_highlight_yellow(), FLAGS_SET(s, TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER) ? "yes" : @@ -1617,14 +1659,14 @@ static int verb_status(int argc, char *argv[], void *userdata) { k = efi_get_reboot_to_firmware(); if (k > 0) - printf(" Boot into FW: %sactive%s\n", ansi_highlight_yellow(), ansi_normal()); + printf(" Boot into FW: %sactive%s\n", ansi_highlight_yellow(), ansi_normal()); else if (k == 0) - printf(" Boot into FW: supported\n"); + printf(" Boot into FW: supported\n"); else if (k == -EOPNOTSUPP) - printf(" Boot into FW: not supported\n"); + printf(" Boot into FW: not supported\n"); else { errno = -k; - printf(" Boot into FW: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); + printf(" Boot into FW: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); } printf("\n"); @@ -1926,6 +1968,8 @@ static int verb_install(int argc, char *argv[], void *userdata) { if (r < 0) return r; + const char *arch = arg_arch_all ? "" : get_efi_arch(); + RUN_WITH_UMASK(0002) { if (install) { /* Don't create any of these directories when we are just updating. When we update @@ -1940,7 +1984,7 @@ static int verb_install(int argc, char *argv[], void *userdata) { return r; } - r = install_binaries(arg_esp_path, install); + r = install_binaries(arg_esp_path, arch, install); if (r < 0) return r; @@ -1969,13 +2013,16 @@ static int verb_install(int argc, char *argv[], void *userdata) { (void) sync_everything(); - if (arg_touch_variables) - r = install_variables(arg_esp_path, - part, pstart, psize, uuid, - "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", - install); + if (!arg_touch_variables) + return 0; - return r; + if (arg_arch_all) { + log_info("Not changing EFI variables with --all-architectures."); + return 0; + } + + char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi"); + return install_variables(arg_esp_path, part, pstart, psize, uuid, path, install); } static int verb_remove(int argc, char *argv[], void *userdata) { @@ -2041,7 +2088,13 @@ static int verb_remove(int argc, char *argv[], void *userdata) { if (!arg_touch_variables) return r; - q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); + if (arg_arch_all) { + log_info("Not changing EFI variables with --all-architectures."); + return r; + } + + char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi"); + q = remove_variables(uuid, path, true); if (q < 0 && r >= 0) r = q; diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index fb08b99333..bb8e8417e4 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -12,6 +12,7 @@ #include "drivers.h" #include "efivars-fundamental.h" #include "graphics.h" +#include "initrd.h" #include "linux.h" #include "measure.h" #include "pe.h" @@ -61,6 +62,7 @@ typedef struct { CHAR16 *loader; CHAR16 *devicetree; CHAR16 *options; + CHAR16 **initrd; CHAR16 key; EFI_STATUS (*call)(void); UINTN tries_done; @@ -82,7 +84,6 @@ typedef struct { CHAR16 *entry_default_efivar; CHAR16 *entry_oneshot; CHAR16 *entry_saved; - CHAR16 *options_edit; BOOLEAN editor; BOOLEAN auto_entries; BOOLEAN auto_firmware; @@ -139,24 +140,20 @@ static void cursor_right( } static BOOLEAN line_edit( - const CHAR16 *line_in, - CHAR16 **line_out, + CHAR16 **line_in, UINTN x_max, UINTN y_pos) { _cleanup_freepool_ CHAR16 *line = NULL, *print = NULL; UINTN size, len, first = 0, cursor = 0, clear = 0; - assert(line_out); + assert(line_in); - if (!line_in) - line_in = L""; - - len = StrLen(line_in); + len = strlen_ptr(*line_in); size = len + 1024; line = xnew(CHAR16, size); print = xnew(CHAR16, x_max + 1); - StrCpy(line, line_in); + StrCpy(line, strempty(*line_in)); for (;;) { EFI_STATUS err; @@ -320,8 +317,10 @@ static BOOLEAN line_edit( case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN): case KEYPRESS(0, CHAR_CARRIAGE_RETURN, 0): /* EZpad Mini 4s firmware sends malformed events */ case KEYPRESS(0, CHAR_CARRIAGE_RETURN, CHAR_CARRIAGE_RETURN): /* Teclast X98+ II firmware sends malformed events */ - if (StrCmp(line, line_in) != 0) - *line_out = TAKE_PTR(line); + if (!streq(line, *line_in)) { + FreePool(*line_in); + *line_in = TAKE_PTR(line); + } return TRUE; case KEYPRESS(0, 0, CHAR_BACKSPACE): @@ -562,6 +561,8 @@ static void print_status(Config *config, CHAR16 *loaded_image_path) { if (entry->device) Print(L" device: %D\n", DevicePathFromHandle(entry->device)); ps_string(L" loader: %s\n", entry->loader); + STRV_FOREACH(initrd, entry->initrd) + Print(L" initrd: %s\n", *initrd); ps_string(L" devicetree: %s\n", entry->devicetree); ps_string(L" options: %s\n", entry->options); ps_bool(L" internal call: %s\n", !!entry->call); @@ -915,7 +916,7 @@ static BOOLEAN menu_run( * Since we cannot paint the last character of the edit line, we simply start * at x-offset 1 for symmetry. */ print_at(1, y_status, COLOR_EDIT, clearline + 2); - exit = line_edit(config->entries[idx_highlight]->options, &config->options_edit, x_max - 2, y_status); + exit = line_edit(&config->entries[idx_highlight]->options, x_max - 2, y_status); print_at(1, y_status, COLOR_NORMAL, clearline + 2); break; @@ -1058,6 +1059,7 @@ static void config_entry_free(ConfigEntry *entry) { FreePool(entry->loader); FreePool(entry->devicetree); FreePool(entry->options); + strv_free(entry->initrd); FreePool(entry->path); FreePool(entry->current_name); FreePool(entry->next_name); @@ -1367,7 +1369,7 @@ good: prefix = xstrdup(file); prefix[i] = 0; - entry->next_name = xpool_print(L"%s+%" PRIuN L"-%" PRIuN L"%s", prefix, next_left, next_done, suffix ?: L""); + entry->next_name = xpool_print(L"%s+%" PRIuN L"-%" PRIuN L"%s", prefix, next_left, next_done, strempty(suffix)); } static void config_entry_bump_counters(ConfigEntry *entry, EFI_FILE *root_dir) { @@ -1430,10 +1432,9 @@ static void config_entry_add_from_file( _cleanup_(config_entry_freep) ConfigEntry *entry = NULL; CHAR8 *line; - UINTN pos = 0; + UINTN pos = 0, n_initrd = 0; CHAR8 *key, *value; EFI_STATUS err; - _cleanup_freepool_ CHAR16 *initrd = NULL; assert(config); assert(device); @@ -1510,18 +1511,12 @@ static void config_entry_add_from_file( } if (strcmpa((CHAR8 *)"initrd", key) == 0) { - _cleanup_freepool_ CHAR16 *new = NULL; - - new = xstra_to_path(value); - if (initrd) { - CHAR16 *s; - - s = xpool_print(L"%s initrd=%s", initrd, new); - FreePool(initrd); - initrd = s; - } else - initrd = xpool_print(L"initrd=%s", new); - + entry->initrd = xreallocate_pool( + entry->initrd, + n_initrd == 0 ? 0 : (n_initrd + 1) * sizeof(UINT16 *), + (n_initrd + 2) * sizeof(UINT16 *)); + entry->initrd[n_initrd++] = xstra_to_path(value); + entry->initrd[n_initrd] = NULL; continue; } @@ -1551,18 +1546,6 @@ static void config_entry_add_from_file( if (EFI_ERROR(err)) return; - /* add initrd= to options */ - if (entry->type == LOADER_LINUX && initrd) { - if (entry->options) { - CHAR16 *s; - - s = xpool_print(L"%s %s", initrd, entry->options); - FreePool(entry->options); - entry->options = s; - } else - entry->options = TAKE_PTR(initrd); - } - entry->device = device; entry->id = xstrdup(file); StrLwr(entry->id); @@ -2316,18 +2299,88 @@ static void config_load_xbootldr( config_load_entries(config, new_device, root_dir, NULL); } +static EFI_STATUS initrd_prepare( + EFI_FILE *root, + const ConfigEntry *entry, + CHAR16 **ret_options, + void **ret_initrd, + UINTN *ret_initrd_size) { + + assert(root); + assert(entry); + assert(ret_options); + assert(ret_initrd); + assert(ret_initrd_size); + + if (entry->type != LOADER_LINUX || !entry->initrd) { + ret_options = NULL; + ret_initrd = NULL; + ret_initrd_size = 0; + return EFI_SUCCESS; + } + + /* Note that order of initrds matters. The kernel will only look for microcode updates in the very + * first one it sees. */ + + /* Add initrd= to options for older kernels that do not support LINUX_INITRD_MEDIA. Should be dropped + * if linux_x86.c is dropped. */ + _cleanup_freepool_ CHAR16 *options = NULL; + + EFI_STATUS err; + UINTN size = 0; + _cleanup_freepool_ UINT8 *initrd = NULL; + + STRV_FOREACH(i, entry->initrd) { + _cleanup_freepool_ CHAR16 *o = options; + if (o) + options = xpool_print(L"%s initrd=%s", o, *i); + else + options = xpool_print(L"initrd=%s", *i); + + _cleanup_(file_closep) EFI_FILE *handle = NULL; + err = root->Open(root, &handle, *i, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(err)) + return err; + + _cleanup_freepool_ EFI_FILE_INFO *info = NULL; + err = get_file_info_harder(handle, &info, NULL); + if (EFI_ERROR(err)) + return err; + + UINTN new_size, read_size = info->FileSize; + if (__builtin_add_overflow(size, read_size, &new_size)) + return EFI_OUT_OF_RESOURCES; + initrd = xreallocate_pool(initrd, size, new_size); + + err = handle->Read(handle, &read_size, initrd + size); + if (EFI_ERROR(err)) + return err; + + /* Make sure the actual read size is what we expected. */ + assert(size + read_size == new_size); + size = new_size; + } + + if (entry->options) { + _cleanup_freepool_ CHAR16 *o = options; + options = xpool_print(L"%s %s", o, entry->options); + } + + *ret_options = TAKE_PTR(options); + *ret_initrd = TAKE_PTR(initrd); + *ret_initrd_size = size; + return EFI_SUCCESS; +} + static EFI_STATUS image_start( EFI_HANDLE parent_image, - const Config *config, const ConfigEntry *entry) { _cleanup_(devicetree_cleanup) struct devicetree_state dtstate = {}; - EFI_HANDLE image; + _cleanup_(unload_imagep) EFI_HANDLE image = NULL; _cleanup_freepool_ EFI_DEVICE_PATH *path = NULL; - CHAR16 *options; EFI_STATUS err; - assert(config); assert(entry); /* If this loader entry has a special way to boot, try that first. */ @@ -2342,6 +2395,13 @@ static EFI_STATUS image_start( if (!path) return log_error_status_stall(EFI_INVALID_PARAMETER, L"Error getting device path."); + UINTN initrd_size = 0; + _cleanup_freepool_ void *initrd = NULL; + _cleanup_freepool_ CHAR16 *options_initrd = NULL; + err = initrd_prepare(image_root, entry, &options_initrd, &initrd, &initrd_size); + if (EFI_ERROR(err)) + return log_error_status_stall(err, L"Error preparing initrd: %r", err); + err = BS->LoadImage(FALSE, parent_image, path, NULL, 0, &image); if (EFI_ERROR(err)) return log_error_status_stall(err, L"Error loading %s: %r", entry->loader, err); @@ -2352,23 +2412,20 @@ static EFI_STATUS image_start( return log_error_status_stall(err, L"Error loading %s: %r", entry->devicetree, err); } - if (config->options_edit) - options = config->options_edit; - else if (entry->options) - options = entry->options; - else - options = NULL; - if (options) { - EFI_LOADED_IMAGE *loaded_image; + _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; + err = initrd_register(initrd, initrd_size, &initrd_handle); + if (EFI_ERROR(err)) + return log_error_status_stall(err, L"Error registering initrd: %r", err); - err = BS->OpenProtocol(image, &LoadedImageProtocol, (void **)&loaded_image, - parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); - if (EFI_ERROR(err)) { - log_error_stall(L"Error getting LoadedImageProtocol handle: %r", err); - goto out_unload; - } + EFI_LOADED_IMAGE *loaded_image; + err = BS->HandleProtocol(image, &LoadedImageProtocol, (void **) &loaded_image); + if (EFI_ERROR(err)) + return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err); + + CHAR16 *options = options_initrd ?: entry->options; + if (options) { loaded_image->LoadOptions = options; - loaded_image->LoadOptionsSize = StrSize(loaded_image->LoadOptions); + loaded_image->LoadOptionsSize = StrSize(options); /* Try to log any options to the TPM, especially to catch manually edited options */ (void) tpm_log_load_options(options); @@ -2376,9 +2433,30 @@ static EFI_STATUS image_start( efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0); err = BS->StartImage(image, NULL, NULL); -out_unload: - BS->UnloadImage(image); - return err; + graphics_mode(FALSE); + if (err == EFI_SUCCESS) + return EFI_SUCCESS; + + /* Try calling the kernel compat entry point if one exists. */ + if (err == EFI_UNSUPPORTED && entry->type == LOADER_LINUX) { + UINT32 kernel_entry_address; + + err = pe_alignment_info(loaded_image->ImageBase, &kernel_entry_address, NULL, NULL); + if (EFI_ERROR(err)) { + if (err != EFI_UNSUPPORTED) + return log_error_status_stall(err, L"Error finding kernel compat entry address: %r", err); + } else { + EFI_IMAGE_ENTRY_POINT kernel_entry = + (EFI_IMAGE_ENTRY_POINT) ((UINT8 *) loaded_image->ImageBase + kernel_entry_address); + + err = kernel_entry(image, ST); + graphics_mode(FALSE); + if (err == EFI_SUCCESS) + return EFI_SUCCESS; + } + } + + return log_error_status_stall(err, L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err); } static void config_free(Config *config) { @@ -2387,7 +2465,6 @@ static void config_free(Config *config) { config_entry_free(config->entries[i]); FreePool(config->entries); FreePool(config->entry_default_config); - FreePool(config->options_edit); FreePool(config->entry_oneshot); } @@ -2622,12 +2699,11 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { /* Optionally, read a random seed off the ESP and pass it to the OS */ (void) process_random_seed(root_dir, config.random_seed_mode); - err = image_start(image, &config, entry); - if (EFI_ERROR(err)) { - graphics_mode(FALSE); - log_error_stall(L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err); + err = image_start(image, entry); + if (err != EFI_SUCCESS) + /* Not using EFI_ERROR here because positive values are also errors like with any + * other (userspace) program. */ goto out; - } menu = TRUE; config.timeout_sec = 0; diff --git a/src/boot/efi/drivers.c b/src/boot/efi/drivers.c index 2ff87261da..65c74c461d 100644 --- a/src/boot/efi/drivers.c +++ b/src/boot/efi/drivers.c @@ -6,17 +6,12 @@ #include "drivers.h" #include "util.h" -static void efi_unload_image(EFI_HANDLE *h) { - if (*h) - (void) BS->UnloadImage(*h); -} - static EFI_STATUS load_one_driver( EFI_HANDLE parent_image, EFI_LOADED_IMAGE *loaded_image, const CHAR16 *fname) { - _cleanup_(efi_unload_image) EFI_HANDLE image = NULL; + _cleanup_(unload_imagep) EFI_HANDLE image = NULL; _cleanup_freepool_ EFI_DEVICE_PATH *path = NULL; _cleanup_freepool_ CHAR16 *spath = NULL; EFI_STATUS err; diff --git a/src/boot/efi/initrd.h b/src/boot/efi/initrd.h index b26a81e369..d1478e3baf 100644 --- a/src/boot/efi/initrd.h +++ b/src/boot/efi/initrd.h @@ -9,3 +9,8 @@ EFI_STATUS initrd_register( EFI_HANDLE *ret_initrd_handle); EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle); + +static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) { + (void) initrd_unregister(*initrd_handle); + *initrd_handle = NULL; +} diff --git a/src/boot/efi/linux.c b/src/boot/efi/linux.c index ce0f4985c0..f04f81fb05 100644 --- a/src/boot/efi/linux.c +++ b/src/boot/efi/linux.c @@ -87,11 +87,6 @@ static EFI_STATUS loaded_image_unregister(EFI_HANDLE loaded_image_handle) { return EFI_SUCCESS; } -static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) { - (void) initrd_unregister(*initrd_handle); - *initrd_handle = NULL; -} - static inline void cleanup_loaded_image(EFI_HANDLE *loaded_image_handle) { (void) loaded_image_unregister(*loaded_image_handle); *loaded_image_handle = NULL; diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index e17669478f..8f1645202b 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -351,6 +351,7 @@ common_sources = files( 'devicetree.c', 'disk.c', 'graphics.c', + 'initrd.c', 'measure.c', 'pe.c', 'secure-boot.c', @@ -369,7 +370,6 @@ systemd_boot_sources = files( stub_sources = files( 'cpio.c', - 'initrd.c', 'splash.c', 'stub.c', ) diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c index e16716498f..568dcf4012 100644 --- a/src/boot/efi/pe.c +++ b/src/boot/efi/pe.c @@ -12,17 +12,22 @@ #define MAX_SECTIONS 96 #if defined(__i386__) - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_IA32 +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_IA32 +# define TARGET_MACHINE_TYPE_COMPATIBILITY EFI_IMAGE_MACHINE_X64 #elif defined(__x86_64__) - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_X64 +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_X64 #elif defined(__aarch64__) - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_AARCH64 +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_AARCH64 #elif defined(__arm__) - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_ARMTHUMB_MIXED +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_ARMTHUMB_MIXED #elif defined(__riscv) && __riscv_xlen == 64 - #define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_RISCV64 +# define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_RISCV64 #else - #error Unknown EFI arch +# error Unknown EFI arch +#endif + +#ifndef TARGET_MACHINE_TYPE_COMPATIBILITY +# define TARGET_MACHINE_TYPE_COMPATIBILITY 0 #endif struct DosFileHeader { @@ -117,10 +122,11 @@ static inline BOOLEAN verify_dos(const struct DosFileHeader *dos) { return CompareMem(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0; } -static inline BOOLEAN verify_pe(const struct PeFileHeader *pe) { +static inline BOOLEAN verify_pe(const struct PeFileHeader *pe, BOOLEAN allow_compatibility) { assert(pe); return CompareMem(pe->Magic, PE_FILE_MAGIC, STRLEN(PE_FILE_MAGIC)) == 0 && - pe->FileHeader.Machine == TARGET_MACHINE_TYPE && + (pe->FileHeader.Machine == TARGET_MACHINE_TYPE || + (allow_compatibility && pe->FileHeader.Machine == TARGET_MACHINE_TYPE_COMPATIBILITY)) && pe->FileHeader.NumberOfSections > 0 && pe->FileHeader.NumberOfSections <= MAX_SECTIONS && IN_SET(pe->OptionalHeader.Magic, OPTHDR32_MAGIC, OPTHDR64_MAGIC); @@ -160,6 +166,49 @@ static void locate_sections( } } +static UINT32 get_compatibility_entry_address(const struct DosFileHeader *dos, const struct PeFileHeader *pe) { + UINTN addr = 0, size = 0; + static const CHAR8 *sections[] = { (CHAR8 *) ".compat", NULL }; + + /* The kernel may provide alternative PE entry points for different PE architectures. This allows + * booting a 64bit kernel on 32bit EFI that is otherwise running on a 64bit CPU. The locations of any + * such compat entry points are located in a special PE section. */ + + locate_sections((const struct PeSectionHeader *) ((const UINT8 *) dos + section_table_offset(dos, pe)), + pe->FileHeader.NumberOfSections, + sections, + &addr, + NULL, + &size); + + if (size == 0) + return 0; + + typedef struct { + UINT8 type; + UINT8 size; + UINT16 machine_type; + UINT32 entry_point; + } _packed_ LinuxPeCompat1; + + while (size >= sizeof(LinuxPeCompat1) && addr % __alignof__(LinuxPeCompat1) == 0) { + LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((UINT8 *) dos + addr); + + if (compat->type == 0 || compat->size == 0 || compat->size > size) + break; + + if (compat->type == 1 && + compat->size >= sizeof(LinuxPeCompat1) && + compat->machine_type == TARGET_MACHINE_TYPE) + return compat->entry_point; + + addr += compat->size; + size -= compat->size; + } + + return 0; +} + EFI_STATUS pe_alignment_info( const void *base, UINT32 *ret_entry_point_address, @@ -171,20 +220,30 @@ EFI_STATUS pe_alignment_info( assert(base); assert(ret_entry_point_address); - assert(ret_size_of_image); - assert(ret_section_alignment); dos = (const struct DosFileHeader *) base; if (!verify_dos(dos)) return EFI_LOAD_ERROR; pe = (const struct PeFileHeader*) ((const UINT8 *)base + dos->ExeHeader); - if (!verify_pe(pe)) + if (!verify_pe(pe, /* allow_compatibility= */ TRUE)) return EFI_LOAD_ERROR; - *ret_entry_point_address = pe->OptionalHeader.AddressOfEntryPoint; - *ret_size_of_image = pe->OptionalHeader.SizeOfImage; - *ret_section_alignment = pe->OptionalHeader.SectionAlignment; + UINT32 entry_address = pe->OptionalHeader.AddressOfEntryPoint; + + /* Look for a compat entry point. */ + if (pe->FileHeader.Machine != TARGET_MACHINE_TYPE) { + entry_address = get_compatibility_entry_address(dos, pe); + if (entry_address == 0) + /* Image type not supported and no compat entry found. */ + return EFI_UNSUPPORTED; + } + + *ret_entry_point_address = entry_address; + if (ret_size_of_image) + *ret_size_of_image = pe->OptionalHeader.SizeOfImage; + if (ret_section_alignment) + *ret_section_alignment = pe->OptionalHeader.SectionAlignment; return EFI_SUCCESS; } @@ -207,7 +266,7 @@ EFI_STATUS pe_memory_locate_sections( return EFI_LOAD_ERROR; pe = (const struct PeFileHeader*)&base[dos->ExeHeader]; - if (!verify_pe(pe)) + if (!verify_pe(pe, /* allow_compatibility= */ FALSE)) return EFI_LOAD_ERROR; offset = section_table_offset(dos, pe); @@ -255,7 +314,7 @@ EFI_STATUS pe_file_locate_sections( err = handle->Read(handle, &len, &pe); if (EFI_ERROR(err)) return err; - if (len != sizeof(pe) || !verify_pe(&pe)) + if (len != sizeof(pe) || !verify_pe(&pe, /* allow_compatibility= */ FALSE)) return EFI_LOAD_ERROR; section_table_len = pe.FileHeader.NumberOfSections * sizeof(struct PeSectionHeader); diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index c45374a08e..af204e9ab5 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -86,6 +86,11 @@ static inline void file_closep(EFI_FILE **handle) { (*handle)->Close(*handle); } +static inline void unload_imagep(EFI_HANDLE *image) { + if (*image) + (void) BS->UnloadImage(*image); +} + /* * Allocated random UUID, intended to be shared across tools that implement * the (ESP)\loader\entries\-.conf convention and the diff --git a/src/fundamental/string-util-fundamental.h b/src/fundamental/string-util-fundamental.h index dc0c1202be..101c2ae1b6 100644 --- a/src/fundamental/string-util-fundamental.h +++ b/src/fundamental/string-util-fundamental.h @@ -50,6 +50,13 @@ static inline sd_bool strcaseeq_ptr(const sd_char *a, const sd_char *b) { return strcasecmp_ptr(a, b) == 0; } +static inline size_t strlen_ptr(const sd_char *s) { + if (!s) + return 0; + + return strlen(s); +} + sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_; #ifndef SD_BOOT sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_; @@ -61,6 +68,10 @@ static inline sd_bool isempty(const sd_char *a) { return !a || a[0] == '\0'; } +static inline const sd_char *strempty(const sd_char *s) { + return s ?: STR_C(""); +} + static inline const sd_char *yes_no(sd_bool b) { return b ? STR_C("yes") : STR_C("no"); } @@ -82,3 +93,9 @@ static inline void *memory_startswith(const void *p, size_t sz, const sd_char *t return (uint8_t*) p + n; } + +#define _STRV_FOREACH(s, l, i) \ + for (typeof(*(l)) *s, *i = (l); (s = i) && *i; i++) + +#define STRV_FOREACH(s, l) \ + _STRV_FOREACH(s, l, UNIQ_T(i, UNIQ))