From f386daa05466b532ff7dcd7b79b55566460e362c Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 7 Feb 2022 13:05:56 +0100 Subject: [PATCH 1/7] boot: Use cleanup handler to unload image This also moves the message about failed image execution into image_start() as we would otherwise show two error messages if any of the preparatory steps failed. --- src/boot/efi/boot.c | 23 +++++++++++------------ src/boot/efi/drivers.c | 7 +------ src/boot/efi/util.h | 5 +++++ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index fb08b99333..8ad2ebe526 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2322,7 +2322,7 @@ static EFI_STATUS image_start( 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; @@ -2363,10 +2363,8 @@ static EFI_STATUS image_start( 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; - } + if (EFI_ERROR(err)) + return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err); loaded_image->LoadOptions = options; loaded_image->LoadOptionsSize = StrSize(loaded_image->LoadOptions); @@ -2376,9 +2374,11 @@ 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 log_error_status_stall(err, L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err); + + return EFI_SUCCESS; } static void config_free(Config *config) { @@ -2623,11 +2623,10 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { (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); + 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/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 From 7b19627697b48a0660b1667fcd4597f32eb14b6a Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Sun, 20 Feb 2022 12:17:10 +0100 Subject: [PATCH 2/7] fundamental: Move some helpers into string-util-fundamental --- src/basic/string-util.h | 11 ----------- src/basic/strv.h | 6 ------ src/fundamental/string-util-fundamental.h | 17 +++++++++++++++++ 3 files changed, 17 insertions(+), 17 deletions(-) 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/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)) From 5c330b368213a6ebf520f0fc47d719cb85281b7a Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 7 Feb 2022 13:26:09 +0100 Subject: [PATCH 3/7] boot: Edit config entry options inline An edited entry command line should not be saved globally as it should be tied to that one entry only. --- src/boot/efi/boot.c | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 8ad2ebe526..7eb8b0dc18 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -82,7 +82,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 +138,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 +315,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): @@ -915,7 +912,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; @@ -1367,7 +1364,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) { @@ -2318,16 +2315,13 @@ static void config_load_xbootldr( static EFI_STATUS image_start( EFI_HANDLE parent_image, - const Config *config, const ConfigEntry *entry) { _cleanup_(devicetree_cleanup) struct devicetree_state dtstate = {}; _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. */ @@ -2352,24 +2346,18 @@ 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) { + if (entry->options) { EFI_LOADED_IMAGE *loaded_image; err = BS->OpenProtocol(image, &LoadedImageProtocol, (void **)&loaded_image, parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(err)) return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err); - loaded_image->LoadOptions = options; + loaded_image->LoadOptions = entry->options; loaded_image->LoadOptionsSize = StrSize(loaded_image->LoadOptions); /* Try to log any options to the TPM, especially to catch manually edited options */ - (void) tpm_log_load_options(options); + (void) tpm_log_load_options(entry->options); } efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0); @@ -2387,7 +2375,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,7 +2609,7 @@ 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); + 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. */ From acdf7d157b4df8a6ed9a6a5cbf51b304593b3c22 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Tue, 8 Feb 2022 11:03:20 +0100 Subject: [PATCH 4/7] boot: Keep initrds separate from cmdline options This is in preparation for LINUX_INITRD_MEDIA support in boot.c. One downside is that adding or changing the used initrds by command line editing is not possible anymore. --- src/boot/efi/boot.c | 87 +++++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 7eb8b0dc18..7b0fcf20ff 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; @@ -559,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); @@ -1055,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); @@ -1427,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); @@ -1507,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; } @@ -1548,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); @@ -2313,6 +2299,41 @@ 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) { + + assert(root); + assert(entry); + assert(ret_options); + + if (entry->type != LOADER_LINUX || !entry->initrd) { + ret_options = NULL; + return EFI_SUCCESS; + } + + /* 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; + + 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); + } + + if (entry->options) { + _cleanup_freepool_ CHAR16 *o = options; + options = xpool_print(L"%s %s", o, entry->options); + } + + *ret_options = TAKE_PTR(options); + return EFI_SUCCESS; +} + static EFI_STATUS image_start( EFI_HANDLE parent_image, const ConfigEntry *entry) { @@ -2336,6 +2357,11 @@ static EFI_STATUS image_start( if (!path) return log_error_status_stall(EFI_INVALID_PARAMETER, L"Error getting device path."); + _cleanup_freepool_ CHAR16 *options_initrd = NULL; + err = initrd_prepare(image_root, entry, &options_initrd); + 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); @@ -2346,18 +2372,19 @@ static EFI_STATUS image_start( return log_error_status_stall(err, L"Error loading %s: %r", entry->devicetree, err); } - if (entry->options) { + CHAR16 *options = options_initrd ?: entry->options; + if (options) { EFI_LOADED_IMAGE *loaded_image; err = BS->OpenProtocol(image, &LoadedImageProtocol, (void **)&loaded_image, parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(err)) return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err); - loaded_image->LoadOptions = entry->options; - loaded_image->LoadOptionsSize = StrSize(loaded_image->LoadOptions); + loaded_image->LoadOptions = options; + loaded_image->LoadOptionsSize = StrSize(options); /* Try to log any options to the TPM, especially to catch manually edited options */ - (void) tpm_log_load_options(entry->options); + (void) tpm_log_load_options(options); } efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0); From 46ce6cf7747bdd228c4e16ec3a7d19661509def8 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 10 Mar 2022 12:53:16 +0100 Subject: [PATCH 5/7] boot: Add LINUX_INITRD_MEDIA support to boot.c --- src/boot/efi/boot.c | 49 ++++++++++++++++++++++++++++++++++++++-- src/boot/efi/initrd.h | 5 ++++ src/boot/efi/linux.c | 5 ---- src/boot/efi/meson.build | 2 +- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 7b0fcf20ff..37bd6c756a 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2302,27 +2302,63 @@ static void config_load_xbootldr( static EFI_STATUS initrd_prepare( EFI_FILE *root, const ConfigEntry *entry, - CHAR16 **ret_options) { + 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) { @@ -2331,6 +2367,8 @@ static EFI_STATUS initrd_prepare( } *ret_options = TAKE_PTR(options); + *ret_initrd = TAKE_PTR(initrd); + *ret_initrd_size = size; return EFI_SUCCESS; } @@ -2357,8 +2395,10 @@ 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); + 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); @@ -2372,6 +2412,11 @@ static EFI_STATUS image_start( return log_error_status_stall(err, L"Error loading %s: %r", entry->devicetree, err); } + _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); + CHAR16 *options = options_initrd ?: entry->options; if (options) { EFI_LOADED_IMAGE *loaded_image; 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', ) From c43a282c29e192c2e7edf762f7ae5c8e9f3a76aa Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Mon, 7 Feb 2022 12:33:45 +0100 Subject: [PATCH 6/7] boot: Support booting in EFI mixed mode The kernel provides a ".compat" PE section that contains a list of compat entry points with their respective arches. This entry point does all the heavy lifting to support running 64bit kernels when the UEFI firmware is 32bit. Note that the EFI handover protocol code in linux_x86.c does not need any adjustments as it already correctly calls the 32bit handover code. Fixes: #17056 --- src/boot/efi/boot.c | 36 +++++++++++++----- src/boot/efi/pe.c | 91 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 102 insertions(+), 25 deletions(-) diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 37bd6c756a..bb8e8417e4 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2417,14 +2417,13 @@ static EFI_STATUS image_start( if (EFI_ERROR(err)) return log_error_status_stall(err, L"Error registering initrd: %r", err); + 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) { - EFI_LOADED_IMAGE *loaded_image; - - err = BS->OpenProtocol(image, &LoadedImageProtocol, (void **)&loaded_image, - parent_image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); - if (EFI_ERROR(err)) - return log_error_status_stall(err, L"Error getting LoadedImageProtocol handle: %r", err); loaded_image->LoadOptions = options; loaded_image->LoadOptionsSize = StrSize(options); @@ -2435,10 +2434,29 @@ static EFI_STATUS image_start( efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0); err = BS->StartImage(image, NULL, NULL); graphics_mode(FALSE); - if (err != EFI_SUCCESS) - return log_error_status_stall(err, L"Failed to execute %s (%s): %r", entry->title_show, entry->loader, err); + if (err == EFI_SUCCESS) + return 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) { 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); From 6e9165397faa1b546d367bdfc28dd4377a8f1d0a Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 17 Feb 2022 13:40:25 +0100 Subject: [PATCH 7/7] bootctl: Add EFI arch detection support --- man/bootctl.xml | 5 +++ src/boot/bootctl.c | 87 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 75 insertions(+), 17 deletions(-) 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/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;