boot: add new 'uki-url' bls type #1 menu items for booting remote UKIs

Companion BLS spec PR:

https://github.com/uapi-group/specifications/pull/135
This commit is contained in:
Lennart Poettering
2025-02-21 10:04:15 +01:00
parent e2a3d56218
commit 1089d0f89e
12 changed files with 307 additions and 44 deletions
+193 -37
View File
@@ -18,6 +18,7 @@
#include "pe.h"
#include "proto/block-io.h"
#include "proto/device-path.h"
#include "proto/load-file.h"
#include "proto/simple-text-io.h"
#include "random-seed.h"
#include "sbat.h"
@@ -27,6 +28,7 @@
#include "ticks.h"
#include "tpm2-pcr.h"
#include "uki.h"
#include "url-discovery.h"
#include "util.h"
#include "version.h"
#include "vmm.h"
@@ -50,6 +52,7 @@ typedef enum LoaderType {
LOADER_EFI, /* Boot loader spec type #1 entries with "efi" line */
LOADER_LINUX, /* Boot loader spec type #1 entries with "linux" line */
LOADER_UKI, /* Boot loader spec type #1 entries with "uki" line */
LOADER_UKI_URL, /* Boot loader spec type #1 entries with "uki-url" line */
LOADER_TYPE2_UKI, /* Boot loader spec type #2 entries */
LOADER_SECURE_BOOT_KEYS,
LOADER_BAD, /* Marker: this boot loader spec type #1 entry is invalid */
@@ -58,13 +61,13 @@ typedef enum LoaderType {
} LoaderType;
/* Which loader types permit command line editing */
#define LOADER_TYPE_ALLOW_EDITOR(t) IN_SET(t, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_TYPE2_UKI)
#define LOADER_TYPE_ALLOW_EDITOR(t) IN_SET(t, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_UKI_URL, LOADER_TYPE2_UKI)
/* Which loader types allow command line editing in SecureBoot mode */
#define LOADER_TYPE_ALLOW_EDITOR_IN_SB(t) IN_SET(t, LOADER_EFI, LOADER_LINUX)
/* Which loader types shall be considered for automatic selection */
#define LOADER_TYPE_MAY_AUTO_SELECT(t) IN_SET(t, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_TYPE2_UKI)
#define LOADER_TYPE_MAY_AUTO_SELECT(t) IN_SET(t, LOADER_EFI, LOADER_LINUX, LOADER_UKI, LOADER_UKI_URL, LOADER_TYPE2_UKI)
typedef struct {
char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry, possibly suffixed with a profile id) */
@@ -77,6 +80,7 @@ typedef struct {
EFI_HANDLE *device;
LoaderType type;
char16_t *loader;
char16_t *url;
char16_t *devicetree;
char16_t *options;
bool options_implied; /* If true, these options are implied if we invoke the PE binary without any parameters (as in: UKI). If false we must specify these options explicitly. */
@@ -620,6 +624,8 @@ static void print_status(Config *config, char16_t *loaded_image_path) {
printf(" device: %ls\n", dp_str);
if (entry->loader)
printf(" loader: %ls\n", entry->loader);
if (entry->url)
printf(" url: %ls\n", entry->url);
STRV_FOREACH(initrd, entry->initrd)
printf(" initrd: %ls\n", *initrd);
if (entry->devicetree)
@@ -1211,6 +1217,7 @@ static BootEntry* boot_entry_free(BootEntry *entry) {
free(entry->version);
free(entry->machine_id);
free(entry->loader);
free(entry->url);
free(entry->devicetree);
free(entry->options);
strv_free(entry->initrd);
@@ -1508,6 +1515,32 @@ static void boot_entry_add_type1(
entry->loader = xstr8_to_path(value);
entry->key = 'l';
} else if (streq8(key, "uki-url")) {
if (!IN_SET(entry->type, LOADER_UNDEFINED, LOADER_UKI_URL)) {
entry->type = LOADER_BAD;
break;
}
_cleanup_free_ char16_t *p = xstr8_to_16(value);
const char16_t *e = startswith(p, u":");
if (e) {
_cleanup_free_ char16_t *origin = disk_get_url(device);
if (!origin) {
/* Automatically hide entries that require an original URL but where none is available. */
entry->type = LOADER_IGNORE;
break;
}
entry->url = url_replace_last_component(origin, p);
} else
entry->url = TAKE_PTR(p);
entry->type = LOADER_UKI_URL;
entry->key = 'l';
} else if (streq8(key, "efi")) {
if (!IN_SET(entry->type, LOADER_UNDEFINED, LOADER_EFI)) {
@@ -1560,11 +1593,13 @@ static void boot_entry_add_type1(
if (IN_SET(entry->type, LOADER_UNDEFINED, LOADER_BAD, LOADER_IGNORE))
return;
/* check existence */
_cleanup_file_close_ EFI_FILE *handle = NULL;
err = root_dir->Open(root_dir, &handle, entry->loader, EFI_FILE_MODE_READ, 0ULL);
if (err != EFI_SUCCESS)
return;
/* Check existence of loader file */
if (entry->loader) {
_cleanup_file_close_ EFI_FILE *handle = NULL;
err = root_dir->Open(root_dir, &handle, entry->loader, EFI_FILE_MODE_READ, 0ULL);
if (err != EFI_SUCCESS)
return;
}
entry->device = device;
entry->id = xstrdup16(file);
@@ -2560,13 +2595,77 @@ static EFI_STATUS initrd_prepare(
return EFI_SUCCESS;
}
static EFI_STATUS expand_path(
EFI_HANDLE parent_image,
EFI_DEVICE_PATH *path,
EFI_DEVICE_PATH **ret_expanded_path) {
EFI_STATUS err;
assert(parent_image);
assert(path);
assert(ret_expanded_path);
_cleanup_free_ EFI_HANDLE *handles = NULL;
size_t n_handles = 0;
err = BS->LocateHandleBuffer(
ByProtocol,
MAKE_GUID_PTR(EFI_LOAD_FILE_PROTOCOL),
/* SearchKey= */ NULL,
&n_handles,
&handles);
if (!IN_SET(err, EFI_SUCCESS, EFI_NOT_FOUND))
return log_error_status(err, "Failed to get list of LoadFile protocol handles: %m");
FOREACH_ARRAY(h, handles, n_handles) {
EFI_LOAD_FILE_PROTOCOL *load_file = NULL;
err = BS->OpenProtocol(
*h,
MAKE_GUID_PTR(EFI_LOAD_FILE_PROTOCOL),
(void**) &load_file,
parent_image,
/* ControllerHandler= */ NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (IN_SET(err, EFI_NOT_FOUND, EFI_INVALID_PARAMETER))
continue; /* Skip over LoadFile() handles that are not suitable for this kind of device path */
if (err != EFI_SUCCESS) {
log_warning_status(err, "Failed to get LoadFile() protocol, ignoring: %m");
continue;
}
/* Issue a LoadFile() request without interest in the actual data (i.e. size is zero and
* buffer pointer is NULL), but with BootPolicy set to true, this has the effect of
* downloading the URL and establishing a handle for it. */
size_t size = 0;
err = load_file->LoadFile(load_file, path, /* BootPolicy= */ true, &size, /* Buffer= */ NULL);
if (IN_SET(err, EFI_NOT_FOUND, EFI_INVALID_PARAMETER))
continue; /* Skip over LoadFile() handles that after all don't consider themselves
* appropriate for this kind of path */
if (err != EFI_BUFFER_TOO_SMALL) {
log_warning_status(err, "Failed to get file via LoadFile() protocol, ignoring: %m");
continue;
}
/* Now read the updated file path */
EFI_DEVICE_PATH *load_file_path = NULL;
err = BS->HandleProtocol(*h, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &load_file_path);
if (err != EFI_SUCCESS)
return log_error_status(err, "Failed to get LoadFile() device path: %m");
/* And return a copy */
*ret_expanded_path = device_path_dup(load_file_path);
return EFI_SUCCESS;
}
return EFI_NOT_FOUND;
}
static EFI_STATUS image_start(
EFI_HANDLE parent_image,
const BootEntry *entry) {
_cleanup_(devicetree_cleanup) struct devicetree_state dtstate = {};
_cleanup_(unload_imagep) EFI_HANDLE image = NULL;
_cleanup_free_ EFI_DEVICE_PATH *path = NULL;
EFI_STATUS err;
assert(entry);
@@ -2576,38 +2675,94 @@ static EFI_STATUS image_start(
(void) entry->call();
_cleanup_file_close_ EFI_FILE *image_root = NULL;
err = open_volume(entry->device, &image_root);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error opening root path: %m");
err = make_file_device_path(entry->device, entry->loader, &path);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error making file device path: %m");
size_t initrd_size = 0;
_cleanup_pages_ Pages initrd_pages = {};
_cleanup_free_ char16_t *options_initrd = NULL;
err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error preparing initrd: %m");
err = shim_load_image(parent_image, path, &image);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error loading %ls: %m", entry->loader);
/* DTBs are loaded by the kernel before ExitBootServices, and they can be used to map and assign
* arbitrary memory ranges, so skip them when secure boot is enabled as the DTB here is unverified.
*/
if (entry->devicetree && !secure_boot_enabled()) {
err = devicetree_install(&dtstate, image_root, entry->devicetree);
_cleanup_free_ EFI_DEVICE_PATH *path = NULL;
bool boot_policy;
if (entry->url) {
/* Generate a device path that only contains the URL */
err = make_url_device_path(entry->url, &path);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error loading %ls: %m", entry->devicetree);
return log_error_status(err, "Error making URL device path: %m");
/* Try to expand this path on all available NICs and IP protocols */
_cleanup_free_ EFI_DEVICE_PATH *expanded_path = NULL;
for (unsigned n_attempt = 0;; n_attempt++) {
err = expand_path(parent_image, path, &expanded_path);
if (err == EFI_SUCCESS) {
/* If this worked then let's try to boot with the expanded path. */
free(path);
path = TAKE_PTR(expanded_path);
break;
}
if (err != EFI_NOT_FOUND || n_attempt > 5) {
log_warning_status(err, "Failed to expand device path, ignoring: %m");
break;
}
/* Maybe the network devices have been configured for this yet (because we are the
* first piece of code trying to do networking)? Then let's connect them, and try
* again. */
reconnect_all_drivers();
}
/* Note: if the path expansion doesn't work, we'll continue with the unexpanded path. Which
* will probably fail on many (most?) firmwares, but it's worth a try. */
boot_policy = true; /* Set BootPolicy parameter to LoadImage() to true, which ultimately
* controls whether the LoadFile (and thus HTTP boot) or LoadFile2 (which
* does not set up HTTP boot) protocol shall be used. */
} else {
assert(entry->device);
assert(entry->loader);
err = open_volume(entry->device, &image_root);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error opening root path: %m");
err = make_file_device_path(entry->device, entry->loader, &path);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error making file device path: %m");
boot_policy = false;
}
/* Authenticate the image before we continue with initrd or DT stuff */
err = shim_load_image(parent_image, path, boot_policy, &image);
if (err != EFI_SUCCESS) {
if (entry->url) {
/* EFI_NOT_FOUND typically indicates that no network stack or NIC was available, let's give the user a hint. */
if (err == EFI_NOT_FOUND) {
log_info("Unable to boot remote UKI %ls, is networking available?", entry->url);
return err;
}
return log_error_status(err, "Error loading loading remote UKI %ls: %m", entry->url);
}
return log_error_status(err, "Error loading EFI binary %ls: %m", entry->loader);
}
_cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
err = initrd_register(PHYSICAL_ADDRESS_TO_POINTER(initrd_pages.addr), initrd_size, &initrd_handle);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error registering initrd: %m");
_cleanup_free_ char16_t *options_initrd = NULL;
_cleanup_pages_ Pages initrd_pages = {};
size_t initrd_size = 0;
if (image_root) {
err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error preparing initrd: %m");
/* DTBs are loaded by the kernel before ExitBootServices(), and they can be used to map and
* assign arbitrary memory ranges, so skip them when secure boot is enabled as the DTB here
* is unverified. */
if (entry->devicetree && !secure_boot_enabled()) {
err = devicetree_install(&dtstate, image_root, entry->devicetree);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error loading %ls: %m", entry->devicetree);
}
err = initrd_register(PHYSICAL_ADDRESS_TO_POINTER(initrd_pages.addr), initrd_size, &initrd_handle);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error registering initrd: %m");
}
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
@@ -2673,7 +2828,7 @@ static EFI_STATUS image_start(
err = EFI_UNSUPPORTED;
}
return log_error_status(err, "Failed to execute %ls (%ls): %m", entry->title_show, entry->loader);
return log_error_status(err, "Failed to execute %ls (%ls): %m", entry->title_show, entry->loader ?: entry->url);
}
static void config_free(Config *config) {
@@ -2803,6 +2958,7 @@ static void export_loader_variables(
EFI_LOADER_FEATURE_MULTI_PROFILE_UKI |
EFI_LOADER_FEATURE_REPORT_URL |
EFI_LOADER_FEATURE_TYPE1_UKI |
EFI_LOADER_FEATURE_TYPE1_UKI_URL |
0;
assert(loaded_image);