1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-14 04:58:28 +03:00

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-11 09:34:20 +01:00
parent e2a3d56218
commit 1089d0f89e
12 changed files with 307 additions and 44 deletions

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);

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "device-path-util.h"
#include "efi-string.h"
#include "util.h"
EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp) {
@ -38,6 +39,38 @@ EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DE
return EFI_SUCCESS;
}
EFI_STATUS make_url_device_path(const char16_t *url, EFI_DEVICE_PATH **ret) {
assert(url);
assert(ret);
/* Turns a URL into a simple one-element URL device path. */
_cleanup_free_ char* u = xstr16_to_ascii(url);
if (!u)
return EFI_INVALID_PARAMETER;
size_t l = strlen8(u);
size_t t = offsetof(URI_DEVICE_PATH, Uri) + l + sizeof(EFI_DEVICE_PATH);
EFI_DEVICE_PATH *dp = xmalloc(t);
URI_DEVICE_PATH *udp = (URI_DEVICE_PATH*) dp;
udp->Header = (EFI_DEVICE_PATH) {
.Type = MESSAGING_DEVICE_PATH,
.SubType = MSG_URI_DP,
.Length = offsetof(URI_DEVICE_PATH, Uri) + l,
};
memcpy(udp->Uri, u, l);
EFI_DEVICE_PATH *end = device_path_next_node(dp);
*end = DEVICE_PATH_END_NODE;
assert(((uint8_t*) end + sizeof(EFI_DEVICE_PATH)) == ((uint8_t*) dp + t));
*ret = TAKE_PTR(dp);
return EFI_SUCCESS;
}
static char16_t *device_path_to_str_internal(const EFI_DEVICE_PATH *dp) {
char16_t *str = NULL;
@ -90,7 +123,7 @@ EFI_STATUS device_path_to_str(const EFI_DEVICE_PATH *dp, char16_t **ret) {
return EFI_SUCCESS;
}
str = dp_to_text->ConvertDevicePathToText(dp, false, false);
str = dp_to_text->ConvertDevicePathToText(dp, /* DisplayOnly=*/ false, /* AllowShortcuts= */ false);
if (!str)
return EFI_OUT_OF_RESOURCES;
@ -136,3 +169,12 @@ EFI_DEVICE_PATH *device_path_replace_node(
*end = DEVICE_PATH_END_NODE;
return ret;
}
size_t device_path_size(const EFI_DEVICE_PATH *dp) {
const EFI_DEVICE_PATH *i = ASSERT_PTR(dp);
for (; !device_path_is_end(i); i = device_path_next_node(i))
;
return (const uint8_t*) i - (const uint8_t*) dp + sizeof(EFI_DEVICE_PATH);
}

View File

@ -2,8 +2,10 @@
#pragma once
#include "proto/device-path.h"
#include "util.h"
EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp);
EFI_STATUS make_url_device_path(const char16_t *url, EFI_DEVICE_PATH **ret);
EFI_STATUS device_path_to_str(const EFI_DEVICE_PATH *dp, char16_t **ret);
bool device_path_startswith(const EFI_DEVICE_PATH *dp, const EFI_DEVICE_PATH *start);
EFI_DEVICE_PATH *device_path_replace_node(
@ -25,3 +27,9 @@ static inline bool device_path_is_end(const EFI_DEVICE_PATH *dp) {
.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, \
.Length = sizeof(EFI_DEVICE_PATH) \
}
size_t device_path_size(const EFI_DEVICE_PATH *dp);
static inline EFI_DEVICE_PATH *device_path_dup(const EFI_DEVICE_PATH *dp) {
return xmemdup(ASSERT_PTR(dp), device_path_size(dp));
}

View File

@ -81,7 +81,12 @@ static bool shim_validate(
return shim_lock->shim_verify(file_buffer, file_size) == EFI_SUCCESS;
}
EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path, EFI_HANDLE *ret_image) {
EFI_STATUS shim_load_image(
EFI_HANDLE parent,
const EFI_DEVICE_PATH *device_path,
bool boot_policy,
EFI_HANDLE *ret_image) {
assert(device_path);
assert(ret_image);
@ -91,8 +96,12 @@ EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path
install_security_override(shim_validate, NULL);
EFI_STATUS ret = BS->LoadImage(
/*BootPolicy=*/false, parent, (EFI_DEVICE_PATH *) device_path, NULL, 0, ret_image);
/* BootPolicy= */ boot_policy,
parent,
(EFI_DEVICE_PATH *) device_path,
/* SourceBuffer= */ NULL,
/* SourceSize= */ 0,
ret_image);
if (have_shim)
uninstall_security_override();

View File

@ -12,5 +12,5 @@
#include "efi.h"
bool shim_loaded(void);
EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path, EFI_HANDLE *ret_image);
EFI_STATUS shim_load_image(EFI_HANDLE parent, const EFI_DEVICE_PATH *device_path, bool boot_policy, EFI_HANDLE *ret_image);
void shim_retain_protocol(void);

View File

@ -598,7 +598,7 @@ static EFI_STATUS load_addons(
/* By using shim_load_image, we cover both the case where the PE files are signed with MoK
* and with DB, and running with or without shim. */
err = shim_load_image(stub_image, addon_path, &addon);
err = shim_load_image(stub_image, addon_path, /* boot_policy= */ false, &addon);
if (err != EFI_SUCCESS) {
log_error_status(err,
"Failed to read '%ls' from '%ls', ignoring: %m",

View File

@ -1,13 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "device-path-util.h"
#include "efi-string.h"
#include "efivars.h"
#include "memory-util-fundamental.h"
#include "proto/device-path.h"
#include "proto/simple-text-io.h"
#include "ticks.h"
#include "util.h"
#include "version.h"
#include "efivars.h"
/* Never try to read more than 16G into memory (and on 32bit 1G) */
#define FILE_READ_MAX MIN(SIZE_MAX/4, UINT64_C(16)*1024U*1024U*1024U)
@ -511,3 +512,40 @@ bool free_and_xstrdup16(char16_t **p, const char16_t *s) {
*p = t;
return true;
}
char16_t *url_replace_last_component(const char16_t *url, const char16_t *filename) {
assert(url);
assert(filename);
/* Find colon separating protocol and hostname */
const char16_t *d = strchr16(url, ':');
if (!d || url == d)
return NULL;
d++;
/* Skip slashes after colon */
d += strspn(d, u"/");
/* Skip everything till next slash or end (i.e. the hostname) */
size_t n = strcspn(d, u"/?#");
if (n == 0)
return NULL;
d += n;
const char16_t *e = d + strcspn(d, u"?#"); /* Cut off "Query" and "Fragment" */
while (e > d && e[-1] == '/') /* Eat trailing slashes */
e--;
const char16_t *p = e;
while (p > d && p[-1] != '/') /* Find component before that */
p--;
if (e <= p)
return NULL;
_cleanup_free_ char16_t *chopped = xstrndup16(url, p - url);
return xasprintf("%ls/%ls", chopped, filename);
}

View File

@ -251,3 +251,5 @@ char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path);
#define xnew0(type, n) ASSERT_PTR(new0(type, n))
#endif
char16_t *url_replace_last_component(const char16_t *url, const char16_t *filename);

View File

@ -392,6 +392,7 @@ int verb_status(int argc, char *argv[], void *userdata) {
{ EFI_LOADER_FEATURE_MULTI_PROFILE_UKI, "Multi-Profile UKIs are supported" },
{ EFI_LOADER_FEATURE_REPORT_URL, "Loader reports network boot URL" },
{ EFI_LOADER_FEATURE_TYPE1_UKI, "Support Type #1 uki field" },
{ EFI_LOADER_FEATURE_TYPE1_UKI_URL, "Support Type #1 uki-url field" },
};
static const struct {
uint64_t flag;

View File

@ -26,6 +26,7 @@
#define EFI_LOADER_FEATURE_MULTI_PROFILE_UKI (UINT64_C(1) << 14)
#define EFI_LOADER_FEATURE_REPORT_URL (UINT64_C(1) << 15)
#define EFI_LOADER_FEATURE_TYPE1_UKI (UINT64_C(1) << 16)
#define EFI_LOADER_FEATURE_TYPE1_UKI_URL (UINT64_C(1) << 17)
/* Features of the stub, i.e. systemd-stub */
#define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0)

View File

@ -8,7 +8,9 @@ struct iovec {
size_t iov_len;
};
DISABLE_WARNING_REDUNDANT_DECLS;
static inline void free(void *p);
REENABLE_WARNING;
#endif
/* This accepts both const and non-const pointers */

View File

@ -52,6 +52,10 @@
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"")
#define DISABLE_WARNING_REDUNDANT_DECLS \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wredundant-decls\"")
#if HAVE_WARNING_ZERO_LENGTH_BOUNDS
# define DISABLE_WARNING_ZERO_LENGTH_BOUNDS \
_Pragma("GCC diagnostic push"); \