summaryrefslogtreecommitdiffstats
path: root/src/boot
diff options
context:
space:
mode:
Diffstat (limited to 'src/boot')
-rw-r--r--src/boot/bless-boot-generator.c56
-rw-r--r--src/boot/bless-boot.c527
-rw-r--r--src/boot/boot-check-no-failures.c113
-rw-r--r--src/boot/bootctl-install.c1101
-rw-r--r--src/boot/bootctl-install.h6
-rw-r--r--src/boot/bootctl-random-seed.c239
-rw-r--r--src/boot/bootctl-random-seed.h6
-rw-r--r--src/boot/bootctl-reboot-to-firmware.c38
-rw-r--r--src/boot/bootctl-reboot-to-firmware.h3
-rw-r--r--src/boot/bootctl-set-efivar.c171
-rw-r--r--src/boot/bootctl-set-efivar.h4
-rw-r--r--src/boot/bootctl-status.c829
-rw-r--r--src/boot/bootctl-status.h5
-rw-r--r--src/boot/bootctl-systemd-efi-options.c43
-rw-r--r--src/boot/bootctl-systemd-efi-options.h4
-rw-r--r--src/boot/bootctl-uki.c39
-rw-r--r--src/boot/bootctl-uki.h4
-rw-r--r--src/boot/bootctl-util.c132
-rw-r--r--src/boot/bootctl-util.h14
-rw-r--r--src/boot/bootctl.c516
-rw-r--r--src/boot/bootctl.h46
-rw-r--r--src/boot/efi/UEFI_SECURITY.md122
-rw-r--r--src/boot/efi/addon.c14
-rw-r--r--src/boot/efi/bcd.c306
-rw-r--r--src/boot/efi/bcd.h6
-rw-r--r--src/boot/efi/boot.c2748
-rw-r--r--src/boot/efi/console.c312
-rw-r--r--src/boot/efi/console.h38
-rw-r--r--src/boot/efi/cpio.c512
-rw-r--r--src/boot/efi/cpio.h31
-rw-r--r--src/boot/efi/device-path-util.c138
-rw-r--r--src/boot/efi/device-path-util.h27
-rw-r--r--src/boot/efi/devicetree.c149
-rw-r--r--src/boot/efi/devicetree.h15
-rw-r--r--src/boot/efi/drivers.c115
-rw-r--r--src/boot/efi/drivers.h11
-rw-r--r--src/boot/efi/efi-string.c1084
-rw-r--r--src/boot/efi/efi-string.h180
-rw-r--r--src/boot/efi/efi.h459
-rw-r--r--src/boot/efi/fuzz-bcd.c24
-rw-r--r--src/boot/efi/fuzz-efi-osrel.c28
-rw-r--r--src/boot/efi/fuzz-efi-printf.c78
-rw-r--r--src/boot/efi/fuzz-efi-string.c42
-rw-r--r--src/boot/efi/graphics.c41
-rw-r--r--src/boot/efi/graphics.h10
-rw-r--r--src/boot/efi/initrd.c136
-rw-r--r--src/boot/efi/initrd.h16
-rw-r--r--src/boot/efi/linux.c156
-rw-r--r--src/boot/efi/linux.h19
-rw-r--r--src/boot/efi/linux_x86.c224
-rw-r--r--src/boot/efi/log.c115
-rw-r--r--src/boot/efi/log.h32
-rw-r--r--src/boot/efi/measure.c296
-rw-r--r--src/boot/efi/measure.h44
-rw-r--r--src/boot/efi/meson.build409
-rw-r--r--src/boot/efi/part-discovery.c298
-rw-r--r--src/boot/efi/part-discovery.h12
-rw-r--r--src/boot/efi/pe.c332
-rw-r--r--src/boot/efi/pe.h19
-rw-r--r--src/boot/efi/proto/block-io.h43
-rw-r--r--src/boot/efi/proto/console-control.h28
-rw-r--r--src/boot/efi/proto/device-path.h86
-rw-r--r--src/boot/efi/proto/dt-fixup.h30
-rw-r--r--src/boot/efi/proto/file-io.h83
-rw-r--r--src/boot/efi/proto/graphics-output.h78
-rw-r--r--src/boot/efi/proto/load-file.h21
-rw-r--r--src/boot/efi/proto/loaded-image.h29
-rw-r--r--src/boot/efi/proto/rng.h20
-rw-r--r--src/boot/efi/proto/security-arch.h32
-rw-r--r--src/boot/efi/proto/shell-parameters.h15
-rw-r--r--src/boot/efi/proto/simple-text-io.h182
-rw-r--r--src/boot/efi/proto/tcg.h117
-rw-r--r--src/boot/efi/random-seed.c325
-rw-r--r--src/boot/efi/random-seed.h6
-rw-r--r--src/boot/efi/secure-boot.c223
-rw-r--r--src/boot/efi/secure-boot.h26
-rw-r--r--src/boot/efi/shim.c108
-rw-r--r--src/boot/efi/shim.h16
-rw-r--r--src/boot/efi/splash.c334
-rw-r--r--src/boot/efi/splash.h6
-rw-r--r--src/boot/efi/stub.c816
-rw-r--r--src/boot/efi/test-bcd.c162
-rw-r--r--src/boot/efi/test-efi-string.c794
-rw-r--r--src/boot/efi/ticks.c111
-rw-r--r--src/boot/efi/ticks.h6
-rw-r--r--src/boot/efi/ubsan.c46
-rw-r--r--src/boot/efi/util.c705
-rw-r--r--src/boot/efi/util.h213
-rw-r--r--src/boot/efi/vmm.c426
-rw-r--r--src/boot/efi/vmm.h13
-rw-r--r--src/boot/measure.c1085
-rw-r--r--src/boot/meson.build69
92 files changed, 18748 insertions, 0 deletions
diff --git a/src/boot/bless-boot-generator.c b/src/boot/bless-boot-generator.c
new file mode 100644
index 0000000..38b2c3a
--- /dev/null
+++ b/src/boot/bless-boot-generator.c
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "efi-loader.h"
+#include "generator.h"
+#include "initrd-util.h"
+#include "log.h"
+#include "mkdir.h"
+#include "special.h"
+#include "string-util.h"
+#include "virt.h"
+
+/* This generator pulls systemd-bless-boot.service into the initial transaction if the "LoaderBootCountPath"
+ * EFI variable is set, i.e. the system boots up with boot counting in effect, which means we should mark the
+ * boot as "good" if we manage to boot up far enough. */
+
+static int run(const char *dest, const char *dest_early, const char *dest_late) {
+
+ if (in_initrd()) {
+ log_debug("Skipping generator, running in the initrd.");
+ return EXIT_SUCCESS;
+ }
+
+ if (detect_container() > 0) {
+ log_debug("Skipping generator, running in a container.");
+ return 0;
+ }
+
+ if (!is_efi_boot()) {
+ log_debug("Skipping generator, not an EFI boot.");
+ return 0;
+ }
+
+ if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderBootCountPath)), F_OK) < 0) {
+
+ if (errno == ENOENT) {
+ log_debug_errno(errno, "Skipping generator, not booted with boot counting in effect.");
+ return 0;
+ }
+
+ return log_error_errno(errno, "Failed to check if LoaderBootCountPath EFI variable exists: %m");
+ }
+
+ /* We pull this in from basic.target so that it ends up in all "regular" boot ups, but not in
+ * rescue.target or even emergency.target. */
+ const char *p = strjoina(dest_early, "/" SPECIAL_BASIC_TARGET ".wants/systemd-bless-boot.service");
+ (void) mkdir_parents(p, 0755);
+ if (symlink(SYSTEM_DATA_UNIT_DIR "/systemd-bless-boot.service", p) < 0)
+ return log_error_errno(errno, "Failed to create symlink '%s': %m", p);
+
+ return 0;
+}
+
+DEFINE_MAIN_GENERATOR_FUNCTION(run);
diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c
new file mode 100644
index 0000000..0c0b4f2
--- /dev/null
+++ b/src/boot/bless-boot.c
@@ -0,0 +1,527 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+#include <stdlib.h>
+
+#include "alloc-util.h"
+#include "bootspec.h"
+#include "build.h"
+#include "devnum-util.h"
+#include "efi-api.h"
+#include "efi-loader.h"
+#include "efivars.h"
+#include "fd-util.h"
+#include "find-esp.h"
+#include "fs-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "sync-util.h"
+#include "terminal-util.h"
+#include "verbs.h"
+#include "virt.h"
+
+static char **arg_path = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep);
+
+static int help(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-bless-boot.service", "8", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%s [OPTIONS...] COMMAND\n"
+ "\n%sMark the boot process as good or bad.%s\n"
+ "\nCommands:\n"
+ " status Show status of current boot loader entry\n"
+ " good Mark this boot as good\n"
+ " bad Mark this boot as bad\n"
+ " indeterminate Undo any marking as good or bad\n"
+ "\nOptions:\n"
+ " -h --help Show this help\n"
+ " --version Print version\n"
+ " --path=PATH Path to the $BOOT partition (may be used multiple times)\n"
+ "\nSee the %s for details.\n",
+ program_invocation_short_name,
+ ansi_highlight(),
+ ansi_normal(),
+ link);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_PATH = 0x100,
+ ARG_VERSION,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "path", required_argument, NULL, ARG_PATH },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ help(0, NULL, NULL);
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_PATH:
+ r = strv_extend(&arg_path, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 1;
+}
+
+static int acquire_path(void) {
+ _cleanup_free_ char *esp_path = NULL, *xbootldr_path = NULL;
+ dev_t esp_devid = 0, xbootldr_devid = 0;
+ char **a;
+ int r;
+
+ if (!strv_isempty(arg_path))
+ return 0;
+
+ r = find_esp_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &esp_path, NULL, NULL, NULL, NULL, &esp_devid);
+ if (r < 0 && r != -ENOKEY) /* ENOKEY means not found, and is the only error the function won't log about on its own */
+ return r;
+
+ r = find_xbootldr_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid);
+ if (r < 0 && r != -ENOKEY)
+ return r;
+
+ if (!esp_path && !xbootldr_path)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "Couldn't find $BOOT partition. It is recommended to mount it to /boot.\n"
+ "Alternatively, use --path= to specify path to mount point.");
+
+ if (esp_path && xbootldr_path && !devnum_set_and_equal(esp_devid, xbootldr_devid)) /* in case the two paths refer to the same inode, suppress one */
+ a = strv_new(esp_path, xbootldr_path);
+ else if (esp_path)
+ a = strv_new(esp_path);
+ else
+ a = strv_new(xbootldr_path);
+ if (!a)
+ return log_oom();
+
+ strv_free_and_replace(arg_path, a);
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *j = NULL;
+
+ j = strv_join(arg_path, ":");
+ log_debug("Using %s as boot loader drop-in search path.", strna(j));
+ }
+
+ return 0;
+}
+
+static int parse_counter(
+ const char *path,
+ const char **p,
+ uint64_t *ret_left,
+ uint64_t *ret_done) {
+
+ uint64_t left, done;
+ const char *z, *e;
+ size_t k;
+ int r;
+
+ assert(path);
+ assert(p);
+
+ e = *p;
+ assert(e);
+ assert(*e == '+');
+
+ e++;
+
+ k = strspn(e, DIGITS);
+ if (k == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Can't parse empty 'tries left' counter from LoaderBootCountPath: %s",
+ path);
+
+ z = strndupa_safe(e, k);
+ r = safe_atou64(z, &left);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse 'tries left' counter from LoaderBootCountPath: %s", path);
+
+ e += k;
+
+ if (*e == '-') {
+ e++;
+
+ k = strspn(e, DIGITS);
+ if (k == 0) /* If there's a "-" there also needs to be at least one digit */
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Can't parse empty 'tries done' counter from LoaderBootCountPath: %s",
+ path);
+
+ z = strndupa_safe(e, k);
+ r = safe_atou64(z, &done);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse 'tries done' counter from LoaderBootCountPath: %s", path);
+
+ e += k;
+ } else
+ done = 0;
+
+ if (done == 0)
+ log_warning("The 'tries done' counter is currently at zero. This can't really be, after all we are running, and this boot must hence count as one. Proceeding anyway.");
+
+ *p = e;
+
+ if (ret_left)
+ *ret_left = left;
+
+ if (ret_done)
+ *ret_done = done;
+
+ return 0;
+}
+
+static int acquire_boot_count_path(
+ char **ret_path,
+ char **ret_prefix,
+ uint64_t *ret_left,
+ uint64_t *ret_done,
+ char **ret_suffix) {
+
+ _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL;
+ const char *last, *e;
+ uint64_t left, done;
+ int r;
+
+ r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderBootCountPath), &path);
+ if (r == -ENOENT)
+ return -EUNATCH; /* in this case, let the caller print a message */
+ if (r < 0)
+ return log_error_errno(r, "Failed to read LoaderBootCountPath EFI variable: %m");
+
+ efi_tilt_backslashes(path);
+
+ if (!path_is_normalized(path))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Path read from LoaderBootCountPath is not normalized, refusing: %s",
+ path);
+
+ if (!path_is_absolute(path))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Path read from LoaderBootCountPath is not absolute, refusing: %s",
+ path);
+
+ last = last_path_component(path);
+ e = strrchr(last, '+');
+ if (!e)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Path read from LoaderBootCountPath does not contain a counter, refusing: %s",
+ path);
+
+ if (ret_prefix) {
+ prefix = strndup(path, e - path);
+ if (!prefix)
+ return log_oom();
+ }
+
+ r = parse_counter(path, &e, &left, &done);
+ if (r < 0)
+ return r;
+
+ if (ret_suffix) {
+ suffix = strdup(e);
+ if (!suffix)
+ return log_oom();
+
+ *ret_suffix = TAKE_PTR(suffix);
+ }
+
+ if (ret_path)
+ *ret_path = TAKE_PTR(path);
+ if (ret_prefix)
+ *ret_prefix = TAKE_PTR(prefix);
+ if (ret_left)
+ *ret_left = left;
+ if (ret_done)
+ *ret_done = done;
+
+ return 0;
+}
+
+static int make_good(const char *prefix, const char *suffix, char **ret) {
+ _cleanup_free_ char *good = NULL;
+
+ assert(prefix);
+ assert(suffix);
+ assert(ret);
+
+ /* Generate the path we'd use on good boots. This one is easy. If we are successful, we simple drop the counter
+ * pair entirely from the name. After all, we know all is good, and the logs will contain information about the
+ * tries we needed to come here, hence it's safe to drop the counters from the name. */
+
+ good = strjoin(prefix, suffix);
+ if (!good)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(good);
+ return 0;
+}
+
+static int make_bad(const char *prefix, uint64_t done, const char *suffix, char **ret) {
+ _cleanup_free_ char *bad = NULL;
+
+ assert(prefix);
+ assert(suffix);
+ assert(ret);
+
+ /* Generate the path we'd use on bad boots. Let's simply set the 'left' counter to zero, and keep the 'done'
+ * counter. The information might be interesting to boot loaders, after all. */
+
+ if (done == 0) {
+ bad = strjoin(prefix, "+0", suffix);
+ if (!bad)
+ return -ENOMEM;
+ } else {
+ if (asprintf(&bad, "%s+0-%" PRIu64 "%s", prefix, done, suffix) < 0)
+ return -ENOMEM;
+ }
+
+ *ret = TAKE_PTR(bad);
+ return 0;
+}
+
+static const char *skip_slash(const char *path) {
+ assert(path);
+ assert(path[0] == '/');
+
+ return path + 1;
+}
+
+static int verb_status(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
+ uint64_t left, done;
+ int r;
+
+ r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix);
+ if (r == -EUNATCH) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */
+ puts("clean");
+ return 0;
+ }
+ if (r < 0)
+ return r;
+
+ r = acquire_path();
+ if (r < 0)
+ return r;
+
+ r = make_good(prefix, suffix, &good);
+ if (r < 0)
+ return log_oom();
+
+ r = make_bad(prefix, done, suffix, &bad);
+ if (r < 0)
+ return log_oom();
+
+ log_debug("Booted file: %s\n"
+ "The same modified for 'good': %s\n"
+ "The same modified for 'bad': %s\n",
+ path,
+ good,
+ bad);
+
+ log_debug("Tries left: %" PRIu64"\n"
+ "Tries done: %" PRIu64"\n",
+ left, done);
+
+ STRV_FOREACH(p, arg_path) {
+ _cleanup_close_ int fd = -EBADF;
+
+ fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
+ }
+
+ if (faccessat(fd, skip_slash(path), F_OK, 0) >= 0) {
+ puts("indeterminate");
+ return 0;
+ }
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to check if '%s' exists: %m", path);
+
+ if (faccessat(fd, skip_slash(good), F_OK, 0) >= 0) {
+ puts("good");
+ return 0;
+ }
+
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to check if '%s' exists: %m", good);
+
+ if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) {
+ puts("bad");
+ return 0;
+ }
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to check if '%s' exists: %m", bad);
+
+ /* We didn't find any of the three? If so, let's try the next directory, before we give up. */
+ }
+
+ return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state: %m");
+}
+
+static int verb_set(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
+ const char *target, *source1, *source2;
+ uint64_t done;
+ int r;
+
+ r = acquire_boot_count_path(&path, &prefix, NULL, &done, &suffix);
+ if (r == -EUNATCH) /* acquire_boot_count_path() won't log on its own for this specific error */
+ return log_error_errno(r, "Not booted with boot counting in effect.");
+ if (r < 0)
+ return r;
+
+ r = acquire_path();
+ if (r < 0)
+ return r;
+
+ r = make_good(prefix, suffix, &good);
+ if (r < 0)
+ return log_oom();
+
+ r = make_bad(prefix, done, suffix, &bad);
+ if (r < 0)
+ return log_oom();
+
+ /* Figure out what rename to what */
+ if (streq(argv[0], "good")) {
+ target = good;
+ source1 = path;
+ source2 = bad; /* Maybe this boot was previously marked as 'bad'? */
+ } else if (streq(argv[0], "bad")) {
+ target = bad;
+ source1 = path;
+ source2 = good; /* Maybe this boot was previously marked as 'good'? */
+ } else {
+ assert(streq(argv[0], "indeterminate"));
+ target = path;
+ source1 = good;
+ source2 = bad;
+ }
+
+ STRV_FOREACH(p, arg_path) {
+ _cleanup_close_ int fd = -EBADF;
+
+ fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
+
+ r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target));
+ if (r == -EEXIST)
+ goto exists;
+ if (r == -ENOENT) {
+
+ r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target));
+ if (r == -EEXIST)
+ goto exists;
+ if (r == -ENOENT) {
+
+ if (faccessat(fd, skip_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */
+ goto exists;
+
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to determine if %s already exists: %m", target);
+
+ /* We found none of the snippets here, try the next directory */
+ continue;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target);
+
+ log_debug("Successfully renamed '%s' to '%s'.", source2, target);
+ } else if (r < 0)
+ return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target);
+ else
+ log_debug("Successfully renamed '%s' to '%s'.", source1, target);
+
+ /* First, fsync() the directory these files are located in */
+ r = fsync_parent_at(fd, skip_slash(target));
+ if (r < 0)
+ log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m");
+
+ /* Secondly, syncfs() the whole file system these files are located in */
+ if (syncfs(fd) < 0)
+ log_debug_errno(errno, "Failed to synchronize $BOOT partition, ignoring: %m");
+
+ log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done);
+ return 0;
+ }
+
+ log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s': %m", target);
+ return 1;
+
+exists:
+ log_debug("Operation already executed before, not doing anything.");
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ static const Verb verbs[] = {
+ { "help", VERB_ANY, VERB_ANY, 0, help },
+ { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
+ { "good", VERB_ANY, 1, 0, verb_set },
+ { "bad", VERB_ANY, 1, 0, verb_set },
+ { "indeterminate", VERB_ANY, 1, 0, verb_set },
+ {}
+ };
+
+ int r;
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ if (detect_container() > 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Marking a boot is not supported in containers.");
+
+ if (!is_efi_boot())
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Marking a boot is only supported on EFI systems.");
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/boot/boot-check-no-failures.c b/src/boot/boot-check-no-failures.c
new file mode 100644
index 0000000..4ff91cb
--- /dev/null
+++ b/src/boot/boot-check-no-failures.c
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "build.h"
+#include "bus-error.h"
+#include "log.h"
+#include "main-func.h"
+#include "pretty-print.h"
+#include "terminal-util.h"
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-boot-check-no-failures.service", "8", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%s [OPTIONS...]\n"
+ "\n%sVerify system operational state.%s\n\n"
+ " -h --help Show this help\n"
+ " --version Print version\n"
+ "\nSee the %s for details.\n",
+ program_invocation_short_name,
+ ansi_highlight(),
+ ansi_normal(),
+ link);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_PATH = 0x100,
+ ARG_VERSION,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 1;
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ uint32_t n;
+ int r;
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to system bus: %m");
+
+ r = sd_bus_get_property_trivial(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "NFailedUnits",
+ &error,
+ 'u',
+ &n);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get failed units counter: %s", bus_error_message(&error, r));
+
+ if (n > 0)
+ log_notice("Health check: %" PRIu32 " units have failed.", n);
+ else
+ log_info("Health check: no failed units.");
+
+ return n > 0;
+}
+
+DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
diff --git a/src/boot/bootctl-install.c b/src/boot/bootctl-install.c
new file mode 100644
index 0000000..bacbbb2
--- /dev/null
+++ b/src/boot/bootctl-install.c
@@ -0,0 +1,1101 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bootctl.h"
+#include "bootctl-install.h"
+#include "bootctl-random-seed.h"
+#include "bootctl-util.h"
+#include "chase.h"
+#include "copy.h"
+#include "dirent-util.h"
+#include "efi-api.h"
+#include "env-file.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "glyph-util.h"
+#include "id128-util.h"
+#include "os-util.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "stat-util.h"
+#include "sync-util.h"
+#include "tmpfile-util.h"
+#include "umask-util.h"
+#include "utf8.h"
+
+static int load_etc_machine_id(void) {
+ int r;
+
+ r = sd_id128_get_machine(&arg_machine_id);
+ if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) /* Not set or empty */
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to get machine-id: %m");
+
+ log_debug("Loaded machine ID %s from /etc/machine-id.", SD_ID128_TO_STRING(arg_machine_id));
+ return 0;
+}
+
+static int load_etc_machine_info(void) {
+ /* systemd v250 added support to store the kernel-install layout setting and the machine ID to use
+ * for setting up the ESP in /etc/machine-info. The newer /etc/kernel/entry-token file, as well as
+ * the $layout field in /etc/kernel/install.conf are better replacements for this though, hence this
+ * has been deprecated and is only returned for compatibility. */
+ _cleanup_free_ char *p = NULL, *s = NULL, *layout = NULL;
+ int r;
+
+ p = path_join(arg_root, "etc/machine-info");
+ if (!p)
+ return log_oom();
+
+ r = parse_env_file(NULL, p,
+ "KERNEL_INSTALL_LAYOUT", &layout,
+ "KERNEL_INSTALL_MACHINE_ID", &s);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse /etc/machine-info: %m");
+
+ if (!isempty(s)) {
+ if (!arg_quiet)
+ log_notice("Read $KERNEL_INSTALL_MACHINE_ID from /etc/machine-info. "
+ "Please move it to /etc/kernel/entry-token.");
+
+ r = sd_id128_from_string(s, &arg_machine_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse KERNEL_INSTALL_MACHINE_ID=%s in /etc/machine-info: %m", s);
+
+ log_debug("Loaded KERNEL_INSTALL_MACHINE_ID=%s from /etc/machine-info.",
+ SD_ID128_TO_STRING(arg_machine_id));
+ }
+
+ if (!isempty(layout)) {
+ if (!arg_quiet)
+ log_notice("Read $KERNEL_INSTALL_LAYOUT from /etc/machine-info. "
+ "Please move it to the layout= setting of /etc/kernel/install.conf.");
+
+ log_debug("KERNEL_INSTALL_LAYOUT=%s is specified in /etc/machine-info.", layout);
+ free_and_replace(arg_install_layout, layout);
+ }
+
+ return 0;
+}
+
+static int load_etc_kernel_install_conf(void) {
+ _cleanup_free_ char *layout = NULL, *p = NULL;
+ int r;
+
+ p = path_join(arg_root, etc_kernel(), "install.conf");
+ if (!p)
+ return log_oom();
+
+ r = parse_env_file(NULL, p, "layout", &layout);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse %s: %m", p);
+
+ if (!isempty(layout)) {
+ log_debug("layout=%s is specified in %s.", layout, p);
+ free_and_replace(arg_install_layout, layout);
+ }
+
+ return 0;
+}
+
+static bool use_boot_loader_spec_type1(void) {
+ /* If the layout is not specified, or if it is set explicitly to "bls" we assume Boot Loader
+ * Specification Type #1 is the chosen format for our boot loader entries */
+ return !arg_install_layout || streq(arg_install_layout, "bls");
+}
+
+static int settle_make_entry_directory(void) {
+ int r;
+
+ r = load_etc_machine_id();
+ if (r < 0)
+ return r;
+
+ r = load_etc_machine_info();
+ if (r < 0)
+ return r;
+
+ r = load_etc_kernel_install_conf();
+ if (r < 0)
+ return r;
+
+ r = settle_entry_token();
+ if (r < 0)
+ return r;
+
+ bool layout_type1 = use_boot_loader_spec_type1();
+ if (arg_make_entry_directory < 0) { /* Automatic mode */
+ if (layout_type1) {
+ if (arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID) {
+ r = path_is_temporary_fs("/etc/machine-id");
+ if (r < 0)
+ return log_debug_errno(r, "Couldn't determine whether /etc/machine-id is on a temporary file system: %m");
+
+ arg_make_entry_directory = r == 0;
+ } else
+ arg_make_entry_directory = true;
+ } else
+ arg_make_entry_directory = false;
+ }
+
+ if (arg_make_entry_directory > 0 && !layout_type1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "KERNEL_INSTALL_LAYOUT=%s is configured, but Boot Loader Specification Type #1 entry directory creation was requested.",
+ arg_install_layout);
+
+ return 0;
+}
+
+static int compare_product(const char *a, const char *b) {
+ size_t x, y;
+
+ assert(a);
+ assert(b);
+
+ x = strcspn(a, " ");
+ y = strcspn(b, " ");
+ if (x != y)
+ return x < y ? -1 : x > y ? 1 : 0;
+
+ return strncmp(a, b, x);
+}
+
+static int compare_version(const char *a, const char *b) {
+ assert(a);
+ assert(b);
+
+ a += strcspn(a, " ");
+ a += strspn(a, " ");
+ b += strcspn(b, " ");
+ b += strspn(b, " ");
+
+ return strverscmp_improved(a, b);
+}
+
+static int version_check(int fd_from, const char *from, int fd_to, const char *to) {
+ _cleanup_free_ char *a = NULL, *b = NULL;
+ int r;
+
+ assert(fd_from >= 0);
+ assert(from);
+ assert(fd_to >= 0);
+ assert(to);
+
+ r = get_file_version(fd_from, &a);
+ if (r == -ESRCH)
+ return log_notice_errno(r, "Source file \"%s\" does not carry version information!", from);
+ if (r < 0)
+ return r;
+
+ r = get_file_version(fd_to, &b);
+ if (r == -ESRCH)
+ return log_notice_errno(r, "Skipping \"%s\", it's owned by another boot loader (no version info found).",
+ to);
+ if (r < 0)
+ return r;
+ if (compare_product(a, b) != 0)
+ return log_notice_errno(SYNTHETIC_ERRNO(ESRCH),
+ "Skipping \"%s\", it's owned by another boot loader.", to);
+
+ r = compare_version(a, b);
+ log_debug("Comparing versions: \"%s\" %s \"%s", a, comparison_operator(r), b);
+ if (r < 0)
+ return log_warning_errno(SYNTHETIC_ERRNO(ESTALE),
+ "Skipping \"%s\", newer boot loader version in place already.", to);
+ if (r == 0)
+ return log_info_errno(SYNTHETIC_ERRNO(ESTALE),
+ "Skipping \"%s\", same boot loader version in place already.", to);
+
+ return 0;
+}
+
+static int copy_file_with_version_check(const char *from, const char *to, bool force) {
+ _cleanup_close_ int fd_from = -EBADF, fd_to = -EBADF;
+ _cleanup_free_ char *t = NULL;
+ int r;
+
+ fd_from = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd_from < 0)
+ return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", from);
+
+ if (!force) {
+ fd_to = open(to, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd_to < 0) {
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to open \"%s\" for reading: %m", to);
+ } else {
+ r = version_check(fd_from, from, fd_to, to);
+ if (r < 0)
+ return r;
+
+ if (lseek(fd_from, 0, SEEK_SET) < 0)
+ return log_error_errno(errno, "Failed to seek in \"%s\": %m", from);
+
+ fd_to = safe_close(fd_to);
+ }
+ }
+
+ r = tempfn_random(to, NULL, &t);
+ if (r < 0)
+ return log_oom();
+
+ WITH_UMASK(0000) {
+ fd_to = open(t, O_WRONLY|O_CREAT|O_CLOEXEC|O_EXCL|O_NOFOLLOW, 0644);
+ if (fd_to < 0)
+ return log_error_errno(errno, "Failed to open \"%s\" for writing: %m", t);
+ }
+
+ r = copy_bytes(fd_from, fd_to, UINT64_MAX, COPY_REFLINK);
+ if (r < 0) {
+ (void) unlink(t);
+ return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", from, t);
+ }
+
+ (void) copy_times(fd_from, fd_to, 0);
+
+ r = fsync_full(fd_to);
+ if (r < 0) {
+ (void) unlink(t);
+ return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", from, t);
+ }
+
+ r = RET_NERRNO(renameat(AT_FDCWD, t, AT_FDCWD, to));
+ if (r < 0) {
+ (void) unlink(t);
+ return log_error_errno(r, "Failed to rename \"%s\" to \"%s\": %m", t, to);
+ }
+
+ log_info("Copied \"%s\" to \"%s\".", from, to);
+
+ return 0;
+}
+
+static int mkdir_one(const char *prefix, const char *suffix) {
+ _cleanup_free_ char *p = NULL;
+
+ p = path_join(prefix, suffix);
+ if (mkdir(p, 0700) < 0) {
+ if (errno != EEXIST)
+ return log_error_errno(errno, "Failed to create \"%s\": %m", p);
+ } else
+ log_info("Created \"%s\".", p);
+
+ return 0;
+}
+
+static const char *const esp_subdirs[] = {
+ /* The directories to place in the ESP */
+ "EFI",
+ "EFI/systemd",
+ "EFI/BOOT",
+ "loader",
+ NULL
+};
+
+static const char *const dollar_boot_subdirs[] = {
+ /* The directories to place in the XBOOTLDR partition or the ESP, depending what exists */
+ "loader",
+ "loader/entries", /* Type #1 entries */
+ "EFI",
+ "EFI/Linux", /* Type #2 entries */
+ NULL
+};
+
+static int create_subdirs(const char *root, const char * const *subdirs) {
+ int r;
+
+ STRV_FOREACH(i, subdirs) {
+ r = mkdir_one(root, *i);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+
+static int copy_one_file(const char *esp_path, const char *name, bool force) {
+ char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL;
+ _cleanup_free_ char *source_path = NULL, *dest_path = NULL, *p = NULL, *q = NULL;
+ const char *e;
+ char *dest_name, *s;
+ int r, ret;
+
+ dest_name = strdupa_safe(name);
+ s = endswith_no_case(dest_name, ".signed");
+ if (s)
+ *s = 0;
+
+ p = path_join(BOOTLIBDIR, name);
+ if (!p)
+ return log_oom();
+
+ r = chase(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
+ /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
+ if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
+ r = chase(p, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &source_path, NULL);
+ if (r < 0)
+ return log_error_errno(r,
+ "Failed to resolve path %s%s%s: %m",
+ p,
+ root ? " under directory " : "",
+ strempty(root));
+
+ q = path_join("/EFI/systemd/", dest_name);
+ if (!q)
+ return log_oom();
+
+ r = chase(q, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &dest_path, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", q, esp_path);
+
+ /* Note that if this fails we do the second copy anyway, but return this error code,
+ * so we stash it away in a separate variable. */
+ ret = copy_file_with_version_check(source_path, dest_path, force);
+
+ e = startswith(dest_name, "systemd-boot");
+ if (e) {
+ _cleanup_free_ char *default_dest_path = NULL;
+ char *v;
+
+ /* Create the EFI default boot loader name (specified for removable devices) */
+ v = strjoina("/EFI/BOOT/BOOT", e);
+ ascii_strupper(strrchr(v, '/') + 1);
+
+ r = chase(v, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_NONEXISTENT, &default_dest_path, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path);
+
+ r = copy_file_with_version_check(source_path, default_dest_path, force);
+ if (r < 0 && ret == 0)
+ ret = r;
+ }
+
+ return ret;
+}
+
+static int install_binaries(const char *esp_path, const char *arch, bool force) {
+ char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL;
+ _cleanup_closedir_ DIR *d = NULL;
+ _cleanup_free_ char *path = NULL;
+ int r;
+
+ r = chase_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+ /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */
+ if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO)
+ r = chase_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &path, &d);
+ if (r == -ENOENT && arg_graceful) {
+ log_debug("Source directory does not exist, ignoring.");
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", strempty(root), BOOTLIBDIR);
+
+ 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 \"%s\": %m", path)) {
+ int k;
+
+ 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 */
+ if (endswith_no_case(de->d_name, ".efi")) {
+ _cleanup_free_ const char *s = strjoin(de->d_name, ".signed");
+ if (!s)
+ return log_oom();
+ if (faccessat(dirfd(d), s, F_OK, 0) >= 0)
+ continue;
+ }
+
+ k = copy_one_file(esp_path, de->d_name, force);
+ /* Don't propagate an error code if no update necessary, installed version already equal or
+ * newer version, or other boot loader in place. */
+ if (arg_graceful && IN_SET(k, -ESTALE, -ESRCH))
+ continue;
+ RET_GATHER(r, k);
+ }
+
+ return r;
+}
+
+static int install_loader_config(const char *esp_path) {
+ _cleanup_(unlink_and_freep) char *t = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ assert(arg_make_entry_directory >= 0);
+
+ p = path_join(esp_path, "/loader/loader.conf");
+ if (!p)
+ return log_oom();
+ if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */
+ return 0;
+
+ r = fopen_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t, &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open \"%s\" for writing: %m", p);
+
+ fprintf(f, "#timeout 3\n"
+ "#console-mode keep\n");
+
+ if (arg_make_entry_directory) {
+ assert(arg_entry_token);
+ fprintf(f, "default %s-*\n", arg_entry_token);
+ }
+
+ r = flink_tmpfile(f, t, p, LINK_TMPFILE_SYNC);
+ if (r == -EEXIST)
+ return 0; /* Silently skip creation if the file exists now (recheck) */
+ if (r < 0)
+ return log_error_errno(r, "Failed to move \"%s\" into place: %m", p);
+
+ t = mfree(t);
+ return 1;
+}
+
+static int install_loader_specification(const char *root) {
+ _cleanup_(unlink_and_freep) char *t = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ p = path_join(root, "/loader/entries.srel");
+ if (!p)
+ return log_oom();
+
+ if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */
+ return 0;
+
+ r = fopen_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t, &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open \"%s\" for writing: %m", p);
+
+ fprintf(f, "type1\n");
+
+ r = flink_tmpfile(f, t, p, LINK_TMPFILE_SYNC);
+ if (r == -EEXIST)
+ return 0; /* Silently skip creation if the file exists now (recheck) */
+ if (r < 0)
+ return log_error_errno(r, "Failed to move \"%s\" into place: %m", p);
+
+ t = mfree(t);
+ return 1;
+}
+
+static int install_entry_directory(const char *root) {
+ assert(root);
+ assert(arg_make_entry_directory >= 0);
+
+ if (!arg_make_entry_directory)
+ return 0;
+
+ assert(arg_entry_token);
+ return mkdir_one(root, arg_entry_token);
+}
+
+static int install_entry_token(void) {
+ _cleanup_free_ char* p = NULL;
+ int r;
+
+ assert(arg_make_entry_directory >= 0);
+ assert(arg_entry_token);
+
+ /* Let's save the used entry token in /etc/kernel/entry-token if we used it to create the entry
+ * directory, or if anything else but the machine ID */
+
+ if (!arg_make_entry_directory && arg_entry_token_type == BOOT_ENTRY_TOKEN_MACHINE_ID)
+ return 0;
+
+ p = path_join(arg_root, etc_kernel(), "entry-token");
+ if (!p)
+ return log_oom();
+
+ r = write_string_file(p, arg_entry_token, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write entry token '%s' to %s: %m", arg_entry_token, p);
+
+ return 0;
+}
+
+static bool same_entry(uint16_t id, sd_id128_t uuid, const char *path) {
+ _cleanup_free_ char *opath = NULL;
+ sd_id128_t ouuid;
+ int r;
+
+ r = efi_get_boot_option(id, NULL, &ouuid, &opath, NULL);
+ if (r < 0)
+ return false;
+ if (!sd_id128_equal(uuid, ouuid))
+ return false;
+
+ /* Some motherboards convert the path to uppercase under certain circumstances
+ * (e.g. after booting into the Boot Menu in the ASUS ROG STRIX B350-F GAMING),
+ * so use case-insensitive checking */
+ if (!strcaseeq_ptr(path, opath))
+ return false;
+
+ return true;
+}
+
+static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) {
+ _cleanup_free_ uint16_t *options = NULL;
+
+ int n = efi_get_boot_options(&options);
+ if (n < 0)
+ return n;
+
+ /* find already existing systemd-boot entry */
+ for (int i = 0; i < n; i++)
+ if (same_entry(options[i], uuid, path)) {
+ *id = options[i];
+ return 1;
+ }
+
+ /* find free slot in the sorted BootXXXX variable list */
+ for (int i = 0; i < n; i++)
+ if (i != options[i]) {
+ *id = i;
+ return 0;
+ }
+
+ /* use the next one */
+ if (n == 0xffff)
+ return -ENOSPC;
+ *id = n;
+ return 0;
+}
+
+static int insert_into_order(uint16_t slot, bool first) {
+ _cleanup_free_ uint16_t *order = NULL;
+ uint16_t *t;
+ int n;
+
+ n = efi_get_boot_order(&order);
+ if (n <= 0)
+ /* no entry, add us */
+ return efi_set_boot_order(&slot, 1);
+
+ /* are we the first and only one? */
+ if (n == 1 && order[0] == slot)
+ return 0;
+
+ /* are we already in the boot order? */
+ for (int i = 0; i < n; i++) {
+ if (order[i] != slot)
+ continue;
+
+ /* we do not require to be the first one, all is fine */
+ if (!first)
+ return 0;
+
+ /* move us to the first slot */
+ memmove(order + 1, order, i * sizeof(uint16_t));
+ order[0] = slot;
+ return efi_set_boot_order(order, n);
+ }
+
+ /* extend array */
+ t = reallocarray(order, n + 1, sizeof(uint16_t));
+ if (!t)
+ return -ENOMEM;
+ order = t;
+
+ /* add us to the top or end of the list */
+ if (first) {
+ memmove(order + 1, order, n * sizeof(uint16_t));
+ order[0] = slot;
+ } else
+ order[n] = slot;
+
+ return efi_set_boot_order(order, n + 1);
+}
+
+static int remove_from_order(uint16_t slot) {
+ _cleanup_free_ uint16_t *order = NULL;
+ int n;
+
+ n = efi_get_boot_order(&order);
+ if (n <= 0)
+ return n;
+
+ for (int i = 0; i < n; i++) {
+ if (order[i] != slot)
+ continue;
+
+ if (i + 1 < n)
+ memmove(order + i, order + i+1, (n - i) * sizeof(uint16_t));
+ return efi_set_boot_order(order, n - 1);
+ }
+
+ return 0;
+}
+
+static const char *pick_efi_boot_option_description(void) {
+ return arg_efi_boot_option_description ?: "Linux Boot Manager";
+}
+
+static int install_variables(
+ const char *esp_path,
+ uint32_t part,
+ uint64_t pstart,
+ uint64_t psize,
+ sd_id128_t uuid,
+ const char *path,
+ bool first,
+ bool graceful) {
+
+ uint16_t slot;
+ int r;
+
+ if (arg_root) {
+ log_info("Acting on %s, skipping EFI variable setup.",
+ arg_image ? "image" : "root directory");
+ return 0;
+ }
+
+ if (!is_efi_boot()) {
+ log_warning("Not booted with EFI, skipping EFI variable setup.");
+ return 0;
+ }
+
+ r = chase_and_access(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Cannot access \"%s/%s\": %m", esp_path, path);
+
+ r = find_slot(uuid, path, &slot);
+ if (r < 0) {
+ int level = graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
+ const char *skip = graceful ? ", skipping" : "";
+
+ log_full_errno(level, r,
+ r == -ENOENT ?
+ "Failed to access EFI variables%s. Is the \"efivarfs\" filesystem mounted?" :
+ "Failed to determine current boot order%s: %m", skip);
+
+ return graceful ? 0 : r;
+ }
+
+ if (first || r == 0) {
+ r = efi_add_boot_option(slot, pick_efi_boot_option_description(),
+ part, pstart, psize,
+ uuid, path);
+ if (r < 0) {
+ int level = graceful ? arg_quiet ? LOG_DEBUG : LOG_INFO : LOG_ERR;
+ const char *skip = graceful ? ", skipping" : "";
+
+ log_full_errno(level, r, "Failed to create EFI Boot variable entry%s: %m", skip);
+
+ return graceful ? 0 : r;
+ }
+
+ log_info("Created EFI boot entry \"%s\".", pick_efi_boot_option_description());
+ }
+
+ return insert_into_order(slot, first);
+}
+
+static int are_we_installed(const char *esp_path) {
+ int r;
+
+ /* Tests whether systemd-boot is installed. It's not obvious what to use as check here: we could
+ * check EFI variables, we could check what binary /EFI/BOOT/BOOT*.EFI points to, or whether the
+ * loader entries directory exists. Here we opted to check whether /EFI/systemd/ is non-empty, which
+ * should be a suitable and very minimal check for a number of reasons:
+ *
+ * → The check is architecture independent (i.e. we check if any systemd-boot loader is installed,
+ * not a specific one.)
+ *
+ * → It doesn't assume we are the only boot loader (i.e doesn't check if we own the main
+ * /EFI/BOOT/BOOT*.EFI fallback binary.
+ *
+ * → It specifically checks for systemd-boot, not for other boot loaders (which a check for
+ * /boot/loader/entries would do). */
+
+ _cleanup_free_ char *p = path_join(esp_path, "/EFI/systemd/");
+ if (!p)
+ return log_oom();
+
+ log_debug("Checking whether %s contains any files%s", p, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
+ r = dir_is_empty(p, /* ignore_hidden_or_backup= */ false);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(r, "Failed to check whether %s contains any files: %m", p);
+
+ return r == 0;
+}
+
+int verb_install(int argc, char *argv[], void *userdata) {
+ sd_id128_t uuid = SD_ID128_NULL;
+ uint64_t pstart = 0, psize = 0;
+ uint32_t part = 0;
+ bool install, graceful;
+ int r;
+
+ /* Invoked for both "update" and "install" */
+
+ install = streq(argv[0], "install");
+ graceful = !install && arg_graceful; /* support graceful mode for updates */
+
+ r = acquire_esp(/* unprivileged_mode= */ false, graceful, &part, &pstart, &psize, &uuid, NULL);
+ if (graceful && r == -ENOKEY)
+ return 0; /* If --graceful is specified and we can't find an ESP, handle this cleanly */
+ if (r < 0)
+ return r;
+
+ if (!install) {
+ /* If we are updating, don't do anything if sd-boot wasn't actually installed. */
+ r = are_we_installed(arg_esp_path);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_debug("Skipping update because sd-boot is not installed in the ESP.");
+ return 0;
+ }
+ }
+
+ r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ r = settle_make_entry_directory();
+ if (r < 0)
+ return r;
+
+ const char *arch = arg_arch_all ? "" : get_efi_arch();
+
+ WITH_UMASK(0002) {
+ if (install) {
+ /* Don't create any of these directories when we are just updating. When we update
+ * we'll drop-in our files (unless there are newer ones already), but we won't create
+ * the directories for them in the first place. */
+ r = create_subdirs(arg_esp_path, esp_subdirs);
+ if (r < 0)
+ return r;
+
+ r = create_subdirs(arg_dollar_boot_path(), dollar_boot_subdirs);
+ if (r < 0)
+ return r;
+ }
+
+ r = install_binaries(arg_esp_path, arch, install);
+ if (r < 0)
+ return r;
+
+ if (install) {
+ r = install_loader_config(arg_esp_path);
+ if (r < 0)
+ return r;
+
+ r = install_entry_directory(arg_dollar_boot_path());
+ if (r < 0)
+ return r;
+
+ r = install_entry_token();
+ if (r < 0)
+ return r;
+
+ r = install_random_seed(arg_esp_path);
+ if (r < 0)
+ return r;
+ }
+
+ r = install_loader_specification(arg_dollar_boot_path());
+ if (r < 0)
+ return r;
+ }
+
+ (void) sync_everything();
+
+ if (!arg_touch_variables)
+ return 0;
+
+ 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, graceful);
+}
+
+static int remove_boot_efi(const char *esp_path) {
+ _cleanup_closedir_ DIR *d = NULL;
+ _cleanup_free_ char *p = NULL;
+ int r, c = 0;
+
+ r = chase_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to open directory \"%s/EFI/BOOT\": %m", esp_path);
+
+ FOREACH_DIRENT(de, d, break) {
+ _cleanup_close_ int fd = -EBADF;
+ _cleanup_free_ char *v = NULL;
+
+ if (!endswith_no_case(de->d_name, ".efi"))
+ continue;
+
+ if (!startswith_no_case(de->d_name, "boot"))
+ continue;
+
+ fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open \"%s/%s\" for reading: %m", p, de->d_name);
+
+ r = get_file_version(fd, &v);
+ if (r == -ESRCH)
+ continue; /* No version information */
+ if (r < 0)
+ return r;
+ if (startswith(v, "systemd-boot ")) {
+ r = unlinkat(dirfd(d), de->d_name, 0);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to remove \"%s/%s\": %m", p, de->d_name);
+
+ log_info("Removed \"%s/%s\".", p, de->d_name);
+ }
+
+ c++;
+ }
+
+ return c;
+}
+
+static int rmdir_one(const char *prefix, const char *suffix) {
+ const char *p;
+
+ p = prefix_roota(prefix, suffix);
+ if (rmdir(p) < 0) {
+ bool ignore = IN_SET(errno, ENOENT, ENOTEMPTY);
+
+ log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, errno,
+ "Failed to remove directory \"%s\": %m", p);
+ if (!ignore)
+ return -errno;
+ } else
+ log_info("Removed \"%s\".", p);
+
+ return 0;
+}
+
+static int remove_subdirs(const char *root, const char *const *subdirs) {
+ int r, q;
+
+ /* We use recursion here to destroy the directories in reverse order. Which should be safe given how
+ * short the array is. */
+
+ if (!subdirs[0]) /* A the end of the list */
+ return 0;
+
+ r = remove_subdirs(root, subdirs + 1);
+ q = rmdir_one(root, subdirs[0]);
+
+ return r < 0 ? r : q;
+}
+
+static int remove_entry_directory(const char *root) {
+ assert(root);
+ assert(arg_make_entry_directory >= 0);
+
+ if (!arg_make_entry_directory || !arg_entry_token)
+ return 0;
+
+ return rmdir_one(root, arg_entry_token);
+}
+
+static int remove_binaries(const char *esp_path) {
+ const char *p;
+ int r, q;
+
+ p = prefix_roota(esp_path, "/EFI/systemd");
+ r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL);
+
+ q = remove_boot_efi(esp_path);
+ if (q < 0 && r == 0)
+ r = q;
+
+ return r;
+}
+
+static int remove_file(const char *root, const char *file) {
+ const char *p;
+
+ assert(root);
+ assert(file);
+
+ p = prefix_roota(root, file);
+ if (unlink(p) < 0) {
+ log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
+ "Failed to unlink file \"%s\": %m", p);
+
+ return errno == ENOENT ? 0 : -errno;
+ }
+
+ log_info("Removed \"%s\".", p);
+ return 1;
+}
+
+static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) {
+ uint16_t slot;
+ int r;
+
+ if (arg_root || !is_efi_boot())
+ return 0;
+
+ r = find_slot(uuid, path, &slot);
+ if (r != 1)
+ return 0;
+
+ r = efi_remove_boot_option(slot);
+ if (r < 0)
+ return r;
+
+ if (in_order)
+ return remove_from_order(slot);
+
+ return 0;
+}
+
+static int remove_loader_variables(void) {
+ int r = 0;
+
+ /* Remove all persistent loader variables we define */
+
+ FOREACH_STRING(var,
+ EFI_LOADER_VARIABLE(LoaderConfigConsoleMode),
+ EFI_LOADER_VARIABLE(LoaderConfigTimeout),
+ EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot),
+ EFI_LOADER_VARIABLE(LoaderEntryDefault),
+ EFI_LOADER_VARIABLE(LoaderEntryLastBooted),
+ EFI_LOADER_VARIABLE(LoaderEntryOneShot),
+ EFI_LOADER_VARIABLE(LoaderSystemToken)){
+
+ int q;
+
+ q = efi_set_variable(var, NULL, 0);
+ if (q == -ENOENT)
+ continue;
+ if (q < 0) {
+ log_warning_errno(q, "Failed to remove EFI variable %s: %m", var);
+ if (r >= 0)
+ r = q;
+ } else
+ log_info("Removed EFI variable %s.", var);
+ }
+
+ return r;
+}
+
+int verb_remove(int argc, char *argv[], void *userdata) {
+ sd_id128_t uuid = SD_ID128_NULL;
+ int r, q;
+
+ r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, NULL, NULL, NULL, &uuid, NULL);
+ if (r < 0)
+ return r;
+
+ r = acquire_xbootldr(/* unprivileged_mode= */ false, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ r = settle_make_entry_directory();
+ if (r < 0)
+ return r;
+
+ r = remove_binaries(arg_esp_path);
+
+ q = remove_file(arg_esp_path, "/loader/loader.conf");
+ if (q < 0 && r >= 0)
+ r = q;
+
+ q = remove_file(arg_esp_path, "/loader/random-seed");
+ if (q < 0 && r >= 0)
+ r = q;
+
+ q = remove_file(arg_esp_path, "/loader/entries.srel");
+ if (q < 0 && r >= 0)
+ r = q;
+
+ q = remove_subdirs(arg_esp_path, esp_subdirs);
+ if (q < 0 && r >= 0)
+ r = q;
+
+ q = remove_subdirs(arg_esp_path, dollar_boot_subdirs);
+ if (q < 0 && r >= 0)
+ r = q;
+
+ q = remove_entry_directory(arg_esp_path);
+ if (q < 0 && r >= 0)
+ r = q;
+
+ if (arg_xbootldr_path) {
+ /* Remove a subset of these also from the XBOOTLDR partition if it exists */
+
+ q = remove_file(arg_xbootldr_path, "/loader/entries.srel");
+ if (q < 0 && r >= 0)
+ r = q;
+
+ q = remove_subdirs(arg_xbootldr_path, dollar_boot_subdirs);
+ if (q < 0 && r >= 0)
+ r = q;
+
+ q = remove_entry_directory(arg_xbootldr_path);
+ if (q < 0 && r >= 0)
+ r = q;
+ }
+
+ (void) sync_everything();
+
+ if (!arg_touch_variables)
+ return r;
+
+ 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;
+
+ q = remove_loader_variables();
+ if (q < 0 && r >= 0)
+ r = q;
+
+ return r;
+}
+
+int verb_is_installed(int argc, char *argv[], void *userdata) {
+ int r;
+
+ r = acquire_esp(/* unprivileged_mode= */ false,
+ /* graceful= */ arg_graceful,
+ NULL, NULL, NULL, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ r = are_we_installed(arg_esp_path);
+ if (r < 0)
+ return r;
+
+ if (r > 0) {
+ if (!arg_quiet)
+ puts("yes");
+ return EXIT_SUCCESS;
+ } else {
+ if (!arg_quiet)
+ puts("no");
+ return EXIT_FAILURE;
+ }
+}
diff --git a/src/boot/bootctl-install.h b/src/boot/bootctl-install.h
new file mode 100644
index 0000000..cd4b725
--- /dev/null
+++ b/src/boot/bootctl-install.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int verb_install(int argc, char *argv[], void *userdata);
+int verb_remove(int argc, char *argv[], void *userdata);
+int verb_is_installed(int argc, char *argv[], void *userdata);
diff --git a/src/boot/bootctl-random-seed.c b/src/boot/bootctl-random-seed.c
new file mode 100644
index 0000000..cfe10c4
--- /dev/null
+++ b/src/boot/bootctl-random-seed.c
@@ -0,0 +1,239 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bootctl.h"
+#include "bootctl-random-seed.h"
+#include "bootctl-util.h"
+#include "efi-api.h"
+#include "env-util.h"
+#include "fd-util.h"
+#include "find-esp.h"
+#include "fs-util.h"
+#include "glyph-util.h"
+#include "io-util.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "random-util.h"
+#include "sha256.h"
+#include "tmpfile-util.h"
+#include "umask-util.h"
+
+static int random_seed_verify_permissions(int fd, mode_t expected_type) {
+ _cleanup_free_ char *full_path = NULL;
+ struct stat st;
+ int r;
+
+ assert(fd >= 0);
+
+ r = fd_get_path(fd, &full_path);
+ if (r < 0)
+ return log_error_errno(r, "Unable to determine full path of random seed fd: %m");
+
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Unable to stat %s: %m", full_path);
+
+ if (((st.st_mode ^ expected_type) & S_IFMT) != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADF),
+ "Unexpected inode type when validating random seed access mode on %s: %m", full_path);
+
+ if ((st.st_mode & 0007) == 0) /* All world bits are off? Then all is good */
+ return 0;
+
+ if (S_ISREG(expected_type))
+ log_warning("%s Random seed file '%s' is world accessible, which is a security hole! %s",
+ special_glyph(SPECIAL_GLYPH_WARNING_SIGN), full_path, special_glyph(SPECIAL_GLYPH_WARNING_SIGN));
+ else {
+ assert(S_ISDIR(expected_type));
+ log_warning("%s Mount point '%s' which backs the random seed file is world accessible, which is a security hole! %s",
+ special_glyph(SPECIAL_GLYPH_WARNING_SIGN), full_path, special_glyph(SPECIAL_GLYPH_WARNING_SIGN));
+ }
+
+ return 1;
+}
+
+static int set_system_token(void) {
+ uint8_t buffer[RANDOM_EFI_SEED_SIZE];
+ size_t token_size;
+ int r;
+
+ if (!arg_touch_variables)
+ return 0;
+
+ if (arg_root) {
+ log_warning("Acting on %s, skipping EFI variable setup.",
+ arg_image ? "image" : "root directory");
+ return 0;
+ }
+
+ if (!is_efi_boot()) {
+ log_notice("Not booted with EFI, skipping EFI variable setup.");
+ return 0;
+ }
+
+ r = getenv_bool("SYSTEMD_WRITE_SYSTEM_TOKEN");
+ if (r < 0) {
+ if (r != -ENXIO)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_WRITE_SYSTEM_TOKEN, ignoring.");
+ } else if (r == 0) {
+ log_notice("Not writing system token, because $SYSTEMD_WRITE_SYSTEM_TOKEN is set to false.");
+ return 0;
+ }
+
+ r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), NULL, NULL, &token_size);
+ if (r == -ENODATA)
+ log_debug_errno(r, "LoaderSystemToken EFI variable is invalid (too short?), replacing.");
+ else if (r < 0) {
+ if (r != -ENOENT)
+ return log_error_errno(r, "Failed to test system token validity: %m");
+ } else {
+ if (token_size >= sizeof(buffer)) {
+ /* Let's avoid writes if we can, and initialize this only once. */
+ log_debug("System token already written, not updating.");
+ return 0;
+ }
+
+ log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sizeof(buffer));
+ }
+
+ r = crypto_random_bytes(buffer, sizeof(buffer));
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire random seed: %m");
+
+ /* Let's write this variable with an umask in effect, so that unprivileged users can't see the token
+ * and possibly get identification information or too much insight into the kernel's entropy pool
+ * state. */
+ WITH_UMASK(0077) {
+ r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), buffer, sizeof(buffer));
+ if (r < 0) {
+ if (!arg_graceful)
+ return log_error_errno(r, "Failed to write 'LoaderSystemToken' EFI variable: %m");
+
+ if (r == -EINVAL)
+ log_notice_errno(r, "Unable to write 'LoaderSystemToken' EFI variable (firmware problem?), ignoring: %m");
+ else
+ log_notice_errno(r, "Unable to write 'LoaderSystemToken' EFI variable, ignoring: %m");
+ } else
+ log_info("Successfully initialized system token in EFI variable with %zu bytes.", sizeof(buffer));
+ }
+
+ return 0;
+}
+
+int install_random_seed(const char *esp) {
+ _cleanup_close_ int esp_fd = -EBADF, loader_dir_fd = -EBADF, fd = -EBADF;
+ _cleanup_free_ char *tmp = NULL;
+ uint8_t buffer[RANDOM_EFI_SEED_SIZE];
+ struct sha256_ctx hash_state;
+ bool refreshed, warned = false;
+ int r;
+
+ assert(esp);
+
+ assert_cc(RANDOM_EFI_SEED_SIZE == SHA256_DIGEST_SIZE);
+
+ esp_fd = open(esp, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+ if (esp_fd < 0)
+ return log_error_errno(errno, "Failed to open ESP directory '%s': %m", esp);
+
+ (void) random_seed_verify_permissions(esp_fd, S_IFDIR);
+
+ loader_dir_fd = open_mkdir_at(esp_fd, "loader", O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOFOLLOW, 0775);
+ if (loader_dir_fd < 0)
+ return log_error_errno(loader_dir_fd, "Failed to open loader directory '%s/loader': %m", esp);
+
+ r = crypto_random_bytes(buffer, sizeof(buffer));
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire random seed: %m");
+
+ sha256_init_ctx(&hash_state);
+ sha256_process_bytes_and_size(buffer, sizeof(buffer), &hash_state);
+
+ fd = openat(loader_dir_fd, "random-seed", O_NOFOLLOW|O_CLOEXEC|O_RDONLY|O_NOCTTY);
+ if (fd < 0) {
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to open old random seed file: %m");
+
+ sha256_process_bytes(&(const ssize_t) { 0 }, sizeof(ssize_t), &hash_state);
+ refreshed = false;
+ } else {
+ ssize_t n;
+
+ warned = random_seed_verify_permissions(fd, S_IFREG) > 0;
+
+ /* Hash the old seed in so that we never regress in entropy. */
+
+ n = read(fd, buffer, sizeof(buffer));
+ if (n < 0)
+ return log_error_errno(errno, "Failed to read old random seed file: %m");
+
+ sha256_process_bytes_and_size(buffer, n, &hash_state);
+
+ fd = safe_close(fd);
+ refreshed = n > 0;
+ }
+
+ sha256_finish_ctx(&hash_state, buffer);
+
+ if (tempfn_random("random-seed", "bootctl", &tmp) < 0)
+ return log_oom();
+
+ fd = openat(loader_dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|O_WRONLY|O_CLOEXEC, 0600);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to open random seed file for writing: %m");
+
+ if (!warned) /* only warn once per seed file */
+ (void) random_seed_verify_permissions(fd, S_IFREG);
+
+ r = loop_write(fd, buffer, sizeof(buffer));
+ if (r < 0) {
+ log_error_errno(r, "Failed to write random seed file: %m");
+ goto fail;
+ }
+
+ if (fsync(fd) < 0 || fsync(loader_dir_fd) < 0) {
+ r = log_error_errno(errno, "Failed to sync random seed file: %m");
+ goto fail;
+ }
+
+ if (renameat(loader_dir_fd, tmp, loader_dir_fd, "random-seed") < 0) {
+ r = log_error_errno(errno, "Failed to move random seed file into place: %m");
+ goto fail;
+ }
+
+ tmp = mfree(tmp);
+
+ if (syncfs(fd) < 0)
+ return log_error_errno(errno, "Failed to sync ESP file system: %m");
+
+ log_info("Random seed file %s/loader/random-seed successfully %s (%zu bytes).", esp, refreshed ? "refreshed" : "written", sizeof(buffer));
+
+ return set_system_token();
+
+fail:
+ assert(tmp);
+ (void) unlinkat(loader_dir_fd, tmp, 0);
+
+ return r;
+}
+
+int verb_random_seed(int argc, char *argv[], void *userdata) {
+ int r;
+
+ r = find_esp_and_warn(arg_root, arg_esp_path, false, &arg_esp_path, NULL, NULL, NULL, NULL, NULL);
+ if (r == -ENOKEY) {
+ /* find_esp_and_warn() doesn't warn about ENOKEY, so let's do that on our own */
+ if (!arg_graceful)
+ return log_error_errno(r, "Unable to find ESP.");
+
+ log_notice("No ESP found, not initializing random seed.");
+ return 0;
+ }
+ if (r < 0)
+ return r;
+
+ r = install_random_seed(arg_esp_path);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
diff --git a/src/boot/bootctl-random-seed.h b/src/boot/bootctl-random-seed.h
new file mode 100644
index 0000000..91596d3
--- /dev/null
+++ b/src/boot/bootctl-random-seed.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int install_random_seed(const char *esp);
+
+int verb_random_seed(int argc, char *argv[], void *userdata);
diff --git a/src/boot/bootctl-reboot-to-firmware.c b/src/boot/bootctl-reboot-to-firmware.c
new file mode 100644
index 0000000..91f2597
--- /dev/null
+++ b/src/boot/bootctl-reboot-to-firmware.c
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bootctl-reboot-to-firmware.h"
+#include "efi-api.h"
+#include "parse-util.h"
+
+int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) {
+ int r;
+
+ if (argc < 2) {
+ r = efi_get_reboot_to_firmware();
+ if (r > 0) {
+ puts("active");
+ return 0; /* success */
+ }
+ if (r == 0) {
+ puts("supported");
+ return 1; /* recognizable error #1 */
+ }
+ if (r == -EOPNOTSUPP) {
+ puts("not supported");
+ return 2; /* recognizable error #2 */
+ }
+
+ log_error_errno(r, "Failed to query reboot-to-firmware state: %m");
+ return 3; /* other kind of error */
+ } else {
+ r = parse_boolean(argv[1]);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse argument: %s", argv[1]);
+
+ r = efi_set_reboot_to_firmware(r);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set reboot-to-firmware option: %m");
+
+ return 0;
+ }
+}
diff --git a/src/boot/bootctl-reboot-to-firmware.h b/src/boot/bootctl-reboot-to-firmware.h
new file mode 100644
index 0000000..0ca4b2c
--- /dev/null
+++ b/src/boot/bootctl-reboot-to-firmware.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+int verb_reboot_to_firmware(int argc, char *argv[], void *userdata);
diff --git a/src/boot/bootctl-set-efivar.c b/src/boot/bootctl-set-efivar.c
new file mode 100644
index 0000000..cb2ed0d
--- /dev/null
+++ b/src/boot/bootctl-set-efivar.c
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <uchar.h>
+#include <unistd.h>
+
+#include "bootctl.h"
+#include "bootctl-set-efivar.h"
+#include "efivars.h"
+#include "efi-loader.h"
+#include "stdio-util.h"
+#include "utf8.h"
+#include "virt.h"
+
+static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_timeout_size) {
+ char utf8[DECIMAL_STR_MAX(usec_t)];
+ char16_t *encoded;
+ usec_t timeout;
+ bool menu_disabled = false;
+ int r;
+
+ assert(arg1);
+ assert(ret_timeout);
+ assert(ret_timeout_size);
+
+ assert_cc(STRLEN("menu-disabled") < ELEMENTSOF(utf8));
+
+ /* Note: Since there is no way to query if the bootloader supports the string tokens, we explicitly
+ * set their numerical value(s) instead. This means that some of the sd-boot internal ABI has leaked
+ * although the ship has sailed and the side-effects are self-contained.
+ */
+ if (streq(arg1, "menu-force"))
+ timeout = USEC_INFINITY;
+ else if (streq(arg1, "menu-hidden"))
+ timeout = 0;
+ else if (streq(arg1, "menu-disabled")) {
+ uint64_t loader_features = 0;
+
+ (void) efi_loader_get_features(&loader_features);
+ if (!(loader_features & EFI_LOADER_FEATURE_MENU_DISABLE)) {
+ if (!arg_graceful)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Loader does not support 'menu-disabled': %m");
+
+ log_warning("Loader does not support 'menu-disabled', setting anyway.");
+ }
+ menu_disabled = true;
+ } else {
+ r = parse_time(arg1, &timeout, USEC_PER_SEC);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse timeout '%s': %m", arg1);
+ if (timeout != USEC_INFINITY && timeout > UINT32_MAX * USEC_PER_SEC)
+ log_warning("Timeout is too long and will be treated as 'menu-force' instead.");
+ }
+
+ if (menu_disabled)
+ xsprintf(utf8, "menu-disabled");
+ else
+ xsprintf(utf8, USEC_FMT, MIN(timeout / USEC_PER_SEC, UINT32_MAX));
+
+ encoded = utf8_to_utf16(utf8, SIZE_MAX);
+ if (!encoded)
+ return log_oom();
+
+ *ret_timeout = encoded;
+ *ret_timeout_size = char16_strlen(encoded) * 2 + 2;
+ return 0;
+}
+
+static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target, size_t *ret_target_size) {
+ char16_t *encoded = NULL;
+ int r;
+
+ assert(arg1);
+ assert(ret_target);
+ assert(ret_target_size);
+
+ if (streq(arg1, "@current")) {
+ r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntrySelected), NULL, (void *) ret_target, ret_target_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get EFI variable 'LoaderEntrySelected': %m");
+
+ } else if (streq(arg1, "@oneshot")) {
+ r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntryOneShot), NULL, (void *) ret_target, ret_target_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryOneShot': %m");
+
+ } else if (streq(arg1, "@default")) {
+ r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntryDefault), NULL, (void *) ret_target, ret_target_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryDefault': %m");
+
+ } else if (arg1[0] == '@' && !streq(arg1, "@saved"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported special entry identifier: %s", arg1);
+ else {
+ encoded = utf8_to_utf16(arg1, SIZE_MAX);
+ if (!encoded)
+ return log_oom();
+
+ *ret_target = encoded;
+ *ret_target_size = char16_strlen(encoded) * 2 + 2;
+ }
+
+ return 0;
+}
+
+int verb_set_efivar(int argc, char *argv[], void *userdata) {
+ int r;
+
+ if (arg_root)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Acting on %s, skipping EFI variable setup.",
+ arg_image ? "image" : "root directory");
+
+ if (!is_efi_boot())
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Not booted with UEFI.");
+
+ if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderInfo)), F_OK) < 0) {
+ if (errno == ENOENT) {
+ log_error_errno(errno, "Not booted with a supported boot loader.");
+ return -EOPNOTSUPP;
+ }
+
+ return log_error_errno(errno, "Failed to detect whether boot loader supports '%s' operation: %m", argv[0]);
+ }
+
+ if (detect_container() > 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "'%s' operation not supported in a container.",
+ argv[0]);
+
+ if (!arg_touch_variables)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "'%s' operation cannot be combined with --no-variables.",
+ argv[0]);
+
+ const char *variable;
+ int (* arg_parser)(const char *, char16_t **, size_t *);
+
+ if (streq(argv[0], "set-default")) {
+ variable = EFI_LOADER_VARIABLE(LoaderEntryDefault);
+ arg_parser = parse_loader_entry_target_arg;
+ } else if (streq(argv[0], "set-oneshot")) {
+ variable = EFI_LOADER_VARIABLE(LoaderEntryOneShot);
+ arg_parser = parse_loader_entry_target_arg;
+ } else if (streq(argv[0], "set-timeout")) {
+ variable = EFI_LOADER_VARIABLE(LoaderConfigTimeout);
+ arg_parser = parse_timeout;
+ } else if (streq(argv[0], "set-timeout-oneshot")) {
+ variable = EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot);
+ arg_parser = parse_timeout;
+ } else
+ assert_not_reached();
+
+ if (isempty(argv[1])) {
+ r = efi_set_variable(variable, NULL, 0);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(r, "Failed to remove EFI variable '%s': %m", variable);
+ } else {
+ _cleanup_free_ char16_t *value = NULL;
+ size_t value_size = 0;
+
+ r = arg_parser(argv[1], &value, &value_size);
+ if (r < 0)
+ return r;
+ r = efi_set_variable(variable, value, value_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to update EFI variable '%s': %m", variable);
+ }
+
+ return 0;
+}
diff --git a/src/boot/bootctl-set-efivar.h b/src/boot/bootctl-set-efivar.h
new file mode 100644
index 0000000..6441681
--- /dev/null
+++ b/src/boot/bootctl-set-efivar.h
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int verb_set_efivar(int argc, char *argv[], void *userdata);
diff --git a/src/boot/bootctl-status.c b/src/boot/bootctl-status.c
new file mode 100644
index 0000000..d171512
--- /dev/null
+++ b/src/boot/bootctl-status.c
@@ -0,0 +1,829 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "bootctl.h"
+#include "bootctl-status.h"
+#include "bootctl-util.h"
+#include "bootspec.h"
+#include "chase.h"
+#include "devnum-util.h"
+#include "dirent-util.h"
+#include "efi-api.h"
+#include "efi-loader.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "find-esp.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "recurse-dir.h"
+#include "terminal-util.h"
+#include "tpm2-util.h"
+
+static int boot_config_load_and_select(
+ BootConfig *config,
+ const char *esp_path,
+ dev_t esp_devid,
+ const char *xbootldr_path,
+ dev_t xbootldr_devid) {
+
+ int r;
+
+ /* If XBOOTLDR and ESP actually refer to the same block device, suppress XBOOTLDR, since it would
+ * find the same entries twice. */
+ bool same = esp_path && xbootldr_path && devnum_set_and_equal(esp_devid, xbootldr_devid);
+
+ r = boot_config_load(config, esp_path, same ? NULL : xbootldr_path);
+ if (r < 0)
+ return r;
+
+ if (!arg_root) {
+ _cleanup_strv_free_ char **efi_entries = NULL;
+
+ r = efi_loader_get_entries(&efi_entries);
+ if (r == -ENOENT || ERRNO_IS_NEG_NOT_SUPPORTED(r))
+ log_debug_errno(r, "Boot loader reported no entries.");
+ else if (r < 0)
+ log_warning_errno(r, "Failed to determine entries reported by boot loader, ignoring: %m");
+ else
+ (void) boot_config_augment_from_loader(config, efi_entries, /* only_auto= */ false);
+ }
+
+ return boot_config_select_special_entries(config, /* skip_efivars= */ !!arg_root);
+}
+
+static int status_entries(
+ const BootConfig *config,
+ const char *esp_path,
+ sd_id128_t esp_partition_uuid,
+ const char *xbootldr_path,
+ sd_id128_t xbootldr_partition_uuid) {
+
+ sd_id128_t dollar_boot_partition_uuid;
+ const char *dollar_boot_path;
+ int r;
+
+ assert(config);
+ assert(esp_path || xbootldr_path);
+
+ if (xbootldr_path) {
+ dollar_boot_path = xbootldr_path;
+ dollar_boot_partition_uuid = xbootldr_partition_uuid;
+ } else {
+ dollar_boot_path = esp_path;
+ dollar_boot_partition_uuid = esp_partition_uuid;
+ }
+
+ printf("%sBoot Loader Entries:%s\n"
+ " $BOOT: %s", ansi_underline(), ansi_normal(), dollar_boot_path);
+ if (!sd_id128_is_null(dollar_boot_partition_uuid))
+ printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")",
+ SD_ID128_FORMAT_VAL(dollar_boot_partition_uuid));
+ if (settle_entry_token() >= 0)
+ printf("\n token: %s", arg_entry_token);
+ printf("\n\n");
+
+ if (config->default_entry < 0)
+ printf("%zu entries, no entry could be determined as default.\n", config->n_entries);
+ else {
+ printf("%sDefault Boot Loader Entry:%s\n", ansi_underline(), ansi_normal());
+
+ r = show_boot_entry(
+ boot_config_default_entry(config),
+ /* show_as_default= */ false,
+ /* show_as_selected= */ false,
+ /* show_discovered= */ false);
+ if (r > 0)
+ /* < 0 is already logged by the function itself, let's just emit an extra warning if
+ the default entry is broken */
+ printf("\nWARNING: default boot entry is broken\n");
+ }
+
+ return 0;
+}
+
+static int print_efi_option(uint16_t id, int *n_printed, bool in_order) {
+ _cleanup_free_ char *title = NULL;
+ _cleanup_free_ char *path = NULL;
+ sd_id128_t partition;
+ bool active;
+ int r;
+
+ assert(n_printed);
+
+ r = efi_get_boot_option(id, &title, &partition, &path, &active);
+ if (r == -ENOENT) {
+ log_debug_errno(r, "Boot option 0x%04X referenced but missing, ignoring: %m", id);
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to read boot option 0x%04X: %m", id);
+
+ /* print only configured entries with partition information */
+ if (!path || sd_id128_is_null(partition)) {
+ log_debug("Ignoring boot entry 0x%04X without partition information.", id);
+ return 0;
+ }
+
+ efi_tilt_backslashes(path);
+
+ if (*n_printed == 0) /* Print section title before first entry */
+ printf("%sBoot Loaders Listed in EFI Variables:%s\n", ansi_underline(), ansi_normal());
+
+ printf(" Title: %s%s%s\n", ansi_highlight(), strna(title), ansi_normal());
+ printf(" ID: 0x%04X\n", id);
+ printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : "");
+ printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n",
+ SD_ID128_FORMAT_VAL(partition));
+ printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path);
+ printf("\n");
+
+ (*n_printed)++;
+ return 1;
+}
+
+static int status_variables(void) {
+ _cleanup_free_ uint16_t *options = NULL, *order = NULL;
+ int n_options, n_order, n_printed = 0;
+
+ n_options = efi_get_boot_options(&options);
+ if (n_options == -ENOENT)
+ return log_error_errno(n_options,
+ "Failed to access EFI variables, efivarfs"
+ " needs to be available at /sys/firmware/efi/efivars/.");
+ if (n_options < 0)
+ return log_error_errno(n_options, "Failed to read EFI boot entries: %m");
+
+ n_order = efi_get_boot_order(&order);
+ if (n_order == -ENOENT)
+ n_order = 0;
+ else if (n_order < 0)
+ return log_error_errno(n_order, "Failed to read EFI boot order: %m");
+
+ /* print entries in BootOrder first */
+ for (int i = 0; i < n_order; i++)
+ (void) print_efi_option(order[i], &n_printed, /* in_order= */ true);
+
+ /* print remaining entries */
+ for (int i = 0; i < n_options; i++) {
+ for (int j = 0; j < n_order; j++)
+ if (options[i] == order[j])
+ goto next_option;
+
+ (void) print_efi_option(options[i], &n_printed, /* in_order= */ false);
+
+ next_option:
+ continue;
+ }
+
+ if (n_printed == 0)
+ printf("No boot loaders listed in EFI Variables.\n\n");
+
+ return 0;
+}
+
+static int enumerate_binaries(
+ const char *esp_path,
+ const char *path,
+ const char *prefix,
+ char **previous,
+ bool *is_first) {
+
+ _cleanup_closedir_ DIR *d = NULL;
+ _cleanup_free_ char *p = NULL;
+ int c = 0, r;
+
+ assert(esp_path);
+ assert(path);
+ assert(previous);
+ assert(is_first);
+
+ r = chase_and_opendir(path, esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to read \"%s/%s\": %m", esp_path, path);
+
+ FOREACH_DIRENT(de, d, break) {
+ _cleanup_free_ char *v = NULL, *filename = NULL;
+ _cleanup_close_ int fd = -EBADF;
+
+ if (!endswith_no_case(de->d_name, ".efi"))
+ continue;
+
+ if (prefix && !startswith_no_case(de->d_name, prefix))
+ continue;
+
+ filename = path_join(p, de->d_name);
+ if (!filename)
+ return log_oom();
+ LOG_SET_PREFIX(filename);
+
+ fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open file for reading: %m");
+
+ r = get_file_version(fd, &v);
+
+ if (r < 0 && r != -ESRCH)
+ return r;
+
+ if (*previous) { /* Let's output the previous entry now, since now we know that there will be
+ * one more, and can draw the tree glyph properly. */
+ printf(" %s %s%s\n",
+ *is_first ? "File:" : " ",
+ special_glyph(SPECIAL_GLYPH_TREE_BRANCH), *previous);
+ *is_first = false;
+ *previous = mfree(*previous);
+ }
+
+ /* Do not output this entry immediately, but store what should be printed in a state
+ * variable, because we only will know the tree glyph to print (branch or final edge) once we
+ * read one more entry */
+ if (r == -ESRCH) /* No systemd-owned file but still interesting to print */
+ r = asprintf(previous, "/%s/%s", path, de->d_name);
+ else /* if (r >= 0) */
+ r = asprintf(previous, "/%s/%s (%s%s%s)", path, de->d_name, ansi_highlight(), v, ansi_normal());
+ if (r < 0)
+ return log_oom();
+
+ c++;
+ }
+
+ return c;
+}
+
+static int status_binaries(const char *esp_path, sd_id128_t partition) {
+ _cleanup_free_ char *last = NULL;
+ bool is_first = true;
+ int r, k;
+
+ printf("%sAvailable Boot Loaders on ESP:%s\n", ansi_underline(), ansi_normal());
+
+ if (!esp_path) {
+ printf(" ESP: Cannot find or access mount point of ESP.\n\n");
+ return -ENOENT;
+ }
+
+ printf(" ESP: %s", esp_path);
+ if (!sd_id128_is_null(partition))
+ printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(partition));
+ printf("\n");
+
+ r = enumerate_binaries(esp_path, "EFI/systemd", NULL, &last, &is_first);
+ if (r < 0)
+ goto fail;
+
+ k = enumerate_binaries(esp_path, "EFI/BOOT", "boot", &last, &is_first);
+ if (k < 0) {
+ r = k;
+ goto fail;
+ }
+
+ if (last) /* let's output the last entry now, since now we know that there will be no more, and can draw the tree glyph properly */
+ printf(" %s %s%s\n",
+ is_first ? "File:" : " ",
+ special_glyph(SPECIAL_GLYPH_TREE_RIGHT), last);
+
+ if (r == 0 && !arg_quiet)
+ log_info("systemd-boot not installed in ESP.");
+ if (k == 0 && !arg_quiet)
+ log_info("No default/fallback boot loader installed in ESP.");
+
+ printf("\n");
+ return 0;
+
+fail:
+ errno = -r;
+ printf(" File: (can't access %s: %m)\n\n", esp_path);
+ return r;
+}
+
+static void read_efi_var(const char *variable, char **ret) {
+ int r;
+
+ r = efi_get_variable_string(variable, ret);
+ if (r < 0 && r != -ENOENT)
+ log_warning_errno(r, "Failed to read EFI variable %s: %m", variable);
+}
+
+static void print_yes_no_line(bool first, bool good, const char *name) {
+ printf("%s%s %s\n",
+ first ? " Features: " : " ",
+ COLOR_MARK_BOOL(good),
+ name);
+}
+
+int verb_status(int argc, char *argv[], void *userdata) {
+ sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL;
+ dev_t esp_devid = 0, xbootldr_devid = 0;
+ int r, k;
+
+ r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, NULL, NULL, NULL, &esp_uuid, &esp_devid);
+ if (arg_print_esp_path) {
+ if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only
+ * error the find_esp_and_warn() won't log on its own) */
+ return log_error_errno(r, "Failed to determine ESP location: %m");
+ if (r < 0)
+ return r;
+
+ puts(arg_esp_path);
+ return 0;
+ }
+
+ r = acquire_xbootldr(/* unprivileged_mode= */ -1, &xbootldr_uuid, &xbootldr_devid);
+ if (arg_print_dollar_boot_path) {
+ if (r == -EACCES)
+ return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
+ if (r < 0)
+ return r;
+
+ const char *path = arg_dollar_boot_path();
+ if (!path)
+ return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Failed to determine XBOOTLDR location: %m");
+
+ puts(path);
+ return 0;
+ }
+
+ r = 0; /* If we couldn't determine the path, then don't consider that a problem from here on, just
+ * show what we can show */
+
+ pager_open(arg_pager_flags);
+
+ if (!arg_root && is_efi_boot()) {
+ static const struct {
+ uint64_t flag;
+ const char *name;
+ } loader_flags[] = {
+ { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" },
+ { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" },
+ { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" },
+ { EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" },
+ { EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" },
+ { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" },
+ { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" },
+ { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" },
+ { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" },
+ { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" },
+ { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" },
+ { EFI_LOADER_FEATURE_SECUREBOOT_ENROLL, "Enroll SecureBoot keys" },
+ { EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" },
+ { EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" },
+ };
+ static const struct {
+ uint64_t flag;
+ const char *name;
+ } stub_flags[] = {
+ { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets ESP information" },
+ { EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" },
+ { EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" },
+ { EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" },
+ { EFI_STUB_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" },
+ { EFI_STUB_FEATURE_CMDLINE_ADDONS, "Pick up .cmdline from addons" },
+ { EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" },
+ { EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" },
+ };
+ _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL;
+ sd_id128_t loader_part_uuid = SD_ID128_NULL;
+ uint64_t loader_features = 0, stub_features = 0;
+ Tpm2Support s;
+ int have;
+
+ read_efi_var(EFI_LOADER_VARIABLE(LoaderFirmwareType), &fw_type);
+ read_efi_var(EFI_LOADER_VARIABLE(LoaderFirmwareInfo), &fw_info);
+ read_efi_var(EFI_LOADER_VARIABLE(LoaderInfo), &loader);
+ read_efi_var(EFI_LOADER_VARIABLE(StubInfo), &stub);
+ read_efi_var(EFI_LOADER_VARIABLE(LoaderImageIdentifier), &loader_path);
+ (void) efi_loader_get_features(&loader_features);
+ (void) efi_stub_get_features(&stub_features);
+
+ if (loader_path)
+ efi_tilt_backslashes(loader_path);
+
+ k = efi_loader_get_device_part_uuid(&loader_part_uuid);
+ if (k < 0 && k != -ENOENT)
+ r = log_warning_errno(k, "Failed to read EFI variable LoaderDevicePartUUID: %m");
+
+ SecureBootMode secure = efi_get_secure_boot_mode();
+ printf("%sSystem:%s\n", ansi_underline(), ansi_normal());
+ 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: %s%s%s",
+ IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED) ? ansi_highlight_green() : ansi_normal(),
+ enabled_disabled(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)),
+ ansi_normal());
+
+ if (secure != SECURE_BOOT_DISABLED)
+ printf(" (%s)\n", secure_boot_mode_to_string(secure));
+ else
+ printf("\n");
+
+ s = tpm2_support();
+ 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" :
+ (s & TPM2_SUPPORT_FIRMWARE) ? "firmware only, driver unavailable" :
+ (s & TPM2_SUPPORT_DRIVER) ? "driver only, firmware unavailable" : "no",
+ ansi_normal());
+
+ k = efi_measured_uki(LOG_DEBUG);
+ if (k > 0)
+ printf(" Measured UKI: %syes%s\n", ansi_highlight_green(), ansi_normal());
+ else if (k == 0)
+ printf(" Measured UKI: no\n");
+ else {
+ errno = -k;
+ printf(" Measured UKI: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal());
+ }
+
+ k = efi_get_reboot_to_firmware();
+ if (k > 0)
+ printf(" Boot into FW: %sactive%s\n", ansi_highlight_yellow(), ansi_normal());
+ else if (k == 0)
+ printf(" Boot into FW: supported\n");
+ else if (k == -EOPNOTSUPP)
+ printf(" Boot into FW: not supported\n");
+ else {
+ errno = -k;
+ printf(" Boot into FW: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal());
+ }
+ printf("\n");
+
+ printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal());
+ printf(" Product: %s%s%s\n", ansi_highlight(), strna(loader), ansi_normal());
+
+ for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++)
+ print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name);
+
+ sd_id128_t bootloader_esp_uuid;
+ bool have_bootloader_esp_uuid = efi_loader_get_device_part_uuid(&bootloader_esp_uuid) >= 0;
+
+ print_yes_no_line(false, have_bootloader_esp_uuid, "Boot loader sets ESP information");
+ if (have_bootloader_esp_uuid && !sd_id128_is_null(esp_uuid) &&
+ !sd_id128_equal(esp_uuid, bootloader_esp_uuid))
+ printf("WARNING: The boot loader reports a different ESP UUID than detected ("SD_ID128_UUID_FORMAT_STR" vs. "SD_ID128_UUID_FORMAT_STR")!\n",
+ SD_ID128_FORMAT_VAL(bootloader_esp_uuid),
+ SD_ID128_FORMAT_VAL(esp_uuid));
+
+ if (stub) {
+ printf(" Stub: %s\n", stub);
+ for (size_t i = 0; i < ELEMENTSOF(stub_flags); i++)
+ print_yes_no_line(i == 0, FLAGS_SET(stub_features, stub_flags[i].flag), stub_flags[i].name);
+ }
+ if (!sd_id128_is_null(loader_part_uuid))
+ printf(" ESP: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n",
+ SD_ID128_FORMAT_VAL(loader_part_uuid));
+ else
+ printf(" ESP: n/a\n");
+ printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(loader_path));
+ printf("\n");
+
+ printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal());
+ have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), F_OK) >= 0;
+ printf(" System Token: %s\n", have ? "set" : "not set");
+
+ if (arg_esp_path) {
+ _cleanup_free_ char *p = NULL;
+
+ p = path_join(arg_esp_path, "/loader/random-seed");
+ if (!p)
+ return log_oom();
+
+ have = access(p, F_OK) >= 0;
+ printf(" Exists: %s\n", yes_no(have));
+ }
+
+ printf("\n");
+ } else
+ printf("%sSystem:%s\n"
+ "Not booted with EFI\n\n",
+ ansi_underline(), ansi_normal());
+
+ if (arg_esp_path)
+ RET_GATHER(r, status_binaries(arg_esp_path, esp_uuid));
+
+ if (!arg_root && is_efi_boot())
+ RET_GATHER(r, status_variables());
+
+ if (arg_esp_path || arg_xbootldr_path) {
+ _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
+
+ k = boot_config_load_and_select(&config,
+ arg_esp_path, esp_devid,
+ arg_xbootldr_path, xbootldr_devid);
+ RET_GATHER(r, k);
+
+ if (k >= 0)
+ RET_GATHER(r,
+ status_entries(&config,
+ arg_esp_path, esp_uuid,
+ arg_xbootldr_path, xbootldr_uuid));
+ }
+
+ return r;
+}
+
+static int ref_file(Hashmap *known_files, const char *fn, int increment) {
+ char *k = NULL;
+ int n, r;
+
+ assert(known_files);
+
+ /* just gracefully ignore this. This way the caller doesn't
+ have to verify whether the bootloader entry is relevant */
+ if (!fn)
+ return 0;
+
+ n = PTR_TO_INT(hashmap_get2(known_files, fn, (void**)&k));
+ n += increment;
+
+ assert(n >= 0);
+
+ if (n == 0) {
+ (void) hashmap_remove(known_files, fn);
+ free(k);
+ } else if (!k) {
+ _cleanup_free_ char *t = NULL;
+
+ t = strdup(fn);
+ if (!t)
+ return -ENOMEM;
+ r = hashmap_put(known_files, t, INT_TO_PTR(n));
+ if (r < 0)
+ return r;
+ TAKE_PTR(t);
+ } else {
+ r = hashmap_update(known_files, fn, INT_TO_PTR(n));
+ if (r < 0)
+ return r;
+ }
+
+ return n;
+}
+
+static void deref_unlink_file(Hashmap *known_files, const char *fn, const char *root) {
+ _cleanup_free_ char *path = NULL;
+ int r;
+
+ assert(known_files);
+
+ /* just gracefully ignore this. This way the caller doesn't
+ have to verify whether the bootloader entry is relevant */
+ if (!fn || !root)
+ return;
+
+ r = ref_file(known_files, fn, -1);
+ if (r < 0)
+ return (void) log_warning_errno(r, "Failed to deref \"%s\", ignoring: %m", fn);
+ if (r > 0)
+ return;
+
+ if (arg_dry_run) {
+ r = chase_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, &path);
+ if (r < 0)
+ log_info_errno(r, "Unable to determine whether \"%s\" exists, ignoring: %m", fn);
+ else
+ log_info("Would remove \"%s\"", path);
+ return;
+ }
+
+ r = chase_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, 0, &path);
+ if (r >= 0)
+ log_info("Removed \"%s\"", path);
+ else if (r != -ENOENT)
+ return (void) log_warning_errno(r, "Failed to remove \"%s\", ignoring: %m", fn);
+
+ _cleanup_free_ char *d = NULL;
+ if (path_extract_directory(fn, &d) >= 0 && !path_equal(d, "/")) {
+ r = chase_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, AT_REMOVEDIR, NULL);
+ if (r < 0 && !IN_SET(r, -ENOTEMPTY, -ENOENT))
+ log_warning_errno(r, "Failed to remove directory \"%s\", ignoring: %m", d);
+ }
+}
+
+static int count_known_files(const BootConfig *config, const char* root, Hashmap **ret_known_files) {
+ _cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL;
+ int r;
+
+ assert(config);
+ assert(ret_known_files);
+
+ known_files = hashmap_new(&path_hash_ops);
+ if (!known_files)
+ return -ENOMEM;
+
+ for (size_t i = 0; i < config->n_entries; i++) {
+ const BootEntry *e = config->entries + i;
+
+ if (!path_equal(e->root, root))
+ continue;
+
+ r = ref_file(known_files, e->kernel, +1);
+ if (r < 0)
+ return r;
+ r = ref_file(known_files, e->efi, +1);
+ if (r < 0)
+ return r;
+ STRV_FOREACH(s, e->initrd) {
+ r = ref_file(known_files, *s, +1);
+ if (r < 0)
+ return r;
+ }
+ r = ref_file(known_files, e->device_tree, +1);
+ if (r < 0)
+ return r;
+ STRV_FOREACH(s, e->device_tree_overlay) {
+ r = ref_file(known_files, *s, +1);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ *ret_known_files = TAKE_PTR(known_files);
+
+ return 0;
+}
+
+static int boot_config_find_in(const BootConfig *config, const char *root, const char *id) {
+ assert(config);
+
+ if (!root || !id)
+ return -ENOENT;
+
+ for (size_t i = 0; i < config->n_entries; i++)
+ if (path_equal(config->entries[i].root, root) &&
+ fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0)
+ return i;
+
+ return -ENOENT;
+}
+
+static int unlink_entry(const BootConfig *config, const char *root, const char *id) {
+ _cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL;
+ const BootEntry *e = NULL;
+ int r;
+
+ assert(config);
+
+ r = count_known_files(config, root, &known_files);
+ if (r < 0)
+ return log_error_errno(r, "Failed to count files in %s: %m", root);
+
+ r = boot_config_find_in(config, root, id);
+ if (r < 0)
+ return r;
+
+ if (r == config->default_entry)
+ log_warning("%s is the default boot entry", id);
+ if (r == config->selected_entry)
+ log_warning("%s is the selected boot entry", id);
+
+ e = &config->entries[r];
+
+ deref_unlink_file(known_files, e->kernel, e->root);
+ deref_unlink_file(known_files, e->efi, e->root);
+ STRV_FOREACH(s, e->initrd)
+ deref_unlink_file(known_files, *s, e->root);
+ deref_unlink_file(known_files, e->device_tree, e->root);
+ STRV_FOREACH(s, e->device_tree_overlay)
+ deref_unlink_file(known_files, *s, e->root);
+
+ if (arg_dry_run)
+ log_info("Would remove \"%s\"", e->path);
+ else {
+ r = chase_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS, 0, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to remove \"%s\": %m", e->path);
+
+ log_info("Removed %s", e->path);
+ }
+
+ return 0;
+}
+
+static int list_remove_orphaned_file(
+ RecurseDirEvent event,
+ const char *path,
+ int dir_fd,
+ int inode_fd,
+ const struct dirent *de,
+ const struct statx *sx,
+ void *userdata) {
+
+ Hashmap *known_files = userdata;
+
+ assert(path);
+ assert(known_files);
+
+ if (event != RECURSE_DIR_ENTRY)
+ return RECURSE_DIR_CONTINUE;
+
+ if (hashmap_get(known_files, path))
+ return RECURSE_DIR_CONTINUE; /* keep! */
+
+ if (arg_dry_run)
+ log_info("Would remove %s", path);
+ else if (unlinkat(dir_fd, de->d_name, 0) < 0)
+ log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", path);
+ else
+ log_info("Removed %s", path);
+
+ return RECURSE_DIR_CONTINUE;
+}
+
+static int cleanup_orphaned_files(
+ const BootConfig *config,
+ const char *root) {
+
+ _cleanup_(hashmap_free_free_keyp) Hashmap *known_files = NULL;
+ _cleanup_free_ char *full = NULL, *p = NULL;
+ _cleanup_close_ int dir_fd = -EBADF;
+ int r;
+
+ assert(config);
+ assert(root);
+
+ log_info("Cleaning %s", root);
+
+ r = settle_entry_token();
+ if (r < 0)
+ return r;
+
+ r = count_known_files(config, root, &known_files);
+ if (r < 0)
+ return log_error_errno(r, "Failed to count files in %s: %m", root);
+
+ dir_fd = chase_and_open(arg_entry_token, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS,
+ O_DIRECTORY|O_CLOEXEC, &full);
+ if (dir_fd == -ENOENT)
+ return 0;
+ if (dir_fd < 0)
+ return log_error_errno(dir_fd, "Failed to open '%s/%s': %m", root, arg_entry_token);
+
+ p = path_join("/", arg_entry_token);
+ if (!p)
+ return log_oom();
+
+ r = recurse_dir(dir_fd, p, 0, UINT_MAX, RECURSE_DIR_SORT, list_remove_orphaned_file, known_files);
+ if (r < 0)
+ return log_error_errno(r, "Failed to cleanup %s: %m", full);
+
+ return r;
+}
+
+int verb_list(int argc, char *argv[], void *userdata) {
+ _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
+ dev_t esp_devid = 0, xbootldr_devid = 0;
+ int r;
+
+ /* If we lack privileges we invoke find_esp_and_warn() in "unprivileged mode" here, which does two
+ * things: turn off logging about access errors and turn off potentially privileged device probing.
+ * Here we're interested in the latter but not the former, hence request the mode, and log about
+ * EACCES. */
+
+ r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, NULL, NULL, NULL, NULL, &esp_devid);
+ if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */
+ return log_error_errno(r, "Failed to determine ESP location: %m");
+ if (r < 0)
+ return r;
+
+ r = acquire_xbootldr(/* unprivileged_mode= */ -1, NULL, &xbootldr_devid);
+ if (r == -EACCES)
+ return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m");
+ if (r < 0)
+ return r;
+
+ r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid);
+ if (r < 0)
+ return r;
+
+ if (config.n_entries == 0 && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
+ log_info("No boot loader entries found.");
+ return 0;
+ }
+
+ if (streq(argv[0], "list")) {
+ pager_open(arg_pager_flags);
+ return show_boot_entries(&config, arg_json_format_flags);
+ } else if (streq(argv[0], "cleanup")) {
+ if (arg_xbootldr_path && xbootldr_devid != esp_devid)
+ cleanup_orphaned_files(&config, arg_xbootldr_path);
+ return cleanup_orphaned_files(&config, arg_esp_path);
+ } else {
+ assert(streq(argv[0], "unlink"));
+ if (arg_xbootldr_path && xbootldr_devid != esp_devid) {
+ r = unlink_entry(&config, arg_xbootldr_path, argv[1]);
+ if (r == 0 || r != -ENOENT)
+ return r;
+ }
+ return unlink_entry(&config, arg_esp_path, argv[1]);
+ }
+}
+
+int verb_unlink(int argc, char *argv[], void *userdata) {
+ return verb_list(argc, argv, userdata);
+}
diff --git a/src/boot/bootctl-status.h b/src/boot/bootctl-status.h
new file mode 100644
index 0000000..f7998a3
--- /dev/null
+++ b/src/boot/bootctl-status.h
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+int verb_status(int argc, char *argv[], void *userdata);
+int verb_list(int argc, char *argv[], void *userdata);
+int verb_unlink(int argc, char *argv[], void *userdata);
diff --git a/src/boot/bootctl-systemd-efi-options.c b/src/boot/bootctl-systemd-efi-options.c
new file mode 100644
index 0000000..7f8308f
--- /dev/null
+++ b/src/boot/bootctl-systemd-efi-options.c
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bootctl.h"
+#include "bootctl-systemd-efi-options.h"
+#include "efi-loader.h"
+
+int verb_systemd_efi_options(int argc, char *argv[], void *userdata) {
+ int r;
+
+ /* This is obsolete and subject to removal */
+
+ if (!arg_quiet)
+ log_notice("Use of the SystemdOptions EFI variable is deprecated.");
+
+ if (argc == 1) {
+ _cleanup_free_ char *line = NULL, *new = NULL;
+
+ r = systemd_efi_options_variable(&line);
+ if (r == -ENODATA)
+ log_debug("No SystemdOptions EFI variable present in cache.");
+ else if (r < 0)
+ return log_error_errno(r, "Failed to read SystemdOptions EFI variable from cache: %m");
+ else
+ puts(line);
+
+ r = systemd_efi_options_efivarfs_if_newer(&new);
+ if (r == -ENODATA) {
+ if (line)
+ log_notice("Note: SystemdOptions EFI variable has been removed since boot.");
+ } else if (r < 0)
+ log_warning_errno(r, "Failed to check SystemdOptions EFI variable in efivarfs, ignoring: %m");
+ else if (new && !streq_ptr(line, new))
+ log_notice("Note: SystemdOptions EFI variable has been modified since boot. New value: %s",
+ new);
+ } else {
+ r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE(SystemdOptions), argv[1]);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set SystemdOptions EFI variable: %m");
+ }
+
+ return 0;
+}
diff --git a/src/boot/bootctl-systemd-efi-options.h b/src/boot/bootctl-systemd-efi-options.h
new file mode 100644
index 0000000..d0243eb
--- /dev/null
+++ b/src/boot/bootctl-systemd-efi-options.h
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int verb_systemd_efi_options(int argc, char *argv[], void *userdata);
diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c
new file mode 100644
index 0000000..8808c30
--- /dev/null
+++ b/src/boot/bootctl-uki.c
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fcntl.h>
+
+#include "alloc-util.h"
+#include "bootctl-uki.h"
+#include "kernel-image.h"
+
+int verb_kernel_identify(int argc, char *argv[], void *userdata) {
+ KernelImageType t;
+ int r;
+
+ r = inspect_kernel(AT_FDCWD, argv[1], &t, NULL, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ puts(kernel_image_type_to_string(t));
+ return 0;
+}
+
+int verb_kernel_inspect(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *cmdline = NULL, *uname = NULL, *pname = NULL;
+ KernelImageType t;
+ int r;
+
+ r = inspect_kernel(AT_FDCWD, argv[1], &t, &cmdline, &uname, &pname);
+ if (r < 0)
+ return r;
+
+ printf("Kernel Type: %s\n", kernel_image_type_to_string(t));
+ if (cmdline)
+ printf(" Cmdline: %s\n", cmdline);
+ if (uname)
+ printf(" Version: %s\n", uname);
+ if (pname)
+ printf(" OS: %s\n", pname);
+
+ return 0;
+}
diff --git a/src/boot/bootctl-uki.h b/src/boot/bootctl-uki.h
new file mode 100644
index 0000000..effb984
--- /dev/null
+++ b/src/boot/bootctl-uki.h
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+int verb_kernel_identify(int argc, char *argv[], void *userdata);
+int verb_kernel_inspect(int argc, char *argv[], void *userdata);
diff --git a/src/boot/bootctl-util.c b/src/boot/bootctl-util.c
new file mode 100644
index 0000000..3cab875
--- /dev/null
+++ b/src/boot/bootctl-util.c
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/mman.h>
+
+#include "bootctl.h"
+#include "bootctl-util.h"
+#include "errno-util.h"
+#include "fileio.h"
+#include "stat-util.h"
+#include "sync-util.h"
+
+int sync_everything(void) {
+ int r = 0, k;
+
+ if (arg_esp_path) {
+ k = syncfs_path(AT_FDCWD, arg_esp_path);
+ if (k < 0)
+ RET_GATHER(r, log_error_errno(k, "Failed to synchronize the ESP '%s': %m", arg_esp_path));
+ }
+
+ if (arg_xbootldr_path) {
+ k = syncfs_path(AT_FDCWD, arg_xbootldr_path);
+ if (k < 0)
+ RET_GATHER(r, log_error_errno(k, "Failed to synchronize $BOOT '%s': %m", arg_xbootldr_path));
+ }
+
+ return r;
+}
+
+const char *get_efi_arch(void) {
+ /* Detect EFI firmware architecture of the running system. On mixed mode systems, it could be 32-bit
+ * while the kernel is running in 64-bit. */
+
+#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 machine type '%s': %m",
+ EFI_MACHINE_TYPE_NAME);
+ 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 machine type '%s'.",
+ platform_size,
+ EFI_MACHINE_TYPE_NAME);
+#endif
+
+ return EFI_MACHINE_TYPE_NAME;
+}
+
+/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */
+int get_file_version(int fd, char **ret) {
+ struct stat st;
+ char *buf;
+ const char *s, *e;
+ char *marker = NULL;
+ int r;
+
+ assert(fd >= 0);
+ assert(ret);
+
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat EFI binary: %m");
+
+ r = stat_verify_regular(&st);
+ if (r < 0) {
+ log_debug_errno(r, "EFI binary is not a regular file, assuming no version information: %m");
+ return -ESRCH;
+ }
+
+ if (st.st_size < 27 || file_offset_beyond_memory_size(st.st_size))
+ return log_debug_errno(SYNTHETIC_ERRNO(ESRCH),
+ "EFI binary size too %s: %"PRIi64,
+ st.st_size < 27 ? "small" : "large", st.st_size);
+
+ buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (buf == MAP_FAILED)
+ return log_error_errno(errno, "Failed to mmap EFI binary: %m");
+
+ s = mempmem_safe(buf, st.st_size - 8, "#### LoaderInfo: ", 17);
+ if (!s) {
+ r = log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has no LoaderInfo marker.");
+ goto finish;
+ }
+
+ e = memmem_safe(s, st.st_size - (s - buf), " ####", 5);
+ if (!e || e - s < 3) {
+ r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "EFI binary has malformed LoaderInfo marker.");
+ goto finish;
+ }
+
+ marker = strndup(s, e - s);
+ if (!marker) {
+ r = log_oom();
+ goto finish;
+ }
+
+ log_debug("EFI binary LoaderInfo marker: \"%s\"", marker);
+ r = 0;
+ *ret = marker;
+finish:
+ (void) munmap(buf, st.st_size);
+ return r;
+}
+
+int settle_entry_token(void) {
+ int r;
+
+ r = boot_entry_token_ensure(
+ arg_root,
+ etc_kernel(),
+ arg_machine_id,
+ /* machine_id_is_random = */ false,
+ &arg_entry_token_type,
+ &arg_entry_token);
+ if (r < 0)
+ return r;
+
+ log_debug("Using entry token: %s", arg_entry_token);
+ return 0;
+}
diff --git a/src/boot/bootctl-util.h b/src/boot/bootctl-util.h
new file mode 100644
index 0000000..147455e
--- /dev/null
+++ b/src/boot/bootctl-util.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int sync_everything(void);
+
+const char *get_efi_arch(void);
+
+int get_file_version(int fd, char **ret);
+
+int settle_entry_token(void);
+
+static inline const char* etc_kernel(void) {
+ return getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/";
+}
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
new file mode 100644
index 0000000..4614ca1
--- /dev/null
+++ b/src/boot/bootctl.c
@@ -0,0 +1,516 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+
+#include "blockdev-util.h"
+#include "bootctl.h"
+#include "bootctl-install.h"
+#include "bootctl-random-seed.h"
+#include "bootctl-reboot-to-firmware.h"
+#include "bootctl-set-efivar.h"
+#include "bootctl-status.h"
+#include "bootctl-systemd-efi-options.h"
+#include "bootctl-uki.h"
+#include "build.h"
+#include "devnum-util.h"
+#include "dissect-image.h"
+#include "escape.h"
+#include "find-esp.h"
+#include "main-func.h"
+#include "mount-util.h"
+#include "pager.h"
+#include "parse-argument.h"
+#include "pretty-print.h"
+#include "utf8.h"
+#include "verbs.h"
+#include "virt.h"
+
+/* EFI_BOOT_OPTION_DESCRIPTION_MAX sets the maximum length for the boot option description
+ * stored in NVRAM. The UEFI spec does not specify a minimum or maximum length for this
+ * string, but we limit the length to something reasonable to prevent from the firmware
+ * having to deal with a potentially too long string. */
+#define EFI_BOOT_OPTION_DESCRIPTION_MAX ((size_t) 255)
+
+char *arg_esp_path = NULL;
+char *arg_xbootldr_path = NULL;
+bool arg_print_esp_path = false;
+bool arg_print_dollar_boot_path = false;
+unsigned arg_print_root_device = 0;
+bool arg_touch_variables = true;
+PagerFlags arg_pager_flags = 0;
+bool arg_graceful = false;
+bool arg_quiet = false;
+int arg_make_entry_directory = false; /* tri-state: < 0 for automatic logic */
+sd_id128_t arg_machine_id = SD_ID128_NULL;
+char *arg_install_layout = NULL;
+BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
+char *arg_entry_token = NULL;
+JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
+bool arg_arch_all = false;
+char *arg_root = NULL;
+char *arg_image = NULL;
+InstallSource arg_install_source = ARG_INSTALL_SOURCE_AUTO;
+char *arg_efi_boot_option_description = NULL;
+bool arg_dry_run = false;
+ImagePolicy *arg_image_policy = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_install_layout, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
+
+int acquire_esp(
+ int unprivileged_mode,
+ bool graceful,
+ uint32_t *ret_part,
+ uint64_t *ret_pstart,
+ uint64_t *ret_psize,
+ sd_id128_t *ret_uuid,
+ dev_t *ret_devid) {
+
+ char *np;
+ int r;
+
+ /* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on
+ * its own, except for ENOKEY (which is good, we want to show our own message in that case,
+ * suggesting use of --esp-path=) and EACCESS (only when we request unprivileged mode; in this case
+ * we simply eat up the error here, so that --list and --status work too, without noise about
+ * this). */
+
+ r = find_esp_and_warn(arg_root, arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid);
+ if (r == -ENOKEY) {
+ if (graceful)
+ return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_INFO, r,
+ "Couldn't find EFI system partition, skipping.");
+
+ return log_error_errno(r,
+ "Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n"
+ "Alternatively, use --esp-path= to specify path to mount point.");
+ }
+ if (r < 0)
+ return r;
+
+ free_and_replace(arg_esp_path, np);
+ log_debug("Using EFI System Partition at %s.", arg_esp_path);
+
+ return 0;
+}
+
+int acquire_xbootldr(
+ int unprivileged_mode,
+ sd_id128_t *ret_uuid,
+ dev_t *ret_devid) {
+
+ char *np;
+ int r;
+
+ r = find_xbootldr_and_warn(arg_root, arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid);
+ if (r == -ENOKEY) {
+ log_debug_errno(r, "Didn't find an XBOOTLDR partition, using the ESP as $BOOT.");
+ arg_xbootldr_path = mfree(arg_xbootldr_path);
+
+ if (ret_uuid)
+ *ret_uuid = SD_ID128_NULL;
+ if (ret_devid)
+ *ret_devid = 0;
+ return 0;
+ }
+ if (r < 0)
+ return r;
+
+ free_and_replace(arg_xbootldr_path, np);
+ log_debug("Using XBOOTLDR partition at %s as $BOOT.", arg_xbootldr_path);
+
+ return 1;
+}
+
+static int help(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ pager_open(arg_pager_flags);
+
+ r = terminal_urlify_man("bootctl", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...] COMMAND ...\n"
+ "\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n"
+ "\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n"
+ " status Show status of installed boot loader and EFI variables\n"
+ " reboot-to-firmware [BOOL]\n"
+ " Query or set reboot-to-firmware EFI flag\n"
+ "\n%3$sBoot Loader Specification Commands:%4$s\n"
+ " list List boot loader entries\n"
+ " unlink ID Remove boot loader entry\n"
+ " cleanup Remove files in ESP not referenced in any boot entry\n"
+ "\n%3$sBoot Loader Interface Commands:%4$s\n"
+ " set-default ID Set default boot loader entry\n"
+ " set-oneshot ID Set default boot loader entry, for next boot only\n"
+ " set-timeout SECONDS Set the menu timeout\n"
+ " set-timeout-oneshot SECONDS\n"
+ " Set the menu timeout for the next boot only\n"
+ "\n%3$ssystemd-boot Commands:%4$s\n"
+ " install Install systemd-boot to the ESP and EFI variables\n"
+ " update Update systemd-boot in the ESP and EFI variables\n"
+ " remove Remove systemd-boot from the ESP and EFI variables\n"
+ " is-installed Test whether systemd-boot is installed in the ESP\n"
+ " random-seed Initialize or refresh random seed in ESP and EFI\n"
+ " variables\n"
+ "\n%3$sKernel Image Commands:%4$s\n"
+ " kernel-identify Identify kernel image type\n"
+ " kernel-inspect Prints details about the kernel image\n"
+ "\n%3$sOptions:%4$s\n"
+ " -h --help Show this help\n"
+ " --version Print version\n"
+ " --esp-path=PATH Path to the EFI System Partition (ESP)\n"
+ " --boot-path=PATH Path to the $BOOT partition\n"
+ " --root=PATH Operate on an alternate filesystem root\n"
+ " --image=PATH Operate on disk image as filesystem root\n"
+ " --image-policy=POLICY\n"
+ " Specify disk image dissection policy\n"
+ " --install-source=auto|image|host\n"
+ " Where to pick files when using --root=/--image=\n"
+ " -p --print-esp-path Print path to the EFI System Partition mount point\n"
+ " -x --print-boot-path Print path to the $BOOT partition mount point\n"
+ " -R --print-root-device\n"
+ " Print path to the block device node backing the\n"
+ " root file system (returns e.g. /dev/nvme0n1p5)\n"
+ " -RR Print path to the whole disk block device node\n"
+ " backing the root FS (returns e.g. /dev/nvme0n1)\n"
+ " --no-variables Don't touch EFI variables\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --graceful Don't fail when the ESP cannot be found or EFI\n"
+ " variables cannot be written\n"
+ " -q --quiet Suppress output\n"
+ " --make-entry-directory=yes|no|auto\n"
+ " Create $BOOT/ENTRY-TOKEN/ directory\n"
+ " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
+ " 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"
+ " --efi-boot-option-description=DESCRIPTION\n"
+ " Description of the entry in the boot option list\n"
+ " --dry-run Dry run (unlink and cleanup)\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal());
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_ESP_PATH = 0x100,
+ ARG_BOOT_PATH,
+ ARG_ROOT,
+ ARG_IMAGE,
+ ARG_IMAGE_POLICY,
+ ARG_INSTALL_SOURCE,
+ ARG_VERSION,
+ ARG_NO_VARIABLES,
+ ARG_NO_PAGER,
+ ARG_GRACEFUL,
+ ARG_MAKE_ENTRY_DIRECTORY,
+ ARG_ENTRY_TOKEN,
+ ARG_JSON,
+ ARG_ARCH_ALL,
+ ARG_EFI_BOOT_OPTION_DESCRIPTION,
+ ARG_DRY_RUN,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "esp-path", required_argument, NULL, ARG_ESP_PATH },
+ { "path", required_argument, NULL, ARG_ESP_PATH }, /* Compatibility alias */
+ { "boot-path", required_argument, NULL, ARG_BOOT_PATH },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "image", required_argument, NULL, ARG_IMAGE },
+ { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+ { "install-source", required_argument, NULL, ARG_INSTALL_SOURCE },
+ { "print-esp-path", no_argument, NULL, 'p' },
+ { "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */
+ { "print-boot-path", no_argument, NULL, 'x' },
+ { "print-root-device", no_argument, NULL, 'R' },
+ { "no-variables", no_argument, NULL, ARG_NO_VARIABLES },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "graceful", no_argument, NULL, ARG_GRACEFUL },
+ { "quiet", no_argument, NULL, 'q' },
+ { "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY },
+ { "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 },
+ { "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION },
+ { "dry-run", no_argument, NULL, ARG_DRY_RUN },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hpxRq", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ help(0, NULL, NULL);
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_ESP_PATH:
+ r = free_and_strdup(&arg_esp_path, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case ARG_BOOT_PATH:
+ r = free_and_strdup(&arg_xbootldr_path, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case ARG_ROOT:
+ r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_IMAGE:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_IMAGE_POLICY:
+ r = parse_image_policy_argument(optarg, &arg_image_policy);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_INSTALL_SOURCE:
+ if (streq(optarg, "auto"))
+ arg_install_source = ARG_INSTALL_SOURCE_AUTO;
+ else if (streq(optarg, "image"))
+ arg_install_source = ARG_INSTALL_SOURCE_IMAGE;
+ else if (streq(optarg, "host"))
+ arg_install_source = ARG_INSTALL_SOURCE_HOST;
+ else
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Unexpected parameter for --install-source=: %s", optarg);
+
+ break;
+
+ case 'p':
+ arg_print_esp_path = true;
+ break;
+
+ case 'x':
+ arg_print_dollar_boot_path = true;
+ break;
+
+ case 'R':
+ arg_print_root_device ++;
+ break;
+
+ case ARG_NO_VARIABLES:
+ arg_touch_variables = false;
+ break;
+
+ case ARG_NO_PAGER:
+ arg_pager_flags |= PAGER_DISABLE;
+ break;
+
+ case ARG_GRACEFUL:
+ arg_graceful = true;
+ break;
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
+ case ARG_ENTRY_TOKEN:
+ r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_MAKE_ENTRY_DIRECTORY:
+ if (streq(optarg, "auto")) /* retained for backwards compatibility */
+ arg_make_entry_directory = -1; /* yes if machine-id is permanent */
+ else {
+ r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
+ if (r < 0)
+ return r;
+
+ arg_make_entry_directory = r;
+ }
+ break;
+
+ case ARG_JSON:
+ 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 ARG_EFI_BOOT_OPTION_DESCRIPTION:
+ if (isempty(optarg) || !(string_is_safe(optarg) && utf8_is_valid(optarg))) {
+ _cleanup_free_ char *escaped = NULL;
+
+ escaped = cescape(optarg);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid --efi-boot-option-description=: %s", strna(escaped));
+ }
+ if (strlen(optarg) > EFI_BOOT_OPTION_DESCRIPTION_MAX)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--efi-boot-option-description= too long: %zu > %zu",
+ strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX);
+ r = free_and_strdup_warn(&arg_efi_boot_option_description, optarg);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_DRY_RUN:
+ arg_dry_run = true;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) > 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R cannot be combined.");
+
+ if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list",
+ "install", "update", "remove", "is-installed", "random-seed", "unlink", "cleanup"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Options --root= and --image= are not supported with verb %s.",
+ argv[optind]);
+
+ if (arg_root && arg_image)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
+
+ if (arg_install_source != ARG_INSTALL_SOURCE_AUTO && !arg_root && !arg_image)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--install-from-host is only supported with --root= or --image=.");
+
+ if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry is only supported with --unlink or --cleanup");
+
+ return 1;
+}
+
+static int bootctl_main(int argc, char *argv[]) {
+ static const Verb verbs[] = {
+ { "help", VERB_ANY, VERB_ANY, 0, help },
+ { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
+ { "install", VERB_ANY, 1, 0, verb_install },
+ { "update", VERB_ANY, 1, 0, verb_install },
+ { "remove", VERB_ANY, 1, 0, verb_remove },
+ { "is-installed", VERB_ANY, 1, 0, verb_is_installed },
+ { "kernel-identify", 2, 2, 0, verb_kernel_identify },
+ { "kernel-inspect", 2, 2, 0, verb_kernel_inspect },
+ { "list", VERB_ANY, 1, 0, verb_list },
+ { "unlink", 2, 2, 0, verb_unlink },
+ { "cleanup", VERB_ANY, 1, 0, verb_list },
+ { "set-default", 2, 2, 0, verb_set_efivar },
+ { "set-oneshot", 2, 2, 0, verb_set_efivar },
+ { "set-timeout", 2, 2, 0, verb_set_efivar },
+ { "set-timeout-oneshot", 2, 2, 0, verb_set_efivar },
+ { "random-seed", VERB_ANY, 1, 0, verb_random_seed },
+ { "systemd-efi-options", VERB_ANY, 2, 0, verb_systemd_efi_options },
+ { "reboot-to-firmware", VERB_ANY, 2, 0, verb_reboot_to_firmware },
+ {}
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
+ _cleanup_(umount_and_freep) char *mounted_dir = NULL;
+ int r;
+
+ log_setup();
+
+ /* If we run in a container, automatically turn off EFI file system access */
+ if (detect_container() > 0)
+ arg_touch_variables = false;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ if (arg_print_root_device > 0) {
+ _cleanup_free_ char *path = NULL;
+ dev_t devno;
+
+ r = blockdev_get_root(LOG_ERR, &devno);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_error("Root file system not backed by a (single) whole block device.");
+ return 80; /* some recognizable error code */
+ }
+
+ if (arg_print_root_device > 1) {
+ r = block_get_whole_disk(devno, &devno);
+ if (r < 0)
+ log_debug_errno(r, "Unable to find whole block device for root block device, ignoring: %m");
+ }
+
+ r = device_path_make_canonical(S_IFBLK, devno, &path);
+ if (r < 0)
+ return log_error_errno(r,
+ "Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m",
+ DEVNUM_FORMAT_VAL(devno));
+
+ puts(path);
+ return 0;
+ }
+
+ /* Open up and mount the image */
+ if (arg_image) {
+ assert(!arg_root);
+
+ r = mount_image_privately_interactively(
+ arg_image,
+ arg_image_policy,
+ DISSECT_IMAGE_GENERIC_ROOT |
+ DISSECT_IMAGE_RELAX_VAR_CHECK,
+ &mounted_dir,
+ /* ret_dir_fd= */ NULL,
+ &loop_device);
+ if (r < 0)
+ return r;
+
+ arg_root = strdup(mounted_dir);
+ if (!arg_root)
+ return log_oom();
+ }
+
+ return bootctl_main(argc, argv);
+}
+
+DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
diff --git a/src/boot/bootctl.h b/src/boot/bootctl.h
new file mode 100644
index 0000000..e395b33
--- /dev/null
+++ b/src/boot/bootctl.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-id128.h"
+
+#include "boot-entry.h"
+#include "image-policy.h"
+#include "json.h"
+#include "pager.h"
+
+typedef enum InstallSource {
+ ARG_INSTALL_SOURCE_IMAGE,
+ ARG_INSTALL_SOURCE_HOST,
+ ARG_INSTALL_SOURCE_AUTO,
+} InstallSource;
+
+extern char *arg_esp_path;
+extern char *arg_xbootldr_path;
+extern bool arg_print_esp_path;
+extern bool arg_print_dollar_boot_path;
+extern unsigned arg_print_root_device;
+extern bool arg_touch_variables;
+extern PagerFlags arg_pager_flags;
+extern bool arg_graceful;
+extern bool arg_quiet;
+extern int arg_make_entry_directory; /* tri-state: < 0 for automatic logic */
+extern sd_id128_t arg_machine_id;
+extern char *arg_install_layout;
+extern BootEntryTokenType arg_entry_token_type;
+extern char *arg_entry_token;
+extern JsonFormatFlags arg_json_format_flags;
+extern bool arg_arch_all;
+extern char *arg_root;
+extern char *arg_image;
+extern InstallSource arg_install_source;
+extern char *arg_efi_boot_option_description;
+extern bool arg_dry_run;
+extern ImagePolicy *arg_image_policy;
+
+static inline const char *arg_dollar_boot_path(void) {
+ /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */
+ return arg_xbootldr_path ?: arg_esp_path;
+}
+
+int acquire_esp(int unprivileged_mode, bool graceful, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid, dev_t *ret_devid);
+int acquire_xbootldr(int unprivileged_mode, sd_id128_t *ret_uuid, dev_t *ret_devid);
diff --git a/src/boot/efi/UEFI_SECURITY.md b/src/boot/efi/UEFI_SECURITY.md
new file mode 100644
index 0000000..9f750d8
--- /dev/null
+++ b/src/boot/efi/UEFI_SECURITY.md
@@ -0,0 +1,122 @@
+# UEFI Components Security Posture
+The systemd project provides a UEFI boot menu, `systemd-boot`, and a stub that can wrap a Linux kernel in a
+PE binary, adding various features, `systemd-stub`. These components fully support UEFI SecureBoot, and
+this document will describe their security posture and how they comply with industry-standard expectations
+for UEFI SecureBoot workflows.
+
+Note that `systemd-stub` is not the same, or an alternative, to the Linux kernel's own EFI stub. The kernel
+stub's role is that of the fundamental entrypoint to kernel execution from UEFI mode, implementing the
+modern Linux boot protocol. `systemd-stub` on the other hand loads various resources, including the kernel
+image, via the EFI LoadImage/StartImage protocol (although it does support the legacy Linux boot protocol,
+as a fallback for older kernels on x86). The purpose of `systemd-stub` is to provide additional features and
+functionality for either or both `systemd-boot` and `systemd` (userspace).
+
+## Fundamental Security Design Goals
+The fundamental security design goals for these components are separation of security policy logic from the
+rest of the functionality, achieved by offloading security-critical tasks to the firmware or earlier stages
+of the boot process (e.g.: `Shim`).
+
+When SecureBoot is enabled, these components are designed to avoid executing, loading or using
+unauthenticated payloads that could compromise the boot process, with special care taken for anything that
+could affect the system before `ExitBootServices()` has been called. For example, when additional resources
+are loaded, if running with SecureBoot enabled, they will be validated before use. The only exceptions are
+the bootloader's own textual configuration files, and parsing metadata out of images for displaying purposes
+only. There are no build time or runtime configuration options that can be set to weaken the security model
+of these components when SecureBoot is enabled.
+
+The role of `systemd-boot` is to discover next stage components in the ESP (and XBOOTLDR if present), via
+filesystem enumeration or explicit configuration files, and present a menu to the user, to choose the next
+step. This auto discovery mechanism is described in details in the [BLS (Boot Loader
+Specification)](https://uapi-group.org/specifications/specs/boot_loader_specification/).
+
+The role of `systemd-stub` is to load and measure in the TPM the post-bootloader stages, such as the kernel,
+initrd and kernel command line, and implement optional features such as augmenting the initrd with
+additional content such as configuration or optional services. [Unified Kernel
+Images](https://uapi-group.org/specifications/specs/unified_kernel_image/) embed `systemd-stub`, a kernel
+and other optional components as sections in a PE signed binary, that can thus be executed in UEFI
+environments.
+
+Since it is embedded in a PE signed binary, `systemd-stub` will temporarily disable the UEFI authentication
+protocol while loading the payload kernel it wraps, in order to avoid redundant duplicate authentication of
+the image, given that the payload kernel was already authenticated and verified as part of the whole image.
+SecureBoot authentication is re-enabled immediately after the kernel image has been loaded.
+
+Various EFI variables, under the vendor UUID `4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`, are set and read by
+these components, to pass metadata and configuration between different stages of the boot process, as
+defined in the [Boot Loader Interface](https://systemd.io/BOOT_LOADER_INTERFACE/).
+
+## Dependencies
+Neither of these components implements cryptographic primitives, cryptographic checks or drivers. File
+access to the ESP is implemented solely via the appropriate UEFI file protocols. Verification of next stage
+payloads is implementend solely via the appropriate UEFI image load protocols, which means authenticode
+signature checks are again done by the firmware or `Shim`. As a consequence, no external security-critical
+libraries (such as OpenSSL or gnu-efi) are used, linked or embedded.
+
+## Additional Resources
+BLS Type #1 entries allow the user to load two types of additional resources that can affect the system
+before `ExitBootServices()` has been called, kernel command line arguments and Devicetree blobs, that are
+not validated before use, as they do not carry signatures. For this reason, when SecureBoot is enabled,
+loading these resources is automatically disabled. There is no override for this security mechanism, neither
+at build time nor at runtime. Note that initrds are also not verified in BLS Type #1 configurations, for
+compatibility with how SecureBoot has been traditionally handled on Linux-based OSes, as the kernel will
+only load them after `ExitBootServices()` has been called.
+
+Another mechanism is supported by `systemd-boot` and `systemd-stub` to add additional payloads to the boot
+process: `addons`. Addons are PE signed binaries that can carry kernel command line arguments or Devicetree
+blobs (more might be added in the future). In contrast to the user-specified additions in the Type #1 case
+described above, these addons are loaded through the UEFI image loading protocol, and thus are subject to
+signature validation, and will be rejected if not signed or if the signature is invalid, following the
+standard SecureBoot model. They are also measured in the TPM.
+
+`systemd-boot` will also load file system drivers that are stored in the ESP, to allow enhancing the
+firmware's capabilities. These are again PE signed binaries and will be verified using the appropriate
+UEFI protocol.
+
+A random seed will be loaded and passed to the kernel for early-boot entropy pool filling if found in the
+ESP. It is mixed with various other sources of entropy available in the UEFI environment, such as the RNG
+protocol, the boot counter and the clock. Moreover, the seed is updated before the kernel is invoked, as
+well as after the kernel is invoked (from userspace), with a new seed derived from the Linux kernel entropy
+pool.
+
+When operating as a virtual machine payload, the loaded payloads can be customized via `SMBIOS Type 11
+Strings`, if the hypervisor specifies them. This is automatically disabled if running inside a confidential
+computing VM.
+
+## Certificates Enrollment
+When SecureBoot is supported but in `setup` mode, `systemd-boot` can enroll user certificates if a set of
+`PK`, `KEK` and `db` certificates is found in the ESP, after which SecureBoot is enabled and a firmware
+reset is performed. When running on bare metal, the certificate(s) will be shown to the user on the console,
+and manual confirmation will be asked before proceeding. When running as a virtual machine payload,
+enrollment is fully automated, without user interaction, unless disabled via a configuration file in the
+ESP. The configuration file can also be used to disable enrollment completely.
+
+## Compiler Hardening
+The PE binaries are built with `-fstack-protector-strong`, and the stack canary is seeded with random data if
+the UEFI RNG protocol is available.
+
+The binaries also are linked with `-z relro` and ship with native PE relocations, with the conversion from
+ELF performed at build time, instead of containing ELF dynamic relocations, so the image loaded by
+firmware/Shim requires fewer writable pages.
+
+The binaries are linked by default with full LTO support, so no code will be shipped unless it's reachable.
+
+Finally, the binaries ship with the `NX_COMPAT` bit set.
+
+The CI infrastructure also employs fuzz testing on various components, including string functions and the
+BCD parser.
+
+## SBAT
+`systemd-boot` and `systemd-stub` are built with an `SBAT` section by default. There are build options to
+allow customizations of the metadata included in the section, that can be used by downstream distributors.
+The `systemd` project will participate in the coordinated `SBAT` disclosure and metadata revision process as
+deemed necessary, in coordination with the Shim Review group.
+
+The upstream project name used to be unified (`systemd`) for both components, but since version v255 has
+been split into separate `systemd-boot` and `systemd-stub` project names, so that each component can be
+revisioned independently. Most of the code tend to be shared between these two components, but there is no
+complete overlap, so it is possible for a vulnerability to affect only one component but not the other.
+
+## Known Vulnerabilities
+There is currently one known (and fixed) security vulnerability affecting `systemd-boot` on arm64 and
+riscv64 systems. For details of the affected and fixed versions, please see the [published security
+advisory.](https://github.com/systemd/systemd/security/advisories/GHSA-6m6p-rjcq-334c)
diff --git a/src/boot/efi/addon.c b/src/boot/efi/addon.c
new file mode 100644
index 0000000..95b29da
--- /dev/null
+++ b/src/boot/efi/addon.c
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "efi.h"
+#include "version.h"
+
+/* Magic string for recognizing our own binaries */
+DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-addon " GIT_VERSION " ####");
+
+/* This is intended to carry data, not to be executed */
+
+EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table);
+EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) {
+ return EFI_UNSUPPORTED;
+}
diff --git a/src/boot/efi/bcd.c b/src/boot/efi/bcd.c
new file mode 100644
index 0000000..4533d47
--- /dev/null
+++ b/src/boot/efi/bcd.c
@@ -0,0 +1,306 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdalign.h>
+
+#include "bcd.h"
+#include "efi-string.h"
+
+enum {
+ SIG_BASE_BLOCK = 1718052210, /* regf */
+ SIG_KEY = 27502, /* nk */
+ SIG_SUBKEY_FAST = 26220, /* lf */
+ SIG_KEY_VALUE = 27510, /* vk */
+};
+
+enum {
+ REG_SZ = 1,
+ REG_MULTI_SZ = 7,
+};
+
+/* These structs contain a lot more members than we care for. They have all
+ * been squashed into _padN for our convenience. */
+
+typedef struct {
+ uint32_t sig;
+ uint32_t primary_seqnum;
+ uint32_t secondary_seqnum;
+ uint64_t _pad1;
+ uint32_t version_major;
+ uint32_t version_minor;
+ uint32_t type;
+ uint32_t _pad2;
+ uint32_t root_cell_offset;
+ uint64_t _pad3[507];
+} _packed_ BaseBlock;
+assert_cc(sizeof(BaseBlock) == 4096);
+assert_cc(offsetof(BaseBlock, sig) == 0);
+assert_cc(offsetof(BaseBlock, primary_seqnum) == 4);
+assert_cc(offsetof(BaseBlock, secondary_seqnum) == 8);
+assert_cc(offsetof(BaseBlock, version_major) == 20);
+assert_cc(offsetof(BaseBlock, version_minor) == 24);
+assert_cc(offsetof(BaseBlock, type) == 28);
+assert_cc(offsetof(BaseBlock, root_cell_offset) == 36);
+
+/* All offsets are relative to the base block and technically point to a hive
+ * cell struct. But for our use case we don't need to bother about that one,
+ * so skip over the cell_size uint32_t. */
+#define HIVE_CELL_OFFSET (sizeof(BaseBlock) + 4)
+
+typedef struct {
+ uint16_t sig;
+ uint16_t _pad1[13];
+ uint32_t subkeys_offset;
+ uint32_t _pad2;
+ uint32_t n_key_values;
+ uint32_t key_values_offset;
+ uint32_t _pad3[7];
+ uint16_t key_name_len;
+ uint16_t _pad4;
+ char key_name[];
+} _packed_ Key;
+assert_cc(offsetof(Key, sig) == 0);
+assert_cc(offsetof(Key, subkeys_offset) == 28);
+assert_cc(offsetof(Key, n_key_values) == 36);
+assert_cc(offsetof(Key, key_values_offset) == 40);
+assert_cc(offsetof(Key, key_name_len) == 72);
+assert_cc(offsetof(Key, key_name) == 76);
+
+typedef struct {
+ uint16_t sig;
+ uint16_t n_entries;
+ struct SubkeyFastEntry {
+ uint32_t key_offset;
+ char name_hint[4];
+ } _packed_ entries[];
+} _packed_ SubkeyFast;
+assert_cc(offsetof(SubkeyFast, sig) == 0);
+assert_cc(offsetof(SubkeyFast, n_entries) == 2);
+assert_cc(offsetof(SubkeyFast, entries) == 4);
+
+typedef struct {
+ uint16_t sig;
+ uint16_t name_len;
+ uint32_t data_size;
+ uint32_t data_offset;
+ uint32_t data_type;
+ uint32_t _pad;
+ char name[];
+} _packed_ KeyValue;
+assert_cc(offsetof(KeyValue, sig) == 0);
+assert_cc(offsetof(KeyValue, name_len) == 2);
+assert_cc(offsetof(KeyValue, data_size) == 4);
+assert_cc(offsetof(KeyValue, data_offset) == 8);
+assert_cc(offsetof(KeyValue, data_type) == 12);
+assert_cc(offsetof(KeyValue, name) == 20);
+
+#define BAD_OFFSET(offset, len, max) \
+ ((uint64_t) (offset) + (len) >= (max))
+
+#define BAD_STRUCT(type, offset, max) \
+ ((uint64_t) (offset) + sizeof(type) >= (max))
+
+#define BAD_ARRAY(type, array, offset, array_len, max) \
+ ((uint64_t) (offset) + offsetof(type, array) + \
+ sizeof((type){}.array[0]) * (uint64_t) (array_len) >= (max))
+
+static const Key *get_key(const uint8_t *bcd, uint32_t bcd_len, uint32_t offset, const char *name);
+
+static const Key *get_subkey(const uint8_t *bcd, uint32_t bcd_len, uint32_t offset, const char *name) {
+ assert(bcd);
+ assert(name);
+
+ if (BAD_STRUCT(SubkeyFast, offset, bcd_len))
+ return NULL;
+
+ const SubkeyFast *subkey = (const SubkeyFast *) (bcd + offset);
+ if (subkey->sig != SIG_SUBKEY_FAST)
+ return NULL;
+
+ if (BAD_ARRAY(SubkeyFast, entries, offset, subkey->n_entries, bcd_len))
+ return NULL;
+
+ for (uint16_t i = 0; i < subkey->n_entries; i++) {
+ if (!strncaseeq8(name, subkey->entries[i].name_hint, sizeof(subkey->entries[i].name_hint)))
+ continue;
+
+ const Key *key = get_key(bcd, bcd_len, subkey->entries[i].key_offset, name);
+ if (key)
+ return key;
+ }
+
+ return NULL;
+}
+
+/* We use NUL as registry path separators for convenience. To start from the root, begin
+ * name with a NUL. Name must end with two NUL. The lookup depth is not restricted, so
+ * name must be properly validated before calling get_key(). */
+static const Key *get_key(const uint8_t *bcd, uint32_t bcd_len, uint32_t offset, const char *name) {
+ assert(bcd);
+ assert(name);
+
+ if (BAD_STRUCT(Key, offset, bcd_len))
+ return NULL;
+
+ const Key *key = (const Key *) (bcd + offset);
+ if (key->sig != SIG_KEY)
+ return NULL;
+
+ if (BAD_ARRAY(Key, key_name, offset, key->key_name_len, bcd_len))
+ return NULL;
+
+ if (*name) {
+ if (strncaseeq8(name, key->key_name, key->key_name_len) && strlen8(name) == key->key_name_len)
+ name += key->key_name_len;
+ else
+ return NULL;
+ }
+
+ name++;
+ return *name ? get_subkey(bcd, bcd_len, key->subkeys_offset, name) : key;
+}
+
+static const KeyValue *get_key_value(const uint8_t *bcd, uint32_t bcd_len, const Key *key, const char *name) {
+ assert(bcd);
+ assert(key);
+ assert(name);
+
+ if (key->n_key_values == 0)
+ return NULL;
+
+ if (BAD_OFFSET(key->key_values_offset, sizeof(uint32_t) * (uint64_t) key->n_key_values, bcd_len) ||
+ (uintptr_t) (bcd + key->key_values_offset) % alignof(uint32_t) != 0)
+ return NULL;
+
+ const uint32_t *key_value_list = (const uint32_t *) (bcd + key->key_values_offset);
+ for (uint32_t i = 0; i < key->n_key_values; i++) {
+ uint32_t offset = *(key_value_list + i);
+
+ if (BAD_STRUCT(KeyValue, offset, bcd_len))
+ continue;
+
+ const KeyValue *kv = (const KeyValue *) (bcd + offset);
+ if (kv->sig != SIG_KEY_VALUE)
+ continue;
+
+ if (BAD_ARRAY(KeyValue, name, offset, kv->name_len, bcd_len))
+ continue;
+
+ /* If most significant bit is set, data is stored in data_offset itself, but
+ * we are only interested in UTF16 strings. The only strings that could fit
+ * would have just one char in it, so let's not bother with this. */
+ if (FLAGS_SET(kv->data_size, UINT32_C(1) << 31))
+ continue;
+
+ if (BAD_OFFSET(kv->data_offset, kv->data_size, bcd_len))
+ continue;
+
+ if (strncaseeq8(name, kv->name, kv->name_len) && strlen8(name) == kv->name_len)
+ return kv;
+ }
+
+ return NULL;
+}
+
+/* The BCD store is really just a regular windows registry hive with a rather cryptic internal
+ * key structure. On a running system it gets mounted to HKEY_LOCAL_MACHINE\BCD00000000.
+ *
+ * Of interest to us are these two keys:
+ * - \Objects\{bootmgr}\Elements\24000001
+ * This key is the "displayorder" property and contains a value of type REG_MULTI_SZ
+ * with the name "Element" that holds a {GUID} list (UTF16, NUL-separated).
+ * - \Objects\{GUID}\Elements\12000004
+ * This key is the "description" property and contains a value of type REG_SZ with the
+ * name "Element" that holds a NUL-terminated UTF16 string.
+ *
+ * The GUIDs and properties are as reported by "bcdedit.exe /v".
+ *
+ * To get a title for the BCD store we first look at the displayorder property of {bootmgr}
+ * (it always has the GUID 9dea862c-5cdd-4e70-acc1-f32b344d4795). If it contains more than
+ * one GUID, the BCD is multi-boot and we stop looking. Otherwise we take that GUID, look it
+ * up, and return its description property. */
+char16_t *get_bcd_title(uint8_t *bcd, size_t bcd_len) {
+ assert(bcd);
+
+ if (HIVE_CELL_OFFSET >= bcd_len)
+ return NULL;
+
+ BaseBlock *base_block = (BaseBlock *) bcd;
+ if (base_block->sig != SIG_BASE_BLOCK ||
+ base_block->version_major != 1 ||
+ base_block->version_minor != 3 ||
+ base_block->type != 0 ||
+ base_block->primary_seqnum != base_block->secondary_seqnum)
+ return NULL;
+
+ bcd += HIVE_CELL_OFFSET;
+ bcd_len -= HIVE_CELL_OFFSET;
+
+ const Key *objects_key = get_key(bcd, bcd_len, base_block->root_cell_offset, "\0Objects\0");
+ if (!objects_key)
+ return NULL;
+
+ const Key *displayorder_key = get_subkey(
+ bcd,
+ bcd_len,
+ objects_key->subkeys_offset,
+ "{9dea862c-5cdd-4e70-acc1-f32b344d4795}\0Elements\00024000001\0");
+ if (!displayorder_key)
+ return NULL;
+
+ const KeyValue *displayorder_value = get_key_value(bcd, bcd_len, displayorder_key, "Element");
+ if (!displayorder_value)
+ return NULL;
+
+ char order_guid[sizeof("{00000000-0000-0000-0000-000000000000}\0")];
+ if (displayorder_value->data_type != REG_MULTI_SZ ||
+ displayorder_value->data_size != sizeof(char16_t[sizeof(order_guid)]) ||
+ (uintptr_t) (bcd + displayorder_value->data_offset) % alignof(char16_t) != 0)
+ /* BCD is multi-boot. */
+ return NULL;
+
+ /* Keys are stored as ASCII in registry hives if the data fits (and GUIDS always should). */
+ char16_t *order_guid_utf16 = (char16_t *) (bcd + displayorder_value->data_offset);
+ for (size_t i = 0; i < sizeof(order_guid) - 2; i++) {
+ char16_t c = order_guid_utf16[i];
+ switch (c) {
+ case '-':
+ case '{':
+ case '}':
+ case '0' ... '9':
+ case 'a' ... 'f':
+ case 'A' ... 'F':
+ order_guid[i] = c;
+ break;
+ default:
+ /* Not a valid GUID. */
+ return NULL;
+ }
+ }
+ /* Our functions expect the lookup key to be double-derminated. */
+ order_guid[sizeof(order_guid) - 2] = '\0';
+ order_guid[sizeof(order_guid) - 1] = '\0';
+
+ const Key *default_key = get_subkey(bcd, bcd_len, objects_key->subkeys_offset, order_guid);
+ if (!default_key)
+ return NULL;
+
+ const Key *description_key = get_subkey(
+ bcd, bcd_len, default_key->subkeys_offset, "Elements\00012000004\0");
+ if (!description_key)
+ return NULL;
+
+ const KeyValue *description_value = get_key_value(bcd, bcd_len, description_key, "Element");
+ if (!description_value)
+ return NULL;
+
+ if (description_value->data_type != REG_SZ ||
+ description_value->data_size < sizeof(char16_t) ||
+ description_value->data_size % sizeof(char16_t) != 0 ||
+ (uintptr_t) (bcd + description_value->data_offset) % alignof(char16_t))
+ return NULL;
+
+ /* The data should already be NUL-terminated. */
+ char16_t *title = (char16_t *) (bcd + description_value->data_offset);
+ title[description_value->data_size / sizeof(char16_t) - 1] = '\0';
+ return title;
+}
diff --git a/src/boot/efi/bcd.h b/src/boot/efi/bcd.h
new file mode 100644
index 0000000..bb12d89
--- /dev/null
+++ b/src/boot/efi/bcd.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+char16_t *get_bcd_title(uint8_t *bcd, size_t bcd_len);
diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c
new file mode 100644
index 0000000..5c0f0ab
--- /dev/null
+++ b/src/boot/efi/boot.c
@@ -0,0 +1,2748 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "bcd.h"
+#include "bootspec-fundamental.h"
+#include "console.h"
+#include "device-path-util.h"
+#include "devicetree.h"
+#include "drivers.h"
+#include "efivars-fundamental.h"
+#include "graphics.h"
+#include "initrd.h"
+#include "linux.h"
+#include "measure.h"
+#include "part-discovery.h"
+#include "pe.h"
+#include "proto/block-io.h"
+#include "proto/device-path.h"
+#include "proto/simple-text-io.h"
+#include "random-seed.h"
+#include "sbat.h"
+#include "secure-boot.h"
+#include "shim.h"
+#include "ticks.h"
+#include "tpm2-pcr.h"
+#include "util.h"
+#include "version.h"
+#include "vmm.h"
+
+/* Magic string for recognizing our own binaries */
+#define SD_MAGIC "#### LoaderInfo: systemd-boot " GIT_VERSION " ####"
+DECLARE_NOALLOC_SECTION(".sdmagic", SD_MAGIC);
+
+/* Makes systemd-boot available from \EFI\Linux\ for testing purposes. */
+DECLARE_NOALLOC_SECTION(
+ ".osrel",
+ "ID=systemd-boot\n"
+ "VERSION=\"" GIT_VERSION "\"\n"
+ "NAME=\"systemd-boot " GIT_VERSION "\"\n");
+
+DECLARE_SBAT(SBAT_BOOT_SECTION_TEXT);
+
+typedef enum LoaderType {
+ LOADER_UNDEFINED,
+ LOADER_AUTO,
+ LOADER_EFI,
+ LOADER_LINUX, /* Boot loader spec type #1 entries */
+ LOADER_UNIFIED_LINUX, /* Boot loader spec type #2 entries */
+ LOADER_SECURE_BOOT_KEYS,
+ _LOADER_TYPE_MAX,
+} LoaderType;
+
+typedef struct {
+ char16_t *id; /* The unique identifier for this entry (typically the filename of the file defining the entry) */
+ char16_t *title_show; /* The string to actually display (this is made unique before showing) */
+ char16_t *title; /* The raw (human readable) title string of the entry (not necessarily unique) */
+ char16_t *sort_key; /* The string to use as primary sort key, usually ID= from os-release, possibly suffixed */
+ char16_t *version; /* The raw (human readable) version string of the entry */
+ char16_t *machine_id;
+ EFI_HANDLE *device;
+ LoaderType type;
+ char16_t *loader;
+ 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. */
+ char16_t **initrd;
+ char16_t key;
+ EFI_STATUS (*call)(void);
+ int tries_done;
+ int tries_left;
+ char16_t *path;
+ char16_t *current_name;
+ char16_t *next_name;
+} BootEntry;
+
+typedef struct {
+ BootEntry **entries;
+ size_t n_entries;
+ size_t idx_default;
+ size_t idx_default_efivar;
+ uint64_t timeout_sec; /* Actual timeout used (efi_main() override > efivar > config). */
+ uint64_t timeout_sec_config;
+ uint64_t timeout_sec_efivar;
+ char16_t *entry_default_config;
+ char16_t *entry_default_efivar;
+ char16_t *entry_oneshot;
+ char16_t *entry_saved;
+ bool editor;
+ bool auto_entries;
+ bool auto_firmware;
+ bool auto_poweroff;
+ bool auto_reboot;
+ bool reboot_for_bitlocker;
+ secure_boot_enroll secure_boot_enroll;
+ bool force_menu;
+ bool use_saved_entry;
+ bool use_saved_entry_efivar;
+ bool beep;
+ int64_t console_mode;
+ int64_t console_mode_efivar;
+} Config;
+
+/* These values have been chosen so that the transitions the user sees could employ unsigned over-/underflow
+ * like this:
+ * efivar unset ↔ force menu ↔ no timeout/skip menu ↔ 1 s ↔ 2 s ↔ …
+ *
+ * Note: all the values below are ABI, so they are not allowed to change. The bootctl tool sets the numerical
+ * value of TIMEOUT_MENU_FORCE and TIMEOUT_MENU_HIDDEN, instead of the string for compatibility reasons.
+ *
+ * The other values may be set by systemd-boot itself and changing those will lead to functional regression
+ * when new version of systemd-boot is installed.
+ *
+ * All the 64bit values are not ABI and will never be written to an efi variable.
+ */
+enum {
+ TIMEOUT_MIN = 1,
+ TIMEOUT_MAX = UINT32_MAX - 2U,
+ TIMEOUT_UNSET = UINT32_MAX - 1U,
+ TIMEOUT_MENU_FORCE = UINT32_MAX,
+ TIMEOUT_MENU_HIDDEN = 0,
+ TIMEOUT_TYPE_MAX = UINT32_MAX,
+ TIMEOUT_MENU_DISABLED = (uint64_t)UINT32_MAX + 1U,
+ TIMEOUT_TYPE_MAX64 = UINT64_MAX,
+};
+
+enum {
+ IDX_MAX = INT16_MAX,
+ IDX_INVALID,
+};
+
+static void cursor_left(size_t *cursor, size_t *first) {
+ assert(cursor);
+ assert(first);
+
+ if ((*cursor) > 0)
+ (*cursor)--;
+ else if ((*first) > 0)
+ (*first)--;
+}
+
+static void cursor_right(size_t *cursor, size_t *first, size_t x_max, size_t len) {
+ assert(cursor);
+ assert(first);
+
+ if ((*cursor)+1 < x_max)
+ (*cursor)++;
+ else if ((*first) + (*cursor) < len)
+ (*first)++;
+}
+
+static bool line_edit(char16_t **line_in, size_t x_max, size_t y_pos) {
+ _cleanup_free_ char16_t *line = NULL, *print = NULL;
+ size_t size, len, first = 0, cursor = 0, clear = 0;
+
+ /* Edit the line and return true if it should be executed, false if not. */
+
+ assert(line_in);
+
+ len = strlen16(*line_in);
+ size = len + 1024;
+ line = xnew(char16_t, size);
+ print = xnew(char16_t, x_max + 1);
+ strcpy16(line, strempty(*line_in));
+
+ for (;;) {
+ EFI_STATUS err;
+ uint64_t key;
+ size_t j, cursor_color = EFI_TEXT_ATTR_SWAP(COLOR_EDIT);
+
+ j = MIN(len - first, x_max);
+ memcpy(print, line + first, j * sizeof(char16_t));
+ while (clear > 0 && j < x_max) {
+ clear--;
+ print[j++] = ' ';
+ }
+ print[j] = '\0';
+
+ /* See comment at edit_line() call site for why we start at 1. */
+ print_at(1, y_pos, COLOR_EDIT, print);
+
+ if (!print[cursor])
+ print[cursor] = ' ';
+ print[cursor+1] = '\0';
+ do {
+ print_at(cursor + 1, y_pos, cursor_color, print + cursor);
+ cursor_color = EFI_TEXT_ATTR_SWAP(cursor_color);
+
+ err = console_key_read(&key, 750 * 1000);
+ if (!IN_SET(err, EFI_SUCCESS, EFI_TIMEOUT, EFI_NOT_READY))
+ return false;
+
+ print_at(cursor + 1, y_pos, COLOR_EDIT, print + cursor);
+ } while (err != EFI_SUCCESS);
+
+ switch (key) {
+ case KEYPRESS(0, SCAN_ESC, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')):
+ return false;
+
+ case KEYPRESS(0, SCAN_HOME, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')):
+ /* beginning-of-line */
+ cursor = 0;
+ first = 0;
+ continue;
+
+ case KEYPRESS(0, SCAN_END, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')):
+ /* end-of-line */
+ cursor = len - first;
+ if (cursor+1 >= x_max) {
+ cursor = x_max-1;
+ first = len - (x_max-1);
+ }
+ continue;
+
+ case KEYPRESS(0, SCAN_DOWN, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0):
+ /* forward-word */
+ while (line[first + cursor] == ' ')
+ cursor_right(&cursor, &first, x_max, len);
+ while (line[first + cursor] && line[first + cursor] != ' ')
+ cursor_right(&cursor, &first, x_max, len);
+ continue;
+
+ case KEYPRESS(0, SCAN_UP, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0):
+ /* backward-word */
+ if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
+ cursor_left(&cursor, &first);
+ while ((first + cursor) > 0 && line[first + cursor] == ' ')
+ cursor_left(&cursor, &first);
+ }
+ while ((first + cursor) > 0 && line[first + cursor-1] != ' ')
+ cursor_left(&cursor, &first);
+ continue;
+
+ case KEYPRESS(0, SCAN_RIGHT, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')):
+ /* forward-char */
+ if (first + cursor == len)
+ continue;
+ cursor_right(&cursor, &first, x_max, len);
+ continue;
+
+ case KEYPRESS(0, SCAN_LEFT, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')):
+ /* backward-char */
+ cursor_left(&cursor, &first);
+ continue;
+
+ case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_DELETE, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'):
+ /* kill-word */
+ clear = 0;
+
+ size_t k;
+ for (k = first + cursor; k < len && line[k] == ' '; k++)
+ clear++;
+ for (; k < len && line[k] != ' '; k++)
+ clear++;
+
+ for (size_t i = first + cursor; i + clear < len; i++)
+ line[i] = line[i + clear];
+ len -= clear;
+ line[len] = '\0';
+ continue;
+
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, '\b'):
+ /* backward-kill-word */
+ clear = 0;
+ if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
+ cursor_left(&cursor, &first);
+ clear++;
+ while ((first + cursor) > 0 && line[first + cursor] == ' ') {
+ cursor_left(&cursor, &first);
+ clear++;
+ }
+ }
+ while ((first + cursor) > 0 && line[first + cursor-1] != ' ') {
+ cursor_left(&cursor, &first);
+ clear++;
+ }
+
+ for (size_t i = first + cursor; i + clear < len; i++)
+ line[i] = line[i + clear];
+ len -= clear;
+ line[len] = '\0';
+ continue;
+
+ case KEYPRESS(0, SCAN_DELETE, 0):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')):
+ if (len == 0)
+ continue;
+ if (first + cursor == len)
+ continue;
+ for (size_t i = first + cursor; i < len; i++)
+ line[i] = line[i+1];
+ clear = 1;
+ len--;
+ continue;
+
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')):
+ /* kill-line */
+ line[first + cursor] = '\0';
+ clear = len - (first + cursor);
+ len = first + cursor;
+ continue;
+
+ case KEYPRESS(0, 0, '\n'):
+ case KEYPRESS(0, 0, '\r'):
+ case KEYPRESS(0, SCAN_F3, 0): /* EZpad Mini 4s firmware sends malformed events */
+ case KEYPRESS(0, SCAN_F3, '\r'): /* Teclast X98+ II firmware sends malformed events */
+ if (!streq16(line, *line_in)) {
+ free(*line_in);
+ *line_in = TAKE_PTR(line);
+ }
+ return true;
+
+ case KEYPRESS(0, 0, '\b'):
+ if (len == 0)
+ continue;
+ if (first == 0 && cursor == 0)
+ continue;
+ for (size_t i = first + cursor-1; i < len; i++)
+ line[i] = line[i+1];
+ clear = 1;
+ len--;
+ if (cursor > 0)
+ cursor--;
+ if (cursor > 0 || first == 0)
+ continue;
+ /* show full line if it fits */
+ if (len < x_max) {
+ cursor = first;
+ first = 0;
+ continue;
+ }
+ /* jump left to see what we delete */
+ if (first > 10) {
+ first -= 10;
+ cursor = 10;
+ } else {
+ cursor = first;
+ first = 0;
+ }
+ continue;
+
+ case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
+ case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
+ if (len+1 == size)
+ continue;
+ for (size_t i = len; i > first + cursor; i--)
+ line[i] = line[i-1];
+ line[first + cursor] = KEYCHAR(key);
+ len++;
+ line[len] = '\0';
+ if (cursor+1 < x_max)
+ cursor++;
+ else if (first + cursor < len)
+ first++;
+ continue;
+ }
+ }
+}
+
+static size_t entry_lookup_key(Config *config, size_t start, char16_t key) {
+ assert(config);
+
+ if (key == 0)
+ return IDX_INVALID;
+
+ /* select entry by number key */
+ if (key >= '1' && key <= '9') {
+ size_t i = key - '0';
+ if (i > config->n_entries)
+ i = config->n_entries;
+ return i-1;
+ }
+
+ /* find matching key in boot entries */
+ for (size_t i = start; i < config->n_entries; i++)
+ if (config->entries[i]->key == key)
+ return i;
+
+ for (size_t i = 0; i < start; i++)
+ if (config->entries[i]->key == key)
+ return i;
+
+ return IDX_INVALID;
+}
+
+static char16_t* update_timeout_efivar(Config *config, bool inc) {
+ assert(config);
+
+ switch (config->timeout_sec) {
+ case TIMEOUT_MAX:
+ config->timeout_sec = inc ? TIMEOUT_MAX : config->timeout_sec - 1;
+ break;
+ case TIMEOUT_UNSET:
+ config->timeout_sec = inc ? TIMEOUT_MENU_FORCE : TIMEOUT_UNSET;
+ break;
+ case TIMEOUT_MENU_DISABLED:
+ config->timeout_sec = inc ? TIMEOUT_MIN : TIMEOUT_MENU_FORCE;
+ break;
+ case TIMEOUT_MENU_FORCE:
+ config->timeout_sec = inc ? TIMEOUT_MENU_HIDDEN : TIMEOUT_MENU_FORCE;
+ break;
+ case TIMEOUT_MENU_HIDDEN:
+ config->timeout_sec = inc ? TIMEOUT_MIN : TIMEOUT_MENU_FORCE;
+ break;
+ default:
+ config->timeout_sec = config->timeout_sec + (inc ? 1 : -1);
+ }
+
+ config->timeout_sec_efivar = config->timeout_sec;
+
+ switch (config->timeout_sec) {
+ case TIMEOUT_UNSET:
+ return xstrdup16(u"Menu timeout defined by configuration file.");
+ case TIMEOUT_MENU_DISABLED:
+ assert_not_reached();
+ case TIMEOUT_MENU_FORCE:
+ return xstrdup16(u"Timeout disabled, menu will always be shown.");
+ case TIMEOUT_MENU_HIDDEN:
+ return xstrdup16(u"Menu hidden. Hold down key at bootup to show menu.");
+ default:
+ return xasprintf("Menu timeout set to %u s.", (uint32_t)config->timeout_sec_efivar);
+ }
+}
+
+static bool unicode_supported(void) {
+ static int cache = -1;
+
+ if (cache < 0)
+ /* Basic unicode box drawing support is mandated by the spec, but it does
+ * not hurt to make sure it works. */
+ cache = ST->ConOut->TestString(ST->ConOut, (char16_t *) u"─") == EFI_SUCCESS;
+
+ return cache;
+}
+
+static bool ps_continue(void) {
+ const char16_t *sep = unicode_supported() ? u"───" : u"---";
+ printf("\n%ls Press any key to continue, ESC or q to quit. %ls\n\n", sep, sep);
+
+ uint64_t key;
+ return console_key_read(&key, UINT64_MAX) == EFI_SUCCESS &&
+ !IN_SET(key, KEYPRESS(0, SCAN_ESC, 0), KEYPRESS(0, 0, 'q'), KEYPRESS(0, 0, 'Q'));
+}
+
+static void print_timeout_status(const char *label, uint64_t t) {
+ switch (t) {
+ case TIMEOUT_UNSET:
+ return;
+ case TIMEOUT_MENU_DISABLED:
+ return (void) printf("%s: menu-disabled\n", label);
+ case TIMEOUT_MENU_FORCE:
+ return (void) printf("%s: menu-force\n", label);
+ case TIMEOUT_MENU_HIDDEN:
+ return (void) printf("%s: menu-hidden\n", label);
+ default:
+ return (void) printf("%s: %u s\n", label, (uint32_t)t);
+ }
+}
+
+static void print_status(Config *config, char16_t *loaded_image_path) {
+ size_t x_max, y_max;
+ uint32_t screen_width = 0, screen_height = 0;
+ SecureBootMode secure;
+ _cleanup_free_ char16_t *device_part_uuid = NULL;
+
+ assert(config);
+
+ clear_screen(COLOR_NORMAL);
+ console_query_mode(&x_max, &y_max);
+ query_screen_resolution(&screen_width, &screen_height);
+
+ secure = secure_boot_mode();
+ (void) efivar_get(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", &device_part_uuid);
+
+ printf(" systemd-boot version: " GIT_VERSION "\n");
+ if (loaded_image_path)
+ printf(" loaded image: %ls\n", loaded_image_path);
+ if (device_part_uuid)
+ printf(" loader partition UUID: %ls\n", device_part_uuid);
+ printf(" architecture: " EFI_MACHINE_TYPE_NAME "\n");
+ printf(" UEFI specification: %u.%02u\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
+ printf(" firmware vendor: %ls\n", ST->FirmwareVendor);
+ printf(" firmware version: %u.%02u\n", ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
+ printf(" OS indications: %#" PRIx64 "\n", get_os_indications_supported());
+ printf(" secure boot: %ls (%ls)\n",
+ yes_no(IN_SET(secure, SECURE_BOOT_USER, SECURE_BOOT_DEPLOYED)),
+ secure_boot_mode_to_string(secure));
+ printf(" shim: %ls\n", yes_no(shim_loaded()));
+ printf(" TPM: %ls\n", yes_no(tpm_present()));
+ printf(" console mode: %i/%" PRIi64 " (%zux%zu @%ux%u)\n",
+ ST->ConOut->Mode->Mode, ST->ConOut->Mode->MaxMode - INT64_C(1),
+ x_max, y_max, screen_width, screen_height);
+
+ if (!ps_continue())
+ return;
+
+ print_timeout_status(" timeout (config)", config->timeout_sec_config);
+ print_timeout_status(" timeout (EFI var)", config->timeout_sec_efivar);
+
+ if (config->entry_default_config)
+ printf(" default (config): %ls\n", config->entry_default_config);
+ if (config->entry_default_efivar)
+ printf(" default (EFI var): %ls\n", config->entry_default_efivar);
+ if (config->entry_oneshot)
+ printf(" default (one-shot): %ls\n", config->entry_oneshot);
+ if (config->entry_saved)
+ printf(" saved entry: %ls\n", config->entry_saved);
+ printf(" editor: %ls\n", yes_no(config->editor));
+ printf(" auto-entries: %ls\n", yes_no(config->auto_entries));
+ printf(" auto-firmware: %ls\n", yes_no(config->auto_firmware));
+ printf(" auto-poweroff: %ls\n", yes_no(config->auto_poweroff));
+ printf(" auto-reboot: %ls\n", yes_no(config->auto_reboot));
+ printf(" beep: %ls\n", yes_no(config->beep));
+ printf(" reboot-for-bitlocker: %ls\n", yes_no(config->reboot_for_bitlocker));
+
+ switch (config->secure_boot_enroll) {
+ case ENROLL_OFF:
+ printf(" secure-boot-enroll: off\n");
+ break;
+ case ENROLL_MANUAL:
+ printf(" secure-boot-enroll: manual\n");
+ break;
+ case ENROLL_IF_SAFE:
+ printf(" secure-boot-enroll: if-safe\n");
+ break;
+ case ENROLL_FORCE:
+ printf(" secure-boot-enroll: force\n");
+ break;
+ default:
+ assert_not_reached();
+ }
+
+ switch (config->console_mode) {
+ case CONSOLE_MODE_AUTO:
+ printf(" console-mode (config): auto\n");
+ break;
+ case CONSOLE_MODE_KEEP:
+ printf(" console-mode (config): keep\n");
+ break;
+ case CONSOLE_MODE_FIRMWARE_MAX:
+ printf(" console-mode (config): max\n");
+ break;
+ default:
+ printf(" console-mode (config): %" PRIi64 "\n", config->console_mode);
+ break;
+ }
+
+ /* EFI var console mode is always a concrete value or unset. */
+ if (config->console_mode_efivar != CONSOLE_MODE_KEEP)
+ printf("console-mode (EFI var): %" PRIi64 "\n", config->console_mode_efivar);
+
+ if (!ps_continue())
+ return;
+
+ for (size_t i = 0; i < config->n_entries; i++) {
+ BootEntry *entry = config->entries[i];
+ EFI_DEVICE_PATH *dp = NULL;
+ _cleanup_free_ char16_t *dp_str = NULL;
+
+ if (entry->device &&
+ BS->HandleProtocol(entry->device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp) ==
+ EFI_SUCCESS)
+ (void) device_path_to_str(dp, &dp_str);
+
+ printf(" boot entry: %zu/%zu\n", i + 1, config->n_entries);
+ printf(" id: %ls\n", entry->id);
+ if (entry->title)
+ printf(" title: %ls\n", entry->title);
+ if (entry->title_show && !streq16(entry->title, entry->title_show))
+ printf(" title show: %ls\n", entry->title_show);
+ if (entry->sort_key)
+ printf(" sort key: %ls\n", entry->sort_key);
+ if (entry->version)
+ printf(" version: %ls\n", entry->version);
+ if (entry->machine_id)
+ printf(" machine-id: %ls\n", entry->machine_id);
+ if (dp_str)
+ printf(" device: %ls\n", dp_str);
+ if (entry->loader)
+ printf(" loader: %ls\n", entry->loader);
+ STRV_FOREACH(initrd, entry->initrd)
+ printf(" initrd: %ls\n", *initrd);
+ if (entry->devicetree)
+ printf(" devicetree: %ls\n", entry->devicetree);
+ if (entry->options)
+ printf(" options: %ls\n", entry->options);
+ printf(" internal call: %ls\n", yes_no(!!entry->call));
+
+ printf("counting boots: %ls\n", yes_no(entry->tries_left >= 0));
+ if (entry->tries_left >= 0) {
+ printf(" tries: %i left, %i done\n", entry->tries_left, entry->tries_done);
+ printf(" current path: %ls\\%ls\n", entry->path, entry->current_name);
+ printf(" next path: %ls\\%ls\n", entry->path, entry->next_name);
+ }
+
+ if (!ps_continue())
+ return;
+ }
+}
+
+static EFI_STATUS set_reboot_into_firmware(void) {
+ uint64_t osind = 0;
+ EFI_STATUS err;
+
+ (void) efivar_get_uint64_le(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"OsIndications", &osind);
+ osind |= EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
+
+ err = efivar_set_uint64_le(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"OsIndications", osind, EFI_VARIABLE_NON_VOLATILE);
+ if (err != EFI_SUCCESS)
+ log_error_status(err, "Error setting OsIndications: %m");
+ return err;
+}
+
+_noreturn_ static EFI_STATUS poweroff_system(void) {
+ RT->ResetSystem(EfiResetShutdown, EFI_SUCCESS, 0, NULL);
+ assert_not_reached();
+}
+
+_noreturn_ static EFI_STATUS reboot_system(void) {
+ RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
+ assert_not_reached();
+}
+
+static EFI_STATUS reboot_into_firmware(void) {
+ EFI_STATUS err;
+
+ err = set_reboot_into_firmware();
+ if (err != EFI_SUCCESS)
+ return err;
+
+ return reboot_system();
+}
+
+static bool menu_run(
+ Config *config,
+ BootEntry **chosen_entry,
+ char16_t *loaded_image_path) {
+
+ assert(config);
+ assert(chosen_entry);
+
+ EFI_STATUS err;
+ size_t visible_max = 0;
+ size_t idx_highlight = config->idx_default, idx_highlight_prev = 0;
+ size_t idx, idx_first = 0, idx_last = 0;
+ bool new_mode = true, clear = true;
+ bool refresh = true, highlight = false;
+ size_t x_start = 0, y_start = 0, y_status = 0, x_max, y_max;
+ _cleanup_(strv_freep) char16_t **lines = NULL;
+ _cleanup_free_ char16_t *clearline = NULL, *separator = NULL, *status = NULL;
+ uint64_t timeout_efivar_saved = config->timeout_sec_efivar;
+ uint32_t timeout_remain = config->timeout_sec == TIMEOUT_MENU_FORCE ? 0 : config->timeout_sec;
+ int64_t console_mode_initial = ST->ConOut->Mode->Mode, console_mode_efivar_saved = config->console_mode_efivar;
+ size_t default_efivar_saved = config->idx_default_efivar;
+
+ enum {
+ ACTION_CONTINUE, /* Continue with loop over user input */
+ ACTION_FIRMWARE_SETUP, /* Ask for confirmation and reboot into firmware setup */
+ ACTION_POWEROFF, /* Power off the machine */
+ ACTION_REBOOT, /* Reboot the machine */
+ ACTION_RUN, /* Execute a boot entry */
+ ACTION_QUIT, /* Return to the firmware */
+ } action = ACTION_CONTINUE;
+
+ graphics_mode(false);
+ ST->ConIn->Reset(ST->ConIn, false);
+ ST->ConOut->EnableCursor(ST->ConOut, false);
+
+ /* draw a single character to make ClearScreen work on some firmware */
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) u" ");
+
+ err = console_set_mode(config->console_mode_efivar != CONSOLE_MODE_KEEP ?
+ config->console_mode_efivar : config->console_mode);
+ if (err != EFI_SUCCESS) {
+ clear_screen(COLOR_NORMAL);
+ log_error_status(err, "Error switching console mode: %m");
+ }
+
+ size_t line_width = 0, entry_padding = 3;
+ while (IN_SET(action, ACTION_CONTINUE, ACTION_FIRMWARE_SETUP)) {
+ uint64_t key;
+
+ if (new_mode) {
+ console_query_mode(&x_max, &y_max);
+
+ /* account for padding+status */
+ visible_max = y_max - 2;
+
+ /* Drawing entries starts at idx_first until idx_last. We want to make
+ * sure that idx_highlight is centered, but not if we are close to the
+ * beginning/end of the entry list. Otherwise we would have a half-empty
+ * screen. */
+ if (config->n_entries <= visible_max || idx_highlight <= visible_max / 2)
+ idx_first = 0;
+ else if (idx_highlight >= config->n_entries - (visible_max / 2))
+ idx_first = config->n_entries - visible_max;
+ else
+ idx_first = idx_highlight - (visible_max / 2);
+ idx_last = idx_first + visible_max - 1;
+
+ /* length of the longest entry */
+ line_width = 0;
+ for (size_t i = 0; i < config->n_entries; i++)
+ line_width = MAX(line_width, strlen16(config->entries[i]->title_show));
+ line_width = MIN(line_width + 2 * entry_padding, x_max);
+
+ /* offsets to center the entries on the screen */
+ x_start = (x_max - (line_width)) / 2;
+ if (config->n_entries < visible_max)
+ y_start = ((visible_max - config->n_entries) / 2) + 1;
+ else
+ y_start = 0;
+
+ /* Put status line after the entry list, but give it some breathing room. */
+ y_status = MIN(y_start + MIN(visible_max, config->n_entries) + 1, y_max - 1);
+
+ lines = strv_free(lines);
+ clearline = mfree(clearline);
+ separator = mfree(separator);
+
+ /* menu entries title lines */
+ lines = xnew(char16_t *, config->n_entries + 1);
+
+ for (size_t i = 0; i < config->n_entries; i++) {
+ size_t j, padding;
+
+ lines[i] = xnew(char16_t, line_width + 1);
+ padding = (line_width - MIN(strlen16(config->entries[i]->title_show), line_width)) / 2;
+
+ for (j = 0; j < padding; j++)
+ lines[i][j] = ' ';
+
+ for (size_t k = 0; config->entries[i]->title_show[k] != '\0' && j < line_width; j++, k++)
+ lines[i][j] = config->entries[i]->title_show[k];
+
+ for (; j < line_width; j++)
+ lines[i][j] = ' ';
+ lines[i][line_width] = '\0';
+ }
+ lines[config->n_entries] = NULL;
+
+ clearline = xnew(char16_t, x_max + 1);
+ separator = xnew(char16_t, x_max + 1);
+ for (size_t i = 0; i < x_max; i++) {
+ clearline[i] = ' ';
+ separator[i] = unicode_supported() ? L'─' : L'-';
+ }
+ clearline[x_max] = 0;
+ separator[x_max] = 0;
+
+ new_mode = false;
+ clear = true;
+ }
+
+ if (clear) {
+ clear_screen(COLOR_NORMAL);
+ clear = false;
+ refresh = true;
+ }
+
+ if (refresh) {
+ for (size_t i = idx_first; i <= idx_last && i < config->n_entries; i++) {
+ print_at(x_start, y_start + i - idx_first,
+ i == idx_highlight ? COLOR_HIGHLIGHT : COLOR_ENTRY,
+ lines[i]);
+ if (i == config->idx_default_efivar)
+ print_at(x_start,
+ y_start + i - idx_first,
+ i == idx_highlight ? COLOR_HIGHLIGHT : COLOR_ENTRY,
+ unicode_supported() ? u" ►" : u"=>");
+ }
+ refresh = false;
+ } else if (highlight) {
+ print_at(x_start, y_start + idx_highlight_prev - idx_first, COLOR_ENTRY, lines[idx_highlight_prev]);
+ print_at(x_start, y_start + idx_highlight - idx_first, COLOR_HIGHLIGHT, lines[idx_highlight]);
+ if (idx_highlight_prev == config->idx_default_efivar)
+ print_at(x_start,
+ y_start + idx_highlight_prev - idx_first,
+ COLOR_ENTRY,
+ unicode_supported() ? u" ►" : u"=>");
+ if (idx_highlight == config->idx_default_efivar)
+ print_at(x_start,
+ y_start + idx_highlight - idx_first,
+ COLOR_HIGHLIGHT,
+ unicode_supported() ? u" ►" : u"=>");
+ highlight = false;
+ }
+
+ if (timeout_remain > 0) {
+ free(status);
+ status = xasprintf("Boot in %u s.", timeout_remain);
+ }
+
+ if (status) {
+ /* If we draw the last char of the last line, the screen will scroll and break our
+ * input. Therefore, draw one less character then we could for the status message.
+ * Note that the same does not apply for the separator line as it will never be drawn
+ * on the last line. */
+ size_t len = strnlen16(status, x_max - 1);
+ size_t x = (x_max - len) / 2;
+ status[len] = '\0';
+ print_at(0, y_status, COLOR_NORMAL, clearline + x_max - x);
+ ST->ConOut->OutputString(ST->ConOut, status);
+ ST->ConOut->OutputString(ST->ConOut, clearline + 1 + x + len);
+
+ len = MIN(MAX(len, line_width) + 2 * entry_padding, x_max);
+ x = (x_max - len) / 2;
+ print_at(x, y_status - 1, COLOR_NORMAL, separator + x_max - len);
+ } else {
+ print_at(0, y_status - 1, COLOR_NORMAL, clearline);
+ print_at(0, y_status, COLOR_NORMAL, clearline + 1); /* See comment above. */
+ }
+
+ /* Beep several times so that the selected entry can be distinguished. */
+ if (config->beep)
+ beep(idx_highlight + 1);
+
+ err = console_key_read(&key, timeout_remain > 0 ? 1000 * 1000 : UINT64_MAX);
+ if (err == EFI_NOT_READY)
+ /* No input device returned a key, try again. This
+ * normally should not happen. */
+ continue;
+ if (err == EFI_TIMEOUT) {
+ assert(timeout_remain > 0);
+ timeout_remain--;
+ if (timeout_remain == 0) {
+ action = ACTION_RUN;
+ break;
+ }
+
+ /* update status */
+ continue;
+ }
+ if (err != EFI_SUCCESS) {
+ action = ACTION_RUN;
+ break;
+ }
+
+ timeout_remain = 0;
+
+ /* clear status after keystroke */
+ status = mfree(status);
+
+ idx_highlight_prev = idx_highlight;
+
+ if (action == ACTION_FIRMWARE_SETUP) {
+ if (IN_SET(key, KEYPRESS(0, 0, '\r'), KEYPRESS(0, 0, '\n')) &&
+ set_reboot_into_firmware() == EFI_SUCCESS)
+ break;
+
+ /* Any key other than newline or a failed attempt cancel the request. */
+ action = ACTION_CONTINUE;
+ continue;
+ }
+
+ switch (key) {
+ case KEYPRESS(0, SCAN_UP, 0):
+ case KEYPRESS(0, 0, 'k'):
+ case KEYPRESS(0, 0, 'K'):
+ if (idx_highlight > 0)
+ idx_highlight--;
+ break;
+
+ case KEYPRESS(0, SCAN_DOWN, 0):
+ case KEYPRESS(0, 0, 'j'):
+ case KEYPRESS(0, 0, 'J'):
+ if (idx_highlight < config->n_entries-1)
+ idx_highlight++;
+ break;
+
+ case KEYPRESS(0, SCAN_HOME, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, '<'):
+ if (idx_highlight > 0) {
+ refresh = true;
+ idx_highlight = 0;
+ }
+ break;
+
+ case KEYPRESS(0, SCAN_END, 0):
+ case KEYPRESS(EFI_ALT_PRESSED, 0, '>'):
+ if (idx_highlight < config->n_entries-1) {
+ refresh = true;
+ idx_highlight = config->n_entries-1;
+ }
+ break;
+
+ case KEYPRESS(0, SCAN_PAGE_UP, 0):
+ if (idx_highlight > visible_max)
+ idx_highlight -= visible_max;
+ else
+ idx_highlight = 0;
+ break;
+
+ case KEYPRESS(0, SCAN_PAGE_DOWN, 0):
+ idx_highlight += visible_max;
+ if (idx_highlight > config->n_entries-1)
+ idx_highlight = config->n_entries-1;
+ break;
+
+ case KEYPRESS(0, 0, '\n'):
+ case KEYPRESS(0, 0, '\r'):
+ case KEYPRESS(0, SCAN_F3, 0): /* EZpad Mini 4s firmware sends malformed events */
+ case KEYPRESS(0, SCAN_F3, '\r'): /* Teclast X98+ II firmware sends malformed events */
+ case KEYPRESS(0, SCAN_RIGHT, 0):
+ action = ACTION_RUN;
+ break;
+
+ case KEYPRESS(0, SCAN_F1, 0):
+ case KEYPRESS(0, 0, 'h'):
+ case KEYPRESS(0, 0, 'H'):
+ case KEYPRESS(0, 0, '?'):
+ /* This must stay below 80 characters! Q/v/Ctrl+l/f deliberately not advertised. */
+ status = xasprintf("(d)efault (t/T)imeout (e)dit (r/R)esolution (p)rint %s%s(h)elp",
+ config->auto_poweroff ? "" : "(O)ff ",
+ config->auto_reboot ? "" : "re(B)oot ");
+ break;
+
+ case KEYPRESS(0, 0, 'Q'):
+ action = ACTION_QUIT;
+ break;
+
+ case KEYPRESS(0, 0, 'd'):
+ case KEYPRESS(0, 0, 'D'):
+ if (config->idx_default_efivar != idx_highlight) {
+ free(config->entry_default_efivar);
+ config->entry_default_efivar = xstrdup16(config->entries[idx_highlight]->id);
+ config->idx_default_efivar = idx_highlight;
+ status = xstrdup16(u"Default boot entry selected.");
+ } else {
+ config->entry_default_efivar = mfree(config->entry_default_efivar);
+ config->idx_default_efivar = IDX_INVALID;
+ status = xstrdup16(u"Default boot entry cleared.");
+ }
+ config->use_saved_entry_efivar = false;
+ refresh = true;
+ break;
+
+ case KEYPRESS(0, 0, '-'):
+ case KEYPRESS(0, 0, 'T'):
+ status = update_timeout_efivar(config, false);
+ break;
+
+ case KEYPRESS(0, 0, '+'):
+ case KEYPRESS(0, 0, 't'):
+ status = update_timeout_efivar(config, true);
+ break;
+
+ case KEYPRESS(0, 0, 'e'):
+ case KEYPRESS(0, 0, 'E'):
+ /* only the options of configured entries can be edited */
+ if (!config->editor ||
+ !IN_SET(config->entries[idx_highlight]->type, LOADER_EFI, LOADER_LINUX, LOADER_UNIFIED_LINUX)) {
+ status = xstrdup16(u"Entry does not support editing the command line.");
+ break;
+ }
+
+ /* Unified kernels that are signed as a whole will not accept command line options
+ * when secure boot is enabled unless there is none embedded in the image. Do not try
+ * to pretend we can edit it to only have it be ignored. */
+ if (config->entries[idx_highlight]->type == LOADER_UNIFIED_LINUX &&
+ secure_boot_enabled() &&
+ config->entries[idx_highlight]->options) {
+ status = xstrdup16(u"Entry not editable in SecureBoot mode.");
+ break;
+ }
+
+ /* The edit line may end up on the last line of the screen. And even though we're
+ * not telling the firmware to advance the line, it still does in this one case,
+ * causing a scroll to happen that screws with our beautiful boot loader output.
+ * 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);
+ if (line_edit(&config->entries[idx_highlight]->options, x_max - 2, y_status))
+ action = ACTION_RUN;
+ print_at(1, y_status, COLOR_NORMAL, clearline + 2);
+
+ /* The options string was now edited, hence we have to pass it to the invoked
+ * binary. */
+ config->entries[idx_highlight]->options_implied = false;
+ break;
+
+ case KEYPRESS(0, 0, 'v'):
+ status = xasprintf(
+ "systemd-boot " GIT_VERSION " (" EFI_MACHINE_TYPE_NAME "), "
+ "UEFI Specification %u.%02u, Vendor %ls %u.%02u",
+ ST->Hdr.Revision >> 16,
+ ST->Hdr.Revision & 0xffff,
+ ST->FirmwareVendor,
+ ST->FirmwareRevision >> 16,
+ ST->FirmwareRevision & 0xffff);
+ break;
+
+ case KEYPRESS(0, 0, 'p'):
+ case KEYPRESS(0, 0, 'P'):
+ print_status(config, loaded_image_path);
+ clear = true;
+ break;
+
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'):
+ case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')):
+ case 'L': /* only uppercase, do not conflict with lower-case 'l' which picks first Linux entry */
+ clear = true;
+ break;
+
+ case KEYPRESS(0, 0, 'r'):
+ err = console_set_mode(CONSOLE_MODE_NEXT);
+ if (err != EFI_SUCCESS)
+ status = xasprintf_status(err, "Error changing console mode: %m");
+ else {
+ config->console_mode_efivar = ST->ConOut->Mode->Mode;
+ status = xasprintf(
+ "Console mode changed to %" PRIi64 ".",
+ config->console_mode_efivar);
+ }
+ new_mode = true;
+ break;
+
+ case KEYPRESS(0, 0, 'R'):
+ config->console_mode_efivar = CONSOLE_MODE_KEEP;
+ err = console_set_mode(config->console_mode == CONSOLE_MODE_KEEP ?
+ console_mode_initial : config->console_mode);
+ if (err != EFI_SUCCESS)
+ status = xasprintf_status(err, "Error resetting console mode: %m");
+ else
+ status = xasprintf(
+ "Console mode reset to %s default.",
+ config->console_mode == CONSOLE_MODE_KEEP ?
+ "firmware" :
+ "configuration file");
+ new_mode = true;
+ break;
+
+ case KEYPRESS(0, 0, 'f'):
+ case KEYPRESS(0, 0, 'F'):
+ case KEYPRESS(0, SCAN_F2, 0): /* Most vendors. */
+ case KEYPRESS(0, SCAN_F10, 0): /* HP and Lenovo. */
+ case KEYPRESS(0, SCAN_DELETE, 0): /* Same as F2. */
+ case KEYPRESS(0, SCAN_ESC, 0): /* HP. */
+ if (FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) {
+ action = ACTION_FIRMWARE_SETUP;
+ /* Let's make sure the user really wants to do this. */
+ status = xstrdup16(u"Press Enter to reboot into firmware interface.");
+ } else
+ status = xstrdup16(u"Reboot into firmware interface not supported.");
+ break;
+
+ case KEYPRESS(0, 0, 'O'): /* Only uppercase, so that it can't be hit so easily fat-fingered,
+ * but still works safely over serial. */
+ action = ACTION_POWEROFF;
+ break;
+
+ case KEYPRESS(0, 0, 'B'): /* ditto */
+ action = ACTION_REBOOT;
+ break;
+
+ default:
+ /* jump with a hotkey directly to a matching entry */
+ idx = entry_lookup_key(config, idx_highlight+1, KEYCHAR(key));
+ if (idx == IDX_INVALID)
+ break;
+ idx_highlight = idx;
+ refresh = true;
+ }
+
+ if (idx_highlight > idx_last) {
+ idx_last = idx_highlight;
+ idx_first = 1 + idx_highlight - visible_max;
+ refresh = true;
+ } else if (idx_highlight < idx_first) {
+ idx_first = idx_highlight;
+ idx_last = idx_highlight + visible_max-1;
+ refresh = true;
+ }
+
+ if (!refresh && idx_highlight != idx_highlight_prev)
+ highlight = true;
+ }
+
+ /* Update EFI vars after we left the menu to reduce NVRAM writes. */
+
+ if (default_efivar_saved != config->idx_default_efivar)
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderEntryDefault", config->entry_default_efivar, EFI_VARIABLE_NON_VOLATILE);
+
+ if (console_mode_efivar_saved != config->console_mode_efivar) {
+ if (config->console_mode_efivar == CONSOLE_MODE_KEEP)
+ efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderConfigConsoleMode", EFI_VARIABLE_NON_VOLATILE);
+ else
+ efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"LoaderConfigConsoleMode",
+ config->console_mode_efivar, EFI_VARIABLE_NON_VOLATILE);
+ }
+
+ if (timeout_efivar_saved != config->timeout_sec_efivar) {
+ switch (config->timeout_sec_efivar) {
+ case TIMEOUT_UNSET:
+ efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout", EFI_VARIABLE_NON_VOLATILE);
+ break;
+ case TIMEOUT_MENU_DISABLED:
+ assert_not_reached();
+ case TIMEOUT_MENU_FORCE:
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout", u"menu-force", EFI_VARIABLE_NON_VOLATILE);
+ break;
+ case TIMEOUT_MENU_HIDDEN:
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout", u"menu-hidden", EFI_VARIABLE_NON_VOLATILE);
+ break;
+ default:
+ assert(config->timeout_sec_efivar < UINT32_MAX);
+ efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeout",
+ config->timeout_sec_efivar, EFI_VARIABLE_NON_VOLATILE);
+ }
+ }
+
+ switch (action) {
+ case ACTION_CONTINUE:
+ assert_not_reached();
+ case ACTION_POWEROFF:
+ poweroff_system();
+ case ACTION_REBOOT:
+ case ACTION_FIRMWARE_SETUP:
+ reboot_system();
+ case ACTION_RUN:
+ case ACTION_QUIT:
+ break;
+ }
+
+ *chosen_entry = config->entries[idx_highlight];
+ clear_screen(COLOR_NORMAL);
+ return action == ACTION_RUN;
+}
+
+static void config_add_entry(Config *config, BootEntry *entry) {
+ assert(config);
+ assert(entry);
+
+ /* This is just for paranoia. */
+ assert(config->n_entries < IDX_MAX);
+
+ if ((config->n_entries & 15) == 0) {
+ config->entries = xrealloc(
+ config->entries,
+ sizeof(void *) * config->n_entries,
+ sizeof(void *) * (config->n_entries + 16));
+ }
+ config->entries[config->n_entries++] = entry;
+}
+
+static BootEntry* boot_entry_free(BootEntry *entry) {
+ if (!entry)
+ return NULL;
+
+ free(entry->id);
+ free(entry->title_show);
+ free(entry->title);
+ free(entry->sort_key);
+ free(entry->version);
+ free(entry->machine_id);
+ free(entry->loader);
+ free(entry->devicetree);
+ free(entry->options);
+ strv_free(entry->initrd);
+ free(entry->path);
+ free(entry->current_name);
+ free(entry->next_name);
+
+ return mfree(entry);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(BootEntry *, boot_entry_free);
+
+static void config_defaults_load_from_file(Config *config, char *content) {
+ char *line;
+ size_t pos = 0;
+ char *key, *value;
+
+ assert(config);
+ assert(content);
+
+ while ((line = line_get_key_value(content, " \t", &pos, &key, &value)))
+ if (streq8(key, "timeout")) {
+ if (streq8(value, "menu-disabled"))
+ config->timeout_sec_config = TIMEOUT_MENU_DISABLED;
+ else if (streq8(value, "menu-force"))
+ config->timeout_sec_config = TIMEOUT_MENU_FORCE;
+ else if (streq8(value, "menu-hidden"))
+ config->timeout_sec_config = TIMEOUT_MENU_HIDDEN;
+ else {
+ uint64_t u;
+ if (!parse_number8(value, &u, NULL) || u > TIMEOUT_TYPE_MAX) {
+ log_error("Error parsing 'timeout' config option, ignoring: %s",
+ value);
+ continue;
+ }
+ config->timeout_sec_config = u;
+ }
+ config->timeout_sec = config->timeout_sec_config;
+
+ } else if (streq8(key, "default")) {
+ if (value[0] == '@' && !strcaseeq8(value, "@saved")) {
+ log_error("Unsupported special entry identifier, ignoring: %s", value);
+ continue;
+ }
+ free(config->entry_default_config);
+ config->entry_default_config = xstr8_to_16(value);
+
+ } else if (streq8(key, "editor")) {
+ if (!parse_boolean(value, &config->editor))
+ log_error("Error parsing 'editor' config option, ignoring: %s", value);
+
+ } else if (streq8(key, "auto-entries")) {
+ if (!parse_boolean(value, &config->auto_entries))
+ log_error("Error parsing 'auto-entries' config option, ignoring: %s", value);
+
+ } else if (streq8(key, "auto-firmware")) {
+ if (!parse_boolean(value, &config->auto_firmware))
+ log_error("Error parsing 'auto-firmware' config option, ignoring: %s", value);
+
+ } else if (streq8(key, "auto-poweroff")) {
+ if (!parse_boolean(value, &config->auto_poweroff))
+ log_error("Error parsing 'auto-poweroff' config option, ignoring: %s", value);
+
+ } else if (streq8(key, "auto-reboot")) {
+ if (!parse_boolean(value, &config->auto_reboot))
+ log_error("Error parsing 'auto-reboot' config option, ignoring: %s", value);
+
+ } else if (streq8(key, "beep")) {
+ if (!parse_boolean(value, &config->beep))
+ log_error("Error parsing 'beep' config option, ignoring: %s", value);
+
+ } else if (streq8(key, "reboot-for-bitlocker")) {
+ if (!parse_boolean(value, &config->reboot_for_bitlocker))
+ log_error("Error parsing 'reboot-for-bitlocker' config option, ignoring: %s",
+ value);
+
+ } else if (streq8(key, "secure-boot-enroll")) {
+ if (streq8(value, "manual"))
+ config->secure_boot_enroll = ENROLL_MANUAL;
+ else if (streq8(value, "force"))
+ config->secure_boot_enroll = ENROLL_FORCE;
+ else if (streq8(value, "if-safe"))
+ config->secure_boot_enroll = ENROLL_IF_SAFE;
+ else if (streq8(value, "off"))
+ config->secure_boot_enroll = ENROLL_OFF;
+ else
+ log_error("Error parsing 'secure-boot-enroll' config option, ignoring: %s",
+ value);
+
+ } else if (streq8(key, "console-mode")) {
+ if (streq8(value, "auto"))
+ config->console_mode = CONSOLE_MODE_AUTO;
+ else if (streq8(value, "max"))
+ config->console_mode = CONSOLE_MODE_FIRMWARE_MAX;
+ else if (streq8(value, "keep"))
+ config->console_mode = CONSOLE_MODE_KEEP;
+ else {
+ uint64_t u;
+ if (!parse_number8(value, &u, NULL) || u > CONSOLE_MODE_RANGE_MAX) {
+ log_error("Error parsing 'console-mode' config option, ignoring: %s",
+ value);
+ continue;
+ }
+ config->console_mode = u;
+ }
+ }
+}
+
+static void boot_entry_parse_tries(
+ BootEntry *entry,
+ const char16_t *path,
+ const char16_t *file,
+ const char16_t *suffix) {
+
+ assert(entry);
+ assert(path);
+ assert(file);
+ assert(suffix);
+
+ /*
+ * Parses a suffix of two counters (one going down, one going up) in the form "+LEFT-DONE" from the end of the
+ * filename (but before the .efi/.conf suffix), where the "-DONE" part is optional and may be left out (in
+ * which case that counter as assumed to be zero, i.e. the missing part is synonymous to "-0").
+ *
+ * Names we grok, and the series they result in:
+ *
+ * foobar+3.efi → foobar+2-1.efi → foobar+1-2.efi → foobar+0-3.efi → STOP!
+ * foobar+4-0.efi → foobar+3-1.efi → foobar+2-2.efi → foobar+1-3.efi → foobar+0-4.efi → STOP!
+ */
+
+ const char16_t *counter = NULL;
+ for (;;) {
+ char16_t *plus = strchr16(counter ?: file, '+');
+ if (plus) {
+ /* We want the last "+". */
+ counter = plus + 1;
+ continue;
+ }
+ if (counter)
+ break;
+
+ /* No boot counter found. */
+ return;
+ }
+
+ uint64_t tries_left, tries_done = 0;
+ size_t prefix_len = counter - file;
+
+ if (!parse_number16(counter, &tries_left, &counter) || tries_left > INT_MAX)
+ return;
+
+ /* Parse done counter only if present. */
+ if (*counter == '-' && (!parse_number16(counter + 1, &tries_done, &counter) || tries_done > INT_MAX))
+ return;
+
+ /* Boot counter in the middle of the name? */
+ if (!streq16(counter, suffix))
+ return;
+
+ entry->tries_left = tries_left;
+ entry->tries_done = tries_done;
+ entry->path = xstrdup16(path);
+ entry->current_name = xstrdup16(file);
+ entry->next_name = xasprintf(
+ "%.*ls%" PRIu64 "-%" PRIu64 "%ls",
+ (int) prefix_len,
+ file,
+ LESS_BY(tries_left, 1u),
+ MIN(tries_done + 1, (uint64_t) INT_MAX),
+ suffix);
+}
+
+static EFI_STATUS boot_entry_bump_counters(BootEntry *entry) {
+ _cleanup_free_ char16_t* old_path = NULL, *new_path = NULL;
+ _cleanup_(file_closep) EFI_FILE *handle = NULL;
+ _cleanup_free_ EFI_FILE_INFO *file_info = NULL;
+ size_t file_info_size;
+ EFI_STATUS err;
+
+ assert(entry);
+
+ if (entry->tries_left < 0)
+ return EFI_SUCCESS;
+
+ if (!entry->path || !entry->current_name || !entry->next_name)
+ return EFI_SUCCESS;
+
+ _cleanup_(file_closep) EFI_FILE *root = NULL;
+ err = open_volume(entry->device, &root);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error opening entry root path: %m");
+
+ old_path = xasprintf("%ls\\%ls", entry->path, entry->current_name);
+
+ err = root->Open(root, &handle, old_path, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error opening boot entry: %m");
+
+ err = get_file_info(handle, &file_info, &file_info_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error getting boot entry file info: %m");
+
+ /* And rename the file */
+ strcpy16(file_info->FileName, entry->next_name);
+ err = handle->SetInfo(handle, MAKE_GUID_PTR(EFI_FILE_INFO), file_info_size, file_info);
+ if (err != EFI_SUCCESS)
+ return log_error_status(
+ err, "Failed to rename '%ls' to '%ls', ignoring: %m", old_path, entry->next_name);
+
+ /* Flush everything to disk, just in case… */
+ err = handle->Flush(handle);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error flushing boot entry file info: %m");
+
+ /* Let's tell the OS that we renamed this file, so that it knows what to rename to the counter-less name on
+ * success */
+ new_path = xasprintf("%ls\\%ls", entry->path, entry->next_name);
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderBootCountPath", new_path, 0);
+
+ /* If the file we just renamed is the loader path, then let's update that. */
+ if (streq16(entry->loader, old_path)) {
+ free(entry->loader);
+ entry->loader = TAKE_PTR(new_path);
+ }
+
+ return EFI_SUCCESS;
+}
+
+static void boot_entry_add_type1(
+ Config *config,
+ EFI_HANDLE *device,
+ EFI_FILE *root_dir,
+ const char16_t *path,
+ const char16_t *file,
+ char *content,
+ const char16_t *loaded_image_path) {
+
+ _cleanup_(boot_entry_freep) BootEntry *entry = NULL;
+ char *line;
+ size_t pos = 0, n_initrd = 0;
+ char *key, *value;
+ EFI_STATUS err;
+
+ assert(config);
+ assert(device);
+ assert(root_dir);
+ assert(path);
+ assert(file);
+ assert(content);
+
+ entry = xnew(BootEntry, 1);
+ *entry = (BootEntry) {
+ .tries_done = -1,
+ .tries_left = -1,
+ };
+
+ while ((line = line_get_key_value(content, " \t", &pos, &key, &value)))
+ if (streq8(key, "title")) {
+ free(entry->title);
+ entry->title = xstr8_to_16(value);
+
+ } else if (streq8(key, "sort-key")) {
+ free(entry->sort_key);
+ entry->sort_key = xstr8_to_16(value);
+
+ } else if (streq8(key, "version")) {
+ free(entry->version);
+ entry->version = xstr8_to_16(value);
+
+ } else if (streq8(key, "machine-id")) {
+ free(entry->machine_id);
+ entry->machine_id = xstr8_to_16(value);
+
+ } else if (streq8(key, "linux")) {
+ free(entry->loader);
+ entry->type = LOADER_LINUX;
+ entry->loader = xstr8_to_path(value);
+ entry->key = 'l';
+
+ } else if (streq8(key, "efi")) {
+ entry->type = LOADER_EFI;
+ free(entry->loader);
+ entry->loader = xstr8_to_path(value);
+
+ /* do not add an entry for ourselves */
+ if (strcaseeq16(entry->loader, loaded_image_path)) {
+ entry->type = LOADER_UNDEFINED;
+ break;
+ }
+
+ } else if (streq8(key, "architecture")) {
+ /* do not add an entry for an EFI image of architecture not matching with that of the image */
+ if (!streq8(value, EFI_MACHINE_TYPE_NAME)) {
+ entry->type = LOADER_UNDEFINED;
+ break;
+ }
+
+ } else if (streq8(key, "devicetree")) {
+ free(entry->devicetree);
+ entry->devicetree = xstr8_to_path(value);
+
+ } else if (streq8(key, "initrd")) {
+ entry->initrd = xrealloc(
+ entry->initrd,
+ n_initrd == 0 ? 0 : (n_initrd + 1) * sizeof(uint16_t *),
+ (n_initrd + 2) * sizeof(uint16_t *));
+ entry->initrd[n_initrd++] = xstr8_to_path(value);
+ entry->initrd[n_initrd] = NULL;
+
+ } else if (streq8(key, "options")) {
+ _cleanup_free_ char16_t *new = NULL;
+
+ new = xstr8_to_16(value);
+ if (entry->options) {
+ char16_t *s = xasprintf("%ls %ls", entry->options, new);
+ free(entry->options);
+ entry->options = s;
+ } else
+ entry->options = TAKE_PTR(new);
+ }
+
+ if (entry->type == LOADER_UNDEFINED)
+ return;
+
+ /* check existence */
+ _cleanup_(file_closep) 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);
+ strtolower16(entry->id);
+
+ config_add_entry(config, entry);
+
+ boot_entry_parse_tries(entry, path, file, u".conf");
+ TAKE_PTR(entry);
+}
+
+static EFI_STATUS efivar_get_timeout(const char16_t *var, uint64_t *ret_value) {
+ _cleanup_free_ char16_t *value = NULL;
+ EFI_STATUS err;
+
+ assert(var);
+ assert(ret_value);
+
+ err = efivar_get(MAKE_GUID_PTR(LOADER), var, &value);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (streq16(value, u"menu-disabled")) {
+ *ret_value = TIMEOUT_MENU_DISABLED;
+ return EFI_SUCCESS;
+ }
+ if (streq16(value, u"menu-force")) {
+ *ret_value = TIMEOUT_MENU_FORCE;
+ return EFI_SUCCESS;
+ }
+ if (streq16(value, u"menu-hidden")) {
+ *ret_value = TIMEOUT_MENU_HIDDEN;
+ return EFI_SUCCESS;
+ }
+
+ uint64_t timeout;
+ if (!parse_number16(value, &timeout, NULL))
+ return EFI_INVALID_PARAMETER;
+
+ *ret_value = MIN(timeout, TIMEOUT_TYPE_MAX);
+ return EFI_SUCCESS;
+}
+
+static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
+ _cleanup_free_ char *content = NULL;
+ size_t content_size, value = 0; /* avoid false maybe-uninitialized warning */
+ EFI_STATUS err;
+
+ assert(root_dir);
+
+ *config = (Config) {
+ .editor = true,
+ .auto_entries = true,
+ .auto_firmware = true,
+ .secure_boot_enroll = ENROLL_IF_SAFE,
+ .idx_default_efivar = IDX_INVALID,
+ .console_mode = CONSOLE_MODE_KEEP,
+ .console_mode_efivar = CONSOLE_MODE_KEEP,
+ .timeout_sec_config = TIMEOUT_UNSET,
+ .timeout_sec_efivar = TIMEOUT_UNSET,
+ };
+
+ err = file_read(root_dir, u"\\loader\\loader.conf", 0, 0, &content, &content_size);
+ if (err == EFI_SUCCESS) {
+ /* First, measure. */
+ err = tpm_log_tagged_event(
+ TPM2_PCR_BOOT_LOADER_CONFIG,
+ POINTER_TO_PHYSICAL_ADDRESS(content),
+ content_size,
+ LOADER_CONF_EVENT_TAG_ID,
+ u"loader.conf",
+ /* ret_measured= */ NULL);
+ if (err != EFI_SUCCESS)
+ log_error_status(err, "Error measuring loader.conf into TPM: %m");
+
+ /* Then: parse */
+ config_defaults_load_from_file(config, content);
+ }
+
+ err = efivar_get_timeout(u"LoaderConfigTimeout", &config->timeout_sec_efivar);
+ if (err == EFI_SUCCESS)
+ config->timeout_sec = config->timeout_sec_efivar;
+ else if (err != EFI_NOT_FOUND)
+ log_error_status(err, "Error reading LoaderConfigTimeout EFI variable: %m");
+
+ err = efivar_get_timeout(u"LoaderConfigTimeoutOneShot", &config->timeout_sec);
+ if (err == EFI_SUCCESS) {
+ /* Unset variable now, after all it's "one shot". */
+ (void) efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderConfigTimeoutOneShot", EFI_VARIABLE_NON_VOLATILE);
+
+ config->force_menu = true; /* force the menu when this is set */
+ } else if (err != EFI_NOT_FOUND)
+ log_error_status(err, "Error reading LoaderConfigTimeoutOneShot EFI variable: %m");
+
+ err = efivar_get_uint_string(MAKE_GUID_PTR(LOADER), u"LoaderConfigConsoleMode", &value);
+ if (err == EFI_SUCCESS)
+ config->console_mode_efivar = value;
+
+ err = efivar_get(MAKE_GUID_PTR(LOADER), u"LoaderEntryOneShot", &config->entry_oneshot);
+ if (err == EFI_SUCCESS)
+ /* Unset variable now, after all it's "one shot". */
+ (void) efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderEntryOneShot", EFI_VARIABLE_NON_VOLATILE);
+
+ (void) efivar_get(MAKE_GUID_PTR(LOADER), u"LoaderEntryDefault", &config->entry_default_efivar);
+
+ strtolower16(config->entry_default_config);
+ strtolower16(config->entry_default_efivar);
+ strtolower16(config->entry_oneshot);
+ strtolower16(config->entry_saved);
+
+ config->use_saved_entry = streq16(config->entry_default_config, u"@saved");
+ config->use_saved_entry_efivar = streq16(config->entry_default_efivar, u"@saved");
+ if (config->use_saved_entry || config->use_saved_entry_efivar)
+ (void) efivar_get(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", &config->entry_saved);
+}
+
+static void config_load_type1_entries(
+ Config *config,
+ EFI_HANDLE *device,
+ EFI_FILE *root_dir,
+ const char16_t *loaded_image_path) {
+
+ _cleanup_(file_closep) EFI_FILE *entries_dir = NULL;
+ _cleanup_free_ EFI_FILE_INFO *f = NULL;
+ size_t f_size = 0;
+ EFI_STATUS err;
+
+ assert(config);
+ assert(device);
+ assert(root_dir);
+
+ /* Adds Boot Loader Type #1 entries (i.e. /loader/entries/….conf) */
+
+ err = open_directory(root_dir, u"\\loader\\entries", &entries_dir);
+ if (err != EFI_SUCCESS)
+ return;
+
+ for (;;) {
+ _cleanup_free_ char *content = NULL;
+
+ err = readdir(entries_dir, &f, &f_size);
+ if (err != EFI_SUCCESS || !f)
+ break;
+
+ if (f->FileName[0] == '.')
+ continue;
+ if (FLAGS_SET(f->Attribute, EFI_FILE_DIRECTORY))
+ continue;
+
+ if (!endswith_no_case(f->FileName, u".conf"))
+ continue;
+ if (startswith(f->FileName, u"auto-"))
+ continue;
+
+ err = file_read(entries_dir, f->FileName, 0, 0, &content, NULL);
+ if (err == EFI_SUCCESS)
+ boot_entry_add_type1(config, device, root_dir, u"\\loader\\entries", f->FileName, content, loaded_image_path);
+ }
+}
+
+static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ /* Order entries that have no tries left to the end of the list */
+ r = CMP(a->tries_left == 0, b->tries_left == 0);
+ if (r != 0)
+ return r;
+
+ /* If there's a sort key defined for *both* entries, then we do new-style ordering, i.e. by
+ * sort-key/machine-id/version, with a final fallback to id. If there's no sort key for either, we do
+ * old-style ordering, i.e. by id only. If one has sort key and the other does not, we put new-style
+ * before old-style. */
+ r = CMP(!a->sort_key, !b->sort_key);
+ if (r != 0) /* one is old-style, one new-style */
+ return r;
+
+ if (a->sort_key && b->sort_key) {
+ r = strcmp16(a->sort_key, b->sort_key);
+ if (r != 0)
+ return r;
+
+ /* If multiple installations of the same OS are around, group by machine ID */
+ r = strcmp16(a->machine_id, b->machine_id);
+ if (r != 0)
+ return r;
+
+ /* If the sort key was defined, then order by version now (downwards, putting the newest first) */
+ r = -strverscmp_improved(a->version, b->version);
+ if (r != 0)
+ return r;
+ }
+
+ /* Now order by ID. The version is likely part of the ID, thus note that this will generatelly put
+ * the newer versions earlier. Specifying a sort key explicitly is preferable, because it gives an
+ * explicit sort order. */
+ r = -strverscmp_improved(a->id, b->id);
+ if (r != 0)
+ return r;
+
+ if (a->tries_left < 0 || b->tries_left < 0)
+ return 0;
+
+ /* If both items have boot counting, and otherwise are identical, put the entry with more tries left first */
+ r = -CMP(a->tries_left, b->tries_left);
+ if (r != 0)
+ return r;
+
+ /* If they have the same number of tries left, then let the one win which was tried fewer times so far */
+ return CMP(a->tries_done, b->tries_done);
+}
+
+static size_t config_find_entry(Config *config, const char16_t *pattern) {
+ assert(config);
+
+ /* We expect pattern and entry IDs to be already case folded. */
+
+ if (!pattern)
+ return IDX_INVALID;
+
+ for (size_t i = 0; i < config->n_entries; i++)
+ if (efi_fnmatch(pattern, config->entries[i]->id))
+ return i;
+
+ return IDX_INVALID;
+}
+
+static void config_select_default_entry(Config *config) {
+ size_t i;
+
+ assert(config);
+
+ i = config_find_entry(config, config->entry_oneshot);
+ if (i != IDX_INVALID) {
+ config->idx_default = i;
+ return;
+ }
+
+ i = config_find_entry(config, config->use_saved_entry_efivar ? config->entry_saved : config->entry_default_efivar);
+ if (i != IDX_INVALID) {
+ config->idx_default = i;
+ config->idx_default_efivar = i;
+ return;
+ }
+
+ if (config->use_saved_entry)
+ /* No need to do the same thing twice. */
+ i = config->use_saved_entry_efivar ? IDX_INVALID : config_find_entry(config, config->entry_saved);
+ else
+ i = config_find_entry(config, config->entry_default_config);
+ if (i != IDX_INVALID) {
+ config->idx_default = i;
+ return;
+ }
+
+ /* select the first suitable entry */
+ for (i = 0; i < config->n_entries; i++)
+ if (config->entries[i]->type != LOADER_AUTO && !config->entries[i]->call) {
+ config->idx_default = i;
+ return;
+ }
+
+ /* If no configured entry to select from was found, enable the menu. */
+ config->idx_default = 0;
+ if (config->timeout_sec == 0)
+ config->timeout_sec = 10;
+}
+
+static bool entries_unique(BootEntry **entries, bool *unique, size_t n_entries) {
+ bool is_unique = true;
+
+ assert(entries);
+ assert(unique);
+
+ for (size_t i = 0; i < n_entries; i++)
+ for (size_t k = i + 1; k < n_entries; k++) {
+ if (!streq16(entries[i]->title_show, entries[k]->title_show))
+ continue;
+
+ is_unique = unique[i] = unique[k] = false;
+ }
+
+ return is_unique;
+}
+
+/* generate unique titles, avoiding non-distinguishable menu entries */
+static void generate_boot_entry_titles(Config *config) {
+ assert(config);
+
+ bool unique[config->n_entries];
+
+ /* set title */
+ for (size_t i = 0; i < config->n_entries; i++) {
+ assert(!config->entries[i]->title_show);
+ unique[i] = true;
+ config->entries[i]->title_show = xstrdup16(config->entries[i]->title ?: config->entries[i]->id);
+ }
+
+ if (entries_unique(config->entries, unique, config->n_entries))
+ return;
+
+ /* add version to non-unique titles */
+ for (size_t i = 0; i < config->n_entries; i++) {
+ if (unique[i])
+ continue;
+
+ unique[i] = true;
+
+ if (!config->entries[i]->version)
+ continue;
+
+ _cleanup_free_ char16_t *t = config->entries[i]->title_show;
+ config->entries[i]->title_show = xasprintf("%ls (%ls)", t, config->entries[i]->version);
+ }
+
+ if (entries_unique(config->entries, unique, config->n_entries))
+ return;
+
+ /* add machine-id to non-unique titles */
+ for (size_t i = 0; i < config->n_entries; i++) {
+ if (unique[i])
+ continue;
+
+ unique[i] = true;
+
+ if (!config->entries[i]->machine_id)
+ continue;
+
+ _cleanup_free_ char16_t *t = config->entries[i]->title_show;
+ config->entries[i]->title_show = xasprintf("%ls (%.8ls)", t, config->entries[i]->machine_id);
+ }
+
+ if (entries_unique(config->entries, unique, config->n_entries))
+ return;
+
+ /* add file name to non-unique titles */
+ for (size_t i = 0; i < config->n_entries; i++) {
+ if (unique[i])
+ continue;
+
+ _cleanup_free_ char16_t *t = config->entries[i]->title_show;
+ config->entries[i]->title_show = xasprintf("%ls (%ls)", t, config->entries[i]->id);
+ }
+}
+
+static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) {
+ EFI_STATUS err;
+ static const char * const sections[] = {
+ ".sdmagic",
+ NULL
+ };
+ size_t offset = 0, size = 0, read;
+ _cleanup_free_ char *content = NULL;
+
+ assert(root_dir);
+ assert(loader_path);
+
+ err = pe_file_locate_sections(root_dir, loader_path, sections, &offset, &size);
+ if (err != EFI_SUCCESS || size != sizeof(SD_MAGIC))
+ return false;
+
+ err = file_read(root_dir, loader_path, offset, size, &content, &read);
+ if (err != EFI_SUCCESS || size != read)
+ return false;
+
+ return memcmp(content, SD_MAGIC, sizeof(SD_MAGIC)) == 0;
+}
+
+static BootEntry* config_add_entry_loader_auto(
+ Config *config,
+ EFI_HANDLE *device,
+ EFI_FILE *root_dir,
+ const char16_t *loaded_image_path,
+ const char16_t *id,
+ char16_t key,
+ const char16_t *title,
+ const char16_t *loader) {
+
+ assert(config);
+ assert(device);
+ assert(root_dir);
+ assert(id);
+ assert(title);
+
+ if (!config->auto_entries)
+ return NULL;
+
+ if (!loader) {
+ loader = u"\\EFI\\BOOT\\BOOT" EFI_MACHINE_TYPE_NAME ".efi";
+
+ /* We are trying to add the default EFI loader here,
+ * but we do not want to do that if that would be us.
+ *
+ * If the default loader is not us, it might be shim. It would
+ * chainload GRUBX64.EFI in that case, which might be us. */
+ if (strcaseeq16(loader, loaded_image_path) ||
+ is_sd_boot(root_dir, loader) ||
+ is_sd_boot(root_dir, u"\\EFI\\BOOT\\GRUB" EFI_MACHINE_TYPE_NAME u".EFI"))
+ return NULL;
+ }
+
+ /* check existence */
+ _cleanup_(file_closep) EFI_FILE *handle = NULL;
+ EFI_STATUS err = root_dir->Open(root_dir, &handle, (char16_t *) loader, EFI_FILE_MODE_READ, 0ULL);
+ if (err != EFI_SUCCESS)
+ return NULL;
+
+ BootEntry *entry = xnew(BootEntry, 1);
+ *entry = (BootEntry) {
+ .id = xstrdup16(id),
+ .type = LOADER_AUTO,
+ .title = xstrdup16(title),
+ .device = device,
+ .loader = xstrdup16(loader),
+ .key = key,
+ .tries_done = -1,
+ .tries_left = -1,
+ };
+
+ config_add_entry(config, entry);
+ return entry;
+}
+
+static void config_add_entry_osx(Config *config) {
+ EFI_STATUS err;
+ size_t n_handles = 0;
+ _cleanup_free_ EFI_HANDLE *handles = NULL;
+
+ assert(config);
+
+ if (!config->auto_entries)
+ return;
+
+ err = BS->LocateHandleBuffer(
+ ByProtocol, MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), NULL, &n_handles, &handles);
+ if (err != EFI_SUCCESS)
+ return;
+
+ for (size_t i = 0; i < n_handles; i++) {
+ _cleanup_(file_closep) EFI_FILE *root = NULL;
+
+ if (open_volume(handles[i], &root) != EFI_SUCCESS)
+ continue;
+
+ if (config_add_entry_loader_auto(
+ config,
+ handles[i],
+ root,
+ NULL,
+ u"auto-osx",
+ 'a',
+ u"macOS",
+ u"\\System\\Library\\CoreServices\\boot.efi"))
+ break;
+ }
+}
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
+static EFI_STATUS boot_windows_bitlocker(void) {
+ _cleanup_free_ EFI_HANDLE *handles = NULL;
+ size_t n_handles;
+ EFI_STATUS err;
+
+ // FIXME: Experimental for now. Should be generalized, and become a per-entry option that can be
+ // enabled independently of BitLocker, and without a BootXXXX entry pre-existing.
+
+ /* BitLocker key cannot be sealed without a TPM present. */
+ if (!tpm_present())
+ return EFI_NOT_FOUND;
+
+ err = BS->LocateHandleBuffer(
+ ByProtocol, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), NULL, &n_handles, &handles);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Look for BitLocker magic string on all block drives. */
+ bool found = false;
+ for (size_t i = 0; i < n_handles; i++) {
+ EFI_BLOCK_IO_PROTOCOL *block_io;
+ err = BS->HandleProtocol(handles[i], MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io);
+ if (err != EFI_SUCCESS || block_io->Media->BlockSize < 512 || block_io->Media->BlockSize > 4096)
+ continue;
+
+ char buf[4096];
+ err = block_io->ReadBlocks(block_io, block_io->Media->MediaId, 0, sizeof(buf), buf);
+ if (err != EFI_SUCCESS)
+ continue;
+
+ if (memcmp(buf + 3, "-FVE-FS-", STRLEN("-FVE-FS-")) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ /* If no BitLocker drive was found, we can just chainload bootmgfw.efi directly. */
+ if (!found)
+ return EFI_NOT_FOUND;
+
+ _cleanup_free_ uint16_t *boot_order = NULL;
+ size_t boot_order_size;
+
+ /* There can be gaps in Boot#### entries. Instead of iterating over the full
+ * EFI var list or uint16_t namespace, just look for "Windows Boot Manager" in BootOrder. */
+ err = efivar_get_raw(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"BootOrder", (char **) &boot_order, &boot_order_size);
+ if (err != EFI_SUCCESS || boot_order_size % sizeof(uint16_t) != 0)
+ return err;
+
+ for (size_t i = 0; i < boot_order_size / sizeof(uint16_t); i++) {
+ _cleanup_free_ char *buf = NULL;
+ size_t buf_size;
+
+ _cleanup_free_ char16_t *name = xasprintf("Boot%04x", boot_order[i]);
+ err = efivar_get_raw(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), name, &buf, &buf_size);
+ if (err != EFI_SUCCESS)
+ continue;
+
+ /* Boot#### are EFI_LOAD_OPTION. But we really are only interested
+ * for the description, which is at this offset. */
+ size_t offset = sizeof(uint32_t) + sizeof(uint16_t);
+ if (buf_size < offset + sizeof(char16_t))
+ continue;
+
+ if (streq16((char16_t *) (buf + offset), u"Windows Boot Manager")) {
+ err = efivar_set_raw(
+ MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE),
+ u"BootNext",
+ boot_order + i,
+ sizeof(boot_order[i]),
+ EFI_VARIABLE_NON_VOLATILE);
+ if (err != EFI_SUCCESS)
+ return err;
+ RT->ResetSystem(EfiResetWarm, EFI_SUCCESS, 0, NULL);
+ assert_not_reached();
+ }
+ }
+
+ return EFI_NOT_FOUND;
+}
+#endif
+
+static void config_add_entry_windows(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir) {
+#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
+ _cleanup_free_ char *bcd = NULL;
+ char16_t *title = NULL;
+ EFI_STATUS err;
+ size_t len;
+
+ assert(config);
+ assert(device);
+ assert(root_dir);
+
+ if (!config->auto_entries)
+ return;
+
+ /* Try to find a better title. */
+ err = file_read(root_dir, u"\\EFI\\Microsoft\\Boot\\BCD", 0, 100*1024, &bcd, &len);
+ if (err == EFI_SUCCESS)
+ title = get_bcd_title((uint8_t *) bcd, len);
+
+ BootEntry *e = config_add_entry_loader_auto(config, device, root_dir, NULL,
+ u"auto-windows", 'w', title ?: u"Windows Boot Manager",
+ u"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
+
+ if (config->reboot_for_bitlocker)
+ e->call = boot_windows_bitlocker;
+#endif
+}
+
+static void config_load_type2_entries(
+ Config *config,
+ EFI_HANDLE *device,
+ EFI_FILE *root_dir) {
+
+ _cleanup_(file_closep) EFI_FILE *linux_dir = NULL;
+ _cleanup_free_ EFI_FILE_INFO *f = NULL;
+ size_t f_size = 0;
+ EFI_STATUS err;
+
+ /* Adds Boot Loader Type #2 entries (i.e. /EFI/Linux/….efi) */
+
+ assert(config);
+ assert(device);
+ assert(root_dir);
+
+ err = open_directory(root_dir, u"\\EFI\\Linux", &linux_dir);
+ if (err != EFI_SUCCESS)
+ return;
+
+ for (;;) {
+ enum {
+ SECTION_CMDLINE,
+ SECTION_OSREL,
+ _SECTION_MAX,
+ };
+
+ static const char * const sections[_SECTION_MAX + 1] = {
+ [SECTION_CMDLINE] = ".cmdline",
+ [SECTION_OSREL] = ".osrel",
+ NULL,
+ };
+
+ _cleanup_free_ char16_t *os_pretty_name = NULL, *os_image_id = NULL, *os_name = NULL, *os_id = NULL,
+ *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL;
+ const char16_t *good_name, *good_version, *good_sort_key;
+ _cleanup_free_ char *content = NULL;
+ size_t offs[_SECTION_MAX] = {}, szs[_SECTION_MAX] = {}, pos = 0;
+ char *line, *key, *value;
+
+ err = readdir(linux_dir, &f, &f_size);
+ if (err != EFI_SUCCESS || !f)
+ break;
+
+ if (f->FileName[0] == '.')
+ continue;
+ if (FLAGS_SET(f->Attribute, EFI_FILE_DIRECTORY))
+ continue;
+ if (!endswith_no_case(f->FileName, u".efi"))
+ continue;
+ if (startswith(f->FileName, u"auto-"))
+ continue;
+
+ /* look for .osrel and .cmdline sections in the .efi binary */
+ err = pe_file_locate_sections(linux_dir, f->FileName, sections, offs, szs);
+ if (err != EFI_SUCCESS || szs[SECTION_OSREL] == 0)
+ continue;
+
+ err = file_read(linux_dir, f->FileName, offs[SECTION_OSREL], szs[SECTION_OSREL], &content, NULL);
+ if (err != EFI_SUCCESS)
+ continue;
+
+ /* read properties from the embedded os-release file */
+ while ((line = line_get_key_value(content, "=", &pos, &key, &value)))
+ if (streq8(key, "PRETTY_NAME")) {
+ free(os_pretty_name);
+ os_pretty_name = xstr8_to_16(value);
+
+ } else if (streq8(key, "IMAGE_ID")) {
+ free(os_image_id);
+ os_image_id = xstr8_to_16(value);
+
+ } else if (streq8(key, "NAME")) {
+ free(os_name);
+ os_name = xstr8_to_16(value);
+
+ } else if (streq8(key, "ID")) {
+ free(os_id);
+ os_id = xstr8_to_16(value);
+
+ } else if (streq8(key, "IMAGE_VERSION")) {
+ free(os_image_version);
+ os_image_version = xstr8_to_16(value);
+
+ } else if (streq8(key, "VERSION")) {
+ free(os_version);
+ os_version = xstr8_to_16(value);
+
+ } else if (streq8(key, "VERSION_ID")) {
+ free(os_version_id);
+ os_version_id = xstr8_to_16(value);
+
+ } else if (streq8(key, "BUILD_ID")) {
+ free(os_build_id);
+ os_build_id = xstr8_to_16(value);
+ }
+
+ if (!bootspec_pick_name_version_sort_key(
+ os_pretty_name,
+ os_image_id,
+ os_name,
+ os_id,
+ os_image_version,
+ os_version,
+ os_version_id,
+ os_build_id,
+ &good_name,
+ &good_version,
+ &good_sort_key))
+ continue;
+
+ BootEntry *entry = xnew(BootEntry, 1);
+ *entry = (BootEntry) {
+ .id = xstrdup16(f->FileName),
+ .type = LOADER_UNIFIED_LINUX,
+ .title = xstrdup16(good_name),
+ .version = xstrdup16(good_version),
+ .device = device,
+ .loader = xasprintf("\\EFI\\Linux\\%ls", f->FileName),
+ .sort_key = xstrdup16(good_sort_key),
+ .key = 'l',
+ .tries_done = -1,
+ .tries_left = -1,
+ };
+
+ strtolower16(entry->id);
+ config_add_entry(config, entry);
+ boot_entry_parse_tries(entry, u"\\EFI\\Linux", f->FileName, u".efi");
+
+ if (szs[SECTION_CMDLINE] == 0)
+ continue;
+
+ content = mfree(content);
+
+ /* read the embedded cmdline file */
+ size_t cmdline_len;
+ err = file_read(linux_dir, f->FileName, offs[SECTION_CMDLINE], szs[SECTION_CMDLINE], &content, &cmdline_len);
+ if (err == EFI_SUCCESS) {
+ entry->options = xstrn8_to_16(content, cmdline_len);
+ mangle_stub_cmdline(entry->options);
+ entry->options_implied = true;
+ }
+ }
+}
+
+static void config_load_xbootldr(
+ Config *config,
+ EFI_HANDLE *device) {
+
+ _cleanup_(file_closep) EFI_FILE *root_dir = NULL;
+ EFI_HANDLE new_device = NULL; /* avoid false maybe-uninitialized warning */
+ EFI_STATUS err;
+
+ assert(config);
+ assert(device);
+
+ err = partition_open(MAKE_GUID_PTR(XBOOTLDR), device, &new_device, &root_dir);
+ if (err != EFI_SUCCESS)
+ return;
+
+ config_load_type2_entries(config, new_device, root_dir);
+ config_load_type1_entries(config, new_device, root_dir, NULL);
+}
+
+static EFI_STATUS initrd_prepare(
+ EFI_FILE *root,
+ const BootEntry *entry,
+ char16_t **ret_options,
+ void **ret_initrd,
+ size_t *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_free_ char16_t *options = NULL;
+
+ EFI_STATUS err;
+ size_t size = 0;
+ _cleanup_free_ uint8_t *initrd = NULL;
+
+ STRV_FOREACH(i, entry->initrd) {
+ _cleanup_free_ char16_t *o = options;
+ if (o)
+ options = xasprintf("%ls initrd=%ls", o, *i);
+ else
+ options = xasprintf("initrd=%ls", *i);
+
+ _cleanup_(file_closep) EFI_FILE *handle = NULL;
+ err = root->Open(root, &handle, *i, EFI_FILE_MODE_READ, 0);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ _cleanup_free_ EFI_FILE_INFO *info = NULL;
+ err = get_file_info(handle, &info, NULL);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (info->FileSize == 0) /* Automatically skip over empty files */
+ continue;
+
+ size_t new_size, read_size = info->FileSize;
+ if (__builtin_add_overflow(size, read_size, &new_size))
+ return EFI_OUT_OF_RESOURCES;
+ initrd = xrealloc(initrd, size, new_size);
+
+ err = chunked_read(handle, &read_size, initrd + size);
+ if (err != EFI_SUCCESS)
+ 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_free_ char16_t *o = options;
+ options = xasprintf("%ls %ls", 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 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);
+
+ /* If this loader entry has a special way to boot, try that first. */
+ if (entry->call)
+ (void) entry->call();
+
+ _cleanup_(file_closep) 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_free_ void *initrd = NULL;
+ _cleanup_free_ char16_t *options_initrd = NULL;
+ err = initrd_prepare(image_root, entry, &options_initrd, &initrd, &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);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error loading %ls: %m", entry->devicetree);
+ }
+
+ _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
+ err = initrd_register(initrd, 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);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error getting LoadedImageProtocol handle: %m");
+
+ /* If we had to append an initrd= entry to the command line, we have to pass it, and measure it.
+ * Otherwise, only pass/measure it if it is not implicit anyway (i.e. embedded into the UKI or
+ * so). */
+ char16_t *options = options_initrd ?: entry->options_implied ? NULL : entry->options;
+ if (options) {
+ loaded_image->LoadOptions = options;
+ loaded_image->LoadOptionsSize = strsize16(options);
+
+ /* Try to log any options to the TPM, especially to catch manually edited options */
+ (void) tpm_log_load_options(options, NULL);
+ }
+
+ efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeExecUSec", 0);
+ err = BS->StartImage(image, NULL, NULL);
+ 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_t compat_address;
+
+ err = pe_kernel_info(loaded_image->ImageBase, &compat_address);
+ if (err != EFI_SUCCESS) {
+ if (err != EFI_UNSUPPORTED)
+ return log_error_status(err, "Error finding kernel compat entry address: %m");
+ } else if (compat_address > 0) {
+ EFI_IMAGE_ENTRY_POINT kernel_entry =
+ (EFI_IMAGE_ENTRY_POINT) ((uint8_t *) loaded_image->ImageBase + compat_address);
+
+ err = kernel_entry(image, ST);
+ graphics_mode(false);
+ if (err == EFI_SUCCESS)
+ return EFI_SUCCESS;
+ } else
+ err = EFI_UNSUPPORTED;
+ }
+
+ return log_error_status(err, "Failed to execute %ls (%ls): %m", entry->title_show, entry->loader);
+}
+
+static void config_free(Config *config) {
+ assert(config);
+ for (size_t i = 0; i < config->n_entries; i++)
+ boot_entry_free(config->entries[i]);
+ free(config->entries);
+ free(config->entry_default_config);
+ free(config->entry_default_efivar);
+ free(config->entry_oneshot);
+ free(config->entry_saved);
+}
+
+static void config_write_entries_to_variable(Config *config) {
+ _cleanup_free_ char *buffer = NULL;
+ size_t sz = 0;
+ char *p;
+
+ assert(config);
+
+ for (size_t i = 0; i < config->n_entries; i++)
+ sz += strsize16(config->entries[i]->id);
+
+ p = buffer = xmalloc(sz);
+
+ for (size_t i = 0; i < config->n_entries; i++)
+ p = mempcpy(p, config->entries[i]->id, strsize16(config->entries[i]->id));
+
+ assert(p == buffer + sz);
+
+ /* Store the full list of discovered entries. */
+ (void) efivar_set_raw(MAKE_GUID_PTR(LOADER), u"LoaderEntries", buffer, sz, 0);
+}
+
+static void save_selected_entry(const Config *config, const BootEntry *entry) {
+ assert(config);
+ assert(entry);
+ assert(entry->loader || !entry->call);
+
+ /* Always export the selected boot entry to the system in a volatile var. */
+ (void) efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderEntrySelected", entry->id, 0);
+
+ /* Do not save or delete if this was a oneshot boot. */
+ if (streq16(config->entry_oneshot, entry->id))
+ return;
+
+ if (config->use_saved_entry_efivar || (!config->entry_default_efivar && config->use_saved_entry)) {
+ /* Avoid unnecessary NVRAM writes. */
+ if (streq16(config->entry_saved, entry->id))
+ return;
+
+ (void) efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", entry->id, EFI_VARIABLE_NON_VOLATILE);
+ } else
+ /* Delete the non-volatile var if not needed. */
+ (void) efivar_unset(MAKE_GUID_PTR(LOADER), u"LoaderEntryLastBooted", EFI_VARIABLE_NON_VOLATILE);
+}
+
+static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) {
+ EFI_STATUS err;
+ _cleanup_(file_closep) EFI_FILE *keys_basedir = NULL;
+
+ if (secure_boot_mode() != SECURE_BOOT_SETUP)
+ return EFI_SUCCESS;
+
+ /* the lack of a 'keys' directory is not fatal and is silently ignored */
+ err = open_directory(root_dir, u"\\loader\\keys", &keys_basedir);
+ if (err == EFI_NOT_FOUND)
+ return EFI_SUCCESS;
+ if (err != EFI_SUCCESS)
+ return err;
+
+ for (;;) {
+ _cleanup_free_ EFI_FILE_INFO *dirent = NULL;
+ size_t dirent_size = 0;
+ BootEntry *entry = NULL;
+
+ err = readdir(keys_basedir, &dirent, &dirent_size);
+ if (err != EFI_SUCCESS || !dirent)
+ return err;
+
+ if (dirent->FileName[0] == '.')
+ continue;
+
+ if (!FLAGS_SET(dirent->Attribute, EFI_FILE_DIRECTORY))
+ continue;
+
+ entry = xnew(BootEntry, 1);
+ *entry = (BootEntry) {
+ .id = xasprintf("secure-boot-keys-%ls", dirent->FileName),
+ .title = xasprintf("Enroll Secure Boot keys: %ls", dirent->FileName),
+ .path = xasprintf("\\loader\\keys\\%ls", dirent->FileName),
+ .type = LOADER_SECURE_BOOT_KEYS,
+ .tries_done = -1,
+ .tries_left = -1,
+ };
+ config_add_entry(config, entry);
+
+ if (IN_SET(config->secure_boot_enroll, ENROLL_IF_SAFE, ENROLL_FORCE) &&
+ strcaseeq16(dirent->FileName, u"auto"))
+ /* If we auto enroll successfully this call does not return.
+ * If it fails we still want to add other potential entries to the menu. */
+ secure_boot_enroll_at(root_dir, entry->path, config->secure_boot_enroll == ENROLL_FORCE);
+ }
+
+ return EFI_SUCCESS;
+}
+
+static void export_variables(
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ const char16_t *loaded_image_path,
+ uint64_t init_usec) {
+
+ static const uint64_t loader_features =
+ EFI_LOADER_FEATURE_CONFIG_TIMEOUT |
+ EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT |
+ EFI_LOADER_FEATURE_ENTRY_DEFAULT |
+ EFI_LOADER_FEATURE_ENTRY_ONESHOT |
+ EFI_LOADER_FEATURE_BOOT_COUNTING |
+ EFI_LOADER_FEATURE_XBOOTLDR |
+ EFI_LOADER_FEATURE_RANDOM_SEED |
+ EFI_LOADER_FEATURE_LOAD_DRIVER |
+ EFI_LOADER_FEATURE_SORT_KEY |
+ EFI_LOADER_FEATURE_SAVED_ENTRY |
+ EFI_LOADER_FEATURE_DEVICETREE |
+ EFI_LOADER_FEATURE_SECUREBOOT_ENROLL |
+ EFI_LOADER_FEATURE_RETAIN_SHIM |
+ EFI_LOADER_FEATURE_MENU_DISABLE |
+ 0;
+
+ _cleanup_free_ char16_t *infostr = NULL, *typestr = NULL;
+
+ assert(loaded_image);
+
+ efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeInitUSec", init_usec);
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderInfo", u"systemd-boot " GIT_VERSION, 0);
+
+ infostr = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", infostr, 0);
+
+ typestr = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", typestr, 0);
+
+ (void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", loader_features, 0);
+
+ /* the filesystem path to this image, to prevent adding ourselves to the menu */
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", loaded_image_path, 0);
+
+ /* export the device path this image is started from */
+ _cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle);
+ if (uuid)
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0);
+}
+
+static void config_load_all_entries(
+ Config *config,
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ const char16_t *loaded_image_path,
+ EFI_FILE *root_dir) {
+
+ assert(config);
+ assert(loaded_image);
+ assert(root_dir);
+
+ config_load_defaults(config, root_dir);
+
+ /* Scan /EFI/Linux/ directory */
+ config_load_type2_entries(config, loaded_image->DeviceHandle, root_dir);
+
+ /* Scan /loader/entries/\*.conf files */
+ config_load_type1_entries(config, loaded_image->DeviceHandle, root_dir, loaded_image_path);
+
+ /* Similar, but on any XBOOTLDR partition */
+ config_load_xbootldr(config, loaded_image->DeviceHandle);
+
+ /* Sort entries after version number */
+ sort_pointer_array((void **) config->entries, config->n_entries, (compare_pointer_func_t) boot_entry_compare);
+
+ /* If we find some well-known loaders, add them to the end of the list */
+ config_add_entry_osx(config);
+ config_add_entry_windows(config, loaded_image->DeviceHandle, root_dir);
+ config_add_entry_loader_auto(config, loaded_image->DeviceHandle, root_dir, NULL,
+ u"auto-efi-shell", 's', u"EFI Shell", u"\\shell" EFI_MACHINE_TYPE_NAME ".efi");
+ config_add_entry_loader_auto(config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
+ u"auto-efi-default", '\0', u"EFI Default Loader", NULL);
+
+ if (config->auto_firmware && FLAGS_SET(get_os_indications_supported(), EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) {
+ BootEntry *entry = xnew(BootEntry, 1);
+ *entry = (BootEntry) {
+ .id = xstrdup16(u"auto-reboot-to-firmware-setup"),
+ .title = xstrdup16(u"Reboot Into Firmware Interface"),
+ .call = reboot_into_firmware,
+ .tries_done = -1,
+ .tries_left = -1,
+ };
+ config_add_entry(config, entry);
+ }
+
+ if (config->auto_poweroff) {
+ BootEntry *entry = xnew(BootEntry, 1);
+ *entry = (BootEntry) {
+ .id = xstrdup16(u"auto-poweroff"),
+ .title = xstrdup16(u"Power Off The System"),
+ .call = poweroff_system,
+ .tries_done = -1,
+ .tries_left = -1,
+ };
+ config_add_entry(config, entry);
+ }
+
+ if (config->auto_reboot) {
+ BootEntry *entry = xnew(BootEntry, 1);
+ *entry = (BootEntry) {
+ .id = xstrdup16(u"auto-reboot"),
+ .title = xstrdup16(u"Reboot The System"),
+ .call = reboot_system,
+ .tries_done = -1,
+ .tries_left = -1,
+ };
+ config_add_entry(config, entry);
+ }
+
+ /* Find secure boot signing keys and autoload them if configured.
+ * Otherwise, create menu entries so that the user can load them manually.
+ * If the secure-boot-enroll variable is set to no (the default), we do not
+ * even search for keys on the ESP */
+ if (config->secure_boot_enroll != ENROLL_OFF)
+ secure_boot_discover_keys(config, root_dir);
+
+ if (config->n_entries == 0)
+ return;
+
+ config_write_entries_to_variable(config);
+
+ generate_boot_entry_titles(config);
+
+ /* Select entry by configured pattern or EFI LoaderDefaultEntry= variable */
+ config_select_default_entry(config);
+}
+
+static EFI_STATUS discover_root_dir(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, EFI_FILE **ret_dir) {
+ if (is_direct_boot(loaded_image->DeviceHandle))
+ return vmm_open(&loaded_image->DeviceHandle, ret_dir);
+ else
+ return open_volume(loaded_image->DeviceHandle, ret_dir);
+}
+
+static EFI_STATUS run(EFI_HANDLE image) {
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
+ _cleanup_(file_closep) EFI_FILE *root_dir = NULL;
+ _cleanup_(config_free) Config config = {};
+ _cleanup_free_ char16_t *loaded_image_path = NULL;
+ EFI_STATUS err;
+ uint64_t init_usec;
+ bool menu = false;
+
+ init_usec = time_usec();
+
+ /* Ask Shim to leave its protocol around, so that the stub can use it to validate PEs.
+ * By default, Shim uninstalls its protocol when calling StartImage(). */
+ shim_retain_protocol();
+
+ err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
+
+ (void) device_path_to_str(loaded_image->FilePath, &loaded_image_path);
+
+ export_variables(loaded_image, loaded_image_path, init_usec);
+
+ err = discover_root_dir(loaded_image, &root_dir);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Unable to open root directory: %m");
+
+ (void) load_drivers(image, loaded_image, root_dir);
+
+ config_load_all_entries(&config, loaded_image, loaded_image_path, root_dir);
+
+ if (config.n_entries == 0)
+ return log_error_status(
+ EFI_NOT_FOUND,
+ "No loader found. Configuration files in \\loader\\entries\\*.conf are needed.");
+
+ /* select entry or show menu when key is pressed or timeout is set */
+ if (config.force_menu || !IN_SET(config.timeout_sec, TIMEOUT_MENU_HIDDEN, TIMEOUT_MENU_DISABLED))
+ menu = true;
+ else if (config.timeout_sec != TIMEOUT_MENU_DISABLED) {
+ uint64_t key;
+
+ /* Block up to 100ms to give firmware time to get input working. */
+ err = console_key_read(&key, 100 * 1000);
+ if (err == EFI_SUCCESS) {
+ /* find matching key in boot entries */
+ size_t idx = entry_lookup_key(&config, config.idx_default, KEYCHAR(key));
+ if (idx != IDX_INVALID)
+ config.idx_default = idx;
+ else
+ menu = true;
+ }
+ }
+
+ for (;;) {
+ BootEntry *entry;
+
+ entry = config.entries[config.idx_default];
+ if (menu) {
+ efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeMenuUSec", 0);
+ if (!menu_run(&config, &entry, loaded_image_path))
+ return EFI_SUCCESS;
+ }
+
+ /* if auto enrollment is activated, we try to load keys for the given entry. */
+ if (entry->type == LOADER_SECURE_BOOT_KEYS && config.secure_boot_enroll != ENROLL_OFF) {
+ err = secure_boot_enroll_at(root_dir, entry->path, /*force=*/ true);
+ if (err != EFI_SUCCESS)
+ return err;
+ continue;
+ }
+
+ /* Run special entry like "reboot" now. Those that have a loader
+ * will be handled by image_start() instead. */
+ if (entry->call && !entry->loader) {
+ entry->call();
+ continue;
+ }
+
+ (void) boot_entry_bump_counters(entry);
+ save_selected_entry(&config, entry);
+
+ /* Optionally, read a random seed off the ESP and pass it to the OS */
+ (void) process_random_seed(root_dir);
+
+ err = image_start(image, entry);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ menu = true;
+ config.timeout_sec = 0;
+ }
+}
+
+DEFINE_EFI_MAIN_FUNCTION(run, "systemd-boot", /*wait_for_debugger=*/false);
diff --git a/src/boot/efi/console.c b/src/boot/efi/console.c
new file mode 100644
index 0000000..067ee7c
--- /dev/null
+++ b/src/boot/efi/console.c
@@ -0,0 +1,312 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "console.h"
+#include "proto/graphics-output.h"
+#include "util.h"
+
+#define SYSTEM_FONT_WIDTH 8
+#define SYSTEM_FONT_HEIGHT 19
+#define HORIZONTAL_MAX_OK 1920
+#define VERTICAL_MAX_OK 1080
+#define VIEWPORT_RATIO 10
+
+static void event_closep(EFI_EVENT *event) {
+ if (!*event)
+ return;
+
+ BS->CloseEvent(*event);
+}
+
+/*
+ * Reading input from the console sounds like an easy task to do, but thanks to broken
+ * firmware it is actually a nightmare.
+ *
+ * There is a SimpleTextInput and SimpleTextInputEx API for this. Ideally we want to use
+ * TextInputEx, because that gives us Ctrl/Alt/Shift key state information. Unfortunately,
+ * it is not always available and sometimes just non-functional.
+ *
+ * On some firmware, calling ReadKeyStroke or ReadKeyStrokeEx on the default console input
+ * device will just freeze no matter what (even though it *reported* being ready).
+ * Also, multiple input protocols can be backed by the same device, but they can be out of
+ * sync. Falling back on a different protocol can end up with double input.
+ *
+ * Therefore, we will preferably use TextInputEx for ConIn if that is available. Additionally,
+ * we look for the first TextInputEx device the firmware gives us as a fallback option. It
+ * will replace ConInEx permanently if it ever reports a key press.
+ * Lastly, a timer event allows us to provide a input timeout without having to call into
+ * any input functions that can freeze on us or using a busy/stall loop. */
+EFI_STATUS console_key_read(uint64_t *key, uint64_t timeout_usec) {
+ static EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *conInEx = NULL, *extraInEx = NULL;
+ static bool checked = false;
+ size_t index;
+ EFI_STATUS err;
+ _cleanup_(event_closep) EFI_EVENT timer = NULL;
+
+ assert(key);
+
+ if (!checked) {
+ /* Get the *first* TextInputEx device. */
+ err = BS->LocateProtocol(
+ MAKE_GUID_PTR(EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL), NULL, (void **) &extraInEx);
+ if (err != EFI_SUCCESS || BS->CheckEvent(extraInEx->WaitForKeyEx) == EFI_INVALID_PARAMETER)
+ /* If WaitForKeyEx fails here, the firmware pretends it talks this
+ * protocol, but it really doesn't. */
+ extraInEx = NULL;
+
+ /* Get the TextInputEx version of ST->ConIn. */
+ err = BS->HandleProtocol(
+ ST->ConsoleInHandle,
+ MAKE_GUID_PTR(EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL),
+ (void **) &conInEx);
+ if (err != EFI_SUCCESS || BS->CheckEvent(conInEx->WaitForKeyEx) == EFI_INVALID_PARAMETER)
+ conInEx = NULL;
+
+ if (conInEx == extraInEx)
+ extraInEx = NULL;
+
+ checked = true;
+ }
+
+ err = BS->CreateEvent(EVT_TIMER, 0, NULL, NULL, &timer);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error creating timer event: %m");
+
+ EFI_EVENT events[] = {
+ timer,
+ conInEx ? conInEx->WaitForKeyEx : ST->ConIn->WaitForKey,
+ extraInEx ? extraInEx->WaitForKeyEx : NULL,
+ };
+ size_t n_events = extraInEx ? 3 : 2;
+
+ /* Watchdog rearming loop in case the user never provides us with input or some
+ * broken firmware never returns from WaitForEvent. */
+ for (;;) {
+ uint64_t watchdog_timeout_sec = 5 * 60,
+ watchdog_ping_usec = watchdog_timeout_sec / 2 * 1000 * 1000;
+
+ /* SetTimer expects 100ns units for some reason. */
+ err = BS->SetTimer(
+ timer,
+ TimerRelative,
+ MIN(timeout_usec, watchdog_ping_usec) * 10);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error arming timer event: %m");
+
+ (void) BS->SetWatchdogTimer(watchdog_timeout_sec, 0x10000, 0, NULL);
+ err = BS->WaitForEvent(n_events, events, &index);
+ (void) BS->SetWatchdogTimer(watchdog_timeout_sec, 0x10000, 0, NULL);
+
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error waiting for events: %m");
+
+ /* We have keyboard input, process it after this loop. */
+ if (timer != events[index])
+ break;
+
+ /* The EFI timer fired instead. If this was a watchdog timeout, loop again. */
+ if (timeout_usec == UINT64_MAX)
+ continue;
+ else if (timeout_usec > watchdog_ping_usec) {
+ timeout_usec -= watchdog_ping_usec;
+ continue;
+ }
+
+ /* The caller requested a timeout? They shall have one! */
+ return EFI_TIMEOUT;
+ }
+
+ /* If the extra input device we found returns something, always use that instead
+ * to work around broken firmware freezing on ConIn/ConInEx. */
+ if (extraInEx && BS->CheckEvent(extraInEx->WaitForKeyEx) == EFI_SUCCESS) {
+ conInEx = extraInEx;
+ extraInEx = NULL;
+ }
+
+ /* Do not fall back to ConIn if we have a ConIn that supports TextInputEx.
+ * The two may be out of sync on some firmware, giving us double input. */
+ if (conInEx) {
+ EFI_KEY_DATA keydata;
+ uint32_t shift = 0;
+
+ err = conInEx->ReadKeyStrokeEx(conInEx, &keydata);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (FLAGS_SET(keydata.KeyState.KeyShiftState, EFI_SHIFT_STATE_VALID)) {
+ /* Do not distinguish between left and right keys (set both flags). */
+ if (keydata.KeyState.KeyShiftState & EFI_CONTROL_PRESSED)
+ shift |= EFI_CONTROL_PRESSED;
+ if (keydata.KeyState.KeyShiftState & EFI_ALT_PRESSED)
+ shift |= EFI_ALT_PRESSED;
+ if (keydata.KeyState.KeyShiftState & EFI_LOGO_PRESSED)
+ shift |= EFI_LOGO_PRESSED;
+
+ /* Shift is not supposed to be reported for keys that can be represented as uppercase
+ * unicode chars (Shift+f is reported as F instead). Some firmware does it anyway, so
+ * filter those out. */
+ if ((keydata.KeyState.KeyShiftState & EFI_SHIFT_PRESSED) &&
+ keydata.Key.UnicodeChar == 0)
+ shift |= EFI_SHIFT_PRESSED;
+ }
+
+ /* 32 bit modifier keys + 16 bit scan code + 16 bit unicode */
+ *key = KEYPRESS(shift, keydata.Key.ScanCode, keydata.Key.UnicodeChar);
+ return EFI_SUCCESS;
+ } else if (BS->CheckEvent(ST->ConIn->WaitForKey) == EFI_SUCCESS) {
+ EFI_INPUT_KEY k;
+
+ err = ST->ConIn->ReadKeyStroke(ST->ConIn, &k);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ *key = KEYPRESS(0, k.ScanCode, k.UnicodeChar);
+ return EFI_SUCCESS;
+ }
+
+ return EFI_NOT_READY;
+}
+
+static EFI_STATUS change_mode(int64_t mode) {
+ EFI_STATUS err;
+ int32_t old_mode;
+
+ /* SetMode expects a size_t, so make sure these values are sane. */
+ mode = CLAMP(mode, CONSOLE_MODE_RANGE_MIN, CONSOLE_MODE_RANGE_MAX);
+ old_mode = MAX(CONSOLE_MODE_RANGE_MIN, ST->ConOut->Mode->Mode);
+
+ log_wait();
+ err = ST->ConOut->SetMode(ST->ConOut, mode);
+ if (err == EFI_SUCCESS)
+ return EFI_SUCCESS;
+
+ /* Something went wrong. Output is probably borked, so try to revert to previous mode. */
+ if (ST->ConOut->SetMode(ST->ConOut, old_mode) == EFI_SUCCESS)
+ return err;
+
+ /* Maybe the device is on fire? */
+ ST->ConOut->Reset(ST->ConOut, true);
+ ST->ConOut->SetMode(ST->ConOut, CONSOLE_MODE_RANGE_MIN);
+ return err;
+}
+
+EFI_STATUS query_screen_resolution(uint32_t *ret_w, uint32_t *ret_h) {
+ EFI_STATUS err;
+ EFI_GRAPHICS_OUTPUT_PROTOCOL *go;
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &go);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (!go->Mode || !go->Mode->Info)
+ return EFI_DEVICE_ERROR;
+
+ *ret_w = go->Mode->Info->HorizontalResolution;
+ *ret_h = go->Mode->Info->VerticalResolution;
+ return EFI_SUCCESS;
+}
+
+static int64_t get_auto_mode(void) {
+ uint32_t screen_width, screen_height;
+
+ if (query_screen_resolution(&screen_width, &screen_height) == EFI_SUCCESS) {
+ bool keep = false;
+
+ /* Start verifying if we are in a resolution larger than Full HD
+ * (1920x1080). If we're not, assume we're in a good mode and do not
+ * try to change it. */
+ if (screen_width <= HORIZONTAL_MAX_OK && screen_height <= VERTICAL_MAX_OK)
+ keep = true;
+ /* For larger resolutions, calculate the ratio of the total screen
+ * area to the text viewport area. If it's less than 10 times bigger,
+ * then assume the text is readable and keep the text mode. */
+ else {
+ uint64_t text_area;
+ size_t x_max, y_max;
+ uint64_t screen_area = (uint64_t)screen_width * (uint64_t)screen_height;
+
+ console_query_mode(&x_max, &y_max);
+ text_area = SYSTEM_FONT_WIDTH * SYSTEM_FONT_HEIGHT * (uint64_t)x_max * (uint64_t)y_max;
+
+ if (text_area != 0 && screen_area/text_area < VIEWPORT_RATIO)
+ keep = true;
+ }
+
+ if (keep)
+ return ST->ConOut->Mode->Mode;
+ }
+
+ /* If we reached here, then we have a high resolution screen and the text
+ * viewport is less than 10% the screen area, so the firmware developer
+ * screwed up. Try to switch to a better mode. Mode number 2 is first non
+ * standard mode, which is provided by the device manufacturer, so it should
+ * be a good mode.
+ * Note: MaxMode is the number of modes, not the last mode. */
+ if (ST->ConOut->Mode->MaxMode > CONSOLE_MODE_FIRMWARE_FIRST)
+ return CONSOLE_MODE_FIRMWARE_FIRST;
+
+ /* Try again with mode different than zero (assume user requests
+ * auto mode due to some problem with mode zero). */
+ if (ST->ConOut->Mode->MaxMode > CONSOLE_MODE_80_50)
+ return CONSOLE_MODE_80_50;
+
+ return CONSOLE_MODE_80_25;
+}
+
+EFI_STATUS console_set_mode(int64_t mode) {
+ switch (mode) {
+ case CONSOLE_MODE_KEEP:
+ /* If the firmware indicates the current mode is invalid, change it anyway. */
+ if (ST->ConOut->Mode->Mode < CONSOLE_MODE_RANGE_MIN)
+ return change_mode(CONSOLE_MODE_RANGE_MIN);
+ return EFI_SUCCESS;
+
+ case CONSOLE_MODE_NEXT:
+ if (ST->ConOut->Mode->MaxMode <= CONSOLE_MODE_RANGE_MIN)
+ return EFI_UNSUPPORTED;
+
+ mode = MAX(CONSOLE_MODE_RANGE_MIN, ST->ConOut->Mode->Mode);
+ do {
+ mode = (mode + 1) % ST->ConOut->Mode->MaxMode;
+ if (change_mode(mode) == EFI_SUCCESS)
+ break;
+ /* If this mode is broken/unsupported, try the next.
+ * If mode is 0, we wrapped around and should stop. */
+ } while (mode > CONSOLE_MODE_RANGE_MIN);
+
+ return EFI_SUCCESS;
+
+ case CONSOLE_MODE_AUTO:
+ return change_mode(get_auto_mode());
+
+ case CONSOLE_MODE_FIRMWARE_MAX:
+ /* Note: MaxMode is the number of modes, not the last mode. */
+ return change_mode(ST->ConOut->Mode->MaxMode - 1LL);
+
+ default:
+ return change_mode(mode);
+ }
+}
+
+EFI_STATUS console_query_mode(size_t *x_max, size_t *y_max) {
+ EFI_STATUS err;
+
+ assert(x_max);
+ assert(y_max);
+
+ err = ST->ConOut->QueryMode(ST->ConOut, ST->ConOut->Mode->Mode, x_max, y_max);
+ if (err != EFI_SUCCESS) {
+ /* Fallback values mandated by UEFI spec. */
+ switch (ST->ConOut->Mode->Mode) {
+ case CONSOLE_MODE_80_50:
+ *x_max = 80;
+ *y_max = 50;
+ break;
+ case CONSOLE_MODE_80_25:
+ default:
+ *x_max = 80;
+ *y_max = 25;
+ }
+ }
+
+ return err;
+}
diff --git a/src/boot/efi/console.h b/src/boot/efi/console.h
new file mode 100644
index 0000000..c4d821a
--- /dev/null
+++ b/src/boot/efi/console.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+#include "proto/simple-text-io.h"
+
+enum {
+ EFI_SHIFT_PRESSED = EFI_RIGHT_SHIFT_PRESSED|EFI_LEFT_SHIFT_PRESSED,
+ EFI_CONTROL_PRESSED = EFI_RIGHT_CONTROL_PRESSED|EFI_LEFT_CONTROL_PRESSED,
+ EFI_ALT_PRESSED = EFI_RIGHT_ALT_PRESSED|EFI_LEFT_ALT_PRESSED,
+ EFI_LOGO_PRESSED = EFI_RIGHT_LOGO_PRESSED|EFI_LEFT_LOGO_PRESSED,
+};
+
+#define KEYPRESS(keys, scan, uni) ((((uint64_t)keys) << 32) | (((uint64_t)scan) << 16) | (uni))
+#define KEYCHAR(k) ((char16_t)(k))
+#define CHAR_CTRL(c) ((c) - 'a' + 1)
+
+enum {
+ /* Console mode is a int32_t in EFI. We use int64_t to make room for our special values. */
+ CONSOLE_MODE_RANGE_MIN = 0,
+ CONSOLE_MODE_RANGE_MAX = INT32_MAX, /* This is just the theoretical limit. */
+ CONSOLE_MODE_INVALID = -1, /* UEFI uses -1 if the device is not in a valid text mode. */
+
+ CONSOLE_MODE_80_25 = 0, /* 80x25 is required by UEFI spec. */
+ CONSOLE_MODE_80_50 = 1, /* 80x50 may be supported. */
+ CONSOLE_MODE_FIRMWARE_FIRST = 2, /* First custom mode, if supported. */
+
+ /* These are our own mode values that map to concrete values at runtime. */
+ CONSOLE_MODE_KEEP = CONSOLE_MODE_RANGE_MAX + 1LL,
+ CONSOLE_MODE_NEXT,
+ CONSOLE_MODE_AUTO,
+ CONSOLE_MODE_FIRMWARE_MAX, /* 'max' in config. */
+};
+
+EFI_STATUS console_key_read(uint64_t *key, uint64_t timeout_usec);
+EFI_STATUS console_set_mode(int64_t mode);
+EFI_STATUS console_query_mode(size_t *x_max, size_t *y_max);
+EFI_STATUS query_screen_resolution(uint32_t *ret_width, uint32_t *ret_height);
diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c
new file mode 100644
index 0000000..5b90e17
--- /dev/null
+++ b/src/boot/efi/cpio.c
@@ -0,0 +1,512 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "cpio.h"
+#include "device-path-util.h"
+#include "measure.h"
+#include "proto/device-path.h"
+#include "util.h"
+
+static char *write_cpio_word(char *p, uint32_t v) {
+ static const char hex[] = "0123456789abcdef";
+
+ assert(p);
+
+ /* Writes a CPIO header 8 character hex value */
+
+ for (size_t i = 0; i < 8; i++)
+ p[7-i] = hex[(v >> (4 * i)) & 0xF];
+
+ return p + 8;
+}
+
+static char *mangle_filename(char *p, const char16_t *f) {
+ char* w;
+
+ assert(p);
+ assert(f);
+
+ /* Basically converts UTF-16 to plain ASCII (note that we filtered non-ASCII filenames beforehand, so
+ * this operation is always safe) */
+
+ for (w = p; *f != 0; f++) {
+ assert(*f <= 0x7fu);
+
+ *(w++) = *f;
+ }
+
+ *(w++) = 0;
+ return w;
+}
+
+static char *pad4(char *p, const char *start) {
+ assert(p);
+ assert(start);
+ assert(p >= start);
+
+ /* Appends NUL bytes to 'p', until the address is divisible by 4, when taken relative to 'start' */
+
+ while ((p - start) % 4 != 0)
+ *(p++) = 0;
+
+ return p;
+}
+
+static EFI_STATUS pack_cpio_one(
+ const char16_t *fname,
+ const void *contents,
+ size_t contents_size,
+ const char *target_dir_prefix,
+ uint32_t access_mode,
+ uint32_t *inode_counter,
+ void **cpio_buffer,
+ size_t *cpio_buffer_size) {
+
+ size_t l, target_dir_prefix_size, fname_size, q;
+ char *a;
+
+ assert(fname);
+ assert(contents_size || contents_size == 0);
+ assert(target_dir_prefix);
+ assert(inode_counter);
+ assert(cpio_buffer);
+ assert(cpio_buffer_size);
+
+ /* Serializes one file in the cpio format understood by the kernel initrd logic.
+ *
+ * See: https://docs.kernel.org/driver-api/early-userspace/buffer-format.html */
+
+ if (contents_size > UINT32_MAX) /* cpio cannot deal with > 32-bit file sizes */
+ return EFI_LOAD_ERROR;
+
+ if (*inode_counter == UINT32_MAX) /* more than 2^32-1 inodes? yikes. cpio doesn't support that either */
+ return EFI_OUT_OF_RESOURCES;
+
+ l = 6 + 13*8 + 1 + 1; /* Fixed CPIO header size, slash separator, and NUL byte after the file name */
+
+ target_dir_prefix_size = strlen8(target_dir_prefix);
+ if (l > SIZE_MAX - target_dir_prefix_size)
+ return EFI_OUT_OF_RESOURCES;
+ l += target_dir_prefix_size;
+
+ fname_size = strlen16(fname);
+ if (l > SIZE_MAX - fname_size)
+ return EFI_OUT_OF_RESOURCES;
+ l += fname_size; /* append space for file name */
+
+ /* CPIO can't deal with fnames longer than 2^32-1 */
+ if (target_dir_prefix_size + fname_size >= UINT32_MAX)
+ return EFI_OUT_OF_RESOURCES;
+
+ /* Align the whole header to 4 byte size */
+ l = ALIGN4(l);
+ if (l == SIZE_MAX) /* overflow check */
+ return EFI_OUT_OF_RESOURCES;
+
+ /* Align the contents to 4 byte size */
+ q = ALIGN4(contents_size);
+ if (q == SIZE_MAX) /* overflow check */
+ return EFI_OUT_OF_RESOURCES;
+
+ if (l > SIZE_MAX - q) /* overflow check */
+ return EFI_OUT_OF_RESOURCES;
+ l += q; /* Add contents to header */
+
+ if (*cpio_buffer_size > SIZE_MAX - l) /* overflow check */
+ return EFI_OUT_OF_RESOURCES;
+ a = xrealloc(*cpio_buffer, *cpio_buffer_size, *cpio_buffer_size + l);
+
+ *cpio_buffer = a;
+ a = (char *) *cpio_buffer + *cpio_buffer_size;
+
+ a = mempcpy(a, "070701", 6); /* magic ID */
+
+ a = write_cpio_word(a, (*inode_counter)++); /* inode */
+ a = write_cpio_word(a, access_mode | 0100000 /* = S_IFREG */); /* mode */
+ a = write_cpio_word(a, 0); /* uid */
+ a = write_cpio_word(a, 0); /* gid */
+ a = write_cpio_word(a, 1); /* nlink */
+
+ /* Note: we don't make any attempt to propagate the mtime here, for two reasons: it's a mess given
+ * that FAT usually is assumed to operate with timezoned timestamps, while UNIX does not. More
+ * importantly though: the modifications times would hamper our goals of providing stable
+ * measurements for the same boots. After all we extend the initrds we generate here into TPM2
+ * PCRs. */
+ a = write_cpio_word(a, 0); /* mtime */
+ a = write_cpio_word(a, contents_size); /* size */
+ a = write_cpio_word(a, 0); /* major(dev) */
+ a = write_cpio_word(a, 0); /* minor(dev) */
+ a = write_cpio_word(a, 0); /* major(rdev) */
+ a = write_cpio_word(a, 0); /* minor(rdev) */
+ a = write_cpio_word(a, target_dir_prefix_size + fname_size + 2); /* fname size */
+ a = write_cpio_word(a, 0); /* "crc" */
+
+ a = mempcpy(a, target_dir_prefix, target_dir_prefix_size);
+ *(a++) = '/';
+ a = mangle_filename(a, fname);
+
+ /* Pad to next multiple of 4 */
+ a = pad4(a, *cpio_buffer);
+
+ a = mempcpy(a, contents, contents_size);
+
+ /* Pad to next multiple of 4 */
+ a = pad4(a, *cpio_buffer);
+
+ assert(a == (char *) *cpio_buffer + *cpio_buffer_size + l);
+ *cpio_buffer_size += l;
+
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS pack_cpio_dir(
+ const char *path,
+ uint32_t access_mode,
+ uint32_t *inode_counter,
+ void **cpio_buffer,
+ size_t *cpio_buffer_size) {
+
+ size_t l, path_size;
+ char *a;
+
+ assert(path);
+ assert(inode_counter);
+ assert(cpio_buffer);
+ assert(cpio_buffer_size);
+
+ /* Serializes one directory inode in cpio format. Note that cpio archives must first create the dirs
+ * they want to place files in. */
+
+ if (*inode_counter == UINT32_MAX)
+ return EFI_OUT_OF_RESOURCES;
+
+ l = 6 + 13*8 + 1; /* Fixed CPIO header size, and NUL byte after the file name */
+
+ path_size = strlen8(path);
+ if (l > SIZE_MAX - path_size)
+ return EFI_OUT_OF_RESOURCES;
+ l += path_size;
+
+ /* Align the whole header to 4 byte size */
+ l = ALIGN4(l);
+ if (l == SIZE_MAX) /* overflow check */
+ return EFI_OUT_OF_RESOURCES;
+
+ if (*cpio_buffer_size > SIZE_MAX - l) /* overflow check */
+ return EFI_OUT_OF_RESOURCES;
+
+ *cpio_buffer = a = xrealloc(*cpio_buffer, *cpio_buffer_size, *cpio_buffer_size + l);
+ a = (char *) *cpio_buffer + *cpio_buffer_size;
+
+ a = mempcpy(a, "070701", 6); /* magic ID */
+
+ a = write_cpio_word(a, (*inode_counter)++); /* inode */
+ a = write_cpio_word(a, access_mode | 0040000 /* = S_IFDIR */); /* mode */
+ a = write_cpio_word(a, 0); /* uid */
+ a = write_cpio_word(a, 0); /* gid */
+ a = write_cpio_word(a, 1); /* nlink */
+ a = write_cpio_word(a, 0); /* mtime */
+ a = write_cpio_word(a, 0); /* size */
+ a = write_cpio_word(a, 0); /* major(dev) */
+ a = write_cpio_word(a, 0); /* minor(dev) */
+ a = write_cpio_word(a, 0); /* major(rdev) */
+ a = write_cpio_word(a, 0); /* minor(rdev) */
+ a = write_cpio_word(a, path_size + 1); /* fname size */
+ a = write_cpio_word(a, 0); /* "crc" */
+
+ a = mempcpy(a, path, path_size + 1);
+
+ /* Pad to next multiple of 4 */
+ a = pad4(a, *cpio_buffer);
+
+ assert(a == (char *) *cpio_buffer + *cpio_buffer_size + l);
+
+ *cpio_buffer_size += l;
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS pack_cpio_prefix(
+ const char *path,
+ uint32_t dir_mode,
+ uint32_t *inode_counter,
+ void **cpio_buffer,
+ size_t *cpio_buffer_size) {
+
+ EFI_STATUS err;
+
+ assert(path);
+ assert(inode_counter);
+ assert(cpio_buffer);
+ assert(cpio_buffer_size);
+
+ /* Serializes directory inodes of all prefix paths of the specified path in cpio format. Note that
+ * (similar to mkdir -p behaviour) all leading paths are created with 0555 access mode, only the
+ * final dir is created with the specified directory access mode. */
+
+ for (const char *p = path;;) {
+ const char *e;
+
+ e = strchr8(p, '/');
+ if (!e)
+ break;
+
+ if (e > p) {
+ _cleanup_free_ char *t = NULL;
+
+ t = xstrndup8(path, e - path);
+ if (!t)
+ return EFI_OUT_OF_RESOURCES;
+
+ err = pack_cpio_dir(t, 0555, inode_counter, cpio_buffer, cpio_buffer_size);
+ if (err != EFI_SUCCESS)
+ return err;
+ }
+
+ p = e + 1;
+ }
+
+ return pack_cpio_dir(path, dir_mode, inode_counter, cpio_buffer, cpio_buffer_size);
+}
+
+static EFI_STATUS pack_cpio_trailer(
+ void **cpio_buffer,
+ size_t *cpio_buffer_size) {
+
+ static const char trailer[] =
+ "070701"
+ "00000000"
+ "00000000"
+ "00000000"
+ "00000000"
+ "00000001"
+ "00000000"
+ "00000000"
+ "00000000"
+ "00000000"
+ "00000000"
+ "00000000"
+ "0000000B"
+ "00000000"
+ "TRAILER!!!\0\0\0"; /* There's a fourth NUL byte appended here, because this is a string */
+
+ /* Generates the cpio trailer record that indicates the end of our initrd cpio archive */
+
+ assert(cpio_buffer);
+ assert(cpio_buffer_size);
+ assert_cc(sizeof(trailer) % 4 == 0);
+
+ *cpio_buffer = xrealloc(*cpio_buffer, *cpio_buffer_size, *cpio_buffer_size + sizeof(trailer));
+ memcpy((uint8_t*) *cpio_buffer + *cpio_buffer_size, trailer, sizeof(trailer));
+ *cpio_buffer_size += sizeof(trailer);
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS pack_cpio(
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ const char16_t *dropin_dir,
+ const char16_t *match_suffix,
+ const char *target_dir_prefix,
+ uint32_t dir_mode,
+ uint32_t access_mode,
+ uint32_t tpm_pcr,
+ const char16_t *tpm_description,
+ void **ret_buffer,
+ size_t *ret_buffer_size,
+ bool *ret_measured) {
+
+ _cleanup_(file_closep) EFI_FILE *root = NULL, *extra_dir = NULL;
+ size_t dirent_size = 0, buffer_size = 0, n_items = 0, n_allocated = 0;
+ _cleanup_free_ char16_t *rel_dropin_dir = NULL;
+ _cleanup_free_ EFI_FILE_INFO *dirent = NULL;
+ _cleanup_(strv_freep) char16_t **items = NULL;
+ _cleanup_free_ void *buffer = NULL;
+ uint32_t inode = 1; /* inode counter, so that each item gets a new inode */
+ EFI_STATUS err;
+
+ assert(loaded_image);
+ assert(target_dir_prefix);
+ assert(ret_buffer);
+ assert(ret_buffer_size);
+
+ if (!loaded_image->DeviceHandle)
+ goto nothing;
+
+ err = open_volume(loaded_image->DeviceHandle, &root);
+ if (err == EFI_UNSUPPORTED)
+ /* Error will be unsupported if the bootloader doesn't implement the file system protocol on
+ * its file handles. */
+ goto nothing;
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Unable to open root directory: %m");
+
+ if (!dropin_dir) {
+ dropin_dir = rel_dropin_dir = get_extra_dir(loaded_image->FilePath);
+ if (!dropin_dir)
+ goto nothing;
+ }
+
+ err = open_directory(root, dropin_dir, &extra_dir);
+ if (err == EFI_NOT_FOUND)
+ /* No extra subdir, that's totally OK */
+ goto nothing;
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to open extra directory of loaded image: %m");
+
+ for (;;) {
+ _cleanup_free_ char16_t *d = NULL;
+
+ err = readdir(extra_dir, &dirent, &dirent_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to read extra directory of loaded image: %m");
+ if (!dirent) /* End of directory */
+ break;
+
+ if (dirent->FileName[0] == '.')
+ continue;
+ if (FLAGS_SET(dirent->Attribute, EFI_FILE_DIRECTORY))
+ continue;
+ if (match_suffix && !endswith_no_case(dirent->FileName, match_suffix))
+ continue;
+ if (!is_ascii(dirent->FileName))
+ continue;
+ if (strlen16(dirent->FileName) > 255) /* Max filename size on Linux */
+ continue;
+
+ d = xstrdup16(dirent->FileName);
+
+ if (n_items+2 > n_allocated) {
+ /* We allocate 16 entries at a time, as a matter of optimization */
+ if (n_items > (SIZE_MAX / sizeof(uint16_t)) - 16) /* Overflow check, just in case */
+ return log_oom();
+
+ size_t m = n_items + 16;
+ items = xrealloc(items, n_allocated * sizeof(uint16_t *), m * sizeof(uint16_t *));
+ n_allocated = m;
+ }
+
+ items[n_items++] = TAKE_PTR(d);
+ items[n_items] = NULL; /* Let's always NUL terminate, to make freeing via strv_free() easy */
+ }
+
+ if (n_items == 0)
+ /* Empty directory */
+ goto nothing;
+
+ /* Now, sort the files we found, to make this uniform and stable (and to ensure the TPM measurements
+ * are not dependent on read order) */
+ sort_pointer_array((void**) items, n_items, (compare_pointer_func_t) strcmp16);
+
+ /* Generate the leading directory inodes right before adding the first files, to the
+ * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */
+ err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to pack cpio prefix: %m");
+
+ for (size_t i = 0; i < n_items; i++) {
+ _cleanup_free_ char *content = NULL;
+ size_t contentsize = 0; /* avoid false maybe-uninitialized warning */
+
+ err = file_read(extra_dir, items[i], 0, 0, &content, &contentsize);
+ if (err != EFI_SUCCESS) {
+ log_error_status(err, "Failed to read %ls, ignoring: %m", items[i]);
+ continue;
+ }
+
+ err = pack_cpio_one(
+ items[i],
+ content, contentsize,
+ target_dir_prefix,
+ access_mode,
+ &inode,
+ &buffer, &buffer_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to pack cpio file %ls: %m", dirent->FileName);
+ }
+
+ err = pack_cpio_trailer(&buffer, &buffer_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to pack cpio trailer: %m");
+
+ err = tpm_log_event(
+ tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured);
+ if (err != EFI_SUCCESS)
+ return log_error_status(
+ err,
+ "Unable to add cpio TPM measurement for PCR %u (%ls), ignoring: %m",
+ tpm_pcr,
+ tpm_description);
+
+ *ret_buffer = TAKE_PTR(buffer);
+ *ret_buffer_size = buffer_size;
+
+ return EFI_SUCCESS;
+
+nothing:
+ *ret_buffer = NULL;
+ *ret_buffer_size = 0;
+
+ if (ret_measured)
+ *ret_measured = false;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS pack_cpio_literal(
+ const void *data,
+ size_t data_size,
+ const char *target_dir_prefix,
+ const char16_t *target_filename,
+ uint32_t dir_mode,
+ uint32_t access_mode,
+ uint32_t tpm_pcr,
+ const char16_t *tpm_description,
+ void **ret_buffer,
+ size_t *ret_buffer_size,
+ bool *ret_measured) {
+
+ uint32_t inode = 1; /* inode counter, so that each item gets a new inode */
+ _cleanup_free_ void *buffer = NULL;
+ size_t buffer_size = 0;
+ EFI_STATUS err;
+
+ assert(data || data_size == 0);
+ assert(target_dir_prefix);
+ assert(target_filename);
+ assert(ret_buffer);
+ assert(ret_buffer_size);
+
+ /* Generate the leading directory inodes right before adding the first files, to the
+ * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */
+
+ err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to pack cpio prefix: %m");
+
+ err = pack_cpio_one(
+ target_filename,
+ data, data_size,
+ target_dir_prefix,
+ access_mode,
+ &inode,
+ &buffer, &buffer_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to pack cpio file %ls: %m", target_filename);
+
+ err = pack_cpio_trailer(&buffer, &buffer_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to pack cpio trailer: %m");
+
+ err = tpm_log_event(
+ tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured);
+ if (err != EFI_SUCCESS)
+ return log_error_status(
+ err,
+ "Unable to add cpio TPM measurement for PCR %u (%ls), ignoring: %m",
+ tpm_pcr,
+ tpm_description);
+
+ *ret_buffer = TAKE_PTR(buffer);
+ *ret_buffer_size = buffer_size;
+
+ return EFI_SUCCESS;
+}
diff --git a/src/boot/efi/cpio.h b/src/boot/efi/cpio.h
new file mode 100644
index 0000000..26851e3
--- /dev/null
+++ b/src/boot/efi/cpio.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+#include "proto/loaded-image.h"
+
+EFI_STATUS pack_cpio(
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ const char16_t *dropin_dir,
+ const char16_t *match_suffix,
+ const char *target_dir_prefix,
+ uint32_t dir_mode,
+ uint32_t access_mode,
+ uint32_t tpm_pcr,
+ const char16_t *tpm_description,
+ void **ret_buffer,
+ size_t *ret_buffer_size,
+ bool *ret_measured);
+
+EFI_STATUS pack_cpio_literal(
+ const void *data,
+ size_t data_size,
+ const char *target_dir_prefix,
+ const char16_t *target_filename,
+ uint32_t dir_mode,
+ uint32_t access_mode,
+ uint32_t tpm_pcr,
+ const char16_t *tpm_description,
+ void **ret_buffer,
+ size_t *ret_buffer_size,
+ bool *ret_measured);
diff --git a/src/boot/efi/device-path-util.c b/src/boot/efi/device-path-util.c
new file mode 100644
index 0000000..2a85e8b
--- /dev/null
+++ b/src/boot/efi/device-path-util.c
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "device-path-util.h"
+#include "util.h"
+
+EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp) {
+ EFI_STATUS err;
+ EFI_DEVICE_PATH *dp;
+
+ assert(file);
+ assert(ret_dp);
+
+ err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ EFI_DEVICE_PATH *end_node = dp;
+ while (!device_path_is_end(end_node))
+ end_node = device_path_next_node(end_node);
+
+ size_t file_size = strsize16(file);
+ size_t dp_size = (uint8_t *) end_node - (uint8_t *) dp;
+
+ /* Make a copy that can also hold a file media device path. */
+ *ret_dp = xmalloc(dp_size + file_size + sizeof(FILEPATH_DEVICE_PATH) + sizeof(EFI_DEVICE_PATH));
+ dp = mempcpy(*ret_dp, dp, dp_size);
+
+ FILEPATH_DEVICE_PATH *file_dp = (FILEPATH_DEVICE_PATH *) dp;
+ file_dp->Header = (EFI_DEVICE_PATH) {
+ .Type = MEDIA_DEVICE_PATH,
+ .SubType = MEDIA_FILEPATH_DP,
+ .Length = sizeof(FILEPATH_DEVICE_PATH) + file_size,
+ };
+ memcpy(file_dp->PathName, file, file_size);
+
+ dp = device_path_next_node(dp);
+ *dp = DEVICE_PATH_END_NODE;
+ return EFI_SUCCESS;
+}
+
+static char16_t *device_path_to_str_internal(const EFI_DEVICE_PATH *dp) {
+ char16_t *str = NULL;
+
+ for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) {
+ _cleanup_free_ char16_t *old = str;
+
+ if (node->Type == END_DEVICE_PATH_TYPE && node->SubType == END_INSTANCE_DEVICE_PATH_SUBTYPE) {
+ str = xasprintf("%ls%s,", strempty(old), old ? "\\" : "");
+ continue;
+ }
+
+ /* Special-case this so that FilePath-only device path string look and behave nicely. */
+ if (node->Type == MEDIA_DEVICE_PATH && node->SubType == MEDIA_FILEPATH_DP) {
+ str = xasprintf("%ls%s%ls",
+ strempty(old),
+ old ? "\\" : "",
+ ((FILEPATH_DEVICE_PATH *) node)->PathName);
+ continue;
+ }
+
+ /* Instead of coding all the different types and sub-types here we just use the
+ * generic node form. This function is a best-effort for firmware that does not
+ * provide the EFI_DEVICE_PATH_TO_TEXT_PROTOCOL after all. */
+
+ size_t size = node->Length - sizeof(EFI_DEVICE_PATH);
+ _cleanup_free_ char16_t *hex_data = hexdump((uint8_t *) node + sizeof(EFI_DEVICE_PATH), size);
+ str = xasprintf("%ls%sPath(%u,%u%s%ls)",
+ strempty(old),
+ old ? "/" : "",
+ node->Type,
+ node->SubType,
+ size == 0 ? "" : ",",
+ hex_data);
+ }
+
+ return str;
+}
+
+EFI_STATUS device_path_to_str(const EFI_DEVICE_PATH *dp, char16_t **ret) {
+ EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *dp_to_text;
+ EFI_STATUS err;
+ _cleanup_free_ char16_t *str = NULL;
+
+ assert(dp);
+ assert(ret);
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_DEVICE_PATH_TO_TEXT_PROTOCOL), NULL, (void **) &dp_to_text);
+ if (err != EFI_SUCCESS) {
+ *ret = device_path_to_str_internal(dp);
+ return EFI_SUCCESS;
+ }
+
+ str = dp_to_text->ConvertDevicePathToText(dp, false, false);
+ if (!str)
+ return EFI_OUT_OF_RESOURCES;
+
+ *ret = TAKE_PTR(str);
+ return EFI_SUCCESS;
+}
+
+bool device_path_startswith(const EFI_DEVICE_PATH *dp, const EFI_DEVICE_PATH *start) {
+ if (!start)
+ return true;
+ if (!dp)
+ return false;
+ for (;;) {
+ if (device_path_is_end(start))
+ return true;
+ if (device_path_is_end(dp))
+ return false;
+ if (start->Length != dp->Length)
+ return false;
+ if (memcmp(dp, start, start->Length) != 0)
+ return false;
+ start = device_path_next_node(start);
+ dp = device_path_next_node(dp);
+ }
+}
+
+EFI_DEVICE_PATH *device_path_replace_node(
+ const EFI_DEVICE_PATH *path, const EFI_DEVICE_PATH *node, const EFI_DEVICE_PATH *new_node) {
+
+ /* Create a new device path as a copy of path, while chopping off the remainder starting at the given
+ * node. If new_node is provided, it is appended at the end of the new path. */
+
+ assert(path);
+ assert(node);
+
+ size_t len = (uint8_t *) node - (uint8_t *) path;
+ EFI_DEVICE_PATH *ret = xmalloc(len + (new_node ? new_node->Length : 0) + sizeof(EFI_DEVICE_PATH));
+ EFI_DEVICE_PATH *end = mempcpy(ret, path, len);
+
+ if (new_node)
+ end = mempcpy(end, new_node, new_node->Length);
+
+ *end = DEVICE_PATH_END_NODE;
+ return ret;
+}
diff --git a/src/boot/efi/device-path-util.h b/src/boot/efi/device-path-util.h
new file mode 100644
index 0000000..08f1a9c
--- /dev/null
+++ b/src/boot/efi/device-path-util.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "proto/device-path.h"
+
+EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DEVICE_PATH **ret_dp);
+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(
+ const EFI_DEVICE_PATH *path, const EFI_DEVICE_PATH *node, const EFI_DEVICE_PATH *new_node);
+
+static inline EFI_DEVICE_PATH *device_path_next_node(const EFI_DEVICE_PATH *dp) {
+ assert(dp);
+ return (EFI_DEVICE_PATH *) ((uint8_t *) dp + dp->Length);
+}
+
+static inline bool device_path_is_end(const EFI_DEVICE_PATH *dp) {
+ assert(dp);
+ return dp->Type == END_DEVICE_PATH_TYPE && dp->SubType == END_ENTIRE_DEVICE_PATH_SUBTYPE;
+}
+
+#define DEVICE_PATH_END_NODE \
+ (EFI_DEVICE_PATH) { \
+ .Type = END_DEVICE_PATH_TYPE, \
+ .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, \
+ .Length = sizeof(EFI_DEVICE_PATH) \
+ }
diff --git a/src/boot/efi/devicetree.c b/src/boot/efi/devicetree.c
new file mode 100644
index 0000000..61a43cd
--- /dev/null
+++ b/src/boot/efi/devicetree.c
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "devicetree.h"
+#include "proto/dt-fixup.h"
+#include "util.h"
+
+#define FDT_V1_SIZE (7*4)
+
+static EFI_STATUS devicetree_allocate(struct devicetree_state *state, size_t size) {
+ size_t pages = DIV_ROUND_UP(size, EFI_PAGE_SIZE);
+ EFI_STATUS err;
+
+ assert(state);
+
+ err = BS->AllocatePages(AllocateAnyPages, EfiACPIReclaimMemory, pages, &state->addr);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ state->pages = pages;
+ return err;
+}
+
+static size_t devicetree_allocated(const struct devicetree_state *state) {
+ assert(state);
+ return state->pages * EFI_PAGE_SIZE;
+}
+
+static EFI_STATUS devicetree_fixup(struct devicetree_state *state, size_t len) {
+ EFI_DT_FIXUP_PROTOCOL *fixup;
+ size_t size;
+ EFI_STATUS err;
+
+ assert(state);
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_DT_FIXUP_PROTOCOL), NULL, (void **) &fixup);
+ /* Skip fixup if we cannot locate device tree fixup protocol */
+ if (err != EFI_SUCCESS)
+ return EFI_SUCCESS;
+
+ size = devicetree_allocated(state);
+ err = fixup->Fixup(fixup, PHYSICAL_ADDRESS_TO_POINTER(state->addr), &size,
+ EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY);
+ if (err == EFI_BUFFER_TOO_SMALL) {
+ EFI_PHYSICAL_ADDRESS oldaddr = state->addr;
+ size_t oldpages = state->pages;
+ void *oldptr = PHYSICAL_ADDRESS_TO_POINTER(state->addr);
+
+ err = devicetree_allocate(state, size);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ memcpy(PHYSICAL_ADDRESS_TO_POINTER(state->addr), oldptr, len);
+ err = BS->FreePages(oldaddr, oldpages);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ size = devicetree_allocated(state);
+ err = fixup->Fixup(fixup, PHYSICAL_ADDRESS_TO_POINTER(state->addr), &size,
+ EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY);
+ }
+
+ return err;
+}
+
+EFI_STATUS devicetree_install(struct devicetree_state *state, EFI_FILE *root_dir, char16_t *name) {
+ _cleanup_(file_closep) EFI_FILE *handle = NULL;
+ _cleanup_free_ EFI_FILE_INFO *info = NULL;
+ size_t len;
+ EFI_STATUS err;
+
+ assert(state);
+ assert(root_dir);
+ assert(name);
+
+ /* Capture the original value for the devicetree table. NULL is not an error in this case so we don't
+ * need to check the return value. NULL simply means the system fw had no devicetree initially (and
+ * is the correct value to use to return to the initial state if needed). */
+ state->orig = find_configuration_table(MAKE_GUID_PTR(EFI_DTB_TABLE));
+
+ err = root_dir->Open(root_dir, &handle, name, EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = get_file_info(handle, &info, NULL);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (info->FileSize < FDT_V1_SIZE || info->FileSize > 32 * 1024 * 1024)
+ /* 32MB device tree blob doesn't seem right */
+ return EFI_INVALID_PARAMETER;
+
+ len = info->FileSize;
+
+ err = devicetree_allocate(state, len);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = handle->Read(handle, &len, PHYSICAL_ADDRESS_TO_POINTER(state->addr));
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = devicetree_fixup(state, len);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ return BS->InstallConfigurationTable(
+ MAKE_GUID_PTR(EFI_DTB_TABLE), PHYSICAL_ADDRESS_TO_POINTER(state->addr));
+}
+
+EFI_STATUS devicetree_install_from_memory(
+ struct devicetree_state *state, const void *dtb_buffer, size_t dtb_length) {
+
+ EFI_STATUS err;
+
+ assert(state);
+ assert(dtb_buffer && dtb_length > 0);
+
+ /* Capture the original value for the devicetree table. NULL is not an error in this case so we don't
+ * need to check the return value. NULL simply means the system fw had no devicetree initially (and
+ * is the correct value to use to return to the initial state if needed). */
+ state->orig = find_configuration_table(MAKE_GUID_PTR(EFI_DTB_TABLE));
+
+ err = devicetree_allocate(state, dtb_length);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ memcpy(PHYSICAL_ADDRESS_TO_POINTER(state->addr), dtb_buffer, dtb_length);
+
+ err = devicetree_fixup(state, dtb_length);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ return BS->InstallConfigurationTable(
+ MAKE_GUID_PTR(EFI_DTB_TABLE), PHYSICAL_ADDRESS_TO_POINTER(state->addr));
+}
+
+void devicetree_cleanup(struct devicetree_state *state) {
+ EFI_STATUS err;
+
+ if (!state->pages)
+ return;
+
+ err = BS->InstallConfigurationTable(MAKE_GUID_PTR(EFI_DTB_TABLE), state->orig);
+ /* don't free the current device tree if we can't reinstate the old one */
+ if (err != EFI_SUCCESS)
+ return;
+
+ BS->FreePages(state->addr, state->pages);
+ state->pages = 0;
+}
diff --git a/src/boot/efi/devicetree.h b/src/boot/efi/devicetree.h
new file mode 100644
index 0000000..33eaa22
--- /dev/null
+++ b/src/boot/efi/devicetree.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+struct devicetree_state {
+ EFI_PHYSICAL_ADDRESS addr;
+ size_t pages;
+ void *orig;
+};
+
+EFI_STATUS devicetree_install(struct devicetree_state *state, EFI_FILE *root_dir, char16_t *name);
+EFI_STATUS devicetree_install_from_memory(
+ struct devicetree_state *state, const void *dtb_buffer, size_t dtb_length);
+void devicetree_cleanup(struct devicetree_state *state);
diff --git a/src/boot/efi/drivers.c b/src/boot/efi/drivers.c
new file mode 100644
index 0000000..0674557
--- /dev/null
+++ b/src/boot/efi/drivers.c
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "device-path-util.h"
+#include "drivers.h"
+#include "util.h"
+
+static EFI_STATUS load_one_driver(
+ EFI_HANDLE parent_image,
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ const char16_t *fname) {
+
+ _cleanup_(unload_imagep) EFI_HANDLE image = NULL;
+ _cleanup_free_ EFI_DEVICE_PATH *path = NULL;
+ _cleanup_free_ char16_t *spath = NULL;
+ EFI_STATUS err;
+
+ assert(parent_image);
+ assert(loaded_image);
+ assert(fname);
+
+ spath = xasprintf("\\EFI\\systemd\\drivers\\%ls", fname);
+ err = make_file_device_path(loaded_image->DeviceHandle, spath, &path);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error making file device path: %m");
+
+ err = BS->LoadImage(false, parent_image, path, NULL, 0, &image);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to load image %ls: %m", fname);
+
+ err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to find protocol in driver image %ls: %m", fname);
+
+ if (loaded_image->ImageCodeType != EfiBootServicesCode &&
+ loaded_image->ImageCodeType != EfiRuntimeServicesCode)
+ return log_error("Image %ls is not a driver, refusing.", fname);
+
+ err = BS->StartImage(image, NULL, NULL);
+ if (err != EFI_SUCCESS) {
+ /* EFI_ABORTED signals an initializing driver. It uses this error code on success
+ * so that it is unloaded after. */
+ if (err != EFI_ABORTED)
+ log_error_status(err, "Failed to start image %ls: %m", fname);
+ return err;
+ }
+
+ TAKE_PTR(image);
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS reconnect_all_drivers(void) {
+ _cleanup_free_ EFI_HANDLE *handles = NULL;
+ size_t n_handles = 0;
+ EFI_STATUS err;
+
+ /* Reconnects all handles, so that any loaded drivers can take effect. */
+
+ err = BS->LocateHandleBuffer(AllHandles, NULL, NULL, &n_handles, &handles);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to get list of handles: %m");
+
+ for (size_t i = 0; i < n_handles; i++)
+ /* Some firmware gives us some bogus handles (or they might become bad due to
+ * reconnecting everything). Security policy may also prevent us from doing so too.
+ * There is nothing we can realistically do on errors anyways, so just ignore them. */
+ (void) BS->ConnectController(handles[i], NULL, NULL, true);
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS load_drivers(
+ EFI_HANDLE parent_image,
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ EFI_FILE *root_dir) {
+
+ _cleanup_(file_closep) EFI_FILE *drivers_dir = NULL;
+ _cleanup_free_ EFI_FILE_INFO *dirent = NULL;
+ size_t dirent_size = 0, n_succeeded = 0;
+ EFI_STATUS err;
+
+ err = open_directory(
+ root_dir,
+ u"\\EFI\\systemd\\drivers",
+ &drivers_dir);
+ if (err == EFI_NOT_FOUND)
+ return EFI_SUCCESS;
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to open \\EFI\\systemd\\drivers: %m");
+
+ for (;;) {
+ err = readdir(drivers_dir, &dirent, &dirent_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to read extra directory of loaded image: %m");
+ if (!dirent) /* End of directory */
+ break;
+
+ if (dirent->FileName[0] == '.')
+ continue;
+ if (FLAGS_SET(dirent->Attribute, EFI_FILE_DIRECTORY))
+ continue;
+ if (!endswith_no_case(dirent->FileName, EFI_MACHINE_TYPE_NAME u".efi"))
+ continue;
+
+ err = load_one_driver(parent_image, loaded_image, dirent->FileName);
+ if (err != EFI_SUCCESS)
+ continue;
+
+ n_succeeded++;
+ }
+
+ if (n_succeeded > 0)
+ (void) reconnect_all_drivers();
+
+ return EFI_SUCCESS;
+}
diff --git a/src/boot/efi/drivers.h b/src/boot/efi/drivers.h
new file mode 100644
index 0000000..ecd0b4e
--- /dev/null
+++ b/src/boot/efi/drivers.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+#include "proto/loaded-image.h"
+
+EFI_STATUS reconnect_all_drivers(void);
+EFI_STATUS load_drivers(
+ EFI_HANDLE parent_image,
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ EFI_FILE *root_dir);
diff --git a/src/boot/efi/efi-string.c b/src/boot/efi/efi-string.c
new file mode 100644
index 0000000..4144c0d
--- /dev/null
+++ b/src/boot/efi/efi-string.c
@@ -0,0 +1,1084 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "efi-string.h"
+
+#if SD_BOOT
+# include "proto/simple-text-io.h"
+# include "util.h"
+#else
+# include <stdlib.h>
+# include "alloc-util.h"
+# define xnew(t, n) ASSERT_SE_PTR(new(t, n))
+# define xmalloc(n) ASSERT_SE_PTR(malloc(n))
+#endif
+
+/* String functions for both char and char16_t that should behave the same way as their respective
+ * counterpart in userspace. Where it makes sense, these accept NULL and do something sensible whereas
+ * userspace does not allow for this (strlen8(NULL) returns 0 like strlen_ptr(NULL) for example). To make it
+ * easier to tell in code which kind of string they work on, we use 8/16 suffixes. This also makes is easier
+ * to unit test them. */
+
+#define DEFINE_STRNLEN(type, name) \
+ size_t name(const type *s, size_t n) { \
+ if (!s) \
+ return 0; \
+ \
+ size_t len = 0; \
+ while (len < n && *s) { \
+ s++; \
+ len++; \
+ } \
+ \
+ return len; \
+ }
+
+DEFINE_STRNLEN(char, strnlen8);
+DEFINE_STRNLEN(char16_t, strnlen16);
+
+#define TOLOWER(c) \
+ ({ \
+ typeof(c) _c = (c); \
+ (_c >= 'A' && _c <= 'Z') ? _c + ('a' - 'A') : _c; \
+ })
+
+#define DEFINE_STRTOLOWER(type, name) \
+ void name(type *s) { \
+ if (!s) \
+ return; \
+ for (; *s; s++) \
+ *s = TOLOWER(*s); \
+ }
+
+DEFINE_STRTOLOWER(char, strtolower8);
+DEFINE_STRTOLOWER(char16_t, strtolower16);
+
+#define DEFINE_STRNCASECMP(type, name, tolower) \
+ int name(const type *s1, const type *s2, size_t n) { \
+ if (!s1 || !s2) \
+ return CMP(s1, s2); \
+ \
+ while (n > 0) { \
+ type c1 = *s1, c2 = *s2; \
+ if (tolower) { \
+ c1 = TOLOWER(c1); \
+ c2 = TOLOWER(c2); \
+ } \
+ if (!c1 || c1 != c2) \
+ return CMP(c1, c2); \
+ \
+ s1++; \
+ s2++; \
+ n--; \
+ } \
+ \
+ return 0; \
+ }
+
+DEFINE_STRNCASECMP(char, strncmp8, false);
+DEFINE_STRNCASECMP(char16_t, strncmp16, false);
+DEFINE_STRNCASECMP(char, strncasecmp8, true);
+DEFINE_STRNCASECMP(char16_t, strncasecmp16, true);
+
+#define DEFINE_STRCPY(type, name) \
+ type *name(type * restrict dest, const type * restrict src) { \
+ type *ret = ASSERT_PTR(dest); \
+ \
+ if (!src) { \
+ *dest = '\0'; \
+ return ret; \
+ } \
+ \
+ while (*src) { \
+ *dest = *src; \
+ dest++; \
+ src++; \
+ } \
+ \
+ *dest = '\0'; \
+ return ret; \
+ }
+
+DEFINE_STRCPY(char, strcpy8);
+DEFINE_STRCPY(char16_t, strcpy16);
+
+#define DEFINE_STRCHR(type, name) \
+ type *name(const type *s, type c) { \
+ if (!s) \
+ return NULL; \
+ \
+ while (*s) { \
+ if (*s == c) \
+ return (type *) s; \
+ s++; \
+ } \
+ \
+ return c ? NULL : (type *) s; \
+ }
+
+DEFINE_STRCHR(char, strchr8);
+DEFINE_STRCHR(char16_t, strchr16);
+
+#define DEFINE_STRNDUP(type, name, len_func) \
+ type *name(const type *s, size_t n) { \
+ if (!s) \
+ return NULL; \
+ \
+ size_t len = len_func(s, n); \
+ size_t size = len * sizeof(type); \
+ \
+ type *dup = xmalloc(size + sizeof(type)); \
+ if (size > 0) \
+ memcpy(dup, s, size); \
+ dup[len] = '\0'; \
+ \
+ return dup; \
+ }
+
+DEFINE_STRNDUP(char, xstrndup8, strnlen8);
+DEFINE_STRNDUP(char16_t, xstrndup16, strnlen16);
+
+static unsigned utf8_to_unichar(const char *utf8, size_t n, char32_t *c) {
+ char32_t unichar;
+ unsigned len;
+
+ assert(utf8);
+ assert(c);
+
+ if (!(utf8[0] & 0x80)) {
+ *c = utf8[0];
+ return 1;
+ } else if ((utf8[0] & 0xe0) == 0xc0) {
+ len = 2;
+ unichar = utf8[0] & 0x1f;
+ } else if ((utf8[0] & 0xf0) == 0xe0) {
+ len = 3;
+ unichar = utf8[0] & 0x0f;
+ } else if ((utf8[0] & 0xf8) == 0xf0) {
+ len = 4;
+ unichar = utf8[0] & 0x07;
+ } else if ((utf8[0] & 0xfc) == 0xf8) {
+ len = 5;
+ unichar = utf8[0] & 0x03;
+ } else if ((utf8[0] & 0xfe) == 0xfc) {
+ len = 6;
+ unichar = utf8[0] & 0x01;
+ } else {
+ *c = UINT32_MAX;
+ return 1;
+ }
+
+ if (len > n) {
+ *c = UINT32_MAX;
+ return len;
+ }
+
+ for (unsigned i = 1; i < len; i++) {
+ if ((utf8[i] & 0xc0) != 0x80) {
+ *c = UINT32_MAX;
+ return len;
+ }
+ unichar <<= 6;
+ unichar |= utf8[i] & 0x3f;
+ }
+
+ *c = unichar;
+ return len;
+}
+
+/* Convert UTF-8 to UCS-2, skipping any invalid or short byte sequences. */
+char16_t *xstrn8_to_16(const char *str8, size_t n) {
+ if (!str8 || n == 0)
+ return NULL;
+
+ size_t i = 0;
+ char16_t *str16 = xnew(char16_t, n + 1);
+
+ while (n > 0 && *str8 != '\0') {
+ char32_t unichar;
+
+ size_t utf8len = utf8_to_unichar(str8, n, &unichar);
+ str8 += utf8len;
+ n = LESS_BY(n, utf8len);
+
+ switch (unichar) {
+ case 0 ... 0xd7ffU:
+ case 0xe000U ... 0xffffU:
+ str16[i++] = unichar;
+ break;
+ }
+ }
+
+ str16[i] = '\0';
+ return str16;
+}
+
+char *startswith8(const char *s, const char *prefix) {
+ size_t l;
+
+ assert(prefix);
+
+ if (!s)
+ return NULL;
+
+ l = strlen8(prefix);
+ if (!strneq8(s, prefix, l))
+ return NULL;
+
+ return (char*) s + l;
+}
+
+static bool efi_fnmatch_prefix(const char16_t *p, const char16_t *h, const char16_t **ret_p, const char16_t **ret_h) {
+ assert(p);
+ assert(h);
+ assert(ret_p);
+ assert(ret_h);
+
+ for (;; p++, h++)
+ switch (*p) {
+ case '\0':
+ /* End of pattern. Check that haystack is now empty. */
+ return *h == '\0';
+
+ case '\\':
+ p++;
+ if (*p == '\0' || *p != *h)
+ /* Trailing escape or no match. */
+ return false;
+ break;
+
+ case '?':
+ if (*h == '\0')
+ /* Early end of haystack. */
+ return false;
+ break;
+
+ case '*':
+ /* Point ret_p at the remainder of the pattern. */
+ while (*p == '*')
+ p++;
+ *ret_p = p;
+ *ret_h = h;
+ return true;
+
+ case '[':
+ if (*h == '\0')
+ /* Early end of haystack. */
+ return false;
+
+ bool first = true, can_range = true, match = false;
+ for (;; first = false) {
+ p++;
+ if (*p == '\0')
+ return false;
+
+ if (*p == '\\') {
+ p++;
+ if (*p == '\0')
+ return false;
+ if (*p == *h)
+ match = true;
+ can_range = true;
+ continue;
+ }
+
+ /* End of set unless it's the first char. */
+ if (*p == ']' && !first)
+ break;
+
+ /* Range pattern if '-' is not first or last in set. */
+ if (*p == '-' && can_range && !first && *(p + 1) != ']') {
+ char16_t low = *(p - 1);
+ p++;
+ if (*p == '\\')
+ p++;
+ if (*p == '\0')
+ return false;
+
+ if (low <= *h && *h <= *p)
+ match = true;
+
+ /* Ranges cannot be chained: [a-c-f] == [-abcf] */
+ can_range = false;
+ continue;
+ }
+
+ if (*p == *h)
+ match = true;
+ can_range = true;
+ }
+
+ if (!match)
+ return false;
+ break;
+
+ default:
+ if (*p != *h)
+ /* Single char mismatch. */
+ return false;
+ }
+}
+
+/* Patterns are fnmatch-compatible (with reduced feature support). */
+bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack) {
+ /* Patterns can be considered as simple patterns (without '*') concatenated by '*'. By doing so we
+ * simply have to make sure the very first simple pattern matches the start of haystack. Then we just
+ * look for the remaining simple patterns *somewhere* within the haystack (in order) as any extra
+ * characters in between would be matches by the '*'. We then only have to ensure that the very last
+ * simple pattern matches at the actual end of the haystack.
+ *
+ * This means we do not need to use backtracking which could have catastrophic runtimes with the
+ * right input data. */
+
+ for (bool first = true;;) {
+ const char16_t *pattern_tail = NULL, *haystack_tail = NULL;
+ bool match = efi_fnmatch_prefix(pattern, haystack, &pattern_tail, &haystack_tail);
+ if (first) {
+ if (!match)
+ /* Initial simple pattern must match. */
+ return false;
+ if (!pattern_tail)
+ /* No '*' was in pattern, we can return early. */
+ return true;
+ first = false;
+ }
+
+ if (pattern_tail) {
+ assert(match);
+ pattern = pattern_tail;
+ haystack = haystack_tail;
+ } else {
+ /* If we have a match this must be at the end of the haystack. Note that
+ * efi_fnmatch_prefix compares the NUL-bytes at the end, so we cannot match the end
+ * of pattern in the middle of haystack). */
+ if (match || *haystack == '\0')
+ return match;
+
+ /* Match one character using '*'. */
+ haystack++;
+ }
+ }
+}
+
+#define DEFINE_PARSE_NUMBER(type, name) \
+ bool name(const type *s, uint64_t *ret_u, const type **ret_tail) { \
+ assert(ret_u); \
+ \
+ if (!s) \
+ return false; \
+ \
+ /* Need at least one digit. */ \
+ if (*s < '0' || *s > '9') \
+ return false; \
+ \
+ uint64_t u = 0; \
+ while (*s >= '0' && *s <= '9') { \
+ if (__builtin_mul_overflow(u, 10, &u)) \
+ return false; \
+ if (__builtin_add_overflow(u, *s - '0', &u)) \
+ return false; \
+ s++; \
+ } \
+ \
+ if (!ret_tail && *s != '\0') \
+ return false; \
+ \
+ *ret_u = u; \
+ if (ret_tail) \
+ *ret_tail = s; \
+ return true; \
+ }
+
+DEFINE_PARSE_NUMBER(char, parse_number8);
+DEFINE_PARSE_NUMBER(char16_t, parse_number16);
+
+bool parse_boolean(const char *v, bool *ret) {
+ assert(ret);
+
+ if (!v)
+ return false;
+
+ if (streq8(v, "1") || streq8(v, "yes") || streq8(v, "y") || streq8(v, "true") || streq8(v, "t") ||
+ streq8(v, "on")) {
+ *ret = true;
+ return true;
+ }
+
+ if (streq8(v, "0") || streq8(v, "no") || streq8(v, "n") || streq8(v, "false") || streq8(v, "f") ||
+ streq8(v, "off")) {
+ *ret = false;
+ return true;
+ }
+
+ return false;
+}
+
+char *line_get_key_value(char *s, const char *sep, size_t *pos, char **ret_key, char **ret_value) {
+ char *line, *value;
+ size_t linelen;
+
+ assert(s);
+ assert(sep);
+ assert(pos);
+ assert(ret_key);
+ assert(ret_value);
+
+ for (;;) {
+ line = s + *pos;
+ if (*line == '\0')
+ return NULL;
+
+ linelen = 0;
+ while (line[linelen] && !strchr8("\n\r", line[linelen]))
+ linelen++;
+
+ /* move pos to next line */
+ *pos += linelen;
+ if (s[*pos])
+ (*pos)++;
+
+ /* empty line */
+ if (linelen == 0)
+ continue;
+
+ /* terminate line */
+ line[linelen] = '\0';
+
+ /* remove leading whitespace */
+ while (linelen > 0 && strchr8(" \t", *line)) {
+ line++;
+ linelen--;
+ }
+
+ /* remove trailing whitespace */
+ while (linelen > 0 && strchr8(" \t", line[linelen - 1]))
+ linelen--;
+ line[linelen] = '\0';
+
+ if (*line == '#')
+ continue;
+
+ /* split key/value */
+ value = line;
+ while (*value && !strchr8(sep, *value))
+ value++;
+ if (*value == '\0')
+ continue;
+ *value = '\0';
+ value++;
+ while (*value && strchr8(sep, *value))
+ value++;
+
+ /* unquote */
+ if (value[0] == '"' && line[linelen - 1] == '"') {
+ value++;
+ line[linelen - 1] = '\0';
+ }
+
+ *ret_key = line;
+ *ret_value = value;
+ return line;
+ }
+}
+
+char16_t *hexdump(const void *data, size_t size) {
+ static const char hex[16] = "0123456789abcdef";
+ const uint8_t *d = data;
+
+ assert(data || size == 0);
+
+ char16_t *buf = xnew(char16_t, size * 2 + 1);
+
+ for (size_t i = 0; i < size; i++) {
+ buf[i * 2] = hex[d[i] >> 4];
+ buf[i * 2 + 1] = hex[d[i] & 0x0F];
+ }
+
+ buf[size * 2] = 0;
+ return buf;
+}
+
+static const char * const warn_table[] = {
+ [EFI_SUCCESS] = "Success",
+ [EFI_WARN_UNKNOWN_GLYPH] = "Unknown glyph",
+ [EFI_WARN_DELETE_FAILURE] = "Delete failure",
+ [EFI_WARN_WRITE_FAILURE] = "Write failure",
+ [EFI_WARN_BUFFER_TOO_SMALL] = "Buffer too small",
+ [EFI_WARN_STALE_DATA] = "Stale data",
+ [EFI_WARN_FILE_SYSTEM] = "File system",
+ [EFI_WARN_RESET_REQUIRED] = "Reset required",
+};
+
+/* Errors have MSB set, remove it to keep the table compact. */
+#define NOERR(err) ((err) & ~EFI_ERROR_MASK)
+
+static const char * const err_table[] = {
+ [NOERR(EFI_ERROR_MASK)] = "Error",
+ [NOERR(EFI_LOAD_ERROR)] = "Load error",
+ [NOERR(EFI_INVALID_PARAMETER)] = "Invalid parameter",
+ [NOERR(EFI_UNSUPPORTED)] = "Unsupported",
+ [NOERR(EFI_BAD_BUFFER_SIZE)] = "Bad buffer size",
+ [NOERR(EFI_BUFFER_TOO_SMALL)] = "Buffer too small",
+ [NOERR(EFI_NOT_READY)] = "Not ready",
+ [NOERR(EFI_DEVICE_ERROR)] = "Device error",
+ [NOERR(EFI_WRITE_PROTECTED)] = "Write protected",
+ [NOERR(EFI_OUT_OF_RESOURCES)] = "Out of resources",
+ [NOERR(EFI_VOLUME_CORRUPTED)] = "Volume corrupt",
+ [NOERR(EFI_VOLUME_FULL)] = "Volume full",
+ [NOERR(EFI_NO_MEDIA)] = "No media",
+ [NOERR(EFI_MEDIA_CHANGED)] = "Media changed",
+ [NOERR(EFI_NOT_FOUND)] = "Not found",
+ [NOERR(EFI_ACCESS_DENIED)] = "Access denied",
+ [NOERR(EFI_NO_RESPONSE)] = "No response",
+ [NOERR(EFI_NO_MAPPING)] = "No mapping",
+ [NOERR(EFI_TIMEOUT)] = "Time out",
+ [NOERR(EFI_NOT_STARTED)] = "Not started",
+ [NOERR(EFI_ALREADY_STARTED)] = "Already started",
+ [NOERR(EFI_ABORTED)] = "Aborted",
+ [NOERR(EFI_ICMP_ERROR)] = "ICMP error",
+ [NOERR(EFI_TFTP_ERROR)] = "TFTP error",
+ [NOERR(EFI_PROTOCOL_ERROR)] = "Protocol error",
+ [NOERR(EFI_INCOMPATIBLE_VERSION)] = "Incompatible version",
+ [NOERR(EFI_SECURITY_VIOLATION)] = "Security violation",
+ [NOERR(EFI_CRC_ERROR)] = "CRC error",
+ [NOERR(EFI_END_OF_MEDIA)] = "End of media",
+ [NOERR(EFI_ERROR_RESERVED_29)] = "Reserved (29)",
+ [NOERR(EFI_ERROR_RESERVED_30)] = "Reserved (30)",
+ [NOERR(EFI_END_OF_FILE)] = "End of file",
+ [NOERR(EFI_INVALID_LANGUAGE)] = "Invalid language",
+ [NOERR(EFI_COMPROMISED_DATA)] = "Compromised data",
+ [NOERR(EFI_IP_ADDRESS_CONFLICT)] = "IP address conflict",
+ [NOERR(EFI_HTTP_ERROR)] = "HTTP error",
+};
+
+static const char *status_to_string(EFI_STATUS status) {
+ if (status <= ELEMENTSOF(warn_table) - 1)
+ return warn_table[status];
+ if (status >= EFI_ERROR_MASK && status <= ((ELEMENTSOF(err_table) - 1) | EFI_ERROR_MASK))
+ return err_table[NOERR(status)];
+ return NULL;
+}
+
+typedef struct {
+ size_t padded_len; /* Field width in printf. */
+ size_t len; /* Precision in printf. */
+ bool pad_zero;
+ bool align_left;
+ bool alternative_form;
+ bool long_arg;
+ bool longlong_arg;
+ bool have_field_width;
+
+ const char *str;
+ const wchar_t *wstr;
+
+ /* For numbers. */
+ bool is_signed;
+ bool lowercase;
+ int8_t base;
+ char sign_pad; /* For + and (space) flags. */
+} SpecifierContext;
+
+typedef struct {
+ char16_t stack_buf[128]; /* We use stack_buf first to avoid allocations in most cases. */
+ char16_t *dyn_buf; /* Allocated buf or NULL if stack_buf is used. */
+ char16_t *buf; /* Points to the current active buf. */
+ size_t n_buf; /* Len of buf (in char16_t's, not bytes!). */
+ size_t n; /* Used len of buf (in char16_t's). This is always <n_buf. */
+
+ EFI_STATUS status;
+ const char *format;
+ va_list ap;
+} FormatContext;
+
+static void grow_buf(FormatContext *ctx, size_t need) {
+ assert(ctx);
+
+ assert_se(!__builtin_add_overflow(ctx->n, need, &need));
+
+ if (need < ctx->n_buf)
+ return;
+
+ /* Greedily allocate if we can. */
+ if (__builtin_mul_overflow(need, 2, &ctx->n_buf))
+ ctx->n_buf = need;
+
+ /* We cannot use realloc here as ctx->buf may be ctx->stack_buf, which we cannot free. */
+ char16_t *new_buf = xnew(char16_t, ctx->n_buf);
+ memcpy(new_buf, ctx->buf, ctx->n * sizeof(*ctx->buf));
+
+ free(ctx->dyn_buf);
+ ctx->buf = ctx->dyn_buf = new_buf;
+}
+
+static void push_padding(FormatContext *ctx, char pad, size_t len) {
+ assert(ctx);
+ while (len > 0) {
+ len--;
+ ctx->buf[ctx->n++] = pad;
+ }
+}
+
+static bool push_str(FormatContext *ctx, SpecifierContext *sp) {
+ assert(ctx);
+ assert(sp);
+
+ sp->padded_len = LESS_BY(sp->padded_len, sp->len);
+
+ grow_buf(ctx, sp->padded_len + sp->len);
+
+ if (!sp->align_left)
+ push_padding(ctx, ' ', sp->padded_len);
+
+ /* In userspace unit tests we cannot just memcpy() the wide string. */
+ if (sp->wstr && sizeof(wchar_t) == sizeof(char16_t)) {
+ memcpy(ctx->buf + ctx->n, sp->wstr, sp->len * sizeof(*sp->wstr));
+ ctx->n += sp->len;
+ } else {
+ assert(sp->str || sp->wstr);
+ for (size_t i = 0; i < sp->len; i++)
+ ctx->buf[ctx->n++] = sp->str ? sp->str[i] : sp->wstr[i];
+ }
+
+ if (sp->align_left)
+ push_padding(ctx, ' ', sp->padded_len);
+
+ assert(ctx->n < ctx->n_buf);
+ return true;
+}
+
+static bool push_num(FormatContext *ctx, SpecifierContext *sp, uint64_t u) {
+ const char *digits = sp->lowercase ? "0123456789abcdef" : "0123456789ABCDEF";
+ char16_t tmp[32];
+ size_t n = 0;
+
+ assert(ctx);
+ assert(sp);
+ assert(IN_SET(sp->base, 10, 16));
+
+ /* "%.0u" prints nothing if value is 0. */
+ if (u == 0 && sp->len == 0)
+ return true;
+
+ if (sp->is_signed && (int64_t) u < 0) {
+ /* We cannot just do "u = -(int64_t)u" here because -INT64_MIN overflows. */
+
+ uint64_t rem = -((int64_t) u % sp->base);
+ u = (int64_t) u / -sp->base;
+ tmp[n++] = digits[rem];
+ sp->sign_pad = '-';
+ }
+
+ while (u > 0 || n == 0) {
+ uint64_t rem = u % sp->base;
+ u /= sp->base;
+ tmp[n++] = digits[rem];
+ }
+
+ /* Note that numbers never get truncated! */
+ size_t prefix = (sp->sign_pad != 0 ? 1 : 0) + (sp->alternative_form ? 2 : 0);
+ size_t number_len = prefix + MAX(n, sp->len);
+ grow_buf(ctx, MAX(sp->padded_len, number_len));
+
+ size_t padding = 0;
+ if (sp->pad_zero)
+ /* Leading zeroes go after the sign or 0x prefix. */
+ number_len = MAX(number_len, sp->padded_len);
+ else
+ padding = LESS_BY(sp->padded_len, number_len);
+
+ if (!sp->align_left)
+ push_padding(ctx, ' ', padding);
+
+ if (sp->sign_pad != 0)
+ ctx->buf[ctx->n++] = sp->sign_pad;
+ if (sp->alternative_form) {
+ ctx->buf[ctx->n++] = '0';
+ ctx->buf[ctx->n++] = sp->lowercase ? 'x' : 'X';
+ }
+
+ push_padding(ctx, '0', LESS_BY(number_len, n + prefix));
+
+ while (n > 0)
+ ctx->buf[ctx->n++] = tmp[--n];
+
+ if (sp->align_left)
+ push_padding(ctx, ' ', padding);
+
+ assert(ctx->n < ctx->n_buf);
+ return true;
+}
+
+/* This helps unit testing. */
+#if SD_BOOT
+# define NULLSTR "(null)"
+# define wcsnlen strnlen16
+#else
+# define NULLSTR "(nil)"
+#endif
+
+static bool handle_format_specifier(FormatContext *ctx, SpecifierContext *sp) {
+ /* Parses one item from the format specifier in ctx and put the info into sp. If we are done with
+ * this specifier returns true, otherwise this function should be called again. */
+
+ /* This implementation assumes 32-bit ints. Also note that all types smaller than int are promoted to
+ * int in vararg functions, which is why we fetch only ints for any such types. The compiler would
+ * otherwise warn about fetching smaller types. */
+ assert_cc(sizeof(int) == 4);
+ assert_cc(sizeof(wchar_t) <= sizeof(int));
+ assert_cc(sizeof(intmax_t) <= sizeof(long long));
+
+ assert(ctx);
+ assert(sp);
+
+ switch (*ctx->format) {
+ case '#':
+ sp->alternative_form = true;
+ return false;
+ case '.':
+ sp->have_field_width = true;
+ return false;
+ case '-':
+ sp->align_left = true;
+ return false;
+ case '+':
+ case ' ':
+ sp->sign_pad = *ctx->format;
+ return false;
+
+ case '0':
+ if (!sp->have_field_width) {
+ sp->pad_zero = true;
+ return false;
+ }
+
+ /* If field width has already been provided then 0 is part of precision (%.0s). */
+ _fallthrough_;
+
+ case '*':
+ case '1' ... '9': {
+ int64_t i;
+
+ if (*ctx->format == '*')
+ i = va_arg(ctx->ap, int);
+ else {
+ uint64_t u;
+ if (!parse_number8(ctx->format, &u, &ctx->format) || u > INT_MAX)
+ assert_not_reached();
+ ctx->format--; /* Point it back to the last digit. */
+ i = u;
+ }
+
+ if (sp->have_field_width) {
+ /* Negative precision is ignored. */
+ if (i >= 0)
+ sp->len = (size_t) i;
+ } else {
+ /* Negative field width is treated as positive field width with '-' flag. */
+ if (i < 0) {
+ i *= -1;
+ sp->align_left = true;
+ }
+ sp->padded_len = i;
+ }
+
+ return false;
+ }
+
+ case 'h':
+ if (*(ctx->format + 1) == 'h')
+ ctx->format++;
+ /* char/short gets promoted to int, nothing to do here. */
+ return false;
+
+ case 'l':
+ if (*(ctx->format + 1) == 'l') {
+ ctx->format++;
+ sp->longlong_arg = true;
+ } else
+ sp->long_arg = true;
+ return false;
+
+ case 'z':
+ sp->long_arg = sizeof(size_t) == sizeof(long);
+ sp->longlong_arg = !sp->long_arg && sizeof(size_t) == sizeof(long long);
+ return false;
+
+ case 'j':
+ sp->long_arg = sizeof(intmax_t) == sizeof(long);
+ sp->longlong_arg = !sp->long_arg && sizeof(intmax_t) == sizeof(long long);
+ return false;
+
+ case 't':
+ sp->long_arg = sizeof(ptrdiff_t) == sizeof(long);
+ sp->longlong_arg = !sp->long_arg && sizeof(ptrdiff_t) == sizeof(long long);
+ return false;
+
+ case '%':
+ sp->str = "%";
+ sp->len = 1;
+ return push_str(ctx, sp);
+
+ case 'c':
+ sp->wstr = &(wchar_t){ va_arg(ctx->ap, int) };
+ sp->len = 1;
+ return push_str(ctx, sp);
+
+ case 's':
+ if (sp->long_arg) {
+ sp->wstr = va_arg(ctx->ap, const wchar_t *) ?: L"(null)";
+ sp->len = wcsnlen(sp->wstr, sp->len);
+ } else {
+ sp->str = va_arg(ctx->ap, const char *) ?: "(null)";
+ sp->len = strnlen8(sp->str, sp->len);
+ }
+ return push_str(ctx, sp);
+
+ case 'd':
+ case 'i':
+ case 'u':
+ case 'x':
+ case 'X':
+ sp->lowercase = *ctx->format == 'x';
+ sp->is_signed = IN_SET(*ctx->format, 'd', 'i');
+ sp->base = IN_SET(*ctx->format, 'x', 'X') ? 16 : 10;
+ if (sp->len == SIZE_MAX)
+ sp->len = 1;
+
+ uint64_t v;
+ if (sp->longlong_arg)
+ v = sp->is_signed ? (uint64_t) va_arg(ctx->ap, long long) :
+ va_arg(ctx->ap, unsigned long long);
+ else if (sp->long_arg)
+ v = sp->is_signed ? (uint64_t) va_arg(ctx->ap, long) : va_arg(ctx->ap, unsigned long);
+ else
+ v = sp->is_signed ? (uint64_t) va_arg(ctx->ap, int) : va_arg(ctx->ap, unsigned);
+
+ return push_num(ctx, sp, v);
+
+ case 'p': {
+ const void *ptr = va_arg(ctx->ap, const void *);
+ if (!ptr) {
+ sp->str = NULLSTR;
+ sp->len = STRLEN(NULLSTR);
+ return push_str(ctx, sp);
+ }
+
+ sp->base = 16;
+ sp->lowercase = true;
+ sp->alternative_form = true;
+ sp->len = 0; /* Precision is ignored for %p. */
+ return push_num(ctx, sp, (uintptr_t) ptr);
+ }
+
+ case 'm': {
+ sp->str = status_to_string(ctx->status);
+ if (sp->str) {
+ sp->len = strlen8(sp->str);
+ return push_str(ctx, sp);
+ }
+
+ sp->base = 16;
+ sp->lowercase = true;
+ sp->alternative_form = true;
+ sp->len = 0;
+ return push_num(ctx, sp, ctx->status);
+ }
+
+ default:
+ assert_not_reached();
+ }
+}
+
+/* printf_internal is largely compatible to userspace vasprintf. Any features omitted should trigger asserts.
+ *
+ * Supported:
+ * - Flags: #, 0, +, -, space
+ * - Lengths: h, hh, l, ll, z, j, t
+ * - Specifiers: %, c, s, u, i, d, x, X, p, m
+ * - Precision and width (inline or as int arg using *)
+ *
+ * Notable differences:
+ * - Passing NULL to %s is permitted and will print "(null)"
+ * - %p will also use "(null)"
+ * - The provided EFI_STATUS is used for %m instead of errno
+ * - "\n" is translated to "\r\n" */
+_printf_(2, 0) static char16_t *printf_internal(EFI_STATUS status, const char *format, va_list ap, bool ret) {
+ assert(format);
+
+ FormatContext ctx = {
+ .buf = ctx.stack_buf,
+ .n_buf = ELEMENTSOF(ctx.stack_buf),
+ .format = format,
+ .status = status,
+ };
+
+ /* We cannot put this into the struct without making a copy. */
+ va_copy(ctx.ap, ap);
+
+ while (*ctx.format != '\0') {
+ SpecifierContext sp = { .len = SIZE_MAX };
+
+ switch (*ctx.format) {
+ case '%':
+ ctx.format++;
+ while (!handle_format_specifier(&ctx, &sp))
+ ctx.format++;
+ ctx.format++;
+ break;
+ case '\n':
+ ctx.format++;
+ sp.str = "\r\n";
+ sp.len = 2;
+ push_str(&ctx, &sp);
+ break;
+ default:
+ sp.str = ctx.format++;
+ while (!IN_SET(*ctx.format, '%', '\n', '\0'))
+ ctx.format++;
+ sp.len = ctx.format - sp.str;
+ push_str(&ctx, &sp);
+ }
+ }
+
+ va_end(ctx.ap);
+
+ assert(ctx.n < ctx.n_buf);
+ ctx.buf[ctx.n++] = '\0';
+
+ if (ret) {
+ if (ctx.dyn_buf)
+ return TAKE_PTR(ctx.dyn_buf);
+
+ char16_t *ret_buf = xnew(char16_t, ctx.n);
+ memcpy(ret_buf, ctx.buf, ctx.n * sizeof(*ctx.buf));
+ return ret_buf;
+ }
+
+#if SD_BOOT
+ ST->ConOut->OutputString(ST->ConOut, ctx.buf);
+#endif
+
+ return mfree(ctx.dyn_buf);
+}
+
+void printf_status(EFI_STATUS status, const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ printf_internal(status, format, ap, false);
+ va_end(ap);
+}
+
+void vprintf_status(EFI_STATUS status, const char *format, va_list ap) {
+ printf_internal(status, format, ap, false);
+}
+
+char16_t *xasprintf_status(EFI_STATUS status, const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ char16_t *ret = printf_internal(status, format, ap, true);
+ va_end(ap);
+ return ret;
+}
+
+char16_t *xvasprintf_status(EFI_STATUS status, const char *format, va_list ap) {
+ return printf_internal(status, format, ap, true);
+}
+
+#if SD_BOOT
+/* To provide the actual implementation for these we need to remove the redirection to the builtins. */
+# undef memchr
+# undef memcmp
+# undef memcpy
+# undef memset
+_used_ void *memchr(const void *p, int c, size_t n);
+_used_ int memcmp(const void *p1, const void *p2, size_t n);
+_used_ void *memcpy(void * restrict dest, const void * restrict src, size_t n);
+_used_ void *memset(void *p, int c, size_t n);
+#else
+/* And for userspace unit testing we need to give them an efi_ prefix. */
+# define memchr efi_memchr
+# define memcmp efi_memcmp
+# define memcpy efi_memcpy
+# define memset efi_memset
+#endif
+
+void *memchr(const void *p, int c, size_t n) {
+ if (!p || n == 0)
+ return NULL;
+
+ const uint8_t *q = p;
+ for (size_t i = 0; i < n; i++)
+ if (q[i] == (unsigned char) c)
+ return (void *) (q + i);
+
+ return NULL;
+}
+
+int memcmp(const void *p1, const void *p2, size_t n) {
+ const uint8_t *up1 = p1, *up2 = p2;
+ int r;
+
+ if (!p1 || !p2)
+ return CMP(p1, p2);
+
+ while (n > 0) {
+ r = CMP(*up1, *up2);
+ if (r != 0)
+ return r;
+
+ up1++;
+ up2++;
+ n--;
+ }
+
+ return 0;
+}
+
+void *memcpy(void * restrict dest, const void * restrict src, size_t n) {
+ if (!dest || !src || n == 0)
+ return dest;
+
+#if SD_BOOT
+ /* The firmware-provided memcpy is likely optimized, so use that. The function is guaranteed to be
+ * available by the UEFI spec. We still make it depend on the boot services pointer being set just in
+ * case the compiler emits a call before it is available. */
+ if (_likely_(BS)) {
+ BS->CopyMem(dest, (void *) src, n);
+ return dest;
+ }
+#endif
+
+ uint8_t *d = dest;
+ const uint8_t *s = src;
+
+ while (n > 0) {
+ *d = *s;
+ d++;
+ s++;
+ n--;
+ }
+
+ return dest;
+}
+
+void *memset(void *p, int c, size_t n) {
+ if (!p || n == 0)
+ return p;
+
+#if SD_BOOT
+ /* See comment in efi_memcpy. Note that the signature has c and n swapped! */
+ if (_likely_(BS)) {
+ BS->SetMem(p, n, c);
+ return p;
+ }
+#endif
+
+ uint8_t *q = p;
+ while (n > 0) {
+ *q = c;
+ q++;
+ n--;
+ }
+
+ return p;
+}
diff --git a/src/boot/efi/efi-string.h b/src/boot/efi/efi-string.h
new file mode 100644
index 0000000..9ac919f
--- /dev/null
+++ b/src/boot/efi/efi-string.h
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+#include "macro-fundamental.h"
+
+size_t strnlen8(const char *s, size_t n);
+size_t strnlen16(const char16_t *s, size_t n);
+
+static inline size_t strlen8(const char *s) {
+ return strnlen8(s, SIZE_MAX);
+}
+
+static inline size_t strlen16(const char16_t *s) {
+ return strnlen16(s, SIZE_MAX);
+}
+
+static inline size_t strsize8(const char *s) {
+ return s ? (strlen8(s) + 1) * sizeof(*s) : 0;
+}
+
+static inline size_t strsize16(const char16_t *s) {
+ return s ? (strlen16(s) + 1) * sizeof(*s) : 0;
+}
+
+void strtolower8(char *s);
+void strtolower16(char16_t *s);
+
+int strncmp8(const char *s1, const char *s2, size_t n);
+int strncmp16(const char16_t *s1, const char16_t *s2, size_t n);
+int strncasecmp8(const char *s1, const char *s2, size_t n);
+int strncasecmp16(const char16_t *s1, const char16_t *s2, size_t n);
+
+static inline int strcmp8(const char *s1, const char *s2) {
+ return strncmp8(s1, s2, SIZE_MAX);
+}
+
+static inline int strcmp16(const char16_t *s1, const char16_t *s2) {
+ return strncmp16(s1, s2, SIZE_MAX);
+}
+
+static inline int strcasecmp8(const char *s1, const char *s2) {
+ return strncasecmp8(s1, s2, SIZE_MAX);
+}
+
+static inline int strcasecmp16(const char16_t *s1, const char16_t *s2) {
+ return strncasecmp16(s1, s2, SIZE_MAX);
+}
+
+static inline bool strneq8(const char *s1, const char *s2, size_t n) {
+ return strncmp8(s1, s2, n) == 0;
+}
+
+static inline bool strneq16(const char16_t *s1, const char16_t *s2, size_t n) {
+ return strncmp16(s1, s2, n) == 0;
+}
+
+static inline bool streq8(const char *s1, const char *s2) {
+ return strcmp8(s1, s2) == 0;
+}
+
+static inline bool streq16(const char16_t *s1, const char16_t *s2) {
+ return strcmp16(s1, s2) == 0;
+}
+
+static inline int strncaseeq8(const char *s1, const char *s2, size_t n) {
+ return strncasecmp8(s1, s2, n) == 0;
+}
+
+static inline int strncaseeq16(const char16_t *s1, const char16_t *s2, size_t n) {
+ return strncasecmp16(s1, s2, n) == 0;
+}
+
+static inline bool strcaseeq8(const char *s1, const char *s2) {
+ return strcasecmp8(s1, s2) == 0;
+}
+
+static inline bool strcaseeq16(const char16_t *s1, const char16_t *s2) {
+ return strcasecmp16(s1, s2) == 0;
+}
+
+char *strcpy8(char * restrict dest, const char * restrict src);
+char16_t *strcpy16(char16_t * restrict dest, const char16_t * restrict src);
+
+char *strchr8(const char *s, char c);
+char16_t *strchr16(const char16_t *s, char16_t c);
+
+char *xstrndup8(const char *s, size_t n);
+char16_t *xstrndup16(const char16_t *s, size_t n);
+
+static inline char *xstrdup8(const char *s) {
+ return xstrndup8(s, SIZE_MAX);
+}
+
+static inline char16_t *xstrdup16(const char16_t *s) {
+ return xstrndup16(s, SIZE_MAX);
+}
+
+char16_t *xstrn8_to_16(const char *str8, size_t n);
+static inline char16_t *xstr8_to_16(const char *str8) {
+ return xstrn8_to_16(str8, strlen8(str8));
+}
+
+char *startswith8(const char *s, const char *prefix);
+
+bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack);
+
+bool parse_number8(const char *s, uint64_t *ret_u, const char **ret_tail);
+bool parse_number16(const char16_t *s, uint64_t *ret_u, const char16_t **ret_tail);
+
+bool parse_boolean(const char *v, bool *ret);
+
+char *line_get_key_value(char *s, const char *sep, size_t *pos, char **ret_key, char **ret_value);
+
+char16_t *hexdump(const void *data, size_t size);
+
+#ifdef __clang__
+# define _gnu_printf_(a, b) _printf_(a, b)
+#else
+# define _gnu_printf_(a, b) __attribute__((format(gnu_printf, a, b)))
+#endif
+
+_gnu_printf_(2, 3) void printf_status(EFI_STATUS status, const char *format, ...);
+_gnu_printf_(2, 0) void vprintf_status(EFI_STATUS status, const char *format, va_list ap);
+_gnu_printf_(2, 3) _warn_unused_result_ char16_t *xasprintf_status(EFI_STATUS status, const char *format, ...);
+_gnu_printf_(2, 0) _warn_unused_result_ char16_t *xvasprintf_status(EFI_STATUS status, const char *format, va_list ap);
+
+#if SD_BOOT
+# define printf(...) printf_status(EFI_SUCCESS, __VA_ARGS__)
+# define xasprintf(...) xasprintf_status(EFI_SUCCESS, __VA_ARGS__)
+
+/* inttypes.h is provided by libc instead of the compiler and is not supposed to be used in freestanding
+ * environments. We could use clang __*_FMT*__ constants for this, bug gcc does not have them. :( */
+
+# if defined(__ILP32__) || defined(__arm__) || defined(__i386__)
+# define PRI64_PREFIX "ll"
+# elif defined(__LP64__)
+# define PRI64_PREFIX "l"
+# elif defined(__LLP64__) || (__SIZEOF_LONG__ == 4 && __SIZEOF_POINTER__ == 8)
+# define PRI64_PREFIX "ll"
+# else
+# error Unknown 64-bit data model
+# endif
+
+# define PRIi32 "i"
+# define PRIu32 "u"
+# define PRIx32 "x"
+# define PRIX32 "X"
+# define PRIiPTR "zi"
+# define PRIuPTR "zu"
+# define PRIxPTR "zx"
+# define PRIXPTR "zX"
+# define PRIi64 PRI64_PREFIX "i"
+# define PRIu64 PRI64_PREFIX "u"
+# define PRIx64 PRI64_PREFIX "x"
+# define PRIX64 PRI64_PREFIX "X"
+
+/* The compiler normally has knowledge about standard functions such as memcmp, but this is not the case when
+ * compiling with -ffreestanding. By referring to builtins, the compiler can check arguments and do
+ * optimizations again. Note that we still need to provide implementations as the compiler is free to not
+ * inline its own implementation and instead issue a library call. */
+# define memchr __builtin_memchr
+# define memcmp __builtin_memcmp
+# define memcpy __builtin_memcpy
+# define memset __builtin_memset
+
+static inline void *mempcpy(void * restrict dest, const void * restrict src, size_t n) {
+ if (!dest || !src || n == 0)
+ return dest;
+ memcpy(dest, src, n);
+ return (uint8_t *) dest + n;
+}
+
+#else
+/* For unit testing. */
+void *efi_memchr(const void *p, int c, size_t n);
+int efi_memcmp(const void *p1, const void *p2, size_t n);
+void *efi_memcpy(void * restrict dest, const void * restrict src, size_t n);
+void *efi_memset(void *p, int c, size_t n);
+#endif
diff --git a/src/boot/efi/efi.h b/src/boot/efi/efi.h
new file mode 100644
index 0000000..fbc5d10
--- /dev/null
+++ b/src/boot/efi/efi.h
@@ -0,0 +1,459 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "macro-fundamental.h"
+
+#if SD_BOOT
+/* uchar.h/wchar.h are not suitable for freestanding environments. */
+typedef __WCHAR_TYPE__ wchar_t;
+typedef __CHAR16_TYPE__ char16_t;
+typedef __CHAR32_TYPE__ char32_t;
+
+/* Let's be paranoid and do some sanity checks. */
+assert_cc(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__);
+assert_cc(__STDC_HOSTED__ == 0);
+assert_cc(sizeof(bool) == 1);
+assert_cc(sizeof(uint8_t) == 1);
+assert_cc(sizeof(uint16_t) == 2);
+assert_cc(sizeof(uint32_t) == 4);
+assert_cc(sizeof(uint64_t) == 8);
+assert_cc(sizeof(wchar_t) == 2);
+assert_cc(sizeof(char16_t) == 2);
+assert_cc(sizeof(char32_t) == 4);
+assert_cc(sizeof(size_t) == sizeof(void *));
+assert_cc(sizeof(size_t) == sizeof(uintptr_t));
+assert_cc(alignof(bool) == 1);
+assert_cc(alignof(uint8_t) == 1);
+assert_cc(alignof(uint16_t) == 2);
+assert_cc(alignof(uint32_t) == 4);
+assert_cc(alignof(uint64_t) == 8);
+assert_cc(alignof(wchar_t) == 2);
+assert_cc(alignof(char16_t) == 2);
+assert_cc(alignof(char32_t) == 4);
+
+# if defined(__x86_64__) && defined(__ILP32__)
+# error Building for x64 requires -m64 on x32 ABI.
+# endif
+#else
+# include <uchar.h>
+# include <wchar.h>
+#endif
+
+/* We use size_t/ssize_t to represent UEFI UINTN/INTN. */
+typedef size_t EFI_STATUS;
+typedef intptr_t ssize_t;
+
+typedef void* EFI_HANDLE;
+typedef void* EFI_EVENT;
+typedef size_t EFI_TPL;
+typedef uint64_t EFI_LBA;
+typedef uint64_t EFI_PHYSICAL_ADDRESS;
+
+#if defined(__x86_64__) && !defined(__ILP32__)
+# define EFIAPI __attribute__((ms_abi))
+#else
+# define EFIAPI
+#endif
+
+#if __SIZEOF_POINTER__ == 8
+# define EFI_ERROR_MASK 0x8000000000000000ULL
+#elif __SIZEOF_POINTER__ == 4
+# define EFI_ERROR_MASK 0x80000000ULL
+#else
+# error Unsupported pointer size
+#endif
+
+#define EFIWARN(s) ((EFI_STATUS) s)
+#define EFIERR(s) ((EFI_STATUS) (s | EFI_ERROR_MASK))
+
+#define EFI_SUCCESS EFIWARN(0)
+#define EFI_WARN_UNKNOWN_GLYPH EFIWARN(1)
+#define EFI_WARN_DELETE_FAILURE EFIWARN(2)
+#define EFI_WARN_WRITE_FAILURE EFIWARN(3)
+#define EFI_WARN_BUFFER_TOO_SMALL EFIWARN(4)
+#define EFI_WARN_STALE_DATA EFIWARN(5)
+#define EFI_WARN_FILE_SYSTEM EFIWARN(6)
+#define EFI_WARN_RESET_REQUIRED EFIWARN(7)
+
+#define EFI_LOAD_ERROR EFIERR(1)
+#define EFI_INVALID_PARAMETER EFIERR(2)
+#define EFI_UNSUPPORTED EFIERR(3)
+#define EFI_BAD_BUFFER_SIZE EFIERR(4)
+#define EFI_BUFFER_TOO_SMALL EFIERR(5)
+#define EFI_NOT_READY EFIERR(6)
+#define EFI_DEVICE_ERROR EFIERR(7)
+#define EFI_WRITE_PROTECTED EFIERR(8)
+#define EFI_OUT_OF_RESOURCES EFIERR(9)
+#define EFI_VOLUME_CORRUPTED EFIERR(10)
+#define EFI_VOLUME_FULL EFIERR(11)
+#define EFI_NO_MEDIA EFIERR(12)
+#define EFI_MEDIA_CHANGED EFIERR(13)
+#define EFI_NOT_FOUND EFIERR(14)
+#define EFI_ACCESS_DENIED EFIERR(15)
+#define EFI_NO_RESPONSE EFIERR(16)
+#define EFI_NO_MAPPING EFIERR(17)
+#define EFI_TIMEOUT EFIERR(18)
+#define EFI_NOT_STARTED EFIERR(19)
+#define EFI_ALREADY_STARTED EFIERR(20)
+#define EFI_ABORTED EFIERR(21)
+#define EFI_ICMP_ERROR EFIERR(22)
+#define EFI_TFTP_ERROR EFIERR(23)
+#define EFI_PROTOCOL_ERROR EFIERR(24)
+#define EFI_INCOMPATIBLE_VERSION EFIERR(25)
+#define EFI_SECURITY_VIOLATION EFIERR(26)
+#define EFI_CRC_ERROR EFIERR(27)
+#define EFI_END_OF_MEDIA EFIERR(28)
+#define EFI_ERROR_RESERVED_29 EFIERR(29)
+#define EFI_ERROR_RESERVED_30 EFIERR(30)
+#define EFI_END_OF_FILE EFIERR(31)
+#define EFI_INVALID_LANGUAGE EFIERR(32)
+#define EFI_COMPROMISED_DATA EFIERR(33)
+#define EFI_IP_ADDRESS_CONFLICT EFIERR(34)
+#define EFI_HTTP_ERROR EFIERR(35)
+
+typedef struct {
+ uint32_t Data1;
+ uint16_t Data2;
+ uint16_t Data3;
+ uint8_t Data4[8];
+} EFI_GUID;
+
+#define GUID_DEF(d1, d2, d3, d4_1, d4_2, d4_3, d4_4, d4_5, d4_6, d4_7, d4_8) \
+ { d1, d2, d3, { d4_1, d4_2, d4_3, d4_4, d4_5, d4_6, d4_7, d4_8 } }
+
+/* Creates a EFI_GUID pointer suitable for EFI APIs. Use of const allows the compiler to merge multiple
+ * uses (although, currently compilers do that regardless). Most EFI APIs declare their EFI_GUID input
+ * as non-const, but almost all of them are in fact const. */
+#define MAKE_GUID_PTR(name) ((EFI_GUID *) &(const EFI_GUID) name##_GUID)
+
+/* These allow MAKE_GUID_PTR() to work without requiring an extra _GUID in the passed name. We want to
+ * keep the GUID definitions in line with the UEFI spec. */
+#define EFI_GLOBAL_VARIABLE_GUID EFI_GLOBAL_VARIABLE
+#define EFI_FILE_INFO_GUID EFI_FILE_INFO_ID
+
+#define EFI_GLOBAL_VARIABLE \
+ GUID_DEF(0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c)
+#define EFI_IMAGE_SECURITY_DATABASE_GUID \
+ GUID_DEF(0xd719b2cb, 0x3d3a, 0x4596, 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f)
+
+#define EVT_TIMER 0x80000000U
+#define EVT_RUNTIME 0x40000000U
+#define EVT_NOTIFY_WAIT 0x00000100U
+#define EVT_NOTIFY_SIGNAL 0x00000200U
+#define EVT_SIGNAL_EXIT_BOOT_SERVICES 0x00000201U
+#define EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE 0x60000202U
+
+#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 0x01U
+#define EFI_OPEN_PROTOCOL_GET_PROTOCOL 0x02U
+#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL 0x04U
+#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 0x08U
+#define EFI_OPEN_PROTOCOL_BY_DRIVER 0x10U
+#define EFI_OPEN_PROTOCOL_EXCLUSIVE 0x20U
+
+#define EFI_VARIABLE_NON_VOLATILE 0x01U
+#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x02U
+#define EFI_VARIABLE_RUNTIME_ACCESS 0x04U
+#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x08U
+#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x10U
+#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x20U
+#define EFI_VARIABLE_APPEND_WRITE 0x40U
+#define EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS 0x80U
+
+#define EFI_TIME_ADJUST_DAYLIGHT 0x001U
+#define EFI_TIME_IN_DAYLIGHT 0x002U
+#define EFI_UNSPECIFIED_TIMEZONE 0x7FFU
+
+#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x01U
+#define EFI_OS_INDICATIONS_TIMESTAMP_REVOCATION 0x02U
+#define EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED 0x04U
+#define EFI_OS_INDICATIONS_FMP_CAPSULE_SUPPORTED 0x08U
+#define EFI_OS_INDICATIONS_CAPSULE_RESULT_VAR_SUPPORTED 0x10U
+#define EFI_OS_INDICATIONS_START_OS_RECOVERY 0x20U
+#define EFI_OS_INDICATIONS_START_PLATFORM_RECOVERY 0x40U
+#define EFI_OS_INDICATIONS_JSON_CONFIG_DATA_REFRESH 0x80U
+
+#define EFI_PAGE_SIZE 4096U
+#define EFI_SIZE_TO_PAGES(s) (((s) + 0xFFFU) >> 12U)
+
+/* These are common enough to warrant forward declaration. We also give them a
+ * shorter name for convenience. */
+typedef struct EFI_FILE_PROTOCOL EFI_FILE;
+typedef struct EFI_DEVICE_PATH_PROTOCOL EFI_DEVICE_PATH;
+
+typedef struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL EFI_SIMPLE_TEXT_INPUT_PROTOCOL;
+typedef struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
+
+typedef enum {
+ TimerCancel,
+ TimerPeriodic,
+ TimerRelative,
+} EFI_TIMER_DELAY;
+
+typedef enum {
+ AllocateAnyPages,
+ AllocateMaxAddress,
+ AllocateAddress,
+ MaxAllocateType,
+} EFI_ALLOCATE_TYPE;
+
+typedef enum {
+ EfiReservedMemoryType,
+ EfiLoaderCode,
+ EfiLoaderData,
+ EfiBootServicesCode,
+ EfiBootServicesData,
+ EfiRuntimeServicesCode,
+ EfiRuntimeServicesData,
+ EfiConventionalMemory,
+ EfiUnusableMemory,
+ EfiACPIReclaimMemory,
+ EfiACPIMemoryNVS,
+ EfiMemoryMappedIO,
+ EfiMemoryMappedIOPortSpace,
+ EfiPalCode,
+ EfiPersistentMemory,
+ EfiUnacceptedMemoryType,
+ EfiMaxMemoryType,
+} EFI_MEMORY_TYPE;
+
+typedef enum {
+ AllHandles,
+ ByRegisterNotify,
+ ByProtocol,
+} EFI_LOCATE_SEARCH_TYPE;
+
+typedef enum {
+ EfiResetCold,
+ EfiResetWarm,
+ EfiResetShutdown,
+ EfiResetPlatformSpecific,
+} EFI_RESET_TYPE;
+
+typedef struct {
+ uint16_t Year;
+ uint8_t Month;
+ uint8_t Day;
+ uint8_t Hour;
+ uint8_t Minute;
+ uint8_t Second;
+ uint8_t Pad1;
+ uint32_t Nanosecond;
+ int16_t TimeZone;
+ uint8_t Daylight;
+ uint8_t Pad2;
+} EFI_TIME;
+
+typedef struct {
+ uint32_t Resolution;
+ uint32_t Accuracy;
+ bool SetsToZero;
+} EFI_TIME_CAPABILITIES;
+
+typedef struct {
+ uint64_t Signature;
+ uint32_t Revision;
+ uint32_t HeaderSize;
+ uint32_t CRC32;
+ uint32_t Reserved;
+} EFI_TABLE_HEADER;
+
+typedef struct {
+ EFI_TABLE_HEADER Hdr;
+ void *RaiseTPL;
+ void *RestoreTPL;
+ EFI_STATUS (EFIAPI *AllocatePages)(
+ EFI_ALLOCATE_TYPE Type,
+ EFI_MEMORY_TYPE MemoryType,
+ size_t Pages,
+ EFI_PHYSICAL_ADDRESS *Memory);
+ EFI_STATUS (EFIAPI *FreePages)(
+ EFI_PHYSICAL_ADDRESS Memory,
+ size_t Pages);
+ void *GetMemoryMap;
+ EFI_STATUS (EFIAPI *AllocatePool)(
+ EFI_MEMORY_TYPE PoolType,
+ size_t Size,
+ void **Buffer);
+ EFI_STATUS (EFIAPI *FreePool)(void *Buffer);
+ EFI_STATUS (EFIAPI *CreateEvent)(
+ uint32_t Type,
+ EFI_TPL NotifyTpl,
+ void *NotifyFunction,
+ void *NotifyContext,
+ EFI_EVENT *Event);
+ EFI_STATUS (EFIAPI *SetTimer)(
+ EFI_EVENT Event,
+ EFI_TIMER_DELAY Type,
+ uint64_t TriggerTime);
+ EFI_STATUS (EFIAPI *WaitForEvent)(
+ size_t NumberOfEvents,
+ EFI_EVENT *Event,
+ size_t *Index);
+ void *SignalEvent;
+ EFI_STATUS (EFIAPI *CloseEvent)(EFI_EVENT Event);
+ EFI_STATUS (EFIAPI *CheckEvent)(EFI_EVENT Event);
+ void *InstallProtocolInterface;
+ EFI_STATUS (EFIAPI *ReinstallProtocolInterface)(
+ EFI_HANDLE Handle,
+ EFI_GUID *Protocol,
+ void *OldInterface,
+ void *NewInterface);
+ void *UninstallProtocolInterface;
+ EFI_STATUS (EFIAPI *HandleProtocol)(
+ EFI_HANDLE Handle,
+ EFI_GUID *Protocol,
+ void **Interface);
+ void *Reserved;
+ void *RegisterProtocolNotify;
+ EFI_STATUS (EFIAPI *LocateHandle)(
+ EFI_LOCATE_SEARCH_TYPE SearchType,
+ EFI_GUID *Protocol,
+ void *SearchKey,
+ size_t *BufferSize,
+ EFI_HANDLE *Buffer);
+ EFI_STATUS (EFIAPI *LocateDevicePath)(
+ EFI_GUID *Protocol,
+ EFI_DEVICE_PATH **DevicePath,
+ EFI_HANDLE *Device);
+ EFI_STATUS (EFIAPI *InstallConfigurationTable)(
+ EFI_GUID *Guid,
+ void *Table);
+ EFI_STATUS (EFIAPI *LoadImage)(
+ bool BootPolicy,
+ EFI_HANDLE ParentImageHandle,
+ EFI_DEVICE_PATH *DevicePath,
+ void *SourceBuffer,
+ size_t SourceSize,
+ EFI_HANDLE *ImageHandle);
+ EFI_STATUS (EFIAPI *StartImage)(
+ EFI_HANDLE ImageHandle,
+ size_t *ExitDataSize,
+ char16_t **ExitData);
+ EFI_STATUS (EFIAPI *Exit)(
+ EFI_HANDLE ImageHandle,
+ EFI_STATUS ExitStatus,
+ size_t ExitDataSize,
+ char16_t *ExitData);
+ EFI_STATUS (EFIAPI *UnloadImage)(EFI_HANDLE ImageHandle);
+ void *ExitBootServices;
+ EFI_STATUS (EFIAPI *GetNextMonotonicCount)(uint64_t *Count);
+ EFI_STATUS (EFIAPI *Stall)(size_t Microseconds);
+ EFI_STATUS (EFIAPI *SetWatchdogTimer)(
+ size_t Timeout,
+ uint64_t WatchdogCode,
+ size_t DataSize,
+ char16_t *WatchdogData);
+ EFI_STATUS (EFIAPI *ConnectController)(
+ EFI_HANDLE ControllerHandle,
+ EFI_HANDLE *DriverImageHandle,
+ EFI_DEVICE_PATH *RemainingDevicePath,
+ bool Recursive);
+ EFI_STATUS (EFIAPI *DisconnectController)(
+ EFI_HANDLE ControllerHandle,
+ EFI_HANDLE DriverImageHandle,
+ EFI_HANDLE ChildHandle);
+ EFI_STATUS (EFIAPI *OpenProtocol)(
+ EFI_HANDLE Handle,
+ EFI_GUID *Protocol,
+ void **Interface,
+ EFI_HANDLE AgentHandle,
+ EFI_HANDLE ControllerHandle,
+ uint32_t Attributes);
+ EFI_STATUS (EFIAPI *CloseProtocol)(
+ EFI_HANDLE Handle,
+ EFI_GUID *Protocol,
+ EFI_HANDLE AgentHandle,
+ EFI_HANDLE ControllerHandle);
+ void *OpenProtocolInformation;
+ EFI_STATUS (EFIAPI *ProtocolsPerHandle)(
+ EFI_HANDLE Handle,
+ EFI_GUID ***ProtocolBuffer,
+ size_t *ProtocolBufferCount);
+ EFI_STATUS (EFIAPI *LocateHandleBuffer)(
+ EFI_LOCATE_SEARCH_TYPE SearchType,
+ EFI_GUID *Protocol,
+ void *SearchKey,
+ size_t *NoHandles,
+ EFI_HANDLE **Buffer);
+ EFI_STATUS (EFIAPI *LocateProtocol)(
+ EFI_GUID *Protocol,
+ void *Registration,
+ void **Interface);
+ EFI_STATUS (EFIAPI *InstallMultipleProtocolInterfaces)(EFI_HANDLE *Handle, ...);
+ EFI_STATUS (EFIAPI *UninstallMultipleProtocolInterfaces)(EFI_HANDLE Handle, ...);
+ EFI_STATUS (EFIAPI *CalculateCrc32)(
+ void *Data,
+ size_t DataSize,
+ uint32_t *Crc32);
+ void (EFIAPI *CopyMem)(
+ void *Destination,
+ void *Source,
+ size_t Length);
+ void (EFIAPI *SetMem)(
+ void *Buffer,
+ size_t Size,
+ uint8_t Value);
+ void *CreateEventEx;
+} EFI_BOOT_SERVICES;
+
+typedef struct {
+ EFI_TABLE_HEADER Hdr;
+ EFI_STATUS (EFIAPI *GetTime)(
+ EFI_TIME *Time,
+ EFI_TIME_CAPABILITIES *Capabilities);
+ EFI_STATUS (EFIAPI *SetTime)(EFI_TIME *Time);
+ void *GetWakeupTime;
+ void *SetWakeupTime;
+ void *SetVirtualAddressMap;
+ void *ConvertPointer;
+ EFI_STATUS (EFIAPI *GetVariable)(
+ char16_t *VariableName,
+ EFI_GUID *VendorGuid,
+ uint32_t *Attributes,
+ size_t *DataSize,
+ void *Data);
+ void *GetNextVariableName;
+ EFI_STATUS (EFIAPI *SetVariable)(
+ char16_t *VariableName,
+ EFI_GUID *VendorGuid,
+ uint32_t Attributes,
+ size_t DataSize,
+ void *Data);
+ EFI_STATUS (EFIAPI *GetNextHighMonotonicCount)(uint32_t *HighCount);
+ void (EFIAPI *ResetSystem)(
+ EFI_RESET_TYPE ResetType,
+ EFI_STATUS ResetStatus,
+ size_t DataSize,
+ void *ResetData);
+ void *UpdateCapsule;
+ void *QueryCapsuleCapabilities;
+ void *QueryVariableInfo;
+} EFI_RUNTIME_SERVICES;
+
+typedef struct {
+ EFI_TABLE_HEADER Hdr;
+ char16_t *FirmwareVendor;
+ uint32_t FirmwareRevision;
+ EFI_HANDLE ConsoleInHandle;
+ EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;
+ EFI_HANDLE ConsoleOutHandle;
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
+ EFI_HANDLE StandardErrorHandle;
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
+ EFI_RUNTIME_SERVICES *RuntimeServices;
+ EFI_BOOT_SERVICES *BootServices;
+ size_t NumberOfTableEntries;
+ struct {
+ EFI_GUID VendorGuid;
+ void *VendorTable;
+ } *ConfigurationTable;
+} EFI_SYSTEM_TABLE;
+
+extern EFI_SYSTEM_TABLE *ST;
+extern EFI_BOOT_SERVICES *BS;
+extern EFI_RUNTIME_SERVICES *RT;
diff --git a/src/boot/efi/fuzz-bcd.c b/src/boot/efi/fuzz-bcd.c
new file mode 100644
index 0000000..cb5be7a
--- /dev/null
+++ b/src/boot/efi/fuzz-bcd.c
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bcd.h"
+#include "fuzz.h"
+#include "utf8.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_free_ void *p = NULL;
+
+ /* This limit was borrowed from src/boot/efi/boot.c */
+ if (outside_size_range(size, 0, 100*1024))
+ return 0;
+
+ fuzz_setup_logging();
+
+ p = memdup(data, size);
+ assert_se(p);
+
+ char16_t *title = get_bcd_title(p, size);
+ /* If we get something, it must be NUL-terminated, but an empty string is still valid! */
+ DO_NOT_OPTIMIZE(title && char16_strlen(title));
+ return 0;
+}
diff --git a/src/boot/efi/fuzz-efi-osrel.c b/src/boot/efi/fuzz-efi-osrel.c
new file mode 100644
index 0000000..1a5a9bc
--- /dev/null
+++ b/src/boot/efi/fuzz-efi-osrel.c
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "efi-string.h"
+#include "fuzz.h"
+
+#define SEP_LEN 4
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (outside_size_range(size, SEP_LEN + 1, 64 * 1024))
+ return 0;
+ if (data[SEP_LEN] != '\0')
+ return 0;
+
+ fuzz_setup_logging();
+
+ _cleanup_free_ char *p = memdup_suffix0(data + SEP_LEN + 1, size - SEP_LEN - 1);
+ assert_se(p);
+
+ size_t pos = 0;
+ char *key, *value;
+ while (line_get_key_value(p, (const char *) data, &pos, &key, &value)) {
+ assert_se(key);
+ assert_se(value);
+ }
+
+ return 0;
+}
diff --git a/src/boot/efi/fuzz-efi-printf.c b/src/boot/efi/fuzz-efi-printf.c
new file mode 100644
index 0000000..6dee830
--- /dev/null
+++ b/src/boot/efi/fuzz-efi-printf.c
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "efi-string.h"
+#include "fuzz.h"
+#include "utf8.h"
+
+typedef struct {
+ EFI_STATUS status;
+ int16_t field_width;
+ int16_t precision;
+ const void *ptr;
+ char c;
+ unsigned char uchar;
+ signed char schar;
+ unsigned short ushort;
+ signed short sshort;
+ unsigned int uint;
+ signed int sint;
+ unsigned long ulong;
+ signed long slong;
+ unsigned long long ulonglong;
+ signed long long slonglong;
+ size_t size;
+ ssize_t ssize;
+ intmax_t intmax;
+ uintmax_t uintmax;
+ ptrdiff_t ptrdiff;
+ char str[];
+} Input;
+
+#define PRINTF_ONE(...) \
+ ({ \
+ _cleanup_free_ char16_t *_ret = xasprintf_status(__VA_ARGS__); \
+ DO_NOT_OPTIMIZE(_ret); \
+ })
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (outside_size_range(size, sizeof(Input), 1024 * 1024))
+ return 0;
+
+ const Input *i = (const Input *) data;
+ size_t len = size - offsetof(Input, str);
+
+ fuzz_setup_logging();
+
+ PRINTF_ONE(i->status, "%*.*s", i->field_width, (int) len, i->str);
+ PRINTF_ONE(i->status, "%*.*ls", i->field_width, (int) (len / sizeof(wchar_t)), (const wchar_t *) i->str);
+
+ PRINTF_ONE(i->status, "%% %*.*m", i->field_width, i->precision);
+ PRINTF_ONE(i->status, "%*p", i->field_width, i->ptr);
+ PRINTF_ONE(i->status, "%*c %12340c %56789c", i->field_width, i->c, i->c, i->c);
+
+ PRINTF_ONE(i->status, "%*.*hhu", i->field_width, i->precision, i->uchar);
+ PRINTF_ONE(i->status, "%*.*hhi", i->field_width, i->precision, i->schar);
+ PRINTF_ONE(i->status, "%*.*hu", i->field_width, i->precision, i->ushort);
+ PRINTF_ONE(i->status, "%*.*hi", i->field_width, i->precision, i->sshort);
+ PRINTF_ONE(i->status, "%*.*u", i->field_width, i->precision, i->uint);
+ PRINTF_ONE(i->status, "%*.*i", i->field_width, i->precision, i->sint);
+ PRINTF_ONE(i->status, "%*.*lu", i->field_width, i->precision, i->ulong);
+ PRINTF_ONE(i->status, "%*.*li", i->field_width, i->precision, i->slong);
+ PRINTF_ONE(i->status, "%*.*llu", i->field_width, i->precision, i->ulonglong);
+ PRINTF_ONE(i->status, "%*.*lli", i->field_width, i->precision, i->slonglong);
+
+ PRINTF_ONE(i->status, "%+*.*hhi", i->field_width, i->precision, i->schar);
+ PRINTF_ONE(i->status, "%-*.*hi", i->field_width, i->precision, i->sshort);
+ PRINTF_ONE(i->status, "% *.*i", i->field_width, i->precision, i->sint);
+ PRINTF_ONE(i->status, "%0*li", i->field_width, i->slong);
+ PRINTF_ONE(i->status, "%#*.*llx", i->field_width, i->precision, i->ulonglong);
+
+ PRINTF_ONE(i->status, "%-*.*zx", i->field_width, i->precision, i->size);
+ PRINTF_ONE(i->status, "% *.*zi", i->field_width, i->precision, i->ssize);
+ PRINTF_ONE(i->status, "%0*ji", i->field_width, i->intmax);
+ PRINTF_ONE(i->status, "%#0*jX", i->field_width, i->uintmax);
+ PRINTF_ONE(i->status, "%*.*ti", i->field_width, i->precision, i->ptrdiff);
+
+ return 0;
+}
diff --git a/src/boot/efi/fuzz-efi-string.c b/src/boot/efi/fuzz-efi-string.c
new file mode 100644
index 0000000..36ecaf9
--- /dev/null
+++ b/src/boot/efi/fuzz-efi-string.c
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "efi-string.h"
+#include "fuzz.h"
+#include "utf8.h"
+
+static char16_t *memdup_str16(const uint8_t *data, size_t size) {
+ char16_t *ret = memdup(data, size);
+ assert_se(ret);
+ ret[size / sizeof(char16_t) - 1] = '\0';
+ return ret;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (outside_size_range(size, sizeof(size_t), 64 * 1024))
+ return 0;
+
+ fuzz_setup_logging();
+
+ size_t len, len2;
+ memcpy(&len, data, sizeof(len));
+ data += sizeof(len);
+ size -= sizeof(len);
+
+ len2 = size - len;
+ if (len > size || len < sizeof(char16_t) || len2 < sizeof(char16_t))
+ return 0;
+
+ const char *tail8 = NULL;
+ _cleanup_free_ char *str8 = ASSERT_SE_PTR(memdup_suffix0(data, size));
+ DO_NOT_OPTIMIZE(parse_number8(str8, &(uint64_t){ 0 }, size % 2 == 0 ? NULL : &tail8));
+
+ const char16_t *tail16 = NULL;
+ _cleanup_free_ char16_t *str16 = memdup_str16(data, size);
+ DO_NOT_OPTIMIZE(parse_number16(str16, &(uint64_t){ 0 }, size % 2 == 0 ? NULL : &tail16));
+
+ _cleanup_free_ char16_t *pattern = memdup_str16(data, len), *haystack = memdup_str16(data + len, len2);
+ DO_NOT_OPTIMIZE(efi_fnmatch(pattern, haystack));
+
+ return 0;
+}
diff --git a/src/boot/efi/graphics.c b/src/boot/efi/graphics.c
new file mode 100644
index 0000000..496fc69
--- /dev/null
+++ b/src/boot/efi/graphics.c
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright © 2013 Intel Corporation
+ * Authored by Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
+ */
+
+#include "graphics.h"
+#include "proto/console-control.h"
+#include "proto/simple-text-io.h"
+#include "util.h"
+
+EFI_STATUS graphics_mode(bool on) {
+ EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL;
+ EFI_CONSOLE_CONTROL_SCREEN_MODE new;
+ EFI_CONSOLE_CONTROL_SCREEN_MODE current;
+ bool uga_exists, stdin_locked;
+ EFI_STATUS err;
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_CONSOLE_CONTROL_PROTOCOL), NULL, (void **) &ConsoleControl);
+ if (err != EFI_SUCCESS)
+ /* console control protocol is nonstandard and might not exist. */
+ return err == EFI_NOT_FOUND ? EFI_SUCCESS : err;
+
+ /* check current mode */
+ err = ConsoleControl->GetMode(ConsoleControl, &current, &uga_exists, &stdin_locked);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* do not touch the mode */
+ new = on ? EfiConsoleControlScreenGraphics : EfiConsoleControlScreenText;
+ if (new == current)
+ return EFI_SUCCESS;
+
+ log_wait();
+ err = ConsoleControl->SetMode(ConsoleControl, new);
+
+ /* some firmware enables the cursor when switching modes */
+ ST->ConOut->EnableCursor(ST->ConOut, false);
+
+ return err;
+}
diff --git a/src/boot/efi/graphics.h b/src/boot/efi/graphics.h
new file mode 100644
index 0000000..33ab7f8
--- /dev/null
+++ b/src/boot/efi/graphics.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright © 2013 Intel Corporation
+ * Authored by Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
+ */
+#pragma once
+
+#include "efi.h"
+
+EFI_STATUS graphics_mode(bool on);
diff --git a/src/boot/efi/initrd.c b/src/boot/efi/initrd.c
new file mode 100644
index 0000000..527b05f
--- /dev/null
+++ b/src/boot/efi/initrd.c
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "initrd.h"
+#include "macro-fundamental.h"
+#include "proto/device-path.h"
+#include "proto/load-file.h"
+#include "util.h"
+
+#define LINUX_INITRD_MEDIA_GUID \
+ GUID_DEF(0x5568e427, 0x68fc, 0x4f3d, 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68)
+
+/* extend LoadFileProtocol */
+struct initrd_loader {
+ EFI_LOAD_FILE_PROTOCOL load_file;
+ const void *address;
+ size_t length;
+};
+
+/* static structure for LINUX_INITRD_MEDIA device path
+ see https://github.com/torvalds/linux/blob/v5.13/drivers/firmware/efi/libstub/efi-stub-helper.c
+ */
+static const struct {
+ VENDOR_DEVICE_PATH vendor;
+ EFI_DEVICE_PATH end;
+} _packed_ efi_initrd_device_path = {
+ .vendor = {
+ .Header = {
+ .Type = MEDIA_DEVICE_PATH,
+ .SubType = MEDIA_VENDOR_DP,
+ .Length = sizeof(efi_initrd_device_path.vendor),
+ },
+ .Guid = LINUX_INITRD_MEDIA_GUID
+ },
+ .end = {
+ .Type = END_DEVICE_PATH_TYPE,
+ .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
+ .Length = sizeof(efi_initrd_device_path.end),
+ }
+};
+
+static EFIAPI EFI_STATUS initrd_load_file(
+ EFI_LOAD_FILE_PROTOCOL *this,
+ EFI_DEVICE_PATH *file_path,
+ bool boot_policy,
+ size_t *buffer_size,
+ void *buffer) {
+
+ struct initrd_loader *loader;
+
+ if (!this || !buffer_size || !file_path)
+ return EFI_INVALID_PARAMETER;
+ if (boot_policy)
+ return EFI_UNSUPPORTED;
+
+ loader = (struct initrd_loader *) this;
+
+ if (loader->length == 0 || !loader->address)
+ return EFI_NOT_FOUND;
+
+ if (!buffer || *buffer_size < loader->length) {
+ *buffer_size = loader->length;
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ memcpy(buffer, loader->address, loader->length);
+ *buffer_size = loader->length;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS initrd_register(
+ const void *initrd_address,
+ size_t initrd_length,
+ EFI_HANDLE *ret_initrd_handle) {
+
+ EFI_STATUS err;
+ EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path;
+ EFI_HANDLE handle;
+ struct initrd_loader *loader;
+
+ assert(ret_initrd_handle);
+
+ if (!initrd_address || initrd_length == 0)
+ return EFI_SUCCESS;
+
+ /* check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registered.
+ LocateDevicePath checks for the "closest DevicePath" and returns its handle,
+ where as InstallMultipleProtocolInterfaces only matches identical DevicePaths.
+ */
+ err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle);
+ if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */
+ return EFI_ALREADY_STARTED;
+
+ loader = xnew(struct initrd_loader, 1);
+ *loader = (struct initrd_loader) {
+ .load_file.LoadFile = initrd_load_file,
+ .address = initrd_address,
+ .length = initrd_length
+ };
+
+ /* create a new handle and register the LoadFile2 protocol with the InitrdMediaPath on it */
+ err = BS->InstallMultipleProtocolInterfaces(
+ ret_initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL),
+ &efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL),
+ loader,
+ NULL);
+ if (err != EFI_SUCCESS)
+ free(loader);
+
+ return err;
+}
+
+EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) {
+ EFI_STATUS err;
+ struct initrd_loader *loader;
+
+ if (!initrd_handle)
+ return EFI_SUCCESS;
+
+ /* get the LoadFile2 protocol that we allocated earlier */
+ err = BS->HandleProtocol(initrd_handle, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), (void **) &loader);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* uninstall all protocols thus destroying the handle */
+ err = BS->UninstallMultipleProtocolInterfaces(
+ initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL),
+ &efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL),
+ loader,
+ NULL);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ initrd_handle = NULL;
+ free(loader);
+ return EFI_SUCCESS;
+}
diff --git a/src/boot/efi/initrd.h b/src/boot/efi/initrd.h
new file mode 100644
index 0000000..e7685ae
--- /dev/null
+++ b/src/boot/efi/initrd.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+EFI_STATUS initrd_register(
+ const void *initrd_address,
+ size_t initrd_length,
+ 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
new file mode 100644
index 0000000..65bc176
--- /dev/null
+++ b/src/boot/efi/linux.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/*
+ * Generic Linux boot protocol using the EFI/PE entry point of the kernel. Passes
+ * initrd with the LINUX_INITRD_MEDIA_GUID DevicePath and cmdline with
+ * EFI LoadedImageProtocol.
+ *
+ * This method works for Linux 5.8 and newer on ARM/Aarch64, x86/x68_64 and RISC-V.
+ */
+
+#include "initrd.h"
+#include "linux.h"
+#include "pe.h"
+#include "proto/device-path.h"
+#include "proto/loaded-image.h"
+#include "secure-boot.h"
+#include "util.h"
+
+#define STUB_PAYLOAD_GUID \
+ { 0x55c5d1f8, 0x04cd, 0x46b5, { 0x8a, 0x20, 0xe5, 0x6c, 0xbb, 0x30, 0x52, 0xd0 } }
+
+typedef struct {
+ const void *addr;
+ size_t len;
+ const EFI_DEVICE_PATH *device_path;
+} ValidationContext;
+
+static bool validate_payload(
+ const void *ctx, const EFI_DEVICE_PATH *device_path, const void *file_buffer, size_t file_size) {
+
+ const ValidationContext *payload = ASSERT_PTR(ctx);
+
+ if (device_path != payload->device_path)
+ return false;
+
+ /* Security arch (1) protocol does not provide a file buffer. Instead we are supposed to fetch the payload
+ * ourselves, which is not needed as we already have everything in memory and the device paths match. */
+ if (file_buffer && (file_buffer != payload->addr || file_size != payload->len))
+ return false;
+
+ return true;
+}
+
+static EFI_STATUS load_image(EFI_HANDLE parent, const void *source, size_t len, EFI_HANDLE *ret_image) {
+ assert(parent);
+ assert(source);
+ assert(ret_image);
+
+ /* We could pass a NULL device path, but it's nicer to provide something and it allows us to identify
+ * the loaded image from within the security hooks. */
+ struct {
+ VENDOR_DEVICE_PATH payload;
+ EFI_DEVICE_PATH end;
+ } _packed_ payload_device_path = {
+ .payload = {
+ .Header = {
+ .Type = MEDIA_DEVICE_PATH,
+ .SubType = MEDIA_VENDOR_DP,
+ .Length = sizeof(payload_device_path.payload),
+ },
+ .Guid = STUB_PAYLOAD_GUID,
+ },
+ .end = {
+ .Type = END_DEVICE_PATH_TYPE,
+ .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
+ .Length = sizeof(payload_device_path.end),
+ },
+ };
+
+ /* We want to support unsigned kernel images as payload, which is safe to do under secure boot
+ * because it is embedded in this stub loader (and since it is already running it must be trusted). */
+ install_security_override(
+ validate_payload,
+ &(ValidationContext) {
+ .addr = source,
+ .len = len,
+ .device_path = &payload_device_path.payload.Header,
+ });
+
+ EFI_STATUS ret = BS->LoadImage(
+ /*BootPolicy=*/false,
+ parent,
+ &payload_device_path.payload.Header,
+ (void *) source,
+ len,
+ ret_image);
+
+ uninstall_security_override();
+
+ return ret;
+}
+
+EFI_STATUS linux_exec(
+ EFI_HANDLE parent,
+ const char16_t *cmdline,
+ const void *linux_buffer,
+ size_t linux_length,
+ const void *initrd_buffer,
+ size_t initrd_length) {
+
+ uint32_t compat_address;
+ EFI_STATUS err;
+
+ assert(parent);
+ assert(linux_buffer && linux_length > 0);
+ assert(initrd_buffer || initrd_length == 0);
+
+ err = pe_kernel_info(linux_buffer, &compat_address);
+#if defined(__i386__) || defined(__x86_64__)
+ if (err == EFI_UNSUPPORTED)
+ /* Kernel is too old to support LINUX_INITRD_MEDIA_GUID, try the deprecated EFI handover
+ * protocol. */
+ return linux_exec_efi_handover(
+ parent,
+ cmdline,
+ linux_buffer,
+ linux_length,
+ initrd_buffer,
+ initrd_length);
+#endif
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Bad kernel image: %m");
+
+ _cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL;
+ err = load_image(parent, linux_buffer, linux_length, &kernel_image);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error loading kernel image: %m");
+
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
+ err = BS->HandleProtocol(
+ kernel_image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error getting kernel loaded image protocol: %m");
+
+ if (cmdline) {
+ loaded_image->LoadOptions = (void *) cmdline;
+ loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions);
+ }
+
+ _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL;
+ err = initrd_register(initrd_buffer, initrd_length, &initrd_handle);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error registering initrd: %m");
+
+ log_wait();
+ err = BS->StartImage(kernel_image, NULL, NULL);
+
+ /* Try calling the kernel compat entry point if one exists. */
+ if (err == EFI_UNSUPPORTED && compat_address > 0) {
+ EFI_IMAGE_ENTRY_POINT compat_entry =
+ (EFI_IMAGE_ENTRY_POINT) ((uint8_t *) loaded_image->ImageBase + compat_address);
+ err = compat_entry(kernel_image, ST);
+ }
+
+ return log_error_status(err, "Error starting kernel image: %m");
+}
diff --git a/src/boot/efi/linux.h b/src/boot/efi/linux.h
new file mode 100644
index 0000000..46b5f4f
--- /dev/null
+++ b/src/boot/efi/linux.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+EFI_STATUS linux_exec(
+ EFI_HANDLE parent,
+ const char16_t *cmdline,
+ const void *linux_buffer,
+ size_t linux_length,
+ const void *initrd_buffer,
+ size_t initrd_length);
+EFI_STATUS linux_exec_efi_handover(
+ EFI_HANDLE parent,
+ const char16_t *cmdline,
+ const void *linux_buffer,
+ size_t linux_length,
+ const void *initrd_buffer,
+ size_t initrd_length);
diff --git a/src/boot/efi/linux_x86.c b/src/boot/efi/linux_x86.c
new file mode 100644
index 0000000..757902d
--- /dev/null
+++ b/src/boot/efi/linux_x86.c
@@ -0,0 +1,224 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/*
+ * x86 specific code to for EFI handover boot protocol
+ * Linux kernels version 5.8 and newer support providing the initrd by
+ * LINUX_INITRD_MEDIA_GUID DevicePath. In order to support older kernels too,
+ * this x86 specific linux_exec function passes the initrd by setting the
+ * corresponding fields in the setup_header struct.
+ *
+ * see https://docs.kernel.org/x86/boot.html
+ */
+
+#include "initrd.h"
+#include "linux.h"
+#include "macro-fundamental.h"
+#include "util.h"
+
+#define KERNEL_SECTOR_SIZE 512u
+#define BOOT_FLAG_MAGIC 0xAA55u
+#define SETUP_MAGIC 0x53726448u /* "HdrS" */
+#define SETUP_VERSION_2_11 0x20bu
+#define SETUP_VERSION_2_12 0x20cu
+#define SETUP_VERSION_2_15 0x20fu
+#define CMDLINE_PTR_MAX 0xA0000u
+
+enum {
+ XLF_KERNEL_64 = 1 << 0,
+ XLF_CAN_BE_LOADED_ABOVE_4G = 1 << 1,
+ XLF_EFI_HANDOVER_32 = 1 << 2,
+ XLF_EFI_HANDOVER_64 = 1 << 3,
+#ifdef __x86_64__
+ XLF_EFI_HANDOVER = XLF_EFI_HANDOVER_64,
+#else
+ XLF_EFI_HANDOVER = XLF_EFI_HANDOVER_32,
+#endif
+};
+
+typedef struct {
+ uint8_t setup_sects;
+ uint16_t root_flags;
+ uint32_t syssize;
+ uint16_t ram_size;
+ uint16_t vid_mode;
+ uint16_t root_dev;
+ uint16_t boot_flag;
+ uint8_t jump; /* We split the 2-byte jump field from the spec in two for convenience. */
+ uint8_t setup_size;
+ uint32_t header;
+ uint16_t version;
+ uint32_t realmode_swtch;
+ uint16_t start_sys_seg;
+ uint16_t kernel_version;
+ uint8_t type_of_loader;
+ uint8_t loadflags;
+ uint16_t setup_move_size;
+ uint32_t code32_start;
+ uint32_t ramdisk_image;
+ uint32_t ramdisk_size;
+ uint32_t bootsect_kludge;
+ uint16_t heap_end_ptr;
+ uint8_t ext_loader_ver;
+ uint8_t ext_loader_type;
+ uint32_t cmd_line_ptr;
+ uint32_t initrd_addr_max;
+ uint32_t kernel_alignment;
+ uint8_t relocatable_kernel;
+ uint8_t min_alignment;
+ uint16_t xloadflags;
+ uint32_t cmdline_size;
+ uint32_t hardware_subarch;
+ uint64_t hardware_subarch_data;
+ uint32_t payload_offset;
+ uint32_t payload_length;
+ uint64_t setup_data;
+ uint64_t pref_address;
+ uint32_t init_size;
+ uint32_t handover_offset;
+} _packed_ SetupHeader;
+
+/* We really only care about a few fields, but we still have to provide a full page otherwise. */
+typedef struct {
+ uint8_t pad[192];
+ uint32_t ext_ramdisk_image;
+ uint32_t ext_ramdisk_size;
+ uint32_t ext_cmd_line_ptr;
+ uint8_t pad2[293];
+ SetupHeader hdr;
+ uint8_t pad3[3480];
+} _packed_ BootParams;
+assert_cc(offsetof(BootParams, ext_ramdisk_image) == 0x0C0);
+assert_cc(sizeof(BootParams) == 4096);
+
+#ifdef __i386__
+# define __regparm0__ __attribute__((regparm(0)))
+#else
+# define __regparm0__
+#endif
+
+typedef void (*handover_f)(void *parent, EFI_SYSTEM_TABLE *table, BootParams *params) __regparm0__
+ __attribute__((sysv_abi));
+
+static void linux_efi_handover(EFI_HANDLE parent, uintptr_t kernel, BootParams *params) {
+ assert(params);
+
+ kernel += (params->hdr.setup_sects + 1) * KERNEL_SECTOR_SIZE; /* 32-bit entry address. */
+
+ /* Old kernels needs this set, while newer ones seem to ignore this. */
+ params->hdr.code32_start = kernel;
+
+#ifdef __x86_64__
+ kernel += KERNEL_SECTOR_SIZE; /* 64-bit entry address. */
+#endif
+
+ kernel += params->hdr.handover_offset; /* 32/64-bit EFI handover address. */
+
+ /* Note in EFI mixed mode this now points to the correct 32-bit handover entry point, allowing a 64-bit
+ * kernel to be booted from a 32-bit sd-stub. */
+
+ handover_f handover = (handover_f) kernel;
+ handover(parent, ST, params);
+}
+
+EFI_STATUS linux_exec_efi_handover(
+ EFI_HANDLE parent,
+ const char16_t *cmdline,
+ const void *linux_buffer,
+ size_t linux_length,
+ const void *initrd_buffer,
+ size_t initrd_length) {
+
+ assert(parent);
+ assert(linux_buffer);
+ assert(initrd_buffer || initrd_length == 0);
+
+ if (linux_length < sizeof(BootParams))
+ return EFI_LOAD_ERROR;
+
+ const BootParams *image_params = (const BootParams *) linux_buffer;
+ if (image_params->hdr.header != SETUP_MAGIC || image_params->hdr.boot_flag != BOOT_FLAG_MAGIC)
+ return log_error_status(EFI_UNSUPPORTED, "Unsupported kernel image.");
+ if (image_params->hdr.version < SETUP_VERSION_2_11)
+ return log_error_status(EFI_UNSUPPORTED, "Kernel too old.");
+ if (!image_params->hdr.relocatable_kernel)
+ return log_error_status(EFI_UNSUPPORTED, "Kernel is not relocatable.");
+
+ /* The xloadflags were added in version 2.12+ of the boot protocol but the handover support predates
+ * that, so we cannot safety-check this for 2.11. */
+ if (image_params->hdr.version >= SETUP_VERSION_2_12 &&
+ !FLAGS_SET(image_params->hdr.xloadflags, XLF_EFI_HANDOVER))
+ return log_error_status(EFI_UNSUPPORTED, "Kernel does not support EFI handover protocol.");
+
+ bool can_4g = image_params->hdr.version >= SETUP_VERSION_2_12 &&
+ FLAGS_SET(image_params->hdr.xloadflags, XLF_CAN_BE_LOADED_ABOVE_4G);
+
+ /* There is no way to pass the high bits of code32_start. Newer kernels seems to handle this
+ * just fine, but older kernels will fail even if they otherwise have above 4G boot support. */
+ _cleanup_pages_ Pages linux_relocated = {};
+ if (POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + linux_length > UINT32_MAX) {
+ linux_relocated = xmalloc_pages(
+ AllocateMaxAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES(linux_length), UINT32_MAX);
+ linux_buffer = memcpy(
+ PHYSICAL_ADDRESS_TO_POINTER(linux_relocated.addr), linux_buffer, linux_length);
+ }
+
+ _cleanup_pages_ Pages initrd_relocated = {};
+ if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) + initrd_length > UINT32_MAX) {
+ initrd_relocated = xmalloc_pages(
+ AllocateMaxAddress, EfiLoaderData, EFI_SIZE_TO_PAGES(initrd_length), UINT32_MAX);
+ initrd_buffer = memcpy(
+ PHYSICAL_ADDRESS_TO_POINTER(initrd_relocated.addr),
+ initrd_buffer,
+ initrd_length);
+ }
+
+ _cleanup_pages_ Pages boot_params_page = xmalloc_pages(
+ can_4g ? AllocateAnyPages : AllocateMaxAddress,
+ EfiLoaderData,
+ EFI_SIZE_TO_PAGES(sizeof(BootParams)),
+ UINT32_MAX /* Below the 4G boundary */);
+ BootParams *boot_params = PHYSICAL_ADDRESS_TO_POINTER(boot_params_page.addr);
+ *boot_params = (BootParams){};
+
+ /* Setup size is determined by offset 0x0202 + byte value at offset 0x0201, which is the same as
+ * offset of the header field and the target from the jump field (which we split for this reason). */
+ memcpy(&boot_params->hdr,
+ &image_params->hdr,
+ offsetof(SetupHeader, header) + image_params->hdr.setup_size);
+
+ boot_params->hdr.type_of_loader = 0xff;
+
+ /* Spec says: For backwards compatibility, if the setup_sects field contains 0, the real value is 4. */
+ if (boot_params->hdr.setup_sects == 0)
+ boot_params->hdr.setup_sects = 4;
+
+ _cleanup_pages_ Pages cmdline_pages = {};
+ if (cmdline) {
+ size_t len = MIN(strlen16(cmdline), image_params->hdr.cmdline_size);
+
+ cmdline_pages = xmalloc_pages(
+ can_4g ? AllocateAnyPages : AllocateMaxAddress,
+ EfiLoaderData,
+ EFI_SIZE_TO_PAGES(len + 1),
+ CMDLINE_PTR_MAX);
+
+ /* Convert cmdline to ASCII. */
+ char *cmdline8 = PHYSICAL_ADDRESS_TO_POINTER(cmdline_pages.addr);
+ for (size_t i = 0; i < len; i++)
+ cmdline8[i] = cmdline[i] <= 0x7E ? cmdline[i] : ' ';
+ cmdline8[len] = '\0';
+
+ boot_params->hdr.cmd_line_ptr = (uint32_t) cmdline_pages.addr;
+ boot_params->ext_cmd_line_ptr = cmdline_pages.addr >> 32;
+ assert(can_4g || cmdline_pages.addr <= CMDLINE_PTR_MAX);
+ }
+
+ boot_params->hdr.ramdisk_image = (uintptr_t) initrd_buffer;
+ boot_params->ext_ramdisk_image = POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) >> 32;
+ boot_params->hdr.ramdisk_size = initrd_length;
+ boot_params->ext_ramdisk_size = ((uint64_t) initrd_length) >> 32;
+
+ log_wait();
+ linux_efi_handover(parent, (uintptr_t) linux_buffer, boot_params);
+ return EFI_LOAD_ERROR;
+}
diff --git a/src/boot/efi/log.c b/src/boot/efi/log.c
new file mode 100644
index 0000000..364471e
--- /dev/null
+++ b/src/boot/efi/log.c
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "log.h"
+#include "proto/rng.h"
+#include "proto/simple-text-io.h"
+#include "util.h"
+
+static unsigned log_count = 0;
+
+void freeze(void) {
+ for (;;)
+ BS->Stall(60 * 1000 * 1000);
+}
+
+_noreturn_ static void panic(const char16_t *message) {
+ if (ST->ConOut->Mode->CursorColumn > 0)
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) u"\r\n");
+ ST->ConOut->SetAttribute(ST->ConOut, EFI_TEXT_ATTR(EFI_LIGHTRED, EFI_BLACK));
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) message);
+ freeze();
+}
+
+void efi_assert(const char *expr, const char *file, unsigned line, const char *function) {
+ static bool asserting = false;
+
+ /* Let's be paranoid. */
+ if (asserting)
+ panic(u"systemd-boot: Nested assertion failure, halting.");
+
+ asserting = true;
+ log_error("systemd-boot: Assertion '%s' failed at %s:%u@%s, halting.", expr, file, line, function);
+ freeze();
+}
+
+EFI_STATUS log_internal(EFI_STATUS status, const char *format, ...) {
+ assert(format);
+
+ int32_t attr = ST->ConOut->Mode->Attribute;
+
+ if (ST->ConOut->Mode->CursorColumn > 0)
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) u"\r\n");
+ ST->ConOut->SetAttribute(ST->ConOut, EFI_TEXT_ATTR(EFI_LIGHTRED, EFI_BLACK));
+
+ va_list ap;
+ va_start(ap, format);
+ vprintf_status(status, format, ap);
+ va_end(ap);
+
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) u"\r\n");
+ ST->ConOut->SetAttribute(ST->ConOut, attr);
+
+ log_count++;
+ return status;
+}
+
+#ifdef EFI_DEBUG
+void log_hexdump(const char16_t *prefix, const void *data, size_t size) {
+ /* Debugging helper — please keep this around, even if not used */
+
+ _cleanup_free_ char16_t *hex = hexdump(data, size);
+ log_internal(EFI_SUCCESS, "%ls[%zu]: %ls", prefix, size, hex);
+}
+#endif
+
+void log_wait(void) {
+ if (log_count == 0)
+ return;
+
+ BS->Stall(MIN(4u, log_count) * 2500 * 1000);
+ log_count = 0;
+}
+
+_used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3;
+
+/* We can only set a random stack canary if this function attribute is available,
+ * otherwise this may create a stack check fail. */
+#if STACK_PROTECTOR_RANDOM
+void __stack_chk_guard_init(void) {
+ EFI_RNG_PROTOCOL *rng;
+ if (BS->LocateProtocol(MAKE_GUID_PTR(EFI_RNG_PROTOCOL), NULL, (void **) &rng) == EFI_SUCCESS)
+ (void) rng->GetRNG(rng, NULL, sizeof(__stack_chk_guard), (void *) &__stack_chk_guard);
+ else
+ /* Better than no extra entropy. */
+ __stack_chk_guard ^= (intptr_t) __executable_start;
+}
+#endif
+
+_used_ _noreturn_ void __stack_chk_fail(void);
+_used_ _noreturn_ void __stack_chk_fail_local(void);
+void __stack_chk_fail(void) {
+ panic(u"systemd-boot: Stack check failed, halting.");
+}
+void __stack_chk_fail_local(void) {
+ __stack_chk_fail();
+}
+
+/* Called by libgcc for some fatal errors like integer overflow with -ftrapv. */
+_used_ _noreturn_ void abort(void);
+void abort(void) {
+ panic(u"systemd-boot: Unknown error, halting.");
+}
+
+#if defined(__ARM_EABI__)
+/* These override the (weak) div0 handlers from libgcc as they would otherwise call raise() instead. */
+_used_ _noreturn_ int __aeabi_idiv0(int return_value);
+_used_ _noreturn_ long long __aeabi_ldiv0(long long return_value);
+
+int __aeabi_idiv0(int return_value) {
+ panic(u"systemd-boot: Division by zero, halting.");
+}
+
+long long __aeabi_ldiv0(long long return_value) {
+ panic(u"systemd-boot: Division by zero, halting.");
+}
+#endif
diff --git a/src/boot/efi/log.h b/src/boot/efi/log.h
new file mode 100644
index 0000000..13f3887
--- /dev/null
+++ b/src/boot/efi/log.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi-string.h"
+
+#if defined __has_attribute
+# if __has_attribute(no_stack_protector)
+# define HAVE_NO_STACK_PROTECTOR_ATTRIBUTE
+# endif
+#endif
+
+#if defined(HAVE_NO_STACK_PROTECTOR_ATTRIBUTE) && \
+ (defined(__SSP__) || defined(__SSP_ALL__) || \
+ defined(__SSP_STRONG__) || defined(__SSP_EXPLICIT__))
+# define STACK_PROTECTOR_RANDOM 1
+__attribute__((no_stack_protector, noinline)) void __stack_chk_guard_init(void);
+#else
+# define STACK_PROTECTOR_RANDOM 0
+# define __stack_chk_guard_init()
+#endif
+
+_noreturn_ void freeze(void);
+void log_wait(void);
+_gnu_printf_(2, 3) EFI_STATUS log_internal(EFI_STATUS status, const char *format, ...);
+#define log_error_status(status, ...) log_internal(status, __VA_ARGS__)
+#define log_error(...) log_internal(EFI_INVALID_PARAMETER, __VA_ARGS__)
+#define log_oom() log_internal(EFI_OUT_OF_RESOURCES, "Out of memory.")
+#define log_trace() log_internal(EFI_SUCCESS, "%s:%i@%s", __FILE__, __LINE__, __func__)
+
+#ifdef EFI_DEBUG
+void log_hexdump(const char16_t *prefix, const void *data, size_t size);
+#endif
diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c
new file mode 100644
index 0000000..01c97c8
--- /dev/null
+++ b/src/boot/efi/measure.c
@@ -0,0 +1,296 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#if ENABLE_TPM
+
+#include "macro-fundamental.h"
+#include "measure.h"
+#include "memory-util-fundamental.h"
+#include "proto/tcg.h"
+#include "tpm2-pcr.h"
+#include "util.h"
+
+static EFI_STATUS tpm1_measure_to_pcr_and_event_log(
+ const EFI_TCG_PROTOCOL *tcg,
+ uint32_t pcrindex,
+ EFI_PHYSICAL_ADDRESS buffer,
+ size_t buffer_size,
+ const char16_t *description) {
+
+ _cleanup_free_ TCG_PCR_EVENT *tcg_event = NULL;
+ EFI_PHYSICAL_ADDRESS event_log_last;
+ uint32_t event_number = 1;
+ size_t desc_len;
+
+ assert(tcg);
+ assert(description);
+
+ desc_len = strsize16(description);
+ tcg_event = xmalloc(offsetof(TCG_PCR_EVENT, Event) + desc_len);
+ *tcg_event = (TCG_PCR_EVENT) {
+ .EventSize = desc_len,
+ .PCRIndex = pcrindex,
+ .EventType = EV_IPL,
+ };
+ memcpy(tcg_event->Event, description, desc_len);
+
+ return tcg->HashLogExtendEvent(
+ (EFI_TCG_PROTOCOL *) tcg,
+ buffer, buffer_size,
+ TCG_ALG_SHA,
+ tcg_event,
+ &event_number,
+ &event_log_last);
+}
+
+static EFI_STATUS tpm2_measure_to_pcr_and_tagged_event_log(
+ EFI_TCG2_PROTOCOL *tcg,
+ uint32_t pcrindex,
+ EFI_PHYSICAL_ADDRESS buffer,
+ uint64_t buffer_size,
+ uint32_t event_id,
+ const char16_t *description) {
+
+ _cleanup_free_ struct event {
+ EFI_TCG2_EVENT tcg_event;
+ EFI_TCG2_TAGGED_EVENT tcg_tagged_event;
+ } _packed_ *event = NULL;
+ size_t desc_len, event_size;
+
+ assert(tcg);
+ assert(description);
+
+ desc_len = strsize16(description);
+ event_size = offsetof(EFI_TCG2_EVENT, Event) + offsetof(EFI_TCG2_TAGGED_EVENT, Event) + desc_len;
+
+ event = xmalloc(event_size);
+ *event = (struct event) {
+ .tcg_event = (EFI_TCG2_EVENT) {
+ .Size = event_size,
+ .Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER),
+ .Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION,
+ .Header.PCRIndex = pcrindex,
+ .Header.EventType = EV_EVENT_TAG,
+ },
+ .tcg_tagged_event = {
+ .EventId = event_id,
+ .EventSize = desc_len,
+ },
+ };
+ memcpy(event->tcg_tagged_event.Event, description, desc_len);
+
+ return tcg->HashLogExtendEvent(
+ tcg,
+ 0,
+ buffer, buffer_size,
+ &event->tcg_event);
+}
+
+static EFI_STATUS tpm2_measure_to_pcr_and_event_log(
+ EFI_TCG2_PROTOCOL *tcg,
+ uint32_t pcrindex,
+ EFI_PHYSICAL_ADDRESS buffer,
+ uint64_t buffer_size,
+ const char16_t *description) {
+
+ _cleanup_free_ EFI_TCG2_EVENT *tcg_event = NULL;
+ size_t desc_len;
+
+ assert(tcg);
+ assert(description);
+
+ /* NB: We currently record everything as EV_IPL. Which sucks, because it makes it hard to
+ * recognize from the event log which of the events are ours. Measurement logs are kinda API hence
+ * this is hard to change for existing, established events. But for future additions, let's use
+ * EV_EVENT_TAG instead, with a tag of our choosing that makes clear what precisely we are measuring
+ * here. */
+
+ desc_len = strsize16(description);
+ tcg_event = xmalloc(offsetof(EFI_TCG2_EVENT, Event) + desc_len);
+ *tcg_event = (EFI_TCG2_EVENT) {
+ .Size = offsetof(EFI_TCG2_EVENT, Event) + desc_len,
+ .Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER),
+ .Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION,
+ .Header.PCRIndex = pcrindex,
+ .Header.EventType = EV_IPL,
+ };
+
+ memcpy(tcg_event->Event, description, desc_len);
+
+ return tcg->HashLogExtendEvent(
+ tcg,
+ 0,
+ buffer, buffer_size,
+ tcg_event);
+}
+
+static EFI_TCG_PROTOCOL *tcg1_interface_check(void) {
+ EFI_PHYSICAL_ADDRESS event_log_location, event_log_last_entry;
+ EFI_TCG_BOOT_SERVICE_CAPABILITY capability = {
+ .Size = sizeof(capability),
+ };
+ EFI_STATUS err;
+ uint32_t features;
+ EFI_TCG_PROTOCOL *tcg;
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_TCG_PROTOCOL), NULL, (void **) &tcg);
+ if (err != EFI_SUCCESS)
+ return NULL;
+
+ err = tcg->StatusCheck(
+ tcg,
+ &capability,
+ &features,
+ &event_log_location,
+ &event_log_last_entry);
+ if (err != EFI_SUCCESS)
+ return NULL;
+
+ if (capability.TPMDeactivatedFlag)
+ return NULL;
+
+ if (!capability.TPMPresentFlag)
+ return NULL;
+
+ return tcg;
+}
+
+static EFI_TCG2_PROTOCOL *tcg2_interface_check(void) {
+ EFI_TCG2_BOOT_SERVICE_CAPABILITY capability = {
+ .Size = sizeof(capability),
+ };
+ EFI_STATUS err;
+ EFI_TCG2_PROTOCOL *tcg;
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_TCG2_PROTOCOL), NULL, (void **) &tcg);
+ if (err != EFI_SUCCESS)
+ return NULL;
+
+ err = tcg->GetCapability(tcg, &capability);
+ if (err != EFI_SUCCESS)
+ return NULL;
+
+ if (capability.StructureVersion.Major == 1 &&
+ capability.StructureVersion.Minor == 0) {
+ EFI_TCG_BOOT_SERVICE_CAPABILITY *caps_1_0 =
+ (EFI_TCG_BOOT_SERVICE_CAPABILITY*) &capability;
+ if (caps_1_0->TPMPresentFlag)
+ return tcg;
+ }
+
+ if (!capability.TPMPresentFlag)
+ return NULL;
+
+ return tcg;
+}
+
+bool tpm_present(void) {
+ return tcg2_interface_check() || tcg1_interface_check();
+}
+
+EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) {
+ EFI_TCG2_PROTOCOL *tpm2;
+ EFI_STATUS err;
+
+ assert(description || pcrindex == UINT32_MAX);
+
+ /* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured
+ * something, or false if measurement was turned off. */
+
+ if (pcrindex == UINT32_MAX) { /* PCR disabled? */
+ if (ret_measured)
+ *ret_measured = false;
+
+ return EFI_SUCCESS;
+ }
+
+ tpm2 = tcg2_interface_check();
+ if (tpm2)
+ err = tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description);
+ else {
+ EFI_TCG_PROTOCOL *tpm1;
+
+ tpm1 = tcg1_interface_check();
+ if (tpm1)
+ err = tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description);
+ else {
+ /* No active TPM found, so don't return an error */
+
+ if (ret_measured)
+ *ret_measured = false;
+
+ return EFI_SUCCESS;
+ }
+ }
+
+ if (err == EFI_SUCCESS && ret_measured)
+ *ret_measured = true;
+
+ return err;
+}
+
+EFI_STATUS tpm_log_tagged_event(
+ uint32_t pcrindex,
+ EFI_PHYSICAL_ADDRESS buffer,
+ size_t buffer_size,
+ uint32_t event_id,
+ const char16_t *description,
+ bool *ret_measured) {
+
+ EFI_TCG2_PROTOCOL *tpm2;
+ EFI_STATUS err;
+
+ assert(description || pcrindex == UINT32_MAX);
+ assert(event_id > 0);
+
+ /* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured
+ * something, or false if measurement was turned off. */
+
+ tpm2 = tcg2_interface_check();
+ if (!tpm2 || pcrindex == UINT32_MAX) { /* PCR disabled? */
+ if (ret_measured)
+ *ret_measured = false;
+
+ return EFI_SUCCESS;
+ }
+
+ err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description);
+ if (err == EFI_SUCCESS && ret_measured)
+ *ret_measured = true;
+
+ return err;
+}
+
+EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured) {
+ _cleanup_free_ char16_t *c = NULL;
+
+ if (description)
+ c = xstr8_to_16(description);
+
+ return tpm_log_event(pcrindex, buffer, buffer_size, c, ret_measured);
+}
+
+EFI_STATUS tpm_log_load_options(const char16_t *load_options, bool *ret_measured) {
+ bool measured = false;
+ EFI_STATUS err;
+
+ /* Measures a load options string into the TPM2, i.e. the kernel command line */
+
+ err = tpm_log_event(
+ TPM2_PCR_KERNEL_CONFIG,
+ POINTER_TO_PHYSICAL_ADDRESS(load_options),
+ strsize16(load_options),
+ load_options,
+ &measured);
+ if (err != EFI_SUCCESS)
+ return log_error_status(
+ err,
+ "Unable to add load options (i.e. kernel command) line measurement to PCR %i: %m",
+ TPM2_PCR_KERNEL_CONFIG);
+
+ if (ret_measured)
+ *ret_measured = measured;
+
+ return EFI_SUCCESS;
+}
+
+#endif
diff --git a/src/boot/efi/measure.h b/src/boot/efi/measure.h
new file mode 100644
index 0000000..c3c4e0a
--- /dev/null
+++ b/src/boot/efi/measure.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#if ENABLE_TPM
+
+bool tpm_present(void);
+EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured);
+EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured);
+EFI_STATUS tpm_log_tagged_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, uint32_t event_id, const char16_t *description, bool *ret_measured);
+EFI_STATUS tpm_log_load_options(const char16_t *cmdline, bool *ret_measured);
+
+#else
+
+static inline bool tpm_present(void) {
+ return false;
+}
+
+static inline EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) {
+ if (ret_measured)
+ *ret_measured = false;
+ return EFI_SUCCESS;
+}
+
+static inline EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured) {
+ if (ret_measured)
+ *ret_measured = false;
+ return EFI_SUCCESS;
+}
+
+static inline EFI_STATUS tpm_log_tagged_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, uint32_t event_id, const char16_t *description, bool *ret_measured) {
+ if (ret_measured)
+ *ret_measured = false;
+ return EFI_SUCCESS;
+}
+
+static inline EFI_STATUS tpm_log_load_options(const char16_t *cmdline, bool *ret_measured) {
+ if (ret_measured)
+ *ret_measured = false;
+ return EFI_SUCCESS;
+}
+
+#endif
diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build
new file mode 100644
index 0000000..c95132e
--- /dev/null
+++ b/src/boot/efi/meson.build
@@ -0,0 +1,409 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+efi_config_h_dir = meson.current_build_dir()
+efi_addon = ''
+
+libefitest = static_library(
+ 'efitest',
+ files(
+ 'bcd.c',
+ 'efi-string.c',
+ ),
+ build_by_default : false,
+ include_directories : [
+ basic_includes,
+ include_directories('.'),
+ ],
+ dependencies : userspace)
+
+efitest_base = {
+ 'link_with' : [
+ libefitest,
+ libshared,
+ ],
+}
+efi_test_template = test_template + efitest_base
+efi_fuzz_template = fuzz_template + efitest_base
+
+executables += [
+ efi_test_template + {
+ 'sources' : files('test-bcd.c'),
+ 'dependencies' : libzstd,
+ 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_ZSTD'],
+ },
+ efi_test_template + {
+ 'sources' : files('test-efi-string.c'),
+ 'conditions' : ['ENABLE_BOOTLOADER'],
+ },
+ efi_fuzz_template + {
+ 'sources' : files('fuzz-bcd.c'),
+ },
+ efi_fuzz_template + {
+ 'sources' : files('fuzz-efi-string.c'),
+ },
+ efi_fuzz_template + {
+ 'sources' : files('fuzz-efi-osrel.c'),
+ },
+ efi_fuzz_template + {
+ 'sources' : files('fuzz-efi-printf.c'),
+ },
+]
+
+if conf.get('ENABLE_BOOTLOADER') != 1
+ subdir_done()
+endif
+
+efi_conf = configuration_data()
+efi_conf.set10('ENABLE_TPM', get_option('tpm'))
+
+foreach ctype : ['color-normal', 'color-entry', 'color-highlight', 'color-edit']
+ c = get_option('efi-' + ctype).split(',')
+ efi_conf.set(ctype.underscorify().to_upper(), 'EFI_TEXT_ATTR(@0@, @1@)'.format(
+ 'EFI_' + c[0].strip().underscorify().to_upper(),
+ 'EFI_' + c[1].strip().underscorify().to_upper()))
+endforeach
+
+if meson.is_cross_build() and get_option('sbat-distro') == 'auto'
+ warning('Auto detection of SBAT information not supported when cross-building, disabling SBAT.')
+elif get_option('sbat-distro') != ''
+ efi_conf.set_quoted('SBAT_PROJECT', meson.project_name())
+ efi_conf.set_quoted('PROJECT_VERSION', meson.project_version())
+ efi_conf.set('PROJECT_URL', conf.get('PROJECT_URL'))
+ if get_option('sbat-distro-generation') < 1
+ error('SBAT Distro Generation must be a positive integer')
+ endif
+ efi_conf.set('SBAT_DISTRO_GENERATION', get_option('sbat-distro-generation'))
+ foreach sbatvar : [['sbat-distro', 'ID'],
+ ['sbat-distro-summary', 'NAME'],
+ ['sbat-distro-url', 'BUG_REPORT_URL']]
+ value = get_option(sbatvar[0])
+ if (value == '' or value == 'auto') and not meson.is_cross_build()
+ cmd = 'if [ -e /etc/os-release ]; then . /etc/os-release; else . /usr/lib/os-release; fi; echo $@0@'.format(sbatvar[1])
+ value = run_command(sh, '-c', cmd, check: true).stdout().strip()
+ endif
+ if value == ''
+ error('Required @0@ option not set and autodetection failed'.format(sbatvar[0]))
+ endif
+ efi_conf.set_quoted(sbatvar[0].underscorify().to_upper(), value)
+ endforeach
+
+ pkgname = get_option('sbat-distro-pkgname')
+ if pkgname == ''
+ pkgname = meson.project_name()
+ endif
+ efi_conf.set_quoted('SBAT_DISTRO_PKGNAME', pkgname)
+
+ pkgver = get_option('sbat-distro-version')
+ if pkgver == ''
+ # This is determined during build, not configuration, so we can't display it yet.
+ efi_conf.set('SBAT_DISTRO_VERSION', 'GIT_VERSION')
+ else
+ efi_conf.set_quoted('SBAT_DISTRO_VERSION', pkgver)
+ endif
+endif
+
+summary({'UEFI architectures' : efi_arch + (efi_arch_alt == '' ? '' : ', ' + efi_arch_alt)},
+ section : 'UEFI')
+
+if efi_conf.get('SBAT_DISTRO', '') != ''
+ summary({
+ 'SBAT distro': efi_conf.get('SBAT_DISTRO'),
+ 'SBAT distro generation': efi_conf.get('SBAT_DISTRO_GENERATION'),
+ 'SBAT distro version': efi_conf.get('SBAT_DISTRO_VERSION'),
+ 'SBAT distro summary': efi_conf.get('SBAT_DISTRO_SUMMARY'),
+ 'SBAT distro URL': efi_conf.get('SBAT_DISTRO_URL')},
+ section : 'UEFI')
+endif
+
+configure_file(
+ output : 'efi_config.h',
+ configuration : efi_conf)
+
+############################################################
+
+efi_includes = [
+ build_dir_include,
+ fundamental_include,
+ include_directories('.'),
+]
+
+efi_c_args = [
+ '-DSD_BOOT=1',
+ '-ffreestanding',
+ '-fno-strict-aliasing',
+ '-fshort-wchar',
+ '-include', 'efi_config.h',
+]
+
+efi_c_args += cc.get_supported_arguments(
+ '-fwide-exec-charset=UCS2',
+ # gcc docs says this is required for ms_abi to work correctly.
+ '-maccumulate-outgoing-args',
+ '-mstack-protector-guard=global',
+)
+
+# Debug information has little value in release builds as no normal human being knows
+# how to attach a debugger to EFI binaries running on real hardware. Anyone who does
+# certainly has the means to do their own dev build.
+if get_option('mode') == 'developer' and get_option('debug')
+ efi_c_args += '-DEFI_DEBUG'
+endif
+
+efi_c_ld_args = [
+ '-lgcc',
+ '-nostdlib',
+ '-static-pie',
+ '-Wl,--entry=efi_main',
+ '-Wl,--fatal-warnings',
+
+ # These flags should be passed by -static-pie, but for whatever reason the flag translation
+ # is not enabled on all architectures. Not passing `-static` would just allow the linker to
+ # use dynamic libraries, (which we can't/don't use anyway). But if `-pie` is missing and the
+ # gcc build does not default to `-pie` we get a regular (no-pie) binary that will be
+ # rightfully rejected by elf2efi. Note that meson also passes `-pie` to the linker driver,
+ # but it is overridden by our `-static-pie`. We also need to pass these directly to the
+ # linker as `-static`+`-pie` seem to get translated differently.
+ '-Wl,-static,-pie,--no-dynamic-linker,-z,text',
+
+ # EFI has 4KiB pages.
+ '-z', 'common-page-size=4096',
+ '-z', 'max-page-size=4096',
+
+ '-z', 'noexecstack',
+ '-z', 'relro',
+ '-z', 'separate-code',
+]
+
+efi_c_ld_args += cc.get_supported_link_arguments(
+ # binutils >= 2.38
+ '-Wl,-z,nopack-relative-relocs',
+)
+
+# efi_c_args is explicitly passed to targets so that they can override distro-provided flags
+# that should not be used for EFI binaries.
+efi_disabled_c_args = cc.get_supported_arguments(
+ '-fcf-protection=none',
+ '-fno-asynchronous-unwind-tables',
+ '-fno-exceptions',
+ '-fno-unwind-tables',
+)
+efi_override_options = [
+ 'b_coverage=false',
+ 'b_pgo=off',
+]
+
+if get_option('b_sanitize') == 'undefined'
+ efi_disabled_c_args += cc.get_supported_arguments('-fno-sanitize-link-runtime')
+else
+ efi_disabled_c_args += cc.get_supported_arguments('-fno-sanitize=all')
+ efi_override_options += 'b_sanitize=none'
+endif
+
+efi_c_args += efi_disabled_c_args
+efi_c_ld_args += efi_disabled_c_args
+
+if cc.get_id() == 'clang'
+ # clang is too picky sometimes.
+ efi_c_args += '-Wno-unused-command-line-argument'
+ efi_c_ld_args += '-Wno-unused-command-line-argument'
+endif
+
+efi_arch_c_args = {
+ 'aarch64' : ['-mgeneral-regs-only'],
+ 'arm' : ['-mgeneral-regs-only'],
+ # Pass -m64/32 explicitly to make building on x32 work.
+ 'x86_64' : ['-m64', '-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'],
+ 'x86' : ['-m32', '-march=i686', '-mgeneral-regs-only', '-malign-double'],
+}
+efi_arch_c_ld_args = {
+ # libgcc is not compiled with -fshort-wchar, but it does not use it anyways,
+ # so it's fine to link against it.
+ 'arm' : cc.get_supported_link_arguments('-Wl,--no-wchar-size-warning'),
+ 'x86_64' : ['-m64'],
+ 'x86' : ['-m32'],
+}
+
+linker_sanity_code = 'void a(void) {}; void _start(void) { a(); }'
+linker_sanity_args = ['-nostdlib', '-Wl,--fatal-warnings']
+if not cc.links(linker_sanity_code,
+ name : 'linker supports -static-pie',
+ args : [linker_sanity_args, '-static-pie'])
+ error('Linker does not support -static-pie.')
+endif
+
+# https://github.com/llvm/llvm-project/issues/67152
+if not cc.links(linker_sanity_code,
+ name : 'linker supports LTO with -nostdlib',
+ args : [linker_sanity_args, '-flto'])
+ efi_c_args += '-fno-lto'
+ efi_c_ld_args += '-fno-lto'
+endif
+
+# https://github.com/llvm/llvm-project/issues/61101
+if efi_cpu_family_alt == 'x86' and not cc.links(linker_sanity_code,
+ name : 'linker supports LTO with -nostdlib (x86)',
+ args : [linker_sanity_args, '-flto', '-m32'])
+ efi_arch_c_args += { 'x86' : efi_arch_c_args['x86'] + '-fno-lto' }
+ efi_arch_c_ld_args += { 'x86' : efi_arch_c_ld_args['x86'] + '-fno-lto' }
+endif
+
+############################################################
+
+libefi_sources = files(
+ 'console.c',
+ 'device-path-util.c',
+ 'devicetree.c',
+ 'drivers.c',
+ 'efi-string.c',
+ 'graphics.c',
+ 'initrd.c',
+ 'log.c',
+ 'measure.c',
+ 'part-discovery.c',
+ 'pe.c',
+ 'random-seed.c',
+ 'secure-boot.c',
+ 'shim.c',
+ 'ticks.c',
+ 'util.c',
+ 'vmm.c',
+)
+
+systemd_boot_sources = files(
+ 'boot.c',
+)
+
+stub_sources = files(
+ 'cpio.c',
+ 'linux.c',
+ 'splash.c',
+ 'stub.c',
+)
+
+addon_sources = files(
+ 'addon.c',
+)
+
+if get_option('b_sanitize') == 'undefined'
+ libefi_sources += files('ubsan.c')
+endif
+
+if host_machine.cpu_family() in ['x86', 'x86_64']
+ stub_sources += files('linux_x86.c')
+endif
+
+# BCD parser only makes sense on arches that Windows supports.
+if host_machine.cpu_family() in ['aarch64', 'arm', 'x86_64', 'x86']
+ systemd_boot_sources += files('bcd.c')
+endif
+
+boot_targets = []
+efi_elf_binaries = []
+efi_archspecs = [
+ {
+ 'arch' : efi_arch,
+ 'c_args' : [
+ efi_c_args,
+ '-DEFI_MACHINE_TYPE_NAME="' + efi_arch + '"',
+ efi_arch_c_args.get(host_machine.cpu_family(), []),
+ ],
+ 'link_args' : [
+ efi_c_ld_args,
+ efi_arch_c_ld_args.get(host_machine.cpu_family(), []),
+ ],
+ },
+]
+if efi_arch_alt != ''
+ efi_archspecs += {
+ 'arch' : efi_arch_alt,
+ 'c_args' : [
+ efi_c_args,
+ '-DEFI_MACHINE_TYPE_NAME="' + efi_arch_alt + '"',
+ efi_arch_c_args.get(efi_cpu_family_alt, []),
+ ],
+ 'link_args' : [
+ efi_c_ld_args,
+ efi_arch_c_ld_args.get(efi_cpu_family_alt, []),
+ ],
+ }
+endif
+
+foreach archspec : efi_archspecs
+ libefi = static_library(
+ 'efi' + archspec['arch'],
+ fundamental_sources,
+ libefi_sources,
+ version_h,
+ include_directories : efi_includes,
+ c_args : archspec['c_args'],
+ gnu_symbol_visibility : 'hidden',
+ override_options : efi_override_options,
+ pic : true)
+
+ kwargs = {
+ 'include_directories' : efi_includes,
+ 'c_args' : archspec['c_args'],
+ 'link_args' : archspec['link_args'],
+ 'gnu_symbol_visibility' : 'hidden',
+ 'override_options' : efi_override_options,
+ 'pie' : true,
+ }
+
+ efi_elf_binaries += executable(
+ 'systemd-boot' + archspec['arch'],
+ sources : [systemd_boot_sources, version_h],
+ link_with : libefi,
+ name_suffix : 'elf',
+ kwargs : kwargs)
+
+ efi_elf_binaries += executable(
+ 'linux' + archspec['arch'],
+ sources : [stub_sources, version_h],
+ link_with : libefi,
+ name_suffix : 'elf.stub',
+ kwargs : kwargs)
+
+ efi_elf_binaries += executable(
+ 'addon' + archspec['arch'],
+ sources : [addon_sources, version_h],
+ name_suffix : 'elf.stub',
+ kwargs : kwargs)
+endforeach
+
+foreach efi_elf_binary : efi_elf_binaries
+ name = efi_elf_binary.name()
+ name += name.startswith('systemd-boot') ? '.efi' : '.efi.stub'
+ # For the addon, given it's empty, we need to explicitly reserve space in the header to account for
+ # the sections that ukify will add.
+ minimum_sections = name.endswith('.stub') ? '15' : '0'
+ exe = custom_target(
+ name,
+ output : name,
+ input : efi_elf_binary,
+ install : true,
+ install_dir : bootlibdir,
+ install_tag : 'systemd-boot',
+ command : [
+ elf2efi_py,
+ '--version-major=' + meson.project_version(),
+ '--version-minor=0',
+ '--efi-major=1',
+ '--efi-minor=1',
+ '--subsystem=10',
+ '--minimum-sections=' + minimum_sections,
+ '--copy-sections=.sbat,.sdmagic,.osrel',
+ '@INPUT@',
+ '@OUTPUT@',
+ ])
+ boot_targets += exe
+ if name.startswith('linux')
+ boot_stubs += exe
+ endif
+
+ # This is supposed to match exactly one time
+ if name == 'addon@0@.efi.stub'.format(efi_arch)
+ efi_addon = exe.full_path()
+ endif
+endforeach
+
+alias_target('systemd-boot', boot_targets)
diff --git a/src/boot/efi/part-discovery.c b/src/boot/efi/part-discovery.c
new file mode 100644
index 0000000..f5b1573
--- /dev/null
+++ b/src/boot/efi/part-discovery.c
@@ -0,0 +1,298 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "device-path-util.h"
+#include "part-discovery.h"
+#include "proto/block-io.h"
+#include "proto/device-path.h"
+#include "util.h"
+
+typedef struct {
+ EFI_GUID PartitionTypeGUID;
+ EFI_GUID UniquePartitionGUID;
+ EFI_LBA StartingLBA;
+ EFI_LBA EndingLBA;
+ uint64_t Attributes;
+ char16_t PartitionName[36];
+} EFI_PARTITION_ENTRY;
+
+typedef struct {
+ EFI_TABLE_HEADER Header;
+ EFI_LBA MyLBA;
+ EFI_LBA AlternateLBA;
+ EFI_LBA FirstUsableLBA;
+ EFI_LBA LastUsableLBA;
+ EFI_GUID DiskGUID;
+ EFI_LBA PartitionEntryLBA;
+ uint32_t NumberOfPartitionEntries;
+ uint32_t SizeOfPartitionEntry;
+ uint32_t PartitionEntryArrayCRC32;
+ uint8_t _pad[420];
+} _packed_ GptHeader;
+assert_cc(sizeof(GptHeader) == 512);
+
+static bool verify_gpt(/*const*/ GptHeader *h, EFI_LBA lba_expected) {
+ uint32_t crc32, crc32_saved;
+ EFI_STATUS err;
+
+ assert(h);
+
+ /* Some superficial validation of the GPT header */
+ if (memcmp(&h->Header.Signature, "EFI PART", sizeof(h->Header.Signature)) != 0)
+ return false;
+
+ if (h->Header.HeaderSize < 92 || h->Header.HeaderSize > 512)
+ return false;
+
+ if (h->Header.Revision != 0x00010000U)
+ return false;
+
+ /* Calculate CRC check */
+ crc32_saved = h->Header.CRC32;
+ h->Header.CRC32 = 0;
+ err = BS->CalculateCrc32(h, h->Header.HeaderSize, &crc32);
+ h->Header.CRC32 = crc32_saved;
+ if (err != EFI_SUCCESS || crc32 != crc32_saved)
+ return false;
+
+ if (h->MyLBA != lba_expected)
+ return false;
+
+ if ((h->SizeOfPartitionEntry % sizeof(EFI_PARTITION_ENTRY)) != 0)
+ return false;
+
+ if (h->NumberOfPartitionEntries <= 0 || h->NumberOfPartitionEntries > 1024)
+ return false;
+
+ /* overflow check */
+ if (h->SizeOfPartitionEntry > SIZE_MAX / h->NumberOfPartitionEntries)
+ return false;
+
+ return true;
+}
+
+static EFI_STATUS try_gpt(
+ const EFI_GUID *type,
+ EFI_BLOCK_IO_PROTOCOL *block_io,
+ EFI_LBA lba,
+ EFI_LBA *ret_backup_lba, /* May be changed even on error! */
+ HARDDRIVE_DEVICE_PATH *ret_hd) {
+
+ _cleanup_free_ EFI_PARTITION_ENTRY *entries = NULL;
+ GptHeader gpt;
+ EFI_STATUS err;
+ uint32_t crc32;
+ size_t size;
+
+ assert(block_io);
+ assert(ret_hd);
+
+ /* Read the GPT header */
+ err = block_io->ReadBlocks(
+ block_io,
+ block_io->Media->MediaId,
+ lba,
+ sizeof(gpt), &gpt);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Indicate the location of backup LBA even if the rest of the header is corrupt. */
+ if (ret_backup_lba)
+ *ret_backup_lba = gpt.AlternateLBA;
+
+ if (!verify_gpt(&gpt, lba))
+ return EFI_NOT_FOUND;
+
+ /* Now load the GPT entry table */
+ size = ALIGN_TO((size_t) gpt.SizeOfPartitionEntry * (size_t) gpt.NumberOfPartitionEntries, 512);
+ entries = xmalloc(size);
+
+ err = block_io->ReadBlocks(
+ block_io,
+ block_io->Media->MediaId,
+ gpt.PartitionEntryLBA,
+ size, entries);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Calculate CRC of entries array, too */
+ err = BS->CalculateCrc32(entries, size, &crc32);
+ if (err != EFI_SUCCESS || crc32 != gpt.PartitionEntryArrayCRC32)
+ return EFI_CRC_ERROR;
+
+ /* Now we can finally look for xbootloader partitions. */
+ for (size_t i = 0; i < gpt.NumberOfPartitionEntries; i++) {
+ EFI_PARTITION_ENTRY *entry =
+ (EFI_PARTITION_ENTRY *) ((uint8_t *) entries + gpt.SizeOfPartitionEntry * i);
+
+ if (!efi_guid_equal(&entry->PartitionTypeGUID, type))
+ continue;
+
+ if (entry->EndingLBA < entry->StartingLBA) /* Bogus? */
+ continue;
+
+ *ret_hd = (HARDDRIVE_DEVICE_PATH) {
+ .Header = {
+ .Type = MEDIA_DEVICE_PATH,
+ .SubType = MEDIA_HARDDRIVE_DP,
+ .Length = sizeof(HARDDRIVE_DEVICE_PATH),
+ },
+ .PartitionNumber = i + 1,
+ .PartitionStart = entry->StartingLBA,
+ .PartitionSize = entry->EndingLBA - entry->StartingLBA + 1,
+ .MBRType = MBR_TYPE_EFI_PARTITION_TABLE_HEADER,
+ .SignatureType = SIGNATURE_TYPE_GUID,
+ };
+ memcpy(ret_hd->Signature, &entry->UniquePartitionGUID, sizeof(ret_hd->Signature));
+
+ return EFI_SUCCESS;
+ }
+
+ /* This GPT was fully valid, but we didn't find what we are looking for. This
+ * means there's no reason to check the second copy of the GPT header */
+ return EFI_NOT_FOUND;
+}
+
+static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVICE_PATH **ret_device_path) {
+ EFI_STATUS err;
+
+ assert(device);
+ assert(ret_device_path);
+
+ EFI_DEVICE_PATH *partition_path;
+ err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &partition_path);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Find the (last) partition node itself. */
+ EFI_DEVICE_PATH *part_node = NULL;
+ for (EFI_DEVICE_PATH *node = partition_path; !device_path_is_end(node);
+ node = device_path_next_node(node)) {
+ if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_HARDDRIVE_DP)
+ continue;
+
+ part_node = node;
+ }
+
+ if (!part_node)
+ return EFI_NOT_FOUND;
+
+ /* Chop off the partition part, leaving us with the full path to the disk itself. */
+ _cleanup_free_ EFI_DEVICE_PATH *disk_path = NULL;
+ EFI_DEVICE_PATH *p = disk_path = device_path_replace_node(partition_path, part_node, NULL);
+
+ EFI_HANDLE disk_handle;
+ EFI_BLOCK_IO_PROTOCOL *block_io;
+ err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &p, &disk_handle);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* The drivers for other partitions on this drive may not be initialized on fastboot firmware, so we
+ * have to ask the firmware to do just that. */
+ (void) BS->ConnectController(disk_handle, NULL, NULL, true);
+
+ err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Filter out some block devices early. (We only care about block devices that aren't
+ * partitions themselves — we look for GPT partition tables to parse after all —, and only
+ * those which contain a medium and have at least 2 blocks.) */
+ if (block_io->Media->LogicalPartition ||
+ !block_io->Media->MediaPresent ||
+ block_io->Media->LastBlock <= 1)
+ return EFI_NOT_FOUND;
+
+ /* Try several copies of the GPT header, in case one is corrupted */
+ EFI_LBA backup_lba = 0;
+ for (size_t nr = 0; nr < 3; nr++) {
+ EFI_LBA lba;
+
+ /* Read the first copy at LBA 1 and then try the backup GPT header pointed
+ * to by the first header if that one was corrupted. As a last resort,
+ * try the very last LBA of this block device. */
+ if (nr == 0)
+ lba = 1;
+ else if (nr == 1 && backup_lba != 0)
+ lba = backup_lba;
+ else if (nr == 2 && backup_lba != block_io->Media->LastBlock)
+ lba = block_io->Media->LastBlock;
+ else
+ continue;
+
+ HARDDRIVE_DEVICE_PATH hd;
+ err = try_gpt(type, block_io, lba,
+ nr == 0 ? &backup_lba : NULL, /* Only get backup LBA location from first GPT header. */
+ &hd);
+ if (err != EFI_SUCCESS) {
+ /* GPT was valid but no XBOOT loader partition found. */
+ if (err == EFI_NOT_FOUND)
+ break;
+ /* Bad GPT, try next one. */
+ continue;
+ }
+
+ /* Patch in the data we found */
+ *ret_device_path = device_path_replace_node(partition_path, part_node, (EFI_DEVICE_PATH *) &hd);
+ return EFI_SUCCESS;
+ }
+
+ /* No xbootloader partition found */
+ return EFI_NOT_FOUND;
+}
+
+EFI_STATUS partition_open(const EFI_GUID *type, EFI_HANDLE *device, EFI_HANDLE *ret_device,
+ EFI_FILE **ret_root_dir) {
+ _cleanup_free_ EFI_DEVICE_PATH *partition_path = NULL;
+ EFI_HANDLE new_device;
+ EFI_FILE *root_dir;
+ EFI_STATUS err;
+
+ assert(type);
+ assert(device);
+ assert(ret_root_dir);
+
+ err = find_device(type, device, &partition_path);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ EFI_DEVICE_PATH *dp = partition_path;
+ err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &dp, &new_device);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = open_volume(new_device, &root_dir);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (ret_device)
+ *ret_device = new_device;
+ *ret_root_dir = root_dir;
+ return EFI_SUCCESS;
+}
+
+char16_t *disk_get_part_uuid(EFI_HANDLE *handle) {
+ EFI_STATUS err;
+ EFI_DEVICE_PATH *dp;
+
+ /* export the device path this image is started from */
+
+ if (!handle)
+ return NULL;
+
+ err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp);
+ if (err != EFI_SUCCESS)
+ return NULL;
+
+ for (; !device_path_is_end(dp); dp = device_path_next_node(dp)) {
+ if (dp->Type != MEDIA_DEVICE_PATH || dp->SubType != MEDIA_HARDDRIVE_DP)
+ continue;
+
+ HARDDRIVE_DEVICE_PATH *hd = (HARDDRIVE_DEVICE_PATH *) dp;
+ if (hd->SignatureType != SIGNATURE_TYPE_GUID)
+ continue;
+
+ return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(hd->SignatureGuid));
+ }
+
+ return NULL;
+}
diff --git a/src/boot/efi/part-discovery.h b/src/boot/efi/part-discovery.h
new file mode 100644
index 0000000..bbc87ff
--- /dev/null
+++ b/src/boot/efi/part-discovery.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define XBOOTLDR_GUID \
+ { 0xbc13c2ff, 0x59e6, 0x4262, { 0xa3, 0x52, 0xb2, 0x75, 0xfd, 0x6f, 0x71, 0x72 } }
+#define ESP_GUID \
+ { 0xc12a7328, 0xf81f, 0x11d2, { 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b } }
+
+EFI_STATUS partition_open(const EFI_GUID *type, EFI_HANDLE *device, EFI_HANDLE *ret_device, EFI_FILE **ret_root_dir);
+char16_t *disk_get_part_uuid(EFI_HANDLE *handle);
diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c
new file mode 100644
index 0000000..829266b
--- /dev/null
+++ b/src/boot/efi/pe.c
@@ -0,0 +1,332 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "pe.h"
+#include "util.h"
+
+#define DOS_FILE_MAGIC "MZ"
+#define PE_FILE_MAGIC "PE\0\0"
+#define MAX_SECTIONS 96
+
+#if defined(__i386__)
+# define TARGET_MACHINE_TYPE 0x014CU
+# define TARGET_MACHINE_TYPE_COMPATIBILITY 0x8664U
+#elif defined(__x86_64__)
+# define TARGET_MACHINE_TYPE 0x8664U
+#elif defined(__aarch64__)
+# define TARGET_MACHINE_TYPE 0xAA64U
+#elif defined(__arm__)
+# define TARGET_MACHINE_TYPE 0x01C2U
+#elif defined(__riscv) && __riscv_xlen == 32
+# define TARGET_MACHINE_TYPE 0x5032U
+#elif defined(__riscv) && __riscv_xlen == 64
+# define TARGET_MACHINE_TYPE 0x5064U
+#elif defined(__loongarch__) && __loongarch_grlen == 32
+# define TARGET_MACHINE_TYPE 0x6232U
+#elif defined(__loongarch__) && __loongarch_grlen == 64
+# define TARGET_MACHINE_TYPE 0x6264U
+#else
+# error Unknown EFI arch
+#endif
+
+#ifndef TARGET_MACHINE_TYPE_COMPATIBILITY
+# define TARGET_MACHINE_TYPE_COMPATIBILITY 0
+#endif
+
+typedef struct DosFileHeader {
+ uint8_t Magic[2];
+ uint16_t LastSize;
+ uint16_t nBlocks;
+ uint16_t nReloc;
+ uint16_t HdrSize;
+ uint16_t MinAlloc;
+ uint16_t MaxAlloc;
+ uint16_t ss;
+ uint16_t sp;
+ uint16_t Checksum;
+ uint16_t ip;
+ uint16_t cs;
+ uint16_t RelocPos;
+ uint16_t nOverlay;
+ uint16_t reserved[4];
+ uint16_t OEMId;
+ uint16_t OEMInfo;
+ uint16_t reserved2[10];
+ uint32_t ExeHeader;
+} _packed_ DosFileHeader;
+
+typedef struct CoffFileHeader {
+ uint16_t Machine;
+ uint16_t NumberOfSections;
+ uint32_t TimeDateStamp;
+ uint32_t PointerToSymbolTable;
+ uint32_t NumberOfSymbols;
+ uint16_t SizeOfOptionalHeader;
+ uint16_t Characteristics;
+} _packed_ CoffFileHeader;
+
+#define OPTHDR32_MAGIC 0x10B /* PE32 OptionalHeader */
+#define OPTHDR64_MAGIC 0x20B /* PE32+ OptionalHeader */
+
+typedef struct PeOptionalHeader {
+ uint16_t Magic;
+ uint8_t LinkerMajor;
+ uint8_t LinkerMinor;
+ uint32_t SizeOfCode;
+ uint32_t SizeOfInitializedData;
+ uint32_t SizeOfUninitializeData;
+ uint32_t AddressOfEntryPoint;
+ uint32_t BaseOfCode;
+ union {
+ struct { /* PE32 */
+ uint32_t BaseOfData;
+ uint32_t ImageBase32;
+ };
+ uint64_t ImageBase64; /* PE32+ */
+ };
+ uint32_t SectionAlignment;
+ uint32_t FileAlignment;
+ uint16_t MajorOperatingSystemVersion;
+ uint16_t MinorOperatingSystemVersion;
+ uint16_t MajorImageVersion;
+ uint16_t MinorImageVersion;
+ uint16_t MajorSubsystemVersion;
+ uint16_t MinorSubsystemVersion;
+ uint32_t Win32VersionValue;
+ uint32_t SizeOfImage;
+ uint32_t SizeOfHeaders;
+ uint32_t CheckSum;
+ uint16_t Subsystem;
+ uint16_t DllCharacteristics;
+ /* fields with different sizes for 32/64 omitted */
+} _packed_ PeOptionalHeader;
+
+typedef struct PeFileHeader {
+ uint8_t Magic[4];
+ CoffFileHeader FileHeader;
+ PeOptionalHeader OptionalHeader;
+} _packed_ PeFileHeader;
+
+typedef struct PeSectionHeader {
+ uint8_t Name[8];
+ uint32_t VirtualSize;
+ uint32_t VirtualAddress;
+ uint32_t SizeOfRawData;
+ uint32_t PointerToRawData;
+ uint32_t PointerToRelocations;
+ uint32_t PointerToLinenumbers;
+ uint16_t NumberOfRelocations;
+ uint16_t NumberOfLinenumbers;
+ uint32_t Characteristics;
+} _packed_ PeSectionHeader;
+
+static bool verify_dos(const DosFileHeader *dos) {
+ assert(dos);
+ return memcmp(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0;
+}
+
+static bool verify_pe(const PeFileHeader *pe, bool allow_compatibility) {
+ assert(pe);
+ return memcmp(pe->Magic, PE_FILE_MAGIC, STRLEN(PE_FILE_MAGIC)) == 0 &&
+ (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);
+}
+
+static size_t section_table_offset(const DosFileHeader *dos, const PeFileHeader *pe) {
+ assert(dos);
+ assert(pe);
+ return dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader;
+}
+
+static void locate_sections(
+ const PeSectionHeader section_table[],
+ size_t n_table,
+ const char * const sections[],
+ size_t *offsets,
+ size_t *sizes,
+ bool in_memory) {
+
+ assert(section_table);
+ assert(sections);
+ assert(offsets);
+ assert(sizes);
+
+ for (size_t i = 0; i < n_table; i++) {
+ const PeSectionHeader *sect = section_table + i;
+
+ for (size_t j = 0; sections[j]; j++) {
+ if (memcmp(sect->Name, sections[j], strlen8(sections[j])) != 0)
+ continue;
+
+ offsets[j] = in_memory ? sect->VirtualAddress : sect->PointerToRawData;
+ sizes[j] = sect->VirtualSize;
+ }
+ }
+}
+
+static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) {
+ size_t addr = 0, size = 0;
+ static const char *sections[] = { ".compat", NULL };
+
+ /* The kernel may provide alternative PE entry points for different PE architectures. This allows
+ * booting a 64-bit kernel on 32-bit EFI that is otherwise running on a 64-bit CPU. The locations of any
+ * such compat entry points are located in a special PE section. */
+
+ locate_sections((const PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)),
+ pe->FileHeader.NumberOfSections,
+ sections,
+ &addr,
+ &size,
+ /*in_memory=*/true);
+
+ if (size == 0)
+ return 0;
+
+ typedef struct {
+ uint8_t type;
+ uint8_t size;
+ uint16_t machine_type;
+ uint32_t entry_point;
+ } _packed_ LinuxPeCompat1;
+
+ while (size >= sizeof(LinuxPeCompat1) && addr % alignof(LinuxPeCompat1) == 0) {
+ LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((uint8_t *) 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_kernel_info(const void *base, uint32_t *ret_compat_address) {
+ assert(base);
+ assert(ret_compat_address);
+
+ const DosFileHeader *dos = (const DosFileHeader *) base;
+ if (!verify_dos(dos))
+ return EFI_LOAD_ERROR;
+
+ const PeFileHeader *pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader);
+ if (!verify_pe(pe, /* allow_compatibility= */ true))
+ return EFI_LOAD_ERROR;
+
+ /* Support for LINUX_INITRD_MEDIA_GUID was added in kernel stub 1.0. */
+ if (pe->OptionalHeader.MajorImageVersion < 1)
+ return EFI_UNSUPPORTED;
+
+ if (pe->FileHeader.Machine == TARGET_MACHINE_TYPE) {
+ *ret_compat_address = 0;
+ return EFI_SUCCESS;
+ }
+
+ uint32_t compat_address = get_compatibility_entry_address(dos, pe);
+ if (compat_address == 0)
+ /* Image type not supported and no compat entry found. */
+ return EFI_UNSUPPORTED;
+
+ *ret_compat_address = compat_address;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS pe_memory_locate_sections(const void *base, const char * const sections[], size_t *addrs, size_t *sizes) {
+ const DosFileHeader *dos;
+ const PeFileHeader *pe;
+ size_t offset;
+
+ assert(base);
+ assert(sections);
+ assert(addrs);
+ assert(sizes);
+
+ dos = (const DosFileHeader *) base;
+ if (!verify_dos(dos))
+ return EFI_LOAD_ERROR;
+
+ pe = (const PeFileHeader *) ((uint8_t *) base + dos->ExeHeader);
+ if (!verify_pe(pe, /* allow_compatibility= */ false))
+ return EFI_LOAD_ERROR;
+
+ offset = section_table_offset(dos, pe);
+ locate_sections((PeSectionHeader *) ((uint8_t *) base + offset),
+ pe->FileHeader.NumberOfSections,
+ sections,
+ addrs,
+ sizes,
+ /*in_memory=*/true);
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS pe_file_locate_sections(
+ EFI_FILE *dir,
+ const char16_t *path,
+ const char * const sections[],
+ size_t *offsets,
+ size_t *sizes) {
+ _cleanup_free_ PeSectionHeader *section_table = NULL;
+ _cleanup_(file_closep) EFI_FILE *handle = NULL;
+ DosFileHeader dos;
+ PeFileHeader pe;
+ size_t len, section_table_len;
+ EFI_STATUS err;
+
+ assert(dir);
+ assert(path);
+ assert(sections);
+ assert(offsets);
+ assert(sizes);
+
+ err = dir->Open(dir, &handle, (char16_t *) path, EFI_FILE_MODE_READ, 0ULL);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ len = sizeof(dos);
+ err = handle->Read(handle, &len, &dos);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (len != sizeof(dos) || !verify_dos(&dos))
+ return EFI_LOAD_ERROR;
+
+ err = handle->SetPosition(handle, dos.ExeHeader);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ len = sizeof(pe);
+ err = handle->Read(handle, &len, &pe);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (len != sizeof(pe) || !verify_pe(&pe, /* allow_compatibility= */ false))
+ return EFI_LOAD_ERROR;
+
+ section_table_len = pe.FileHeader.NumberOfSections * sizeof(PeSectionHeader);
+ section_table = xmalloc(section_table_len);
+ if (!section_table)
+ return EFI_OUT_OF_RESOURCES;
+
+ err = handle->SetPosition(handle, section_table_offset(&dos, &pe));
+ if (err != EFI_SUCCESS)
+ return err;
+
+ len = section_table_len;
+ err = handle->Read(handle, &len, section_table);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (len != section_table_len)
+ return EFI_LOAD_ERROR;
+
+ locate_sections(section_table, pe.FileHeader.NumberOfSections,
+ sections, offsets, sizes, /*in_memory=*/false);
+
+ return EFI_SUCCESS;
+}
diff --git a/src/boot/efi/pe.h b/src/boot/efi/pe.h
new file mode 100644
index 0000000..7e2258f
--- /dev/null
+++ b/src/boot/efi/pe.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+EFI_STATUS pe_memory_locate_sections(
+ const void *base,
+ const char * const sections[],
+ size_t *addrs,
+ size_t *sizes);
+
+EFI_STATUS pe_file_locate_sections(
+ EFI_FILE *dir,
+ const char16_t *path,
+ const char * const sections[],
+ size_t *offsets,
+ size_t *sizes);
+
+EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address);
diff --git a/src/boot/efi/proto/block-io.h b/src/boot/efi/proto/block-io.h
new file mode 100644
index 0000000..e977f70
--- /dev/null
+++ b/src/boot/efi/proto/block-io.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_BLOCK_IO_PROTOCOL_GUID \
+ GUID_DEF(0x0964e5b21, 0x6459, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
+
+typedef struct EFI_BLOCK_IO_PROTOCOL EFI_BLOCK_IO_PROTOCOL;
+struct EFI_BLOCK_IO_PROTOCOL {
+ uint64_t Revision;
+ struct {
+ uint32_t MediaId;
+ bool RemovableMedia;
+ bool MediaPresent;
+ bool LogicalPartition;
+ bool ReadOnly;
+ bool WriteCaching;
+ uint32_t BlockSize;
+ uint32_t IoAlign;
+ EFI_LBA LastBlock;
+ EFI_LBA LowestAlignedLba;
+ uint32_t LogicalBlocksPerPhysicalBlock;
+ uint32_t OptimalTransferLengthGranularity;
+ } *Media;
+
+ EFI_STATUS (EFIAPI *Reset)(
+ EFI_BLOCK_IO_PROTOCOL *This,
+ bool ExtendedVerification);
+ EFI_STATUS (EFIAPI *ReadBlocks)(
+ EFI_BLOCK_IO_PROTOCOL *This,
+ uint32_t MediaId,
+ EFI_LBA LBA,
+ size_t BufferSize,
+ void *Buffer);
+ EFI_STATUS (EFIAPI *WriteBlocks)(
+ EFI_BLOCK_IO_PROTOCOL *This,
+ uint32_t MediaId,
+ EFI_LBA LBA,
+ size_t BufferSize,
+ void *Buffer);
+ EFI_STATUS (EFIAPI *FlushBlocks)(EFI_BLOCK_IO_PROTOCOL *This);
+};
diff --git a/src/boot/efi/proto/console-control.h b/src/boot/efi/proto/console-control.h
new file mode 100644
index 0000000..d3a92ea
--- /dev/null
+++ b/src/boot/efi/proto/console-control.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_CONSOLE_CONTROL_PROTOCOL_GUID \
+ GUID_DEF(0xf42f7782, 0x12e, 0x4c12, 0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21)
+
+typedef enum {
+ EfiConsoleControlScreenText,
+ EfiConsoleControlScreenGraphics,
+ EfiConsoleControlScreenMaxValue,
+} EFI_CONSOLE_CONTROL_SCREEN_MODE;
+
+typedef struct EFI_CONSOLE_CONTROL_PROTOCOL EFI_CONSOLE_CONTROL_PROTOCOL;
+struct EFI_CONSOLE_CONTROL_PROTOCOL {
+ EFI_STATUS (EFIAPI *GetMode)(
+ EFI_CONSOLE_CONTROL_PROTOCOL *This,
+ EFI_CONSOLE_CONTROL_SCREEN_MODE *Mode,
+ bool *UgaExists,
+ bool *StdInLocked);
+ EFI_STATUS (EFIAPI *SetMode)(
+ EFI_CONSOLE_CONTROL_PROTOCOL *This,
+ EFI_CONSOLE_CONTROL_SCREEN_MODE Mode);
+ EFI_STATUS(EFIAPI *LockStdIn)(
+ EFI_CONSOLE_CONTROL_PROTOCOL *This,
+ char16_t *Password);
+};
diff --git a/src/boot/efi/proto/device-path.h b/src/boot/efi/proto/device-path.h
new file mode 100644
index 0000000..0fabae1
--- /dev/null
+++ b/src/boot/efi/proto/device-path.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_DEVICE_PATH_PROTOCOL_GUID \
+ GUID_DEF(0x09576e91, 0x6d3f, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
+#define EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID \
+ GUID_DEF(0x8b843e20, 0x8132, 0x4852, 0x90, 0xcc, 0x55, 0x1a, 0x4e, 0x4a, 0x7f, 0x1c)
+#define EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL_GUID \
+ GUID_DEF(0x05c99a21, 0xc70f, 0x4ad2, 0x8a, 0x5f, 0x35, 0xdf, 0x33, 0x43, 0xf5, 0x1e)
+
+/* Device path types. */
+enum {
+ HARDWARE_DEVICE_PATH = 0x01,
+ ACPI_DEVICE_PATH = 0x02,
+ MESSAGING_DEVICE_PATH = 0x03,
+ MEDIA_DEVICE_PATH = 0x04,
+ BBS_DEVICE_PATH = 0x05,
+ END_DEVICE_PATH_TYPE = 0x7f,
+};
+
+/* Device path sub-types. */
+enum {
+ END_INSTANCE_DEVICE_PATH_SUBTYPE = 0x01,
+ END_ENTIRE_DEVICE_PATH_SUBTYPE = 0xff,
+
+ MEDIA_HARDDRIVE_DP = 0x01,
+ MEDIA_VENDOR_DP = 0x03,
+ MEDIA_FILEPATH_DP = 0x04,
+ MEDIA_PIWG_FW_FILE_DP = 0x06,
+ MEDIA_PIWG_FW_VOL_DP = 0x07,
+};
+
+struct _packed_ EFI_DEVICE_PATH_PROTOCOL {
+ uint8_t Type;
+ uint8_t SubType;
+ uint16_t Length;
+};
+
+typedef struct {
+ EFI_DEVICE_PATH Header;
+ EFI_GUID Guid;
+} _packed_ VENDOR_DEVICE_PATH;
+
+#define MBR_TYPE_PCAT 0x01U
+#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02U
+#define NO_DISK_SIGNATURE 0x00U
+#define SIGNATURE_TYPE_MBR 0x01U
+#define SIGNATURE_TYPE_GUID 0x02U
+
+typedef struct {
+ EFI_DEVICE_PATH Header;
+ uint32_t PartitionNumber;
+ uint64_t PartitionStart;
+ uint64_t PartitionSize;
+ union {
+ uint8_t Signature[16];
+ EFI_GUID SignatureGuid;
+ };
+ uint8_t MBRType;
+ uint8_t SignatureType;
+} _packed_ HARDDRIVE_DEVICE_PATH;
+
+typedef struct {
+ EFI_DEVICE_PATH Header;
+ char16_t PathName[];
+} _packed_ FILEPATH_DEVICE_PATH;
+
+typedef struct {
+ char16_t* (EFIAPI *ConvertDeviceNodeToText)(
+ const EFI_DEVICE_PATH *DeviceNode,
+ bool DisplayOnly,
+ bool AllowShortcuts);
+ char16_t* (EFIAPI *ConvertDevicePathToText)(
+ const EFI_DEVICE_PATH *DevicePath,
+ bool DisplayOnly,
+ bool AllowShortcuts);
+} EFI_DEVICE_PATH_TO_TEXT_PROTOCOL;
+
+typedef struct {
+ EFI_DEVICE_PATH* (EFIAPI *ConvertTextToDevicNode)(
+ const char16_t *TextDeviceNode);
+ EFI_DEVICE_PATH* (EFIAPI *ConvertTextToDevicPath)(
+ const char16_t *ConvertTextToDevicPath);
+} EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL;
diff --git a/src/boot/efi/proto/dt-fixup.h b/src/boot/efi/proto/dt-fixup.h
new file mode 100644
index 0000000..6edbef5
--- /dev/null
+++ b/src/boot/efi/proto/dt-fixup.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_DTB_TABLE_GUID \
+ GUID_DEF(0xb1b621d5, 0xf19c, 0x41a5, 0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0)
+#define EFI_DT_FIXUP_PROTOCOL_GUID \
+ GUID_DEF(0xe617d64c, 0xfe08, 0x46da, 0xf4, 0xdc, 0xbb, 0xd5, 0x87, 0x0c, 0x73, 0x00)
+
+#define EFI_DT_FIXUP_PROTOCOL_REVISION 0x00010000
+
+/* Add nodes and update properties */
+#define EFI_DT_APPLY_FIXUPS 0x00000001
+
+/*
+ * Reserve memory according to the /reserved-memory node
+ * and the memory reservation block
+ */
+#define EFI_DT_RESERVE_MEMORY 0x00000002
+
+typedef struct EFI_DT_FIXUP_PROTOCOL EFI_DT_FIXUP_PROTOCOL;
+struct EFI_DT_FIXUP_PROTOCOL {
+ uint64_t Revision;
+ EFI_STATUS (EFIAPI *Fixup)(
+ EFI_DT_FIXUP_PROTOCOL *This,
+ void *Fdt,
+ size_t *BufferSize,
+ uint32_t Flags);
+};
diff --git a/src/boot/efi/proto/file-io.h b/src/boot/efi/proto/file-io.h
new file mode 100644
index 0000000..001ad48
--- /dev/null
+++ b/src/boot/efi/proto/file-io.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID \
+ GUID_DEF(0x0964e5b22, 0x6459, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
+#define EFI_FILE_INFO_ID \
+ GUID_DEF(0x009576e92, 0x6d3f, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
+
+#define EFI_FILE_MODE_READ 0x0000000000000001U
+#define EFI_FILE_MODE_WRITE 0x0000000000000002U
+#define EFI_FILE_MODE_CREATE 0x8000000000000000U
+
+#define EFI_FILE_READ_ONLY 0x01U
+#define EFI_FILE_HIDDEN 0x02U
+#define EFI_FILE_SYSTEM 0x04U
+#define EFI_FILE_RESERVED 0x08U
+#define EFI_FILE_DIRECTORY 0x10U
+#define EFI_FILE_ARCHIVE 0x20U
+#define EFI_FILE_VALID_ATTR 0x37U
+
+typedef struct {
+ uint64_t Size;
+ uint64_t FileSize;
+ uint64_t PhysicalSize;
+ EFI_TIME CreateTime;
+ EFI_TIME LastAccessTime;
+ EFI_TIME ModificationTime;
+ uint64_t Attribute;
+ char16_t FileName[];
+} EFI_FILE_INFO;
+
+/* Some broken firmware violates the EFI spec by still advancing the readdir
+ * position when returning EFI_BUFFER_TOO_SMALL, effectively skipping over any files when
+ * the buffer was too small. Therefore, we always start with a buffer that should handle FAT32
+ * max file name length. */
+#define EFI_FILE_INFO_MIN_SIZE (offsetof(EFI_FILE_INFO, FileName) + 256U * sizeof(char16_t))
+
+typedef struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;
+struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL {
+ uint64_t Revision;
+ EFI_STATUS (EFIAPI *OpenVolume)(
+ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This,
+ EFI_FILE **Root);
+};
+
+struct EFI_FILE_PROTOCOL {
+ uint64_t Revision;
+ EFI_STATUS (EFIAPI *Open)(
+ EFI_FILE *This,
+ EFI_FILE **NewHandle,
+ char16_t *FileName,
+ uint64_t OpenMode,
+ uint64_t Attributes);
+ EFI_STATUS (EFIAPI *Close)(EFI_FILE *This);
+ EFI_STATUS (EFIAPI *Delete)(EFI_FILE *This);
+ EFI_STATUS (EFIAPI *Read)(
+ EFI_FILE *This,
+ size_t *BufferSize,
+ void *Buffer);
+ EFI_STATUS (EFIAPI *Write)(
+ EFI_FILE *This,
+ size_t *BufferSize,
+ void *Buffer);
+ EFI_STATUS (EFIAPI *GetPosition)(EFI_FILE *This, uint64_t *Position);
+ EFI_STATUS (EFIAPI *SetPosition)(EFI_FILE *This, uint64_t Position);
+ EFI_STATUS (EFIAPI *GetInfo)(
+ EFI_FILE *This,
+ EFI_GUID *InformationType,
+ size_t *BufferSize,
+ void *Buffer);
+ EFI_STATUS (EFIAPI *SetInfo)(
+ EFI_FILE *This,
+ EFI_GUID *InformationType,
+ size_t BufferSize,
+ void *Buffer);
+ EFI_STATUS (EFIAPI *Flush)(EFI_FILE *This);
+ void *OpenEx;
+ void *ReadEx;
+ void *WriteEx;
+ void *FlushEx;
+};
diff --git a/src/boot/efi/proto/graphics-output.h b/src/boot/efi/proto/graphics-output.h
new file mode 100644
index 0000000..f49e580
--- /dev/null
+++ b/src/boot/efi/proto/graphics-output.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID \
+ GUID_DEF(0x9042a9de, 0x23dc, 0x4a38, 0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a)
+
+typedef enum {
+ PixelRedGreenBlueReserved8BitPerColor,
+ PixelBlueGreenRedReserved8BitPerColor,
+ PixelBitMask,
+ PixelBltOnly,
+ PixelFormatMax,
+} EFI_GRAPHICS_PIXEL_FORMAT;
+
+typedef enum {
+ EfiBltVideoFill,
+ EfiBltVideoToBltBuffer,
+ EfiBltBufferToVideo,
+ EfiBltVideoToVideo,
+ EfiGraphicsOutputBltOperationMax,
+} EFI_GRAPHICS_OUTPUT_BLT_OPERATION;
+
+typedef struct {
+ uint32_t RedMask;
+ uint32_t GreenMask;
+ uint32_t BlueMask;
+ uint32_t ReservedMask;
+} EFI_PIXEL_BITMASK;
+
+typedef struct {
+ uint8_t Blue;
+ uint8_t Green;
+ uint8_t Red;
+ uint8_t Reserved;
+} EFI_GRAPHICS_OUTPUT_BLT_PIXEL;
+
+typedef struct {
+ uint32_t Version;
+ uint32_t HorizontalResolution;
+ uint32_t VerticalResolution;
+ EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
+ EFI_PIXEL_BITMASK PixelInformation;
+ uint32_t PixelsPerScanLine;
+} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
+
+typedef struct EFI_GRAPHICS_OUTPUT_PROTOCOL EFI_GRAPHICS_OUTPUT_PROTOCOL;
+struct EFI_GRAPHICS_OUTPUT_PROTOCOL {
+ EFI_STATUS (EFIAPI *QueryMode)(
+ EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
+ uint32_t ModeNumber,
+ size_t *SizeOfInfo,
+ EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info);
+ EFI_STATUS(EFIAPI *SetMode)(
+ EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
+ uint32_t ModeNumber);
+ EFI_STATUS (EFIAPI *Blt)(
+ EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer,
+ EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
+ size_t SourceX,
+ size_t SourceY,
+ size_t DestinationX,
+ size_t DestinationY,
+ size_t Width,
+ size_t Height,
+ size_t Delta);
+
+ struct {
+ uint32_t MaxMode;
+ uint32_t Mode;
+ EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
+ size_t SizeOfInfo;
+ EFI_PHYSICAL_ADDRESS FrameBufferBase;
+ size_t FrameBufferSize;
+ } *Mode;
+};
diff --git a/src/boot/efi/proto/load-file.h b/src/boot/efi/proto/load-file.h
new file mode 100644
index 0000000..2e01ce5
--- /dev/null
+++ b/src/boot/efi/proto/load-file.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_LOAD_FILE_PROTOCOL_GUID \
+ GUID_DEF(0x56EC3091, 0x954C, 0x11d2, 0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
+#define EFI_LOAD_FILE2_PROTOCOL_GUID \
+ GUID_DEF(0x4006c0c1, 0xfcb3, 0x403e, 0x99, 0x6d, 0x4a, 0x6c, 0x87, 0x24, 0xe0, 0x6d)
+
+typedef struct EFI_LOAD_FILE_PROTOCOL EFI_LOAD_FILE_PROTOCOL;
+typedef EFI_LOAD_FILE_PROTOCOL EFI_LOAD_FILE2_PROTOCOL;
+
+struct EFI_LOAD_FILE_PROTOCOL {
+ EFI_STATUS (EFIAPI *LoadFile)(
+ EFI_LOAD_FILE_PROTOCOL *This,
+ EFI_DEVICE_PATH *FilePath,
+ bool BootPolicy,
+ size_t *BufferSize,
+ void *Buffer);
+};
diff --git a/src/boot/efi/proto/loaded-image.h b/src/boot/efi/proto/loaded-image.h
new file mode 100644
index 0000000..46371e7
--- /dev/null
+++ b/src/boot/efi/proto/loaded-image.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_LOADED_IMAGE_PROTOCOL_GUID \
+ GUID_DEF(0x5B1B31A1, 0x9562, 0x11d2, 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B)
+#define EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID \
+ GUID_DEF(0xbc62157e, 0x3e33, 0x4fec, 0x99, 0x20, 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf)
+
+typedef EFI_STATUS (EFIAPI *EFI_IMAGE_ENTRY_POINT)(
+ EFI_HANDLE ImageHandle,
+ EFI_SYSTEM_TABLE *SystemTable);
+
+typedef struct {
+ uint32_t Revision;
+ EFI_HANDLE ParentHandle;
+ EFI_SYSTEM_TABLE *SystemTable;
+ EFI_HANDLE DeviceHandle;
+ EFI_DEVICE_PATH *FilePath;
+ void *Reserved;
+ uint32_t LoadOptionsSize;
+ void *LoadOptions;
+ void *ImageBase;
+ uint64_t ImageSize;
+ EFI_MEMORY_TYPE ImageCodeType;
+ EFI_MEMORY_TYPE ImageDataType;
+ EFI_STATUS (EFIAPI *Unload)(EFI_HANDLE ImageHandle);
+} EFI_LOADED_IMAGE_PROTOCOL;
diff --git a/src/boot/efi/proto/rng.h b/src/boot/efi/proto/rng.h
new file mode 100644
index 0000000..8ed1fd4
--- /dev/null
+++ b/src/boot/efi/proto/rng.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_RNG_PROTOCOL_GUID \
+ GUID_DEF(0x3152bca5, 0xeade, 0x433d, 0x86, 0x2e, 0xc0, 0x1c, 0xdc, 0x29, 0x1f, 0x44)
+
+typedef struct EFI_RNG_PROTOCOL EFI_RNG_PROTOCOL;
+struct EFI_RNG_PROTOCOL {
+ EFI_STATUS (EFIAPI *GetInfo)(
+ EFI_RNG_PROTOCOL *This,
+ size_t *RNGAlgorithmListSize,
+ EFI_GUID *RNGAlgorithmList);
+ EFI_STATUS (EFIAPI *GetRNG)(
+ EFI_RNG_PROTOCOL *This,
+ EFI_GUID *RNGAlgorithm,
+ size_t RNGValueLength,
+ uint8_t *RNGValue);
+};
diff --git a/src/boot/efi/proto/security-arch.h b/src/boot/efi/proto/security-arch.h
new file mode 100644
index 0000000..2675c61
--- /dev/null
+++ b/src/boot/efi/proto/security-arch.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_SECURITY_ARCH_PROTOCOL_GUID \
+ GUID_DEF(0xA46423E3, 0x4617, 0x49f1, 0xB9, 0xFF, 0xD1, 0xBF, 0xA9, 0x11, 0x58, 0x39)
+#define EFI_SECURITY2_ARCH_PROTOCOL_GUID \
+ GUID_DEF(0x94ab2f58, 0x1438, 0x4ef1, 0x91, 0x52, 0x18, 0x94, 0x1a, 0x3a, 0x0e, 0x68)
+
+typedef struct EFI_SECURITY_ARCH_PROTOCOL EFI_SECURITY_ARCH_PROTOCOL;
+typedef struct EFI_SECURITY2_ARCH_PROTOCOL EFI_SECURITY2_ARCH_PROTOCOL;
+
+typedef EFI_STATUS (EFIAPI *EFI_SECURITY_FILE_AUTHENTICATION_STATE)(
+ const EFI_SECURITY_ARCH_PROTOCOL *This,
+ uint32_t AuthenticationStatus,
+ const EFI_DEVICE_PATH *File);
+
+typedef EFI_STATUS (EFIAPI *EFI_SECURITY2_FILE_AUTHENTICATION)(
+ const EFI_SECURITY2_ARCH_PROTOCOL *This,
+ const EFI_DEVICE_PATH *DevicePath,
+ void *FileBuffer,
+ size_t FileSize,
+ bool BootPolicy);
+
+struct EFI_SECURITY_ARCH_PROTOCOL {
+ EFI_SECURITY_FILE_AUTHENTICATION_STATE FileAuthenticationState;
+};
+
+struct EFI_SECURITY2_ARCH_PROTOCOL {
+ EFI_SECURITY2_FILE_AUTHENTICATION FileAuthentication;
+};
diff --git a/src/boot/efi/proto/shell-parameters.h b/src/boot/efi/proto/shell-parameters.h
new file mode 100644
index 0000000..8080922
--- /dev/null
+++ b/src/boot/efi/proto/shell-parameters.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_SHELL_PARAMETERS_PROTOCOL_GUID \
+ GUID_DEF(0x752f3136, 0x4e16, 0x4fdc, 0xa2, 0x2a, 0xe5, 0xf4, 0x68, 0x12, 0xf4, 0xca)
+
+typedef struct {
+ char16_t **Argv;
+ size_t Argc;
+ void *StdIn;
+ void *StdOut;
+ void *StdErr;
+} EFI_SHELL_PARAMETERS_PROTOCOL;
diff --git a/src/boot/efi/proto/simple-text-io.h b/src/boot/efi/proto/simple-text-io.h
new file mode 100644
index 0000000..95016d3
--- /dev/null
+++ b/src/boot/efi/proto/simple-text-io.h
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_SIMPLE_TEXT_INPUT_PROTOCOL_GUID \
+ GUID_DEF(0x387477c1, 0x69c7, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
+#define EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID \
+ GUID_DEF(0xdd9e7534, 0x7762, 0x4698, 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa)
+#define EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_GUID \
+ GUID_DEF(0x387477c2, 0x69c7, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b)
+
+#define EFI_SHIFT_STATE_VALID 0x80000000U
+#define EFI_RIGHT_SHIFT_PRESSED 0x00000001U
+#define EFI_LEFT_SHIFT_PRESSED 0x00000002U
+#define EFI_RIGHT_CONTROL_PRESSED 0x00000004U
+#define EFI_LEFT_CONTROL_PRESSED 0x00000008U
+#define EFI_RIGHT_ALT_PRESSED 0x00000010U
+#define EFI_LEFT_ALT_PRESSED 0x00000020U
+#define EFI_RIGHT_LOGO_PRESSED 0x00000040U
+#define EFI_LEFT_LOGO_PRESSED 0x00000080U
+#define EFI_MENU_KEY_PRESSED 0x00000100U
+#define EFI_SYS_REQ_PRESSED 0x00000200U
+
+#define EFI_TOGGLE_STATE_VALID 0x80U
+#define EFI_KEY_STATE_EXPOSED 0x40U
+#define EFI_SCROLL_LOCK_ACTIVE 0x01U
+#define EFI_NUM_LOCK_ACTIVE 0x02U
+#define EFI_CAPS_LOCK_ACTIVE 0x04U
+
+enum {
+ EFI_BLACK = 0x00,
+ EFI_BLUE = 0x01,
+ EFI_GREEN = 0x02,
+ EFI_CYAN = EFI_BLUE | EFI_GREEN,
+ EFI_RED = 0x04,
+ EFI_MAGENTA = EFI_BLUE | EFI_RED,
+ EFI_BROWN = EFI_GREEN | EFI_RED,
+ EFI_LIGHTGRAY = EFI_BLUE | EFI_GREEN | EFI_RED,
+ EFI_BRIGHT = 0x08,
+ EFI_DARKGRAY = EFI_BLACK | EFI_BRIGHT,
+ EFI_LIGHTBLUE = EFI_BLUE | EFI_BRIGHT,
+ EFI_LIGHTGREEN = EFI_GREEN | EFI_BRIGHT,
+ EFI_LIGHTCYAN = EFI_CYAN | EFI_BRIGHT,
+ EFI_LIGHTRED = EFI_RED | EFI_BRIGHT,
+ EFI_LIGHTMAGENTA = EFI_MAGENTA | EFI_BRIGHT,
+ EFI_YELLOW = EFI_BROWN | EFI_BRIGHT,
+ EFI_WHITE = EFI_BLUE | EFI_GREEN | EFI_RED | EFI_BRIGHT,
+};
+
+#define EFI_TEXT_ATTR(fg, bg) ((fg) | ((bg) << 4))
+#define EFI_TEXT_ATTR_SWAP(c) EFI_TEXT_ATTR(((c) & 0xF0U) >> 4, (c) & 0xFU)
+
+enum {
+ SCAN_NULL = 0x000,
+ SCAN_UP = 0x001,
+ SCAN_DOWN = 0x002,
+ SCAN_RIGHT = 0x003,
+ SCAN_LEFT = 0x004,
+ SCAN_HOME = 0x005,
+ SCAN_END = 0x006,
+ SCAN_INSERT = 0x007,
+ SCAN_DELETE = 0x008,
+ SCAN_PAGE_UP = 0x009,
+ SCAN_PAGE_DOWN = 0x00A,
+ SCAN_F1 = 0x00B,
+ SCAN_F2 = 0x00C,
+ SCAN_F3 = 0x00D,
+ SCAN_F4 = 0x00E,
+ SCAN_F5 = 0x00F,
+ SCAN_F6 = 0x010,
+ SCAN_F7 = 0x011,
+ SCAN_F8 = 0x012,
+ SCAN_F9 = 0x013,
+ SCAN_F10 = 0x014,
+ SCAN_F11 = 0x015,
+ SCAN_F12 = 0x016,
+ SCAN_ESC = 0x017,
+ SCAN_PAUSE = 0x048,
+ SCAN_F13 = 0x068,
+ SCAN_F14 = 0x069,
+ SCAN_F15 = 0x06A,
+ SCAN_F16 = 0x06B,
+ SCAN_F17 = 0x06C,
+ SCAN_F18 = 0x06D,
+ SCAN_F19 = 0x06E,
+ SCAN_F20 = 0x06F,
+ SCAN_F21 = 0x070,
+ SCAN_F22 = 0x071,
+ SCAN_F23 = 0x072,
+ SCAN_F24 = 0x073,
+ SCAN_MUTE = 0x07F,
+ SCAN_VOLUME_UP = 0x080,
+ SCAN_VOLUME_DOWN = 0x081,
+ SCAN_BRIGHTNESS_UP = 0x100,
+ SCAN_BRIGHTNESS_DOWN = 0x101,
+ SCAN_SUSPEND = 0x102,
+ SCAN_HIBERNATE = 0x103,
+ SCAN_TOGGLE_DISPLAY = 0x104,
+ SCAN_RECOVERY = 0x105,
+ SCAN_EJECT = 0x106,
+};
+
+typedef struct {
+ uint16_t ScanCode;
+ char16_t UnicodeChar;
+} EFI_INPUT_KEY;
+
+typedef struct {
+ uint32_t KeyShiftState;
+ uint8_t KeyToggleState;
+} EFI_KEY_STATE;
+
+typedef struct {
+ EFI_INPUT_KEY Key;
+ EFI_KEY_STATE KeyState;
+} EFI_KEY_DATA;
+
+struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL {
+ EFI_STATUS (EFIAPI *Reset)(
+ EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
+ bool ExtendedVerification);
+ EFI_STATUS (EFIAPI *ReadKeyStroke)(
+ EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
+ EFI_INPUT_KEY *Key);
+ EFI_EVENT WaitForKey;
+};
+
+typedef struct EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL;
+struct EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL {
+ EFI_STATUS (EFIAPI *Reset)(
+ EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
+ bool ExtendedVerification);
+ EFI_STATUS (EFIAPI *ReadKeyStrokeEx)(
+ EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
+ EFI_KEY_DATA *KeyData);
+ EFI_EVENT WaitForKeyEx;
+ void *SetState;
+ void *RegisterKeyNotify;
+ void *UnregisterKeyNotify;
+};
+
+typedef struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
+struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
+ EFI_STATUS (EFIAPI *Reset)(
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
+ bool ExtendedVerification);
+ EFI_STATUS (EFIAPI *OutputString)(
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
+ char16_t *String);
+ EFI_STATUS (EFIAPI *TestString)(
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
+ char16_t *String);
+ EFI_STATUS (EFIAPI *QueryMode)(
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
+ size_t ModeNumber,
+ size_t *Columns,
+ size_t *Rows);
+ EFI_STATUS (EFIAPI *SetMode)(
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
+ size_t ModeNumber);
+ EFI_STATUS (EFIAPI *SetAttribute)(
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
+ size_t Attribute);
+ EFI_STATUS (EFIAPI *ClearScreen)(
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This);
+ EFI_STATUS (EFIAPI *SetCursorPosition)(
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
+ size_t Column,
+ size_t Row);
+ EFI_STATUS (EFIAPI *EnableCursor)(
+ EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
+ bool Visible);
+ struct {
+ int32_t MaxMode;
+ int32_t Mode;
+ int32_t Attribute;
+ int32_t CursorColumn;
+ int32_t CursorRow;
+ bool CursorVisible;
+ } *Mode;
+};
diff --git a/src/boot/efi/proto/tcg.h b/src/boot/efi/proto/tcg.h
new file mode 100644
index 0000000..b4b8296
--- /dev/null
+++ b/src/boot/efi/proto/tcg.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_TCG_PROTOCOL_GUID \
+ GUID_DEF(0xf541796d, 0xa62e, 0x4954, 0xa7, 0x75, 0x95, 0x84, 0xf6, 0x1b, 0x9c, 0xdd)
+#define EFI_TCG2_PROTOCOL_GUID \
+ GUID_DEF(0x607f766c, 0x7455, 0x42be, 0x93, 0x0b, 0xe4, 0xd7, 0x6d, 0xb2, 0x72, 0x0f)
+
+#define TCG_ALG_SHA 0x4
+#define EFI_TCG2_EVENT_HEADER_VERSION 1
+#define EV_IPL 13
+#define EV_EVENT_TAG UINT32_C(6)
+
+typedef struct {
+ uint8_t Major;
+ uint8_t Minor;
+ uint8_t RevMajor;
+ uint8_t RevMinor;
+} TCG_VERSION;
+
+typedef struct {
+ uint8_t Major;
+ uint8_t Minor;
+} EFI_TCG2_VERSION;
+
+typedef struct {
+ uint8_t Size;
+ TCG_VERSION StructureVersion;
+ TCG_VERSION ProtocolSpecVersion;
+ uint8_t HashAlgorithmBitmap;
+ bool TPMPresentFlag;
+ bool TPMDeactivatedFlag;
+} EFI_TCG_BOOT_SERVICE_CAPABILITY;
+
+typedef struct {
+ uint8_t Size;
+ EFI_TCG2_VERSION StructureVersion;
+ EFI_TCG2_VERSION ProtocolVersion;
+ uint32_t HashAlgorithmBitmap;
+ uint32_t SupportedEventLogs;
+ bool TPMPresentFlag;
+ uint16_t MaxCommandSize;
+ uint16_t MaxResponseSize;
+ uint32_t ManufacturerID;
+ uint32_t NumberOfPCRBanks;
+ uint32_t ActivePcrBanks;
+} EFI_TCG2_BOOT_SERVICE_CAPABILITY;
+
+typedef struct {
+ uint32_t PCRIndex;
+ uint32_t EventType;
+ struct {
+ uint8_t Digest[20];
+ } Digest;
+ uint32_t EventSize;
+ uint8_t Event[];
+} _packed_ TCG_PCR_EVENT;
+
+typedef struct {
+ uint32_t HeaderSize;
+ uint16_t HeaderVersion;
+ uint32_t PCRIndex;
+ uint32_t EventType;
+} _packed_ EFI_TCG2_EVENT_HEADER;
+
+typedef struct {
+ uint32_t Size;
+ EFI_TCG2_EVENT_HEADER Header;
+ uint8_t Event[];
+} _packed_ EFI_TCG2_EVENT;
+
+typedef struct {
+ uint32_t EventId;
+ uint32_t EventSize;
+ uint8_t Event[];
+} _packed_ EFI_TCG2_TAGGED_EVENT;
+
+typedef struct EFI_TCG_PROTOCOL EFI_TCG_PROTOCOL;
+struct EFI_TCG_PROTOCOL {
+ EFI_STATUS (EFIAPI *StatusCheck)(
+ EFI_TCG_PROTOCOL *This,
+ EFI_TCG_BOOT_SERVICE_CAPABILITY *ProtocolCapability,
+ uint32_t *TCGFeatureFlags,
+ EFI_PHYSICAL_ADDRESS *EventLogLocation,
+ EFI_PHYSICAL_ADDRESS *EventLogLastEntry);
+ void *HashAll;
+ void *LogEvent;
+ void *PassThroughToTpm;
+ EFI_STATUS (EFIAPI *HashLogExtendEvent)(
+ EFI_TCG_PROTOCOL *This,
+ EFI_PHYSICAL_ADDRESS HashData,
+ uint64_t HashDataLen,
+ uint32_t AlgorithmId,
+ TCG_PCR_EVENT *TCGLogData,
+ uint32_t *EventNumber,
+ EFI_PHYSICAL_ADDRESS *EventLogLastEntry);
+};
+
+typedef struct EFI_TCG2_PROTOCOL EFI_TCG2_PROTOCOL;
+struct EFI_TCG2_PROTOCOL {
+ EFI_STATUS (EFIAPI *GetCapability)(
+ EFI_TCG2_PROTOCOL *This,
+ EFI_TCG2_BOOT_SERVICE_CAPABILITY *ProtocolCapability);
+ void *GetEventLog;
+ EFI_STATUS (EFIAPI *HashLogExtendEvent)(
+ EFI_TCG2_PROTOCOL *This,
+ uint64_t Flags,
+ EFI_PHYSICAL_ADDRESS DataToHash,
+ uint64_t DataToHashLen,
+ EFI_TCG2_EVENT *EfiTcgEvent);
+ void *SubmitCommand;
+ void *GetActivePcrBanks;
+ void *SetActivePcrBanks;
+ void *GetResultOfSetActivePcrBanks;
+};
diff --git a/src/boot/efi/random-seed.c b/src/boot/efi/random-seed.c
new file mode 100644
index 0000000..8147e54
--- /dev/null
+++ b/src/boot/efi/random-seed.c
@@ -0,0 +1,325 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "memory-util-fundamental.h"
+#include "proto/rng.h"
+#include "random-seed.h"
+#include "secure-boot.h"
+#include "sha256.h"
+#include "util.h"
+
+#define RANDOM_MAX_SIZE_MIN (32U)
+#define RANDOM_MAX_SIZE_MAX (32U*1024U)
+
+struct linux_efi_random_seed {
+ uint32_t size;
+ uint8_t seed[];
+};
+
+#define LINUX_EFI_RANDOM_SEED_TABLE_GUID \
+ { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } }
+
+/* SHA256 gives us 256/8=32 bytes */
+#define HASH_VALUE_SIZE 32
+
+/* Linux's RNG is 256 bits, so let's provide this much */
+#define DESIRED_SEED_SIZE 32
+
+/* Some basic domain separation in case somebody uses this data elsewhere */
+#define HASH_LABEL "systemd-boot random seed label v1"
+
+static EFI_STATUS acquire_rng(void *ret, size_t size) {
+ EFI_RNG_PROTOCOL *rng;
+ EFI_STATUS err;
+
+ assert(ret);
+
+ /* Try to acquire the specified number of bytes from the UEFI RNG */
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_RNG_PROTOCOL), NULL, (void **) &rng);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (!rng)
+ return EFI_UNSUPPORTED;
+
+ err = rng->GetRNG(rng, NULL, size, ret);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to acquire RNG data: %m");
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS acquire_system_token(void **ret, size_t *ret_size) {
+ _cleanup_free_ char *data = NULL;
+ EFI_STATUS err;
+ size_t size;
+
+ assert(ret);
+ assert(ret_size);
+
+ err = efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderSystemToken", &data, &size);
+ if (err != EFI_SUCCESS) {
+ if (err != EFI_NOT_FOUND)
+ log_error_status(err, "Failed to read LoaderSystemToken EFI variable: %m");
+ return err;
+ }
+
+ if (size <= 0)
+ return log_error_status(EFI_NOT_FOUND, "System token too short, ignoring.");
+
+ *ret = TAKE_PTR(data);
+ *ret_size = size;
+
+ return EFI_SUCCESS;
+}
+
+static void validate_sha256(void) {
+
+#ifdef EFI_DEBUG
+ /* Let's validate our SHA256 implementation. We stole it from glibc, and converted it to UEFI
+ * style. We better check whether it does the right stuff. We use the simpler test vectors from the
+ * SHA spec. Note that we strip this out in optimization builds. */
+
+ static const struct {
+ const char *string;
+ uint8_t hash[HASH_VALUE_SIZE];
+ } array[] = {
+ { "abc",
+ { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
+ 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
+ 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
+ 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad }},
+
+ { "",
+ { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
+ 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
+ 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
+ 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 }},
+
+ { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ { 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8,
+ 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39,
+ 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67,
+ 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1 }},
+
+ { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ { 0xcf, 0x5b, 0x16, 0xa7, 0x78, 0xaf, 0x83, 0x80,
+ 0x03, 0x6c, 0xe5, 0x9e, 0x7b, 0x04, 0x92, 0x37,
+ 0x0b, 0x24, 0x9b, 0x11, 0xe8, 0xf0, 0x7a, 0x51,
+ 0xaf, 0xac, 0x45, 0x03, 0x7a, 0xfe, 0xe9, 0xd1 }},
+ };
+
+ for (size_t i = 0; i < ELEMENTSOF(array); i++)
+ assert(memcmp(SHA256_DIRECT(array[i].string, strlen8(array[i].string)), array[i].hash, HASH_VALUE_SIZE) == 0);
+#endif
+}
+
+EFI_STATUS process_random_seed(EFI_FILE *root_dir) {
+ uint8_t random_bytes[DESIRED_SEED_SIZE], hash_key[HASH_VALUE_SIZE];
+ _cleanup_free_ struct linux_efi_random_seed *new_seed_table = NULL;
+ struct linux_efi_random_seed *previous_seed_table = NULL;
+ _cleanup_free_ void *seed = NULL, *system_token = NULL;
+ _cleanup_(file_closep) EFI_FILE *handle = NULL;
+ _cleanup_free_ EFI_FILE_INFO *info = NULL;
+ struct sha256_ctx hash;
+ uint64_t uefi_monotonic_counter = 0;
+ size_t size, rsize, wsize;
+ bool seeded_by_efi = false;
+ EFI_STATUS err;
+ EFI_TIME now;
+
+ CLEANUP_ERASE(random_bytes);
+ CLEANUP_ERASE(hash_key);
+ CLEANUP_ERASE(hash);
+
+ assert(root_dir);
+ assert_cc(DESIRED_SEED_SIZE == HASH_VALUE_SIZE);
+
+ validate_sha256();
+
+ /* hash = LABEL || sizeof(input1) || input1 || ... || sizeof(inputN) || inputN */
+ sha256_init_ctx(&hash);
+
+ /* Some basic domain separation in case somebody uses this data elsewhere */
+ sha256_process_bytes(HASH_LABEL, sizeof(HASH_LABEL) - 1, &hash);
+
+ previous_seed_table = find_configuration_table(MAKE_GUID_PTR(LINUX_EFI_RANDOM_SEED_TABLE));
+ if (!previous_seed_table) {
+ size = 0;
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ } else {
+ size = previous_seed_table->size;
+ seeded_by_efi = size >= DESIRED_SEED_SIZE;
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(previous_seed_table->seed, size, &hash);
+
+ /* Zero and free the previous seed table only at the end after we've managed to install a new
+ * one, so that in case this function fails or aborts, Linux still receives whatever the
+ * previous bootloader chain set. So, the next line of this block is not an explicit_bzero()
+ * call. */
+ }
+
+ /* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
+ * idea to use it because it helps us for cases where users mistakenly include a random seed in
+ * golden master images that are replicated many times. */
+ err = acquire_rng(random_bytes, sizeof(random_bytes));
+ if (err != EFI_SUCCESS) {
+ size = 0;
+ /* If we can't get any randomness from EFI itself, then we'll only be relying on what's in
+ * ESP. But ESP is mutable, so if secure boot is enabled, we probably shouldn't trust that
+ * alone, in which case we bail out early. */
+ if (!seeded_by_efi && secure_boot_enabled())
+ return EFI_NOT_FOUND;
+ } else {
+ seeded_by_efi = true;
+ size = sizeof(random_bytes);
+ }
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(random_bytes, size, &hash);
+
+ /* Get some system specific seed that the installer might have placed in an EFI variable. We include
+ * it in our hash. This is protection against golden master image sloppiness, and it remains on the
+ * system, even when disk images are duplicated or swapped out. */
+ size = 0;
+ err = acquire_system_token(&system_token, &size);
+ if ((err != EFI_SUCCESS || size < DESIRED_SEED_SIZE) && !seeded_by_efi)
+ return err;
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ if (system_token) {
+ sha256_process_bytes(system_token, size, &hash);
+ explicit_bzero_safe(system_token, size);
+ }
+
+ err = root_dir->Open(
+ root_dir,
+ &handle,
+ (char16_t *) u"\\loader\\random-seed",
+ EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE,
+ 0);
+ if (err != EFI_SUCCESS) {
+ if (err != EFI_NOT_FOUND && err != EFI_WRITE_PROTECTED)
+ log_error_status(err, "Failed to open random seed file: %m");
+ return err;
+ }
+
+ err = get_file_info(handle, &info, NULL);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to get file info for random seed: %m");
+
+ size = info->FileSize;
+ if (size < RANDOM_MAX_SIZE_MIN)
+ return log_error("Random seed file is too short.");
+
+ if (size > RANDOM_MAX_SIZE_MAX)
+ return log_error("Random seed file is too large.");
+
+ seed = xmalloc(size);
+ rsize = size;
+ err = handle->Read(handle, &rsize, seed);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to read random seed file: %m");
+ if (rsize != size) {
+ explicit_bzero_safe(seed, rsize);
+ return log_error_status(EFI_PROTOCOL_ERROR, "Short read on random seed file.");
+ }
+
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(seed, size, &hash);
+ explicit_bzero_safe(seed, size);
+
+ err = handle->SetPosition(handle, 0);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to seek to beginning of random seed file: %m");
+
+ /* Let's also include the UEFI monotonic counter (which is supposedly increasing on every single
+ * boot) in the hash, so that even if the changes to the ESP for some reason should not be
+ * persistent, the random seed we generate will still be different on every single boot. */
+ err = BS->GetNextMonotonicCount(&uefi_monotonic_counter);
+ if (err != EFI_SUCCESS && !seeded_by_efi)
+ return log_error_status(err, "Failed to acquire UEFI monotonic counter: %m");
+ size = sizeof(uefi_monotonic_counter);
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(&uefi_monotonic_counter, size, &hash);
+
+ err = RT->GetTime(&now, NULL);
+ size = err == EFI_SUCCESS ? sizeof(now) : 0; /* Known to be flaky, so don't bark on error. */
+ sha256_process_bytes(&size, sizeof(size), &hash);
+ sha256_process_bytes(&now, size, &hash);
+
+ /* hash_key = HASH(hash) */
+ sha256_finish_ctx(&hash, hash_key);
+
+ /* hash = hash_key || 0 */
+ sha256_init_ctx(&hash);
+ sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
+ sha256_process_bytes(&(const uint8_t){ 0 }, sizeof(uint8_t), &hash);
+ /* random_bytes = HASH(hash) */
+ sha256_finish_ctx(&hash, random_bytes);
+
+ size = sizeof(random_bytes);
+ /* If the file size is too large, zero out the remaining bytes on disk. */
+ if (size < info->FileSize) {
+ err = handle->SetPosition(handle, size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to seek to offset of random seed file: %m");
+ wsize = info->FileSize - size;
+ err = handle->Write(handle, &wsize, seed /* All zeros now */);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to write random seed file: %m");
+ if (wsize != info->FileSize - size)
+ return log_error_status(EFI_PROTOCOL_ERROR, "Short write on random seed file.");
+ err = handle->Flush(handle);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to flush random seed file: %m");
+ err = handle->SetPosition(handle, 0);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to seek to beginning of random seed file: %m");
+
+ /* We could truncate the file here with something like:
+ *
+ * info->FileSize = size;
+ * err = handle->SetInfo(handle, &GenericFileInfo, info->Size, info);
+ * if (err != EFI_SUCCESS)
+ * return log_error_status(err, "Failed to truncate random seed file: %u");
+ *
+ * But this is considered slightly risky, because EFI filesystem drivers are a little bit
+ * flimsy. So instead we rely on userspace eventually truncating this when it writes a new
+ * seed. For now the best we do is zero it. */
+ }
+ /* Update the random seed on disk before we use it */
+ wsize = size;
+ err = handle->Write(handle, &wsize, random_bytes);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to write random seed file: %m");
+ if (wsize != size)
+ return log_error_status(EFI_PROTOCOL_ERROR, "Short write on random seed file.");
+ err = handle->Flush(handle);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to flush random seed file: %m");
+
+ err = BS->AllocatePool(EfiACPIReclaimMemory,
+ offsetof(struct linux_efi_random_seed, seed) + DESIRED_SEED_SIZE,
+ (void **) &new_seed_table);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to allocate EFI table for random seed: %m");
+ new_seed_table->size = DESIRED_SEED_SIZE;
+
+ /* hash = hash_key || 1 */
+ sha256_init_ctx(&hash);
+ sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
+ sha256_process_bytes(&(const uint8_t){ 1 }, sizeof(uint8_t), &hash);
+ /* new_seed_table->seed = HASH(hash) */
+ sha256_finish_ctx(&hash, new_seed_table->seed);
+
+ err = BS->InstallConfigurationTable(MAKE_GUID_PTR(LINUX_EFI_RANDOM_SEED_TABLE), new_seed_table);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to install EFI table for random seed: %m");
+ TAKE_PTR(new_seed_table);
+
+ if (previous_seed_table) {
+ /* Now that we've succeeded in installing the new table, we can safely nuke the old one. */
+ explicit_bzero_safe(previous_seed_table->seed, previous_seed_table->size);
+ explicit_bzero_safe(previous_seed_table, sizeof(*previous_seed_table));
+ free(previous_seed_table);
+ }
+
+ return EFI_SUCCESS;
+}
diff --git a/src/boot/efi/random-seed.h b/src/boot/efi/random-seed.h
new file mode 100644
index 0000000..67f005d
--- /dev/null
+++ b/src/boot/efi/random-seed.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+EFI_STATUS process_random_seed(EFI_FILE *root_dir);
diff --git a/src/boot/efi/secure-boot.c b/src/boot/efi/secure-boot.c
new file mode 100644
index 0000000..f76d2f4
--- /dev/null
+++ b/src/boot/efi/secure-boot.c
@@ -0,0 +1,223 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "console.h"
+#include "proto/security-arch.h"
+#include "secure-boot.h"
+#include "util.h"
+#include "vmm.h"
+
+bool secure_boot_enabled(void) {
+ bool secure = false; /* avoid false maybe-uninitialized warning */
+ EFI_STATUS err;
+
+ err = efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"SecureBoot", &secure);
+
+ return err == EFI_SUCCESS && secure;
+}
+
+SecureBootMode secure_boot_mode(void) {
+ bool secure, audit = false, deployed = false, setup = false;
+ EFI_STATUS err;
+
+ err = efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"SecureBoot", &secure);
+ if (err != EFI_SUCCESS)
+ return SECURE_BOOT_UNSUPPORTED;
+
+ /* We can assume false for all these if they are abscent (AuditMode and
+ * DeployedMode may not exist on older firmware). */
+ (void) efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"AuditMode", &audit);
+ (void) efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"DeployedMode", &deployed);
+ (void) efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"SetupMode", &setup);
+
+ return decode_secure_boot_mode(secure, audit, deployed, setup);
+}
+
+EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path, bool force) {
+ assert(root_dir);
+ assert(path);
+
+ EFI_STATUS err;
+
+ clear_screen(COLOR_NORMAL);
+
+ /* Enrolling secure boot keys is safe to do in virtualized environments as there is nothing
+ * we can brick there. */
+ bool is_safe = in_hypervisor();
+
+ if (!is_safe && !force)
+ return EFI_SUCCESS;
+
+ printf("Enrolling secure boot keys from directory: %ls\n", path);
+
+ if (!is_safe) {
+ printf("Warning: Enrolling custom Secure Boot keys might soft-brick your machine!\n");
+
+ unsigned timeout_sec = 15;
+ for (;;) {
+ printf("\rEnrolling in %2u s, press any key to abort.", timeout_sec);
+
+ uint64_t key;
+ err = console_key_read(&key, 1000 * 1000);
+ if (err == EFI_NOT_READY)
+ continue;
+ if (err == EFI_TIMEOUT) {
+ if (timeout_sec == 0) /* continue enrolling keys */
+ break;
+ timeout_sec--;
+ continue;
+ }
+ if (err != EFI_SUCCESS)
+ return log_error_status(
+ err,
+ "Error waiting for user input to enroll Secure Boot keys: %m");
+
+ /* user aborted, returning EFI_SUCCESS here allows the user to go back to the menu */
+ return EFI_SUCCESS;
+ }
+
+ printf("\n");
+ }
+
+ _cleanup_(file_closep) EFI_FILE *dir = NULL;
+
+ err = open_directory(root_dir, path, &dir);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed opening keys directory %ls: %m", path);
+
+ struct {
+ const char16_t *name;
+ const char16_t *filename;
+ const EFI_GUID vendor;
+ char *buffer;
+ size_t size;
+ } sb_vars[] = {
+ { u"db", u"db.auth", EFI_IMAGE_SECURITY_DATABASE_GUID, NULL, 0 },
+ { u"KEK", u"KEK.auth", EFI_GLOBAL_VARIABLE, NULL, 0 },
+ { u"PK", u"PK.auth", EFI_GLOBAL_VARIABLE, NULL, 0 },
+ };
+
+ /* Make sure all keys files exist before we start enrolling them by loading them from the disk first. */
+ for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++) {
+ err = file_read(dir, sb_vars[i].filename, 0, 0, &sb_vars[i].buffer, &sb_vars[i].size);
+ if (err != EFI_SUCCESS) {
+ log_error_status(err, "Failed reading file %ls\\%ls: %m", path, sb_vars[i].filename);
+ goto out_deallocate;
+ }
+ }
+
+ for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++) {
+ uint32_t sb_vars_opts =
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS |
+ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
+
+ err = efivar_set_raw(&sb_vars[i].vendor, sb_vars[i].name, sb_vars[i].buffer, sb_vars[i].size, sb_vars_opts);
+ if (err != EFI_SUCCESS) {
+ log_error_status(err, "Failed to write %ls secure boot variable: %m", sb_vars[i].name);
+ goto out_deallocate;
+ }
+ }
+
+ printf("Custom Secure Boot keys successfully enrolled, rebooting the system now!\n");
+ /* The system should be in secure boot mode now and we could continue a regular boot. But at least
+ * TPM PCR7 measurements should change on next boot. Reboot now so that any OS we load does not end
+ * up relying on the old PCR state. */
+ RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
+ assert_not_reached();
+
+out_deallocate:
+ for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++)
+ free(sb_vars[i].buffer);
+
+ return err;
+}
+
+static struct SecurityOverride {
+ EFI_SECURITY_ARCH_PROTOCOL *security;
+ EFI_SECURITY2_ARCH_PROTOCOL *security2;
+ EFI_SECURITY_FILE_AUTHENTICATION_STATE original_hook;
+ EFI_SECURITY2_FILE_AUTHENTICATION original_hook2;
+
+ security_validator_t validator;
+ const void *validator_ctx;
+} security_override;
+
+static EFIAPI EFI_STATUS security_hook(
+ const EFI_SECURITY_ARCH_PROTOCOL *this,
+ uint32_t authentication_status,
+ const EFI_DEVICE_PATH *file) {
+
+ assert(security_override.validator);
+ assert(security_override.security);
+ assert(security_override.original_hook);
+
+ if (security_override.validator(security_override.validator_ctx, file, NULL, 0))
+ return EFI_SUCCESS;
+
+ return security_override.original_hook(security_override.security, authentication_status, file);
+}
+
+static EFIAPI EFI_STATUS security2_hook(
+ const EFI_SECURITY2_ARCH_PROTOCOL *this,
+ const EFI_DEVICE_PATH *device_path,
+ void *file_buffer,
+ size_t file_size,
+ bool boot_policy) {
+
+ assert(security_override.validator);
+ assert(security_override.security2);
+ assert(security_override.original_hook2);
+
+ if (security_override.validator(security_override.validator_ctx, device_path, file_buffer, file_size))
+ return EFI_SUCCESS;
+
+ return security_override.original_hook2(
+ security_override.security2, device_path, file_buffer, file_size, boot_policy);
+}
+
+/* This replaces the platform provided security arch protocols hooks (defined in the UEFI Platform
+ * Initialization Specification) with our own that uses the given validator to decide if a image is to be
+ * trusted. If not running in secure boot or the protocols are not available nothing happens. The override
+ * must be removed with uninstall_security_override() after LoadImage() has been called.
+ *
+ * This is a hack as we do not own the security protocol instances and modifying them is not an official part
+ * of their spec. But there is little else we can do to circumvent secure boot short of implementing our own
+ * PE loader. We could replace the firmware instances with our own instance using
+ * ReinstallProtocolInterface(), but some firmware will still use the old ones. */
+void install_security_override(security_validator_t validator, const void *validator_ctx) {
+ EFI_STATUS err;
+
+ assert(validator);
+
+ if (!secure_boot_enabled())
+ return;
+
+ security_override = (struct SecurityOverride) {
+ .validator = validator,
+ .validator_ctx = validator_ctx,
+ };
+
+ EFI_SECURITY_ARCH_PROTOCOL *security = NULL;
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_SECURITY_ARCH_PROTOCOL), NULL, (void **) &security);
+ if (err == EFI_SUCCESS) {
+ security_override.security = security;
+ security_override.original_hook = security->FileAuthenticationState;
+ security->FileAuthenticationState = security_hook;
+ }
+
+ EFI_SECURITY2_ARCH_PROTOCOL *security2 = NULL;
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_SECURITY2_ARCH_PROTOCOL), NULL, (void **) &security2);
+ if (err == EFI_SUCCESS) {
+ security_override.security2 = security2;
+ security_override.original_hook2 = security2->FileAuthentication;
+ security2->FileAuthentication = security2_hook;
+ }
+}
+
+void uninstall_security_override(void) {
+ if (security_override.original_hook)
+ security_override.security->FileAuthenticationState = security_override.original_hook;
+ if (security_override.original_hook2)
+ security_override.security2->FileAuthentication = security_override.original_hook2;
+}
diff --git a/src/boot/efi/secure-boot.h b/src/boot/efi/secure-boot.h
new file mode 100644
index 0000000..3471131
--- /dev/null
+++ b/src/boot/efi/secure-boot.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+#include "efivars-fundamental.h"
+
+typedef enum {
+ ENROLL_OFF, /* no Secure Boot key enrollment whatsoever, even manual entries are not generated */
+ ENROLL_MANUAL, /* Secure Boot key enrollment is strictly manual: manual entries are generated and need to be selected by the user */
+ ENROLL_IF_SAFE, /* Automatically enroll if it is safe (if we are running inside a VM, for example). */
+ ENROLL_FORCE, /* Secure Boot key enrollment may be automatic if it is available but might not be safe */
+} secure_boot_enroll;
+
+bool secure_boot_enabled(void);
+SecureBootMode secure_boot_mode(void);
+
+EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path, bool force);
+
+typedef bool (*security_validator_t)(
+ const void *ctx,
+ const EFI_DEVICE_PATH *device_path,
+ const void *file_buffer,
+ size_t file_size);
+
+void install_security_override(security_validator_t validator, const void *validator_ctx);
+void uninstall_security_override(void);
diff --git a/src/boot/efi/shim.c b/src/boot/efi/shim.c
new file mode 100644
index 0000000..df136ed
--- /dev/null
+++ b/src/boot/efi/shim.c
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Port to systemd-boot
+ * Copyright © 2017 Max Resch <resch.max@gmail.com>
+ *
+ * Security Policy Handling
+ * Copyright © 2012 <James.Bottomley@HansenPartnership.com>
+ * https://github.com/mjg59/efitools
+ */
+
+#include "device-path-util.h"
+#include "secure-boot.h"
+#include "shim.h"
+#include "util.h"
+
+#if defined(__x86_64__) || defined(__i386__)
+#define __sysv_abi__ __attribute__((sysv_abi))
+#else
+#define __sysv_abi__
+#endif
+
+struct ShimLock {
+ EFI_STATUS __sysv_abi__ (*shim_verify) (const void *buffer, uint32_t size);
+
+ /* context is actually a struct for the PE header, but it isn't needed so void is sufficient just do define the interface
+ * see shim.c/shim.h and PeHeader.h in the github shim repo */
+ EFI_STATUS __sysv_abi__ (*generate_hash) (void *data, uint32_t datasize, void *context, uint8_t *sha256hash, uint8_t *sha1hash);
+
+ EFI_STATUS __sysv_abi__ (*read_header) (void *data, uint32_t datasize, void *context);
+};
+
+#define SHIM_LOCK_GUID \
+ { 0x605dab50, 0xe046, 0x4300, { 0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23 } }
+
+bool shim_loaded(void) {
+ struct ShimLock *shim_lock;
+
+ return BS->LocateProtocol(MAKE_GUID_PTR(SHIM_LOCK), NULL, (void **) &shim_lock) == EFI_SUCCESS;
+}
+
+static bool shim_validate(
+ const void *ctx, const EFI_DEVICE_PATH *device_path, const void *file_buffer, size_t file_size) {
+
+ EFI_STATUS err;
+ _cleanup_free_ char *file_buffer_owned = NULL;
+
+ if (!file_buffer) {
+ if (!device_path)
+ return false;
+
+ EFI_HANDLE device_handle;
+ EFI_DEVICE_PATH *file_dp = (EFI_DEVICE_PATH *) device_path;
+ err = BS->LocateDevicePath(
+ MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), &file_dp, &device_handle);
+ if (err != EFI_SUCCESS)
+ return false;
+
+ _cleanup_(file_closep) EFI_FILE *root = NULL;
+ err = open_volume(device_handle, &root);
+ if (err != EFI_SUCCESS)
+ return false;
+
+ _cleanup_free_ char16_t *dp_str = NULL;
+ err = device_path_to_str(file_dp, &dp_str);
+ if (err != EFI_SUCCESS)
+ return false;
+
+ err = file_read(root, dp_str, 0, 0, &file_buffer_owned, &file_size);
+ if (err != EFI_SUCCESS)
+ return false;
+
+ file_buffer = file_buffer_owned;
+ }
+
+ struct ShimLock *shim_lock;
+ err = BS->LocateProtocol(MAKE_GUID_PTR(SHIM_LOCK), NULL, (void **) &shim_lock);
+ if (err != EFI_SUCCESS)
+ return false;
+
+ 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) {
+ assert(device_path);
+ assert(ret_image);
+
+ bool have_shim = shim_loaded();
+
+ if (have_shim)
+ install_security_override(shim_validate, NULL);
+
+ EFI_STATUS ret = BS->LoadImage(
+ /*BootPolicy=*/false, parent, (EFI_DEVICE_PATH *) device_path, NULL, 0, ret_image);
+
+ if (have_shim)
+ uninstall_security_override();
+
+ return ret;
+}
+
+void shim_retain_protocol(void) {
+ uint8_t value = 1;
+
+ /* Ask Shim to avoid uninstalling its security protocol, so that we can use it from sd-stub to
+ * validate PE addons. By default, Shim uninstalls its protocol when calling StartImage().
+ * Requires Shim 15.8. */
+ (void) efivar_set_raw(MAKE_GUID_PTR(SHIM_LOCK), u"ShimRetainProtocol", &value, sizeof(value), 0);
+}
diff --git a/src/boot/efi/shim.h b/src/boot/efi/shim.h
new file mode 100644
index 0000000..e0cb39f
--- /dev/null
+++ b/src/boot/efi/shim.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Port to systemd-boot
+ * Copyright © 2017 Max Resch <resch.max@gmail.com>
+ *
+ * Security Policy Handling
+ * Copyright © 2012 <James.Bottomley@HansenPartnership.com>
+ * https://github.com/mjg59/efitools
+ */
+#pragma once
+
+#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);
+void shim_retain_protocol(void);
diff --git a/src/boot/efi/splash.c b/src/boot/efi/splash.c
new file mode 100644
index 0000000..8daeb71
--- /dev/null
+++ b/src/boot/efi/splash.c
@@ -0,0 +1,334 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "graphics.h"
+#include "logarithm.h"
+#include "proto/graphics-output.h"
+#include "splash.h"
+#include "unaligned-fundamental.h"
+#include "util.h"
+
+struct bmp_file {
+ char signature[2];
+ uint32_t size;
+ uint16_t reserved[2];
+ uint32_t offset;
+} _packed_;
+
+/* we require at least BITMAPINFOHEADER, later versions are
+ accepted, but their features ignored */
+struct bmp_dib {
+ uint32_t size;
+ uint32_t x;
+ uint32_t y;
+ uint16_t planes;
+ uint16_t depth;
+ uint32_t compression;
+ uint32_t image_size;
+ int32_t x_pixel_meter;
+ int32_t y_pixel_meter;
+ uint32_t colors_used;
+ uint32_t colors_important;
+ uint32_t channel_mask_r;
+ uint32_t channel_mask_g;
+ uint32_t channel_mask_b;
+ uint32_t channel_mask_a;
+} _packed_;
+
+#define SIZEOF_BMP_DIB offsetof(struct bmp_dib, channel_mask_r)
+#define SIZEOF_BMP_DIB_RGB offsetof(struct bmp_dib, channel_mask_a)
+#define SIZEOF_BMP_DIB_RGBA sizeof(struct bmp_dib)
+
+struct bmp_map {
+ uint8_t blue;
+ uint8_t green;
+ uint8_t red;
+ uint8_t reserved;
+} _packed_;
+
+static EFI_STATUS bmp_parse_header(
+ const uint8_t *bmp,
+ size_t size,
+ struct bmp_dib **ret_dib,
+ struct bmp_map **ret_map,
+ const uint8_t **pixmap) {
+
+ assert(bmp);
+ assert(ret_dib);
+ assert(ret_map);
+ assert(pixmap);
+
+ if (size < sizeof(struct bmp_file) + SIZEOF_BMP_DIB)
+ return EFI_INVALID_PARAMETER;
+
+ /* check file header */
+ struct bmp_file *file = (struct bmp_file *) bmp;
+ if (file->signature[0] != 'B' || file->signature[1] != 'M')
+ return EFI_INVALID_PARAMETER;
+ if (file->size != size)
+ return EFI_INVALID_PARAMETER;
+ if (file->size < file->offset)
+ return EFI_INVALID_PARAMETER;
+
+ /* check device-independent bitmap */
+ struct bmp_dib *dib = (struct bmp_dib *) (bmp + sizeof(struct bmp_file));
+ if (dib->size < SIZEOF_BMP_DIB)
+ return EFI_UNSUPPORTED;
+
+ switch (dib->depth) {
+ case 1:
+ case 4:
+ case 8:
+ case 24:
+ if (dib->compression != 0)
+ return EFI_UNSUPPORTED;
+
+ break;
+
+ case 16:
+ case 32:
+ if (dib->compression != 0 && dib->compression != 3)
+ return EFI_UNSUPPORTED;
+
+ break;
+
+ default:
+ return EFI_UNSUPPORTED;
+ }
+
+ size_t row_size = ((size_t) dib->depth * dib->x + 31) / 32 * 4;
+ if (file->size - file->offset < dib->y * row_size)
+ return EFI_INVALID_PARAMETER;
+ if (row_size * dib->y > 64 * 1024 * 1024)
+ return EFI_INVALID_PARAMETER;
+
+ /* check color table */
+ struct bmp_map *map = (struct bmp_map *) (bmp + sizeof(struct bmp_file) + dib->size);
+ if (file->offset < sizeof(struct bmp_file) + dib->size)
+ return EFI_INVALID_PARAMETER;
+
+ if (file->offset > sizeof(struct bmp_file) + dib->size) {
+ uint32_t map_count = 0;
+
+ if (dib->colors_used)
+ map_count = dib->colors_used;
+ else if (IN_SET(dib->depth, 1, 4, 8))
+ map_count = 1 << dib->depth;
+
+ size_t map_size = file->offset - (sizeof(struct bmp_file) + dib->size);
+ if (map_size != sizeof(struct bmp_map) * map_count)
+ return EFI_INVALID_PARAMETER;
+ }
+
+ *ret_map = map;
+ *ret_dib = dib;
+ *pixmap = bmp + file->offset;
+
+ return EFI_SUCCESS;
+}
+
+enum Channels { R, G, B, A, _CHANNELS_MAX };
+static void read_channel_maks(
+ const struct bmp_dib *dib,
+ uint32_t channel_mask[static _CHANNELS_MAX],
+ uint8_t channel_shift[static _CHANNELS_MAX],
+ uint8_t channel_scale[static _CHANNELS_MAX]) {
+
+ assert(dib);
+
+ if (IN_SET(dib->depth, 16, 32) && dib->size >= SIZEOF_BMP_DIB_RGB) {
+ channel_mask[R] = dib->channel_mask_r;
+ channel_mask[G] = dib->channel_mask_g;
+ channel_mask[B] = dib->channel_mask_b;
+ channel_shift[R] = __builtin_ctz(dib->channel_mask_r);
+ channel_shift[G] = __builtin_ctz(dib->channel_mask_g);
+ channel_shift[B] = __builtin_ctz(dib->channel_mask_b);
+ channel_scale[R] = 0xff / ((1 << popcount(dib->channel_mask_r)) - 1);
+ channel_scale[G] = 0xff / ((1 << popcount(dib->channel_mask_g)) - 1);
+ channel_scale[B] = 0xff / ((1 << popcount(dib->channel_mask_b)) - 1);
+
+ if (dib->size >= SIZEOF_BMP_DIB_RGBA && dib->channel_mask_a != 0) {
+ channel_mask[A] = dib->channel_mask_a;
+ channel_shift[A] = __builtin_ctz(dib->channel_mask_a);
+ channel_scale[A] = 0xff / ((1 << popcount(dib->channel_mask_a)) - 1);
+ } else {
+ channel_mask[A] = 0;
+ channel_shift[A] = 0;
+ channel_scale[A] = 0;
+ }
+ } else {
+ bool bpp16 = dib->depth == 16;
+ channel_mask[R] = bpp16 ? 0x7C00 : 0xFF0000;
+ channel_mask[G] = bpp16 ? 0x03E0 : 0x00FF00;
+ channel_mask[B] = bpp16 ? 0x001F : 0x0000FF;
+ channel_mask[A] = bpp16 ? 0x0000 : 0x000000;
+ channel_shift[R] = bpp16 ? 0xA : 0x10;
+ channel_shift[G] = bpp16 ? 0x5 : 0x08;
+ channel_shift[B] = bpp16 ? 0x0 : 0x00;
+ channel_shift[A] = bpp16 ? 0x0 : 0x00;
+ channel_scale[R] = bpp16 ? 0x08 : 0x1;
+ channel_scale[G] = bpp16 ? 0x08 : 0x1;
+ channel_scale[B] = bpp16 ? 0x08 : 0x1;
+ channel_scale[A] = bpp16 ? 0x00 : 0x0;
+ }
+}
+
+static EFI_STATUS bmp_to_blt(
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf,
+ struct bmp_dib *dib,
+ struct bmp_map *map,
+ const uint8_t *pixmap) {
+
+ const uint8_t *in;
+
+ assert(buf);
+ assert(dib);
+ assert(map);
+ assert(pixmap);
+
+ uint32_t channel_mask[_CHANNELS_MAX];
+ uint8_t channel_shift[_CHANNELS_MAX], channel_scale[_CHANNELS_MAX];
+ read_channel_maks(dib, channel_mask, channel_shift, channel_scale);
+
+ /* transform and copy pixels */
+ in = pixmap;
+ for (uint32_t y = 0; y < dib->y; y++) {
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out = &buf[(dib->y - y - 1) * dib->x];
+
+ for (uint32_t x = 0; x < dib->x; x++, in++, out++) {
+ switch (dib->depth) {
+ case 1: {
+ for (unsigned i = 0; i < 8 && x < dib->x; i++) {
+ out->Red = map[((*in) >> (7 - i)) & 1].red;
+ out->Green = map[((*in) >> (7 - i)) & 1].green;
+ out->Blue = map[((*in) >> (7 - i)) & 1].blue;
+ out++;
+ x++;
+ }
+ out--;
+ x--;
+ break;
+ }
+
+ case 4: {
+ unsigned i = (*in) >> 4;
+ out->Red = map[i].red;
+ out->Green = map[i].green;
+ out->Blue = map[i].blue;
+ if (x < (dib->x - 1)) {
+ out++;
+ x++;
+ i = (*in) & 0x0f;
+ out->Red = map[i].red;
+ out->Green = map[i].green;
+ out->Blue = map[i].blue;
+ }
+ break;
+ }
+
+ case 8:
+ out->Red = map[*in].red;
+ out->Green = map[*in].green;
+ out->Blue = map[*in].blue;
+ break;
+
+ case 24:
+ out->Red = in[2];
+ out->Green = in[1];
+ out->Blue = in[0];
+ in += 2;
+ break;
+
+ case 16:
+ case 32: {
+ uint32_t i = dib->depth == 16 ? unaligned_read_ne16(in) :
+ unaligned_read_ne32(in);
+
+ uint8_t r = ((i & channel_mask[R]) >> channel_shift[R]) * channel_scale[R],
+ g = ((i & channel_mask[G]) >> channel_shift[G]) * channel_scale[G],
+ b = ((i & channel_mask[B]) >> channel_shift[B]) * channel_scale[B],
+ a = 0xFFu;
+ if (channel_mask[A] != 0)
+ a = ((i & channel_mask[A]) >> channel_shift[A]) * channel_scale[A];
+
+ out->Red = (out->Red * (0xFFu - a) + r * a) >> 8;
+ out->Green = (out->Green * (0xFFu - a) + g * a) >> 8;
+ out->Blue = (out->Blue * (0xFFu - a) + b * a) >> 8;
+
+ in += dib->depth == 16 ? 1 : 3;
+ break;
+ }
+ }
+ }
+
+ /* add row padding; new lines always start at 32 bit boundary */
+ size_t row_size = in - pixmap;
+ in += ((row_size + 3) & ~3) - row_size;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS graphics_splash(const uint8_t *content, size_t len) {
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL background = {};
+ EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
+ struct bmp_dib *dib;
+ struct bmp_map *map;
+ const uint8_t *pixmap;
+ size_t x_pos = 0, y_pos = 0;
+ EFI_STATUS err;
+
+ if (len == 0)
+ return EFI_SUCCESS;
+
+ assert(content);
+
+ if (strcaseeq16(ST->FirmwareVendor, u"Apple")) {
+ background.Red = 0xc0;
+ background.Green = 0xc0;
+ background.Blue = 0xc0;
+ }
+
+ err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &GraphicsOutput);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = bmp_parse_header(content, len, &dib, &map, &pixmap);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (dib->x < GraphicsOutput->Mode->Info->HorizontalResolution)
+ x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2;
+ if (dib->y < GraphicsOutput->Mode->Info->VerticalResolution)
+ y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2;
+
+ err = GraphicsOutput->Blt(
+ GraphicsOutput, &background,
+ EfiBltVideoFill, 0, 0, 0, 0,
+ GraphicsOutput->Mode->Info->HorizontalResolution,
+ GraphicsOutput->Mode->Info->VerticalResolution, 0);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Read in current screen content to perform proper alpha blending. */
+ _cleanup_free_ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *blt = xnew(
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL, dib->x * dib->y);
+ err = GraphicsOutput->Blt(
+ GraphicsOutput, blt,
+ EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0,
+ dib->x, dib->y, 0);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = bmp_to_blt(blt, dib, map, pixmap);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = graphics_mode(true);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ return GraphicsOutput->Blt(
+ GraphicsOutput, blt,
+ EfiBltBufferToVideo, 0, 0, x_pos, y_pos,
+ dib->x, dib->y, 0);
+}
diff --git a/src/boot/efi/splash.h b/src/boot/efi/splash.h
new file mode 100644
index 0000000..a66eb24
--- /dev/null
+++ b/src/boot/efi/splash.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+EFI_STATUS graphics_splash(const uint8_t *content, size_t len);
diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c
new file mode 100644
index 0000000..7ef3e76
--- /dev/null
+++ b/src/boot/efi/stub.c
@@ -0,0 +1,816 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "cpio.h"
+#include "device-path-util.h"
+#include "devicetree.h"
+#include "graphics.h"
+#include "linux.h"
+#include "measure.h"
+#include "memory-util-fundamental.h"
+#include "part-discovery.h"
+#include "pe.h"
+#include "proto/shell-parameters.h"
+#include "random-seed.h"
+#include "sbat.h"
+#include "secure-boot.h"
+#include "shim.h"
+#include "splash.h"
+#include "tpm2-pcr.h"
+#include "uki.h"
+#include "util.h"
+#include "version.h"
+#include "vmm.h"
+
+/* magic string to find in the binary image */
+DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####");
+
+DECLARE_SBAT(SBAT_STUB_SECTION_TEXT);
+
+static EFI_STATUS combine_initrd(
+ EFI_PHYSICAL_ADDRESS initrd_base, size_t initrd_size,
+ const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds,
+ Pages *ret_initr_pages, size_t *ret_initrd_size) {
+
+ size_t n;
+
+ assert(ret_initr_pages);
+ assert(ret_initrd_size);
+
+ /* Combines four initrds into one, by simple concatenation in memory */
+
+ n = ALIGN4(initrd_size); /* main initrd might not be padded yet */
+
+ for (size_t i = 0; i < n_extra_initrds; i++) {
+ if (!extra_initrds[i])
+ continue;
+
+ if (n > SIZE_MAX - extra_initrd_sizes[i])
+ return EFI_OUT_OF_RESOURCES;
+
+ n += extra_initrd_sizes[i];
+ }
+
+ _cleanup_pages_ Pages pages = xmalloc_pages(
+ AllocateMaxAddress,
+ EfiLoaderData,
+ EFI_SIZE_TO_PAGES(n),
+ UINT32_MAX /* Below 4G boundary. */);
+ uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr);
+ if (initrd_base != 0) {
+ size_t pad;
+
+ /* Order matters, the real initrd must come first, since it might include microcode updates
+ * which the kernel only looks for in the first cpio archive */
+ p = mempcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
+
+ pad = ALIGN4(initrd_size) - initrd_size;
+ if (pad > 0) {
+ memzero(p, pad);
+ p += pad;
+ }
+ }
+
+ for (size_t i = 0; i < n_extra_initrds; i++) {
+ if (!extra_initrds[i])
+ continue;
+
+ p = mempcpy(p, extra_initrds[i], extra_initrd_sizes[i]);
+ }
+
+ assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p);
+
+ *ret_initr_pages = pages;
+ *ret_initrd_size = n;
+ pages.n_pages = 0;
+
+ return EFI_SUCCESS;
+}
+
+static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
+ static const uint64_t stub_features =
+ EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */
+ EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */
+ EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */
+ EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */
+ EFI_STUB_FEATURE_RANDOM_SEED | /* We pass a random seed to the kernel */
+ EFI_STUB_FEATURE_CMDLINE_ADDONS | /* We pick up .cmdline addons */
+ EFI_STUB_FEATURE_CMDLINE_SMBIOS | /* We support extending kernel cmdline from SMBIOS Type #11 */
+ EFI_STUB_FEATURE_DEVICETREE_ADDONS | /* We pick up .dtb addons */
+ 0;
+
+ assert(loaded_image);
+
+ /* Export the device path this image is started from, if it's not set yet */
+ if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) {
+ _cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle);
+ if (uuid)
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0);
+ }
+
+ /* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the
+ * UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note
+ * that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us,
+ * in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong
+ * here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath
+ * is non-NULL explicitly.) */
+ if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS &&
+ loaded_image->FilePath) {
+ _cleanup_free_ char16_t *s = NULL;
+ if (device_path_to_str(loaded_image->FilePath, &s) == EFI_SUCCESS)
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", s, 0);
+ }
+
+ /* if LoaderFirmwareInfo is not set, let's set it */
+ if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) {
+ _cleanup_free_ char16_t *s = NULL;
+ s = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", s, 0);
+ }
+
+ /* ditto for LoaderFirmwareType */
+ if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) {
+ _cleanup_free_ char16_t *s = NULL;
+ s = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
+ efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", s, 0);
+ }
+
+
+ /* add StubInfo (this is one is owned by the stub, hence we unconditionally override this with our
+ * own data) */
+ (void) efivar_set(MAKE_GUID_PTR(LOADER), u"StubInfo", u"systemd-stub " GIT_VERSION, 0);
+
+ (void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"StubFeatures", stub_features, 0);
+}
+
+static bool use_load_options(
+ EFI_HANDLE stub_image,
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ bool have_cmdline,
+ char16_t **ret) {
+
+ assert(stub_image);
+ assert(loaded_image);
+ assert(ret);
+
+ /* We only allow custom command lines if we aren't in secure boot or if no cmdline was baked into
+ * the stub image.
+ * We also don't allow it if we are in confidential vms and secureboot is on. */
+ if (secure_boot_enabled() && (have_cmdline || is_confidential_vm()))
+ return false;
+
+ /* We also do a superficial check whether first character of passed command line
+ * is printable character (for compat with some Dell systems which fill in garbage?). */
+ if (loaded_image->LoadOptionsSize < sizeof(char16_t) || ((char16_t *) loaded_image->LoadOptions)[0] <= 0x1F)
+ return false;
+
+ /* The UEFI shell registers EFI_SHELL_PARAMETERS_PROTOCOL onto images it runs. This lets us know that
+ * LoadOptions starts with the stub binary path which we want to strip off. */
+ EFI_SHELL_PARAMETERS_PROTOCOL *shell;
+ if (BS->HandleProtocol(stub_image, MAKE_GUID_PTR(EFI_SHELL_PARAMETERS_PROTOCOL), (void **) &shell)
+ != EFI_SUCCESS) {
+ /* Not running from EFI shell, use entire LoadOptions. Note that LoadOptions is a void*, so
+ * it could be anything! */
+ *ret = xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t));
+ mangle_stub_cmdline(*ret);
+ return true;
+ }
+
+ if (shell->Argc < 2)
+ /* No arguments were provided? Then we fall back to built-in cmdline. */
+ return false;
+
+ /* Assemble the command line ourselves without our stub path. */
+ *ret = xstrdup16(shell->Argv[1]);
+ for (size_t i = 2; i < shell->Argc; i++) {
+ _cleanup_free_ char16_t *old = *ret;
+ *ret = xasprintf("%ls %ls", old, shell->Argv[i]);
+ }
+
+ mangle_stub_cmdline(*ret);
+ return true;
+}
+
+static EFI_STATUS load_addons_from_dir(
+ EFI_FILE *root,
+ const char16_t *prefix,
+ char16_t ***items,
+ size_t *n_items,
+ size_t *n_allocated) {
+
+ _cleanup_(file_closep) EFI_FILE *extra_dir = NULL;
+ _cleanup_free_ EFI_FILE_INFO *dirent = NULL;
+ size_t dirent_size = 0;
+ EFI_STATUS err;
+
+ assert(root);
+ assert(prefix);
+ assert(items);
+ assert(n_items);
+ assert(n_allocated);
+
+ err = open_directory(root, prefix, &extra_dir);
+ if (err == EFI_NOT_FOUND)
+ /* No extra subdir, that's totally OK */
+ return EFI_SUCCESS;
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to open addons directory '%ls': %m", prefix);
+
+ for (;;) {
+ _cleanup_free_ char16_t *d = NULL;
+
+ err = readdir(extra_dir, &dirent, &dirent_size);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to read addons directory of loaded image: %m");
+ if (!dirent) /* End of directory */
+ break;
+
+ if (dirent->FileName[0] == '.')
+ continue;
+ if (FLAGS_SET(dirent->Attribute, EFI_FILE_DIRECTORY))
+ continue;
+ if (!is_ascii(dirent->FileName))
+ continue;
+ if (strlen16(dirent->FileName) > 255) /* Max filename size on Linux */
+ continue;
+ if (!endswith_no_case(dirent->FileName, u".addon.efi"))
+ continue;
+
+ d = xstrdup16(dirent->FileName);
+
+ if (*n_items + 2 > *n_allocated) {
+ /* We allocate 16 entries at a time, as a matter of optimization */
+ if (*n_items > (SIZE_MAX / sizeof(uint16_t)) - 16) /* Overflow check, just in case */
+ return log_oom();
+
+ size_t m = *n_items + 16;
+ *items = xrealloc(*items, *n_allocated * sizeof(uint16_t *), m * sizeof(uint16_t *));
+ *n_allocated = m;
+ }
+
+ (*items)[(*n_items)++] = TAKE_PTR(d);
+ (*items)[*n_items] = NULL; /* Let's always NUL terminate, to make freeing via strv_free() easy */
+ }
+
+ return EFI_SUCCESS;
+}
+
+static void cmdline_append_and_measure_addons(
+ char16_t *cmdline_global,
+ char16_t *cmdline_uki,
+ char16_t **cmdline_append,
+ bool *ret_parameters_measured) {
+
+ _cleanup_free_ char16_t *tmp = NULL, *merged = NULL;
+ bool m = false;
+
+ assert(cmdline_append);
+ assert(ret_parameters_measured);
+
+ if (isempty(cmdline_global) && isempty(cmdline_uki))
+ return;
+
+ merged = xasprintf("%ls%ls%ls",
+ strempty(cmdline_global),
+ isempty(cmdline_global) || isempty(cmdline_uki) ? u"" : u" ",
+ strempty(cmdline_uki));
+
+ mangle_stub_cmdline(merged);
+
+ if (isempty(merged))
+ return;
+
+ (void) tpm_log_load_options(merged, &m);
+ *ret_parameters_measured = m;
+
+ tmp = TAKE_PTR(*cmdline_append);
+ *cmdline_append = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", merged);
+}
+
+static void dtb_install_addons(
+ struct devicetree_state *dt_state,
+ void **dt_bases,
+ size_t *dt_sizes,
+ char16_t **dt_filenames,
+ size_t n_dts,
+ bool *ret_parameters_measured) {
+
+ int parameters_measured = -1;
+ EFI_STATUS err;
+
+ assert(dt_state);
+ assert(n_dts == 0 || (dt_bases && dt_sizes && dt_filenames));
+ assert(ret_parameters_measured);
+
+ for (size_t i = 0; i < n_dts; ++i) {
+ err = devicetree_install_from_memory(dt_state, dt_bases[i], dt_sizes[i]);
+ if (err != EFI_SUCCESS)
+ log_error_status(err, "Error loading addon devicetree, ignoring: %m");
+ else {
+ bool m = false;
+
+ err = tpm_log_tagged_event(
+ TPM2_PCR_KERNEL_CONFIG,
+ POINTER_TO_PHYSICAL_ADDRESS(dt_bases[i]),
+ dt_sizes[i],
+ DEVICETREE_ADDON_EVENT_TAG_ID,
+ dt_filenames[i],
+ &m);
+ if (err != EFI_SUCCESS)
+ return (void) log_error_status(
+ err,
+ "Unable to add measurement of DTB addon #%zu to PCR %i: %m",
+ i,
+ TPM2_PCR_KERNEL_CONFIG);
+
+ parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
+ }
+ }
+
+ *ret_parameters_measured = parameters_measured;
+}
+
+static void dt_bases_free(void **dt_bases, size_t n_dt) {
+ assert(dt_bases || n_dt == 0);
+
+ for (size_t i = 0; i < n_dt; ++i)
+ free(dt_bases[i]);
+
+ free(dt_bases);
+}
+
+static void dt_filenames_free(char16_t **dt_filenames, size_t n_dt) {
+ assert(dt_filenames || n_dt == 0);
+
+ for (size_t i = 0; i < n_dt; ++i)
+ free(dt_filenames[i]);
+
+ free(dt_filenames);
+}
+
+static EFI_STATUS load_addons(
+ EFI_HANDLE stub_image,
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
+ const char16_t *prefix,
+ const char *uname,
+ char16_t **ret_cmdline,
+ void ***ret_dt_bases,
+ size_t **ret_dt_sizes,
+ char16_t ***ret_dt_filenames,
+ size_t *ret_n_dt) {
+
+ _cleanup_free_ size_t *dt_sizes = NULL;
+ _cleanup_(strv_freep) char16_t **items = NULL;
+ _cleanup_(file_closep) EFI_FILE *root = NULL;
+ _cleanup_free_ char16_t *cmdline = NULL;
+ size_t n_items = 0, n_allocated = 0, n_dt = 0;
+ char16_t **dt_filenames = NULL;
+ void **dt_bases = NULL;
+ EFI_STATUS err;
+
+ assert(stub_image);
+ assert(loaded_image);
+ assert(prefix);
+ assert(!!ret_dt_bases == !!ret_dt_sizes);
+ assert(!!ret_dt_bases == !!ret_n_dt);
+ assert(!!ret_dt_filenames == !!ret_n_dt);
+
+ if (!loaded_image->DeviceHandle)
+ return EFI_SUCCESS;
+
+ CLEANUP_ARRAY(dt_bases, n_dt, dt_bases_free);
+ CLEANUP_ARRAY(dt_filenames, n_dt, dt_filenames_free);
+
+ err = open_volume(loaded_image->DeviceHandle, &root);
+ if (err == EFI_UNSUPPORTED)
+ /* Error will be unsupported if the bootloader doesn't implement the file system protocol on
+ * its file handles. */
+ return EFI_SUCCESS;
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Unable to open root directory: %m");
+
+ err = load_addons_from_dir(root, prefix, &items, &n_items, &n_allocated);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (n_items == 0)
+ return EFI_SUCCESS; /* Empty directory */
+
+ /* Now, sort the files we found, to make this uniform and stable (and to ensure the TPM measurements
+ * are not dependent on read order) */
+ sort_pointer_array((void**) items, n_items, (compare_pointer_func_t) strcmp16);
+
+ for (size_t i = 0; i < n_items; i++) {
+ size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
+ _cleanup_free_ EFI_DEVICE_PATH *addon_path = NULL;
+ _cleanup_(unload_imagep) EFI_HANDLE addon = NULL;
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_addon = NULL;
+ _cleanup_free_ char16_t *addon_spath = NULL;
+
+ addon_spath = xasprintf("%ls\\%ls", prefix, items[i]);
+ err = make_file_device_path(loaded_image->DeviceHandle, addon_spath, &addon_path);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error making device path for %ls: %m", addon_spath);
+
+ /* 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);
+ if (err != EFI_SUCCESS) {
+ log_error_status(err,
+ "Failed to read '%ls' from '%ls', ignoring: %m",
+ items[i],
+ addon_spath);
+ continue;
+ }
+
+ err = BS->HandleProtocol(addon,
+ MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL),
+ (void **) &loaded_addon);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Failed to find protocol in %ls: %m", items[i]);
+
+ err = pe_memory_locate_sections(loaded_addon->ImageBase, unified_sections, addrs, szs);
+ if (err != EFI_SUCCESS ||
+ (szs[UNIFIED_SECTION_CMDLINE] == 0 && szs[UNIFIED_SECTION_DTB] == 0)) {
+ if (err == EFI_SUCCESS)
+ err = EFI_NOT_FOUND;
+ log_error_status(err,
+ "Unable to locate embedded .cmdline/.dtb sections in %ls, ignoring: %m",
+ items[i]);
+ continue;
+ }
+
+ /* We want to enforce that addons are not UKIs, i.e.: they must not embed a kernel. */
+ if (szs[UNIFIED_SECTION_LINUX] > 0) {
+ log_error_status(EFI_INVALID_PARAMETER, "%ls is a UKI, not an addon, ignoring: %m", items[i]);
+ continue;
+ }
+
+ /* Also enforce that, in case it is specified, .uname matches as a quick way to allow
+ * enforcing compatibility with a specific UKI only */
+ if (uname && szs[UNIFIED_SECTION_UNAME] > 0 &&
+ !strneq8(uname,
+ (char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_UNAME],
+ szs[UNIFIED_SECTION_UNAME])) {
+ log_error(".uname mismatch between %ls and UKI, ignoring", items[i]);
+ continue;
+ }
+
+ if (ret_cmdline && szs[UNIFIED_SECTION_CMDLINE] > 0) {
+ _cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline),
+ *extra16 = xstrn8_to_16((char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_CMDLINE],
+ szs[UNIFIED_SECTION_CMDLINE]);
+ cmdline = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", extra16);
+ }
+
+ if (ret_dt_bases && szs[UNIFIED_SECTION_DTB] > 0) {
+ dt_sizes = xrealloc(dt_sizes,
+ n_dt * sizeof(size_t),
+ (n_dt + 1) * sizeof(size_t));
+ dt_sizes[n_dt] = szs[UNIFIED_SECTION_DTB];
+
+ dt_bases = xrealloc(dt_bases,
+ n_dt * sizeof(void *),
+ (n_dt + 1) * sizeof(void *));
+ dt_bases[n_dt] = xmemdup((uint8_t*)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_DTB],
+ dt_sizes[n_dt]);
+
+ dt_filenames = xrealloc(dt_filenames,
+ n_dt * sizeof(char16_t *),
+ (n_dt + 1) * sizeof(char16_t *));
+ dt_filenames[n_dt] = xstrdup16(items[i]);
+
+ ++n_dt;
+ }
+ }
+
+ if (ret_cmdline && !isempty(cmdline))
+ *ret_cmdline = TAKE_PTR(cmdline);
+
+ if (ret_n_dt && n_dt > 0) {
+ *ret_dt_filenames = TAKE_PTR(dt_filenames);
+ *ret_dt_bases = TAKE_PTR(dt_bases);
+ *ret_dt_sizes = TAKE_PTR(dt_sizes);
+ *ret_n_dt = n_dt;
+ }
+
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS run(EFI_HANDLE image) {
+ _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL;
+ size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
+ void **dt_bases_addons_global = NULL, **dt_bases_addons_uki = NULL;
+ char16_t **dt_filenames_addons_global = NULL, **dt_filenames_addons_uki = NULL;
+ _cleanup_free_ size_t *dt_sizes_addons_global = NULL, *dt_sizes_addons_uki = NULL;
+ size_t linux_size, initrd_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0;
+ EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base;
+ _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
+ EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
+ size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
+ _cleanup_free_ char16_t *cmdline = NULL, *cmdline_addons_global = NULL, *cmdline_addons_uki = NULL;
+ int sections_measured = -1, parameters_measured = -1;
+ _cleanup_free_ char *uname = NULL;
+ bool sysext_measured = false, m;
+ uint64_t loader_features = 0;
+ EFI_STATUS err;
+
+ err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
+ if (err != EFI_SUCCESS)
+ return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
+
+ if (loaded_image->DeviceHandle && /* Handle case, where bootloader doesn't support DeviceHandle. */
+ (efivar_get_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", &loader_features) != EFI_SUCCESS ||
+ !FLAGS_SET(loader_features, EFI_LOADER_FEATURE_RANDOM_SEED))) {
+ _cleanup_(file_closep) EFI_FILE *esp_dir = NULL;
+
+ err = partition_open(MAKE_GUID_PTR(ESP), loaded_image->DeviceHandle, NULL, &esp_dir);
+ if (err == EFI_SUCCESS) /* Non-fatal on failure, so that we still boot without it. */
+ (void) process_random_seed(esp_dir);
+ }
+
+ err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, addrs, szs);
+ if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) {
+ if (err == EFI_SUCCESS)
+ err = EFI_NOT_FOUND;
+ return log_error_status(err, "Unable to locate embedded .linux section: %m");
+ }
+
+ CLEANUP_ARRAY(dt_bases_addons_global, n_dts_addons_global, dt_bases_free);
+ CLEANUP_ARRAY(dt_bases_addons_uki, n_dts_addons_uki, dt_bases_free);
+ CLEANUP_ARRAY(dt_filenames_addons_global, n_dts_addons_global, dt_filenames_free);
+ CLEANUP_ARRAY(dt_filenames_addons_uki, n_dts_addons_uki, dt_filenames_free);
+
+ /* Now that we have the UKI sections loaded, also load global first and then local (per-UKI)
+ * addons. The data is loaded at once, and then used later. */
+ err = load_addons(
+ image,
+ loaded_image,
+ u"\\loader\\addons",
+ uname,
+ &cmdline_addons_global,
+ &dt_bases_addons_global,
+ &dt_sizes_addons_global,
+ &dt_filenames_addons_global,
+ &n_dts_addons_global);
+ if (err != EFI_SUCCESS)
+ log_error_status(err, "Error loading global addons, ignoring: %m");
+
+ /* Some bootloaders always pass NULL in FilePath, so we need to check for it here. */
+ _cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath);
+ if (dropin_dir) {
+ err = load_addons(
+ image,
+ loaded_image,
+ dropin_dir,
+ uname,
+ &cmdline_addons_uki,
+ &dt_bases_addons_uki,
+ &dt_sizes_addons_uki,
+ &dt_filenames_addons_uki,
+ &n_dts_addons_uki);
+ if (err != EFI_SUCCESS)
+ log_error_status(err, "Error loading UKI-specific addons, ignoring: %m");
+ }
+
+ /* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written
+ * into so far), so that we have one PCR that we can nicely write policies against because it
+ * contains all static data of this image, and thus can be easily be pre-calculated. */
+ for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) {
+
+ if (!unified_section_measure(section)) /* shall not measure? */
+ continue;
+
+ if (szs[section] == 0) /* not found */
+ continue;
+
+ m = false;
+
+ /* First measure the name of the section */
+ (void) tpm_log_event_ascii(
+ TPM2_PCR_KERNEL_BOOT,
+ POINTER_TO_PHYSICAL_ADDRESS(unified_sections[section]),
+ strsize8(unified_sections[section]), /* including NUL byte */
+ unified_sections[section],
+ &m);
+
+ sections_measured = sections_measured < 0 ? m : (sections_measured && m);
+
+ /* Then measure the data of the section */
+ (void) tpm_log_event_ascii(
+ TPM2_PCR_KERNEL_BOOT,
+ POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section],
+ szs[section],
+ unified_sections[section],
+ &m);
+
+ sections_measured = sections_measured < 0 ? m : (sections_measured && m);
+ }
+
+ /* After we are done, set an EFI variable that tells userspace this was done successfully, and encode
+ * in it which PCR was used. */
+ if (sections_measured > 0)
+ (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelImage", TPM2_PCR_KERNEL_BOOT, 0);
+
+ /* Show splash screen as early as possible */
+ graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]);
+
+ if (szs[UNIFIED_SECTION_UNAME] > 0)
+ uname = xstrndup8((char *)loaded_image->ImageBase + addrs[UNIFIED_SECTION_UNAME],
+ szs[UNIFIED_SECTION_UNAME]);
+
+ if (use_load_options(image, loaded_image, szs[UNIFIED_SECTION_CMDLINE] > 0, &cmdline)) {
+ /* Let's measure the passed kernel command line into the TPM. Note that this possibly
+ * duplicates what we already did in the boot menu, if that was already used. However, since
+ * we want the boot menu to support an EFI binary, and want to this stub to be usable from
+ * any boot menu, let's measure things anyway. */
+ m = false;
+ (void) tpm_log_load_options(cmdline, &m);
+ parameters_measured = m;
+ } else if (szs[UNIFIED_SECTION_CMDLINE] > 0) {
+ cmdline = xstrn8_to_16(
+ (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE],
+ szs[UNIFIED_SECTION_CMDLINE]);
+ mangle_stub_cmdline(cmdline);
+ }
+
+ /* If we have any extra command line to add via PE addons, load them now and append, and
+ * measure the additions together, after the embedded options, but before the smbios ones,
+ * so that the order is reversed from "most hardcoded" to "most dynamic". The global addons are
+ * loaded first, and the image-specific ones later, for the same reason. */
+ cmdline_append_and_measure_addons(cmdline_addons_global, cmdline_addons_uki, &cmdline, &m);
+ parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
+
+ /* SMBIOS OEM Strings data is controlled by the host admin and not covered
+ * by the VM attestation, so MUST NOT be trusted when in a confidential VM */
+ if (!is_confidential_vm()) {
+ const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra");
+ if (extra) {
+ _cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), *extra16 = xstr8_to_16(extra);
+ cmdline = xasprintf("%ls %ls", tmp, extra16);
+
+ /* SMBIOS strings are measured in PCR1, but we also want to measure them in our specific
+ * PCR12, as firmware-owned PCRs are very difficult to use as they'll contain unpredictable
+ * measurements that are not under control of the machine owner. */
+ m = false;
+ (void) tpm_log_load_options(extra16, &m);
+ parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
+ }
+ }
+
+ export_variables(loaded_image);
+
+ if (pack_cpio(loaded_image,
+ NULL,
+ u".cred",
+ ".extra/credentials",
+ /* dir_mode= */ 0500,
+ /* access_mode= */ 0400,
+ /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG,
+ u"Credentials initrd",
+ &credential_initrd,
+ &credential_initrd_size,
+ &m) == EFI_SUCCESS)
+ parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
+
+ if (pack_cpio(loaded_image,
+ u"\\loader\\credentials",
+ u".cred",
+ ".extra/global_credentials",
+ /* dir_mode= */ 0500,
+ /* access_mode= */ 0400,
+ /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG,
+ u"Global credentials initrd",
+ &global_credential_initrd,
+ &global_credential_initrd_size,
+ &m) == EFI_SUCCESS)
+ parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
+
+ if (pack_cpio(loaded_image,
+ NULL,
+ u".raw",
+ ".extra/sysext",
+ /* dir_mode= */ 0555,
+ /* access_mode= */ 0444,
+ /* tpm_pcr= */ TPM2_PCR_SYSEXTS,
+ u"System extension initrd",
+ &sysext_initrd,
+ &sysext_initrd_size,
+ &m) == EFI_SUCCESS)
+ sysext_measured = m;
+
+ dt_size = szs[UNIFIED_SECTION_DTB];
+ dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0;
+
+ /* First load the base device tree, then fix it up using addons - global first, then per-UKI. */
+ if (dt_size > 0) {
+ err = devicetree_install_from_memory(
+ &dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size);
+ if (err != EFI_SUCCESS)
+ log_error_status(err, "Error loading embedded devicetree: %m");
+ }
+
+ dtb_install_addons(&dt_state,
+ dt_bases_addons_global,
+ dt_sizes_addons_global,
+ dt_filenames_addons_global,
+ n_dts_addons_global,
+ &m);
+ parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
+ dtb_install_addons(&dt_state,
+ dt_bases_addons_uki,
+ dt_sizes_addons_uki,
+ dt_filenames_addons_uki,
+ n_dts_addons_uki,
+ &m);
+ parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
+
+ if (parameters_measured > 0)
+ (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM2_PCR_KERNEL_CONFIG, 0);
+ if (sysext_measured)
+ (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM2_PCR_SYSEXTS, 0);
+
+ /* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it
+ * to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section
+ * is not measured, neither as raw section (see above), nor as cpio (here), because it is the
+ * signature of expected PCR values, i.e. its input are PCR measurements, and hence it shouldn't
+ * itself be input for PCR measurements. */
+ if (szs[UNIFIED_SECTION_PCRSIG] > 0)
+ (void) pack_cpio_literal(
+ (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRSIG],
+ szs[UNIFIED_SECTION_PCRSIG],
+ ".extra",
+ u"tpm2-pcr-signature.json",
+ /* dir_mode= */ 0555,
+ /* access_mode= */ 0444,
+ /* tpm_pcr= */ UINT32_MAX,
+ /* tpm_description= */ NULL,
+ &pcrsig_initrd,
+ &pcrsig_initrd_size,
+ /* ret_measured= */ NULL);
+
+ /* If the public key used for the PCR signatures was embedded in the PE image, then let's wrap it in
+ * a cpio and also pass it to the kernel, so that it can be read from
+ * /.extra/tpm2-pcr-public-key.pem. This section is already measure above, hence we won't measure the
+ * cpio. */
+ if (szs[UNIFIED_SECTION_PCRPKEY] > 0)
+ (void) pack_cpio_literal(
+ (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRPKEY],
+ szs[UNIFIED_SECTION_PCRPKEY],
+ ".extra",
+ u"tpm2-pcr-public-key.pem",
+ /* dir_mode= */ 0555,
+ /* access_mode= */ 0444,
+ /* tpm_pcr= */ UINT32_MAX,
+ /* tpm_description= */ NULL,
+ &pcrpkey_initrd,
+ &pcrpkey_initrd_size,
+ /* ret_measured= */ NULL);
+
+ linux_size = szs[UNIFIED_SECTION_LINUX];
+ linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX];
+
+ initrd_size = szs[UNIFIED_SECTION_INITRD];
+ initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0;
+
+ _cleanup_pages_ Pages initrd_pages = {};
+ if (credential_initrd || global_credential_initrd || sysext_initrd || pcrsig_initrd || pcrpkey_initrd) {
+ /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */
+ err = combine_initrd(
+ initrd_base, initrd_size,
+ (const void*const[]) {
+ credential_initrd,
+ global_credential_initrd,
+ sysext_initrd,
+ pcrsig_initrd,
+ pcrpkey_initrd,
+ },
+ (const size_t[]) {
+ credential_initrd_size,
+ global_credential_initrd_size,
+ sysext_initrd_size,
+ pcrsig_initrd_size,
+ pcrpkey_initrd_size,
+ },
+ 5,
+ &initrd_pages, &initrd_size);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ initrd_base = initrd_pages.addr;
+
+ /* Given these might be large let's free them explicitly, quickly. */
+ credential_initrd = mfree(credential_initrd);
+ global_credential_initrd = mfree(global_credential_initrd);
+ sysext_initrd = mfree(sysext_initrd);
+ pcrsig_initrd = mfree(pcrsig_initrd);
+ pcrpkey_initrd = mfree(pcrpkey_initrd);
+ }
+
+ err = linux_exec(image, cmdline,
+ PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size,
+ PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
+ graphics_mode(false);
+ return err;
+}
+
+DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /*wait_for_debugger=*/false);
diff --git a/src/boot/efi/test-bcd.c b/src/boot/efi/test-bcd.c
new file mode 100644
index 0000000..3f93ca0
--- /dev/null
+++ b/src/boot/efi/test-bcd.c
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "bcd.h"
+#include "compress.h"
+#include "fileio.h"
+#include "tests.h"
+#include "utf8.h"
+
+/* Include the implementation directly, so we can poke at some internals. */
+#include "bcd.c"
+
+static void load_bcd(const char *path, void **ret_bcd, size_t *ret_bcd_len) {
+ size_t len;
+ _cleanup_free_ char *fn = NULL, *compressed = NULL;
+
+ assert_se(get_testdata_dir(path, &fn) >= 0);
+ assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, SIZE_MAX, 0, NULL, &compressed, &len) >= 0);
+ assert_se(decompress_blob_zstd(compressed, len, ret_bcd, ret_bcd_len, SIZE_MAX) >= 0);
+}
+
+static void test_get_bcd_title_one(
+ const char *path,
+ const char16_t *title_expect,
+ size_t title_len_expect) {
+
+ size_t len;
+ _cleanup_free_ void *bcd = NULL;
+
+ log_info("/* %s(%s) */", __func__, path);
+
+ load_bcd(path, &bcd, &len);
+
+ char16_t *title = get_bcd_title(bcd, len);
+ if (title_expect) {
+ assert_se(title);
+ assert_se(memcmp(title, title_expect, title_len_expect) == 0);
+ } else
+ assert_se(!title);
+}
+
+TEST(get_bcd_title) {
+ test_get_bcd_title_one("test-bcd/win10.bcd.zst", u"Windows 10", sizeof(u"Windows 10"));
+
+ test_get_bcd_title_one("test-bcd/description-bad-type.bcd.zst", NULL, 0);
+ test_get_bcd_title_one("test-bcd/description-empty.bcd.zst", NULL, 0);
+ test_get_bcd_title_one("test-bcd/description-missing.bcd.zst", NULL, 0);
+ test_get_bcd_title_one("test-bcd/description-too-small.bcd.zst", NULL, 0);
+ test_get_bcd_title_one("test-bcd/displayorder-bad-name.bcd.zst", NULL, 0);
+ test_get_bcd_title_one("test-bcd/displayorder-bad-size.bcd.zst", NULL, 0);
+ test_get_bcd_title_one("test-bcd/displayorder-bad-type.bcd.zst", NULL, 0);
+ test_get_bcd_title_one("test-bcd/empty.bcd.zst", NULL, 0);
+}
+
+TEST(base_block) {
+ size_t len;
+ BaseBlock backup;
+ uint8_t *bcd_base;
+ _cleanup_free_ BaseBlock *bcd = NULL;
+
+ load_bcd("test-bcd/win10.bcd.zst", (void **) &bcd, &len);
+ backup = *bcd;
+ bcd_base = (uint8_t *) bcd;
+
+ assert_se(get_bcd_title(bcd_base, len));
+
+ /* Try various "corruptions" of the base block. */
+
+ assert_se(!get_bcd_title(bcd_base, sizeof(BaseBlock) - 1));
+
+ bcd->sig = 0;
+ assert_se(!get_bcd_title(bcd_base, len));
+ *bcd = backup;
+
+ bcd->version_minor = 2;
+ assert_se(!get_bcd_title(bcd_base, len));
+ *bcd = backup;
+
+ bcd->version_major = 4;
+ assert_se(!get_bcd_title(bcd_base, len));
+ *bcd = backup;
+
+ bcd->type = 1;
+ assert_se(!get_bcd_title(bcd_base, len));
+ *bcd = backup;
+
+ bcd->primary_seqnum++;
+ assert_se(!get_bcd_title(bcd_base, len));
+ *bcd = backup;
+}
+
+TEST(bad_bcd) {
+ size_t len;
+ uint8_t *hbins;
+ uint32_t offset;
+ _cleanup_free_ void *bcd = NULL;
+
+ /* This BCD hive has been manipulated to have bad offsets/sizes at various places. */
+ load_bcd("test-bcd/corrupt.bcd.zst", &bcd, &len);
+
+ assert_se(len >= HIVE_CELL_OFFSET);
+ hbins = (uint8_t *) bcd + HIVE_CELL_OFFSET;
+ len -= HIVE_CELL_OFFSET;
+ offset = ((BaseBlock *) bcd)->root_cell_offset;
+
+ const Key *root = get_key(hbins, len, offset, "\0");
+ assert_se(root);
+ assert_se(!get_key(hbins, sizeof(Key) - 1, offset, "\0"));
+
+ assert_se(!get_key(hbins, len, offset, "\0BadOffset\0"));
+ assert_se(!get_key(hbins, len, offset, "\0BadSig\0"));
+ assert_se(!get_key(hbins, len, offset, "\0BadKeyNameLen\0"));
+ assert_se(!get_key(hbins, len, offset, "\0SubkeyBadOffset\0Dummy\0"));
+ assert_se(!get_key(hbins, len, offset, "\0SubkeyBadSig\0Dummy\0"));
+ assert_se(!get_key(hbins, len, offset, "\0SubkeyBadNEntries\0Dummy\0"));
+
+ assert_se(!get_key_value(hbins, len, root, "Dummy"));
+
+ const Key *kv_bad_offset = get_key(hbins, len, offset, "\0KeyValuesBadOffset\0");
+ assert_se(kv_bad_offset);
+ assert_se(!get_key_value(hbins, len, kv_bad_offset, "Dummy"));
+
+ const Key *kv_bad_n_key_values = get_key(hbins, len, offset, "\0KeyValuesBadNKeyValues\0");
+ assert_se(kv_bad_n_key_values);
+ assert_se(!get_key_value(hbins, len, kv_bad_n_key_values, "Dummy"));
+
+ const Key *kv = get_key(hbins, len, offset, "\0KeyValues\0");
+ assert_se(kv);
+
+ assert_se(!get_key_value(hbins, len, kv, "BadOffset"));
+ assert_se(!get_key_value(hbins, len, kv, "BadSig"));
+ assert_se(!get_key_value(hbins, len, kv, "BadNameLen"));
+ assert_se(!get_key_value(hbins, len, kv, "InlineData"));
+ assert_se(!get_key_value(hbins, len, kv, "BadDataOffset"));
+ assert_se(!get_key_value(hbins, len, kv, "BadDataSize"));
+}
+
+TEST(argv_bcds) {
+ for (int i = 1; i < saved_argc; i++) {
+ size_t len;
+ _cleanup_free_ void *bcd = NULL;
+
+ assert_se(read_full_file_full(
+ AT_FDCWD,
+ saved_argv[i],
+ UINT64_MAX,
+ SIZE_MAX,
+ 0,
+ NULL,
+ (char **) &bcd,
+ &len) >= 0);
+
+ char16_t *title = get_bcd_title(bcd, len);
+ if (title) {
+ _cleanup_free_ char *title_utf8 = utf16_to_utf8(title, SIZE_MAX);
+ log_info("%s: \"%s\"", saved_argv[i], title_utf8);
+ } else
+ log_info("%s: Bad BCD", saved_argv[i]);
+ }
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
diff --git a/src/boot/efi/test-efi-string.c b/src/boot/efi/test-efi-string.c
new file mode 100644
index 0000000..b71a0c3
--- /dev/null
+++ b/src/boot/efi/test-efi-string.c
@@ -0,0 +1,794 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <fnmatch.h>
+
+#include "efi-string.h"
+#include "fileio.h"
+#include "tests.h"
+
+TEST(strlen8) {
+ assert_se(strlen8(NULL) == 0);
+ assert_se(strlen8("") == 0);
+ assert_se(strlen8("1") == 1);
+ assert_se(strlen8("11") == 2);
+ assert_se(strlen8("123456789") == 9);
+ assert_se(strlen8("12\0004") == 2);
+}
+
+TEST(strlen16) {
+ assert_se(strlen16(NULL) == 0);
+ assert_se(strlen16(u"") == 0);
+ assert_se(strlen16(u"1") == 1);
+ assert_se(strlen16(u"11") == 2);
+ assert_se(strlen16(u"123456789") == 9);
+ assert_se(strlen16(u"12\0004") == 2);
+}
+
+TEST(strnlen8) {
+ assert_se(strnlen8(NULL, 0) == 0);
+ assert_se(strnlen8(NULL, 10) == 0);
+ assert_se(strnlen8("", 10) == 0);
+ assert_se(strnlen8("1", 10) == 1);
+ assert_se(strnlen8("11", 1) == 1);
+ assert_se(strnlen8("123456789", 7) == 7);
+ assert_se(strnlen8("12\0004", 5) == 2);
+}
+
+TEST(strnlen16) {
+ assert_se(strnlen16(NULL, 0) == 0);
+ assert_se(strnlen16(NULL, 10) == 0);
+ assert_se(strnlen16(u"", 10) == 0);
+ assert_se(strnlen16(u"1", 10) == 1);
+ assert_se(strnlen16(u"11", 1) == 1);
+ assert_se(strnlen16(u"123456789", 7) == 7);
+ assert_se(strnlen16(u"12\0004", 5) == 2);
+}
+
+TEST(strsize8) {
+ assert_se(strsize8(NULL) == 0);
+ assert_se(strsize8("") == 1);
+ assert_se(strsize8("1") == 2);
+ assert_se(strsize8("11") == 3);
+ assert_se(strsize8("123456789") == 10);
+ assert_se(strsize8("12\0004") == 3);
+}
+
+TEST(strsize16) {
+ assert_se(strsize16(NULL) == 0);
+ assert_se(strsize16(u"") == 2);
+ assert_se(strsize16(u"1") == 4);
+ assert_se(strsize16(u"11") == 6);
+ assert_se(strsize16(u"123456789") == 20);
+ assert_se(strsize16(u"12\0004") == 6);
+}
+
+TEST(strtolower8) {
+ char s[] = "\0001234abcDEF!\0zZ";
+
+ strtolower8(NULL);
+
+ strtolower8(s);
+ assert_se(memcmp(s, "\0001234abcDEF!\0zZ", sizeof(s)) == 0);
+
+ s[0] = '#';
+ strtolower8(s);
+ assert_se(memcmp(s, "#1234abcdef!\0zZ", sizeof(s)) == 0);
+}
+
+TEST(strtolower16) {
+ char16_t s[] = u"\0001234abcDEF!\0zZ";
+
+ strtolower16(NULL);
+
+ strtolower16(s);
+ assert_se(memcmp(s, u"\0001234abcDEF!\0zZ", sizeof(s)) == 0);
+
+ s[0] = '#';
+ strtolower16(s);
+ assert_se(memcmp(s, u"#1234abcdef!\0zZ", sizeof(s)) == 0);
+}
+
+TEST(strncmp8) {
+ assert_se(strncmp8(NULL, "", 10) < 0);
+ assert_se(strncmp8("", NULL, 10) > 0);
+ assert_se(strncmp8(NULL, NULL, 0) == 0);
+ assert_se(strncmp8(NULL, NULL, 10) == 0);
+ assert_se(strncmp8("", "", 10) == 0);
+ assert_se(strncmp8("abc", "abc", 2) == 0);
+ assert_se(strncmp8("aBc", "aBc", 3) == 0);
+ assert_se(strncmp8("aBC", "aBC", 4) == 0);
+ assert_se(strncmp8("", "a", 0) == 0);
+ assert_se(strncmp8("b", "a", 0) == 0);
+ assert_se(strncmp8("", "a", 3) < 0);
+ assert_se(strncmp8("=", "=", 1) == 0);
+ assert_se(strncmp8("A", "a", 1) < 0);
+ assert_se(strncmp8("a", "A", 2) > 0);
+ assert_se(strncmp8("a", "Aa", 2) > 0);
+ assert_se(strncmp8("12\00034", "12345", 4) < 0);
+ assert_se(strncmp8("12\00034", "12345", SIZE_MAX) < 0);
+ assert_se(strncmp8("abc\0def", "abc", SIZE_MAX) == 0);
+ assert_se(strncmp8("abc\0def", "abcdef", SIZE_MAX) < 0);
+
+ assert_se(strncmp8((char[]){ CHAR_MIN }, (char[]){ CHAR_MIN }, 1) == 0);
+ assert_se(strncmp8((char[]){ CHAR_MAX }, (char[]){ CHAR_MAX }, 1) == 0);
+ assert_se(strncmp8((char[]){ CHAR_MIN }, (char[]){ CHAR_MAX }, 1) < 0);
+ assert_se(strncmp8((char[]){ CHAR_MAX }, (char[]){ CHAR_MIN }, 1) > 0);
+}
+
+TEST(strncmp16) {
+ assert_se(strncmp16(NULL, u"", 10) < 0);
+ assert_se(strncmp16(u"", NULL, 10) > 0);
+ assert_se(strncmp16(NULL, NULL, 0) == 0);
+ assert_se(strncmp16(NULL, NULL, 10) == 0);
+ assert_se(strncmp16(u"", u"", 0) == 0);
+ assert_se(strncmp16(u"", u"", 10) == 0);
+ assert_se(strncmp16(u"abc", u"abc", 2) == 0);
+ assert_se(strncmp16(u"aBc", u"aBc", 3) == 0);
+ assert_se(strncmp16(u"aBC", u"aBC", 4) == 0);
+ assert_se(strncmp16(u"", u"a", 0) == 0);
+ assert_se(strncmp16(u"b", u"a", 0) == 0);
+ assert_se(strncmp16(u"", u"a", 3) < 0);
+ assert_se(strncmp16(u"=", u"=", 1) == 0);
+ assert_se(strncmp16(u"A", u"a", 1) < 0);
+ assert_se(strncmp16(u"a", u"A", 2) > 0);
+ assert_se(strncmp16(u"a", u"Aa", 2) > 0);
+ assert_se(strncmp16(u"12\00034", u"12345", 4) < 0);
+ assert_se(strncmp16(u"12\00034", u"12345", SIZE_MAX) < 0);
+ assert_se(strncmp16(u"abc\0def", u"abc", SIZE_MAX) == 0);
+ assert_se(strncmp16(u"abc\0def", u"abcdef", SIZE_MAX) < 0);
+
+ assert_se(strncmp16((char16_t[]){ UINT16_MAX }, (char16_t[]){ UINT16_MAX }, 1) == 0);
+ assert_se(strncmp16((char16_t[]){ 0 }, (char16_t[]){ UINT16_MAX }, 1) < 0);
+ assert_se(strncmp16((char16_t[]){ UINT16_MAX }, (char16_t[]){ 0 }, 1) > 0);
+}
+
+TEST(strncasecmp8) {
+ assert_se(strncasecmp8(NULL, "", 10) < 0);
+ assert_se(strncasecmp8("", NULL, 10) > 0);
+ assert_se(strncasecmp8(NULL, NULL, 0) == 0);
+ assert_se(strncasecmp8(NULL, NULL, 10) == 0);
+ assert_se(strncasecmp8("", "", 10) == 0);
+ assert_se(strncasecmp8("abc", "abc", 2) == 0);
+ assert_se(strncasecmp8("aBc", "AbC", 3) == 0);
+ assert_se(strncasecmp8("aBC", "Abc", 4) == 0);
+ assert_se(strncasecmp8("", "a", 0) == 0);
+ assert_se(strncasecmp8("b", "a", 0) == 0);
+ assert_se(strncasecmp8("", "a", 3) < 0);
+ assert_se(strncasecmp8("=", "=", 1) == 0);
+ assert_se(strncasecmp8("A", "a", 1) == 0);
+ assert_se(strncasecmp8("a", "A", 2) == 0);
+ assert_se(strncasecmp8("a", "Aa", 2) < 0);
+ assert_se(strncasecmp8("12\00034", "12345", 4) < 0);
+ assert_se(strncasecmp8("12\00034", "12345", SIZE_MAX) < 0);
+ assert_se(strncasecmp8("abc\0def", "ABC", SIZE_MAX) == 0);
+ assert_se(strncasecmp8("abc\0def", "ABCDEF", SIZE_MAX) < 0);
+
+ assert_se(strncasecmp8((char[]){ CHAR_MIN }, (char[]){ CHAR_MIN }, 1) == 0);
+ assert_se(strncasecmp8((char[]){ CHAR_MAX }, (char[]){ CHAR_MAX }, 1) == 0);
+ assert_se(strncasecmp8((char[]){ CHAR_MIN }, (char[]){ CHAR_MAX }, 1) < 0);
+ assert_se(strncasecmp8((char[]){ CHAR_MAX }, (char[]){ CHAR_MIN }, 1) > 0);
+}
+
+TEST(strncasecmp16) {
+ assert_se(strncasecmp16(NULL, u"", 10) < 0);
+ assert_se(strncasecmp16(u"", NULL, 10) > 0);
+ assert_se(strncasecmp16(NULL, NULL, 0) == 0);
+ assert_se(strncasecmp16(NULL, NULL, 10) == 0);
+ assert_se(strncasecmp16(u"", u"", 10) == 0);
+ assert_se(strncasecmp16(u"abc", u"abc", 2) == 0);
+ assert_se(strncasecmp16(u"aBc", u"AbC", 3) == 0);
+ assert_se(strncasecmp16(u"aBC", u"Abc", 4) == 0);
+ assert_se(strncasecmp16(u"", u"a", 0) == 0);
+ assert_se(strncasecmp16(u"b", u"a", 0) == 0);
+ assert_se(strncasecmp16(u"", u"a", 3) < 0);
+ assert_se(strncasecmp16(u"=", u"=", 1) == 0);
+ assert_se(strncasecmp16(u"A", u"a", 1) == 0);
+ assert_se(strncasecmp16(u"a", u"A", 2) == 0);
+ assert_se(strncasecmp16(u"a", u"Aa", 2) < 0);
+ assert_se(strncasecmp16(u"12\00034", u"12345", 4) < 0);
+ assert_se(strncasecmp16(u"12\00034", u"12345", SIZE_MAX) < 0);
+ assert_se(strncasecmp16(u"abc\0def", u"ABC", SIZE_MAX) == 0);
+ assert_se(strncasecmp16(u"abc\0def", u"ABCDEF", SIZE_MAX) < 0);
+
+ assert_se(strncasecmp16((char16_t[]){ UINT16_MAX }, (char16_t[]){ UINT16_MAX }, 1) == 0);
+ assert_se(strncasecmp16((char16_t[]){ 0 }, (char16_t[]){ UINT16_MAX }, 1) < 0);
+ assert_se(strncasecmp16((char16_t[]){ UINT16_MAX }, (char16_t[]){ 0 }, 1) > 0);
+}
+
+TEST(strcpy8) {
+ char buf[128];
+
+ assert_se(strcpy8(buf, "123") == buf);
+ assert_se(streq8(buf, "123"));
+ assert_se(strcpy8(buf, "") == buf);
+ assert_se(streq8(buf, ""));
+ assert_se(strcpy8(buf, "A") == buf);
+ assert_se(streq8(buf, "A"));
+ assert_se(strcpy8(buf, NULL) == buf);
+ assert_se(streq8(buf, ""));
+}
+
+TEST(strcpy16) {
+ char16_t buf[128];
+
+ assert_se(strcpy16(buf, u"123") == buf);
+ assert_se(streq16(buf, u"123"));
+ assert_se(strcpy16(buf, u"") == buf);
+ assert_se(streq16(buf, u""));
+ assert_se(strcpy16(buf, u"A") == buf);
+ assert_se(streq16(buf, u"A"));
+ assert_se(strcpy16(buf, NULL) == buf);
+ assert_se(streq16(buf, u""));
+}
+
+TEST(strchr8) {
+ assert_se(!strchr8(NULL, 'a'));
+ assert_se(!strchr8("", 'a'));
+ assert_se(!strchr8("123", 'a'));
+
+ const char str[] = "abcaBc";
+ assert_se(strchr8(str, 'a') == &str[0]);
+ assert_se(strchr8(str, 'c') == &str[2]);
+ assert_se(strchr8(str, 'B') == &str[4]);
+
+ assert_se(strchr8(str, 0) == str + strlen8(str));
+}
+
+TEST(strchr16) {
+ assert_se(!strchr16(NULL, 'a'));
+ assert_se(!strchr16(u"", 'a'));
+ assert_se(!strchr16(u"123", 'a'));
+
+ const char16_t str[] = u"abcaBc";
+ assert_se(strchr16(str, 'a') == &str[0]);
+ assert_se(strchr16(str, 'c') == &str[2]);
+ assert_se(strchr16(str, 'B') == &str[4]);
+
+ assert_se(strchr16(str, 0) == str + strlen16(str));
+}
+
+TEST(xstrndup8) {
+ char *s = NULL;
+
+ assert_se(xstrndup8(NULL, 0) == NULL);
+ assert_se(xstrndup8(NULL, 10) == NULL);
+
+ assert_se(s = xstrndup8("", 10));
+ assert_se(streq8(s, ""));
+ free(s);
+
+ assert_se(s = xstrndup8("abc", 0));
+ assert_se(streq8(s, ""));
+ free(s);
+
+ assert_se(s = xstrndup8("ABC", 3));
+ assert_se(streq8(s, "ABC"));
+ free(s);
+
+ assert_se(s = xstrndup8("123abcDEF", 5));
+ assert_se(streq8(s, "123ab"));
+ free(s);
+}
+
+TEST(xstrdup8) {
+ char *s = NULL;
+
+ assert_se(xstrdup8(NULL) == NULL);
+
+ assert_se(s = xstrdup8(""));
+ assert_se(streq8(s, ""));
+ free(s);
+
+ assert_se(s = xstrdup8("1"));
+ assert_se(streq8(s, "1"));
+ free(s);
+
+ assert_se(s = xstrdup8("123abcDEF"));
+ assert_se(streq8(s, "123abcDEF"));
+ free(s);
+}
+
+TEST(xstrndup16) {
+ char16_t *s = NULL;
+
+ assert_se(xstrndup16(NULL, 0) == NULL);
+ assert_se(xstrndup16(NULL, 10) == NULL);
+
+ assert_se(s = xstrndup16(u"", 10));
+ assert_se(streq16(s, u""));
+ free(s);
+
+ assert_se(s = xstrndup16(u"abc", 0));
+ assert_se(streq16(s, u""));
+ free(s);
+
+ assert_se(s = xstrndup16(u"ABC", 3));
+ assert_se(streq16(s, u"ABC"));
+ free(s);
+
+ assert_se(s = xstrndup16(u"123abcDEF", 5));
+ assert_se(streq16(s, u"123ab"));
+ free(s);
+}
+
+TEST(xstrdup16) {
+ char16_t *s = NULL;
+
+ assert_se(xstrdup16(NULL) == NULL);
+
+ assert_se(s = xstrdup16(u""));
+ assert_se(streq16(s, u""));
+ free(s);
+
+ assert_se(s = xstrdup16(u"1"));
+ assert_se(streq16(s, u"1"));
+ free(s);
+
+ assert_se(s = xstrdup16(u"123abcDEF"));
+ assert_se(streq16(s, u"123abcDEF"));
+ free(s);
+}
+
+TEST(xstrn8_to_16) {
+ char16_t *s = NULL;
+
+ assert_se(xstrn8_to_16(NULL, 1) == NULL);
+ assert_se(xstrn8_to_16("a", 0) == NULL);
+
+ assert_se(s = xstrn8_to_16("", 1));
+ assert_se(streq16(s, u""));
+ free(s);
+
+ assert_se(s = xstrn8_to_16("1", 1));
+ assert_se(streq16(s, u"1"));
+ free(s);
+
+ assert_se(s = xstr8_to_16("abcxyzABCXYZ09 .,-_#*!\"§$%&/()=?`~"));
+ assert_se(streq16(s, u"abcxyzABCXYZ09 .,-_#*!\"§$%&/()=?`~"));
+ free(s);
+
+ assert_se(s = xstr8_to_16("ÿⱿ𝇉 😺"));
+ assert_se(streq16(s, u"ÿⱿ "));
+ free(s);
+
+ assert_se(s = xstrn8_to_16("¶¶", 3));
+ assert_se(streq16(s, u"¶"));
+ free(s);
+}
+
+TEST(startswith8) {
+ assert_se(streq8(startswith8("", ""), ""));
+ assert_se(streq8(startswith8("x", ""), "x"));
+ assert_se(!startswith8("", "x"));
+ assert_se(!startswith8("", "xxxxxxxx"));
+ assert_se(streq8(startswith8("xxx", "x"), "xx"));
+ assert_se(streq8(startswith8("xxx", "xx"), "x"));
+ assert_se(streq8(startswith8("xxx", "xxx"), ""));
+ assert_se(!startswith8("xxx", "xxxx"));
+ assert_se(!startswith8(NULL, ""));
+}
+
+#define TEST_FNMATCH_ONE(pattern, haystack, expect) \
+ ({ \
+ assert_se(fnmatch(pattern, haystack, 0) == (expect ? 0 : FNM_NOMATCH)); \
+ assert_se(efi_fnmatch(u##pattern, u##haystack) == expect); \
+ })
+
+TEST(efi_fnmatch) {
+ TEST_FNMATCH_ONE("", "", true);
+ TEST_FNMATCH_ONE("abc", "abc", true);
+ TEST_FNMATCH_ONE("aBc", "abc", false);
+ TEST_FNMATCH_ONE("b", "a", false);
+ TEST_FNMATCH_ONE("b", "", false);
+ TEST_FNMATCH_ONE("abc", "a", false);
+ TEST_FNMATCH_ONE("a?c", "azc", true);
+ TEST_FNMATCH_ONE("???", "?.9", true);
+ TEST_FNMATCH_ONE("1?", "1", false);
+ TEST_FNMATCH_ONE("***", "", true);
+ TEST_FNMATCH_ONE("*", "123", true);
+ TEST_FNMATCH_ONE("**", "abcd", true);
+ TEST_FNMATCH_ONE("*b*", "abcd", true);
+ TEST_FNMATCH_ONE("abc*d", "abc", false);
+ TEST_FNMATCH_ONE("start*end", "startend", true);
+ TEST_FNMATCH_ONE("start*end", "startendend", true);
+ TEST_FNMATCH_ONE("start*end", "startenddne", false);
+ TEST_FNMATCH_ONE("start*end", "startendstartend", true);
+ TEST_FNMATCH_ONE("start*end", "starten", false);
+ TEST_FNMATCH_ONE("*.conf", "arch.conf", true);
+ TEST_FNMATCH_ONE("debian-*.conf", "debian-wheezy.conf", true);
+ TEST_FNMATCH_ONE("debian-*.*", "debian-wheezy.efi", true);
+ TEST_FNMATCH_ONE("ab*cde", "abzcd", false);
+ TEST_FNMATCH_ONE("\\*\\a\\[", "*a[", true);
+ TEST_FNMATCH_ONE("[abc] [abc] [abc]", "a b c", true);
+ TEST_FNMATCH_ONE("abc]", "abc]", true);
+ TEST_FNMATCH_ONE("[abc]", "z", false);
+ TEST_FNMATCH_ONE("[abc", "a", false);
+ TEST_FNMATCH_ONE("[][!] [][!] [][!]", "[ ] !", true);
+ TEST_FNMATCH_ONE("[]-] []-]", "] -", true);
+ TEST_FNMATCH_ONE("[1\\]] [1\\]]", "1 ]", true);
+ TEST_FNMATCH_ONE("[$-\\+]", "&", true);
+ TEST_FNMATCH_ONE("[1-3A-C] [1-3A-C]", "2 B", true);
+ TEST_FNMATCH_ONE("[3-5] [3-5] [3-5]", "3 4 5", true);
+ TEST_FNMATCH_ONE("[f-h] [f-h] [f-h]", "f g h", true);
+ TEST_FNMATCH_ONE("[a-c-f] [a-c-f] [a-c-f] [a-c-f] [a-c-f]", "a b c - f", true);
+ TEST_FNMATCH_ONE("[a-c-f]", "e", false);
+ TEST_FNMATCH_ONE("[--0] [--0] [--0]", "- . 0", true);
+ TEST_FNMATCH_ONE("[+--] [+--] [+--]", "+ , -", true);
+ TEST_FNMATCH_ONE("[f-l]", "m", false);
+ TEST_FNMATCH_ONE("[b]", "z-a", false);
+ TEST_FNMATCH_ONE("[a\\-z]", "b", false);
+ TEST_FNMATCH_ONE("?a*b[.-0]c", "/a/b/c", true);
+ TEST_FNMATCH_ONE("debian-*-*-*.*", "debian-jessie-2018-06-17-kernel-image-5.10.0-16-amd64.efi", true);
+
+ /* These would take forever with a backtracking implementation. */
+ TEST_FNMATCH_ONE(
+ "a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*",
+ "aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyy",
+ false);
+ TEST_FNMATCH_ONE(
+ "a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*",
+ "aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz!!!!",
+ true);
+}
+
+TEST(parse_number8) {
+ uint64_t u;
+ const char *tail;
+
+ assert_se(!parse_number8(NULL, &u, NULL));
+ assert_se(!parse_number8("", &u, NULL));
+ assert_se(!parse_number8("a1", &u, NULL));
+ assert_se(!parse_number8("1a", &u, NULL));
+ assert_se(!parse_number8("-42", &u, NULL));
+ assert_se(!parse_number8("18446744073709551616", &u, NULL));
+
+ assert_se(parse_number8("0", &u, NULL));
+ assert_se(u == 0);
+ assert_se(parse_number8("1", &u, NULL));
+ assert_se(u == 1);
+ assert_se(parse_number8("999", &u, NULL));
+ assert_se(u == 999);
+ assert_se(parse_number8("18446744073709551615", &u, NULL));
+ assert_se(u == UINT64_MAX);
+ assert_se(parse_number8("42", &u, &tail));
+ assert_se(u == 42);
+ assert_se(streq8(tail, ""));
+ assert_se(parse_number8("54321rest", &u, &tail));
+ assert_se(u == 54321);
+ assert_se(streq8(tail, "rest"));
+}
+
+TEST(parse_number16) {
+ uint64_t u;
+ const char16_t *tail;
+
+ assert_se(!parse_number16(NULL, &u, NULL));
+ assert_se(!parse_number16(u"", &u, NULL));
+ assert_se(!parse_number16(u"a1", &u, NULL));
+ assert_se(!parse_number16(u"1a", &u, NULL));
+ assert_se(!parse_number16(u"-42", &u, NULL));
+ assert_se(!parse_number16(u"18446744073709551616", &u, NULL));
+
+ assert_se(parse_number16(u"0", &u, NULL));
+ assert_se(u == 0);
+ assert_se(parse_number16(u"1", &u, NULL));
+ assert_se(u == 1);
+ assert_se(parse_number16(u"999", &u, NULL));
+ assert_se(u == 999);
+ assert_se(parse_number16(u"18446744073709551615", &u, NULL));
+ assert_se(u == UINT64_MAX);
+ assert_se(parse_number16(u"42", &u, &tail));
+ assert_se(u == 42);
+ assert_se(streq16(tail, u""));
+ assert_se(parse_number16(u"54321rest", &u, &tail));
+ assert_se(u == 54321);
+ assert_se(streq16(tail, u"rest"));
+}
+
+TEST(parse_boolean) {
+ bool b;
+
+ assert_se(!parse_boolean(NULL, &b));
+ assert_se(!parse_boolean("", &b));
+ assert_se(!parse_boolean("ja", &b));
+ assert_se(parse_boolean("1", &b) && b == true);
+ assert_se(parse_boolean("y", &b) && b == true);
+ assert_se(parse_boolean("yes", &b) && b == true);
+ assert_se(parse_boolean("t", &b) && b == true);
+ assert_se(parse_boolean("true", &b) && b == true);
+ assert_se(parse_boolean("on", &b) && b == true);
+ assert_se(parse_boolean("0", &b) && b == false);
+ assert_se(parse_boolean("n", &b) && b == false);
+ assert_se(parse_boolean("no", &b) && b == false);
+ assert_se(parse_boolean("f", &b) && b == false);
+ assert_se(parse_boolean("false", &b) && b == false);
+ assert_se(parse_boolean("off", &b) && b == false);
+}
+
+TEST(line_get_key_value) {
+ char s1[] = "key=value\n"
+ " \t # comment line \n"
+ "k-e-y=\"quoted value\"\n\r"
+ " wrong= 'quotes' \n"
+ "odd= stripping # with comments ";
+ char s2[] = "this parser\n"
+ "\t\t\t# is\t\r"
+ " also\tused \r\n"
+ "for \"the conf\"\n"
+ "format\t !!";
+ size_t pos = 0;
+ char *key, *value;
+
+ assert_se(!line_get_key_value((char[]){ "" }, "=", &pos, &key, &value));
+ assert_se(!line_get_key_value((char[]){ "\t" }, " \t", &pos, &key, &value));
+
+ pos = 0;
+ assert_se(line_get_key_value(s1, "=", &pos, &key, &value));
+ assert_se(streq8(key, "key"));
+ assert_se(streq8(value, "value"));
+ assert_se(line_get_key_value(s1, "=", &pos, &key, &value));
+ assert_se(streq8(key, "k-e-y"));
+ assert_se(streq8(value, "quoted value"));
+ assert_se(line_get_key_value(s1, "=", &pos, &key, &value));
+ assert_se(streq8(key, "wrong"));
+ assert_se(streq8(value, " 'quotes'"));
+ assert_se(line_get_key_value(s1, "=", &pos, &key, &value));
+ assert_se(streq8(key, "odd"));
+ assert_se(streq8(value, " stripping # with comments"));
+ assert_se(!line_get_key_value(s1, "=", &pos, &key, &value));
+
+ pos = 0;
+ assert_se(line_get_key_value(s2, " \t", &pos, &key, &value));
+ assert_se(streq8(key, "this"));
+ assert_se(streq8(value, "parser"));
+ assert_se(line_get_key_value(s2, " \t", &pos, &key, &value));
+ assert_se(streq8(key, "also"));
+ assert_se(streq8(value, "used"));
+ assert_se(line_get_key_value(s2, " \t", &pos, &key, &value));
+ assert_se(streq8(key, "for"));
+ assert_se(streq8(value, "the conf"));
+ assert_se(line_get_key_value(s2, " \t", &pos, &key, &value));
+ assert_se(streq8(key, "format"));
+ assert_se(streq8(value, "!!"));
+ assert_se(!line_get_key_value(s2, " \t", &pos, &key, &value));
+
+ /* Let's make sure we don't fail on real os-release data. */
+ _cleanup_free_ char *osrel = NULL;
+ if (read_full_file("/usr/lib/os-release", &osrel, NULL) >= 0) {
+ pos = 0;
+ while (line_get_key_value(osrel, "=", &pos, &key, &value)) {
+ assert_se(key);
+ assert_se(value);
+ printf("%s = %s\n", key, value);
+ }
+ }
+}
+
+TEST(hexdump) {
+ char16_t *hex;
+
+ hex = hexdump(NULL, 0);
+ assert(streq16(hex, u""));
+ free(hex);
+
+ hex = hexdump("1", 2);
+ assert(streq16(hex, u"3100"));
+ free(hex);
+
+ hex = hexdump("abc", 4);
+ assert(streq16(hex, u"61626300"));
+ free(hex);
+
+ hex = hexdump((uint8_t[]){ 0x0, 0x42, 0xFF, 0xF1, 0x1F }, 5);
+ assert(streq16(hex, u"0042fff11f"));
+ free(hex);
+}
+
+_printf_(1, 2) static void test_printf_one(const char *format, ...) {
+ va_list ap, ap_efi;
+ va_start(ap, format);
+ va_copy(ap_efi, ap);
+
+ _cleanup_free_ char *buf = NULL;
+ int r = vasprintf(&buf, format, ap);
+ assert_se(r >= 0);
+ log_info("/* %s(%s) -> \"%.100s\" */", __func__, format, buf);
+
+ _cleanup_free_ char16_t *buf_efi = xvasprintf_status(0, format, ap_efi);
+
+ bool eq = true;
+ for (size_t i = 0; i <= (size_t) r; i++) {
+ if (buf[i] != buf_efi[i])
+ eq = false;
+ buf[i] = buf_efi[i];
+ }
+
+ log_info("%.100s", buf);
+ assert_se(eq);
+
+ va_end(ap);
+ va_end(ap_efi);
+}
+
+TEST(xvasprintf_status) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-zero-length"
+ test_printf_one("");
+#pragma GCC diagnostic pop
+ test_printf_one("string");
+ test_printf_one("%%-%%%%");
+
+ test_printf_one("%p %p %32p %*p %*p", NULL, (void *) 0xF, &errno, 0, &saved_argc, 20, &saved_argv);
+ test_printf_one("%-10p %-32p %-*p %-*p", NULL, &errno, 0, &saved_argc, 20, &saved_argv);
+
+ test_printf_one("%c %3c %*c %*c %-8c", '1', '!', 0, 'a', 9, '_', '>');
+
+ test_printf_one("%s %s %s", "012345", "6789", "ab");
+ test_printf_one("%.4s %.4s %.4s %.0s", "cdefgh", "ijkl", "mn", "@");
+ test_printf_one("%8s %8s %8s", "opqrst", "uvwx", "yz");
+ test_printf_one("%8.4s %8.4s %8.4s %8.0s", "ABCDEF", "GHIJ", "KL", "$");
+ test_printf_one("%4.8s %4.8s %4.8s", "ABCDEFGHIJ", "ABCDEFGH", "ABCD");
+
+ test_printf_one("%.*s %.*s %.*s %.*s", 4, "012345", 4, "6789", 4, "ab", 0, "&");
+ test_printf_one("%*s %*s %*s", 8, "cdefgh", 8, "ijkl", 8, "mn");
+ test_printf_one("%*.*s %*.*s %*.*s %*.*s", 8, 4, "opqrst", 8, 4, "uvwx", 8, 4, "yz", 8, 0, "#");
+ test_printf_one("%*.*s %*.*s %*.*s", 3, 8, "OPQRST", 3, 8, "UVWX", 3, 8, "YZ");
+
+ test_printf_one("%ls %ls %ls", L"012345", L"6789", L"ab");
+ test_printf_one("%.4ls %.4ls %.4ls %.0ls", L"cdefgh", L"ijkl", L"mn", L"@");
+ test_printf_one("%8ls %8ls %8ls", L"opqrst", L"uvwx", L"yz");
+ test_printf_one("%8.4ls %8.4ls %8.4ls %8.0ls", L"ABCDEF", L"GHIJ", L"KL", L"$");
+ test_printf_one("%4.8ls %4.8ls %4.8ls", L"ABCDEFGHIJ", L"ABCDEFGH", L"ABCD");
+
+ test_printf_one("%.*ls %.*ls %.*ls %.*ls", 4, L"012345", 4, L"6789", 4, L"ab", 0, L"&");
+ test_printf_one("%*ls %*ls %*ls", 8, L"cdefgh", 8, L"ijkl", 8, L"mn");
+ test_printf_one("%*.*ls %*.*ls %*.*ls %*.*ls", 8, 4, L"opqrst", 8, 4, L"uvwx", 8, 4, L"yz", 8, 0, L"#");
+ test_printf_one("%*.*ls %*.*ls %*.*ls", 3, 8, L"OPQRST", 3, 8, L"UVWX", 3, 8, L"YZ");
+
+ test_printf_one("%-14s %-10.0s %-10.3s", "left", "", "chopped");
+ test_printf_one("%-14ls %-10.0ls %-10.3ls", L"left", L"", L"chopped");
+
+ test_printf_one("%.6s", (char[]){ 'n', 'o', ' ', 'n', 'u', 'l' });
+ test_printf_one("%.6ls", (wchar_t[]){ 'n', 'o', ' ', 'n', 'u', 'l' });
+
+ test_printf_one("%u %u %u", 0U, 42U, 1234567890U);
+ test_printf_one("%i %i %i", 0, -42, -1234567890);
+ test_printf_one("%x %x %x", 0x0U, 0x42U, 0x123ABCU);
+ test_printf_one("%X %X %X", 0x1U, 0x43U, 0x234BCDU);
+ test_printf_one("%#x %#x %#x", 0x2U, 0x44U, 0x345CDEU);
+ test_printf_one("%#X %#X %#X", 0x3U, 0x45U, 0x456FEDU);
+
+ test_printf_one("%u %lu %llu %zu", UINT_MAX, ULONG_MAX, ULLONG_MAX, SIZE_MAX);
+ test_printf_one("%i %i %zi", INT_MIN, INT_MAX, SSIZE_MAX);
+ test_printf_one("%li %li %lli %lli", LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX);
+ test_printf_one("%x %#lx %llx %#zx", UINT_MAX, ULONG_MAX, ULLONG_MAX, SIZE_MAX);
+ test_printf_one("%X %#lX %llX %#zX", UINT_MAX, ULONG_MAX, ULLONG_MAX, SIZE_MAX);
+ test_printf_one("%ju %ji %ji", UINTMAX_MAX, INTMAX_MIN, INTMAX_MAX);
+ test_printf_one("%ti %ti", PTRDIFF_MIN, PTRDIFF_MAX);
+
+ test_printf_one("%" PRIu32 " %" PRIi32 " %" PRIi32, UINT32_MAX, INT32_MIN, INT32_MAX);
+ test_printf_one("%" PRIx32 " %" PRIX32, UINT32_MAX, UINT32_MAX);
+ test_printf_one("%#" PRIx32 " %#" PRIX32, UINT32_MAX, UINT32_MAX);
+
+ test_printf_one("%" PRIu64 " %" PRIi64 " %" PRIi64, UINT64_MAX, INT64_MIN, INT64_MAX);
+ test_printf_one("%" PRIx64 " %" PRIX64, UINT64_MAX, UINT64_MAX);
+ test_printf_one("%#" PRIx64 " %#" PRIX64, UINT64_MAX, UINT64_MAX);
+
+ test_printf_one("%.11u %.11i %.11x %.11X %#.11x %#.11X", 1U, -2, 3U, 0xA1U, 0xB2U, 0xC3U);
+ test_printf_one("%13u %13i %13x %13X %#13x %#13X", 4U, -5, 6U, 0xD4U, 0xE5U, 0xF6U);
+ test_printf_one("%9.5u %9.5i %9.5x %9.5X %#9.5x %#9.5X", 7U, -8, 9U, 0xA9U, 0xB8U, 0xC7U);
+ test_printf_one("%09u %09i %09x %09X %#09x %#09X", 4U, -5, 6U, 0xD6U, 0xE5U, 0xF4U);
+
+ test_printf_one("%*u %.*u %*i %.*i", 15, 42U, 15, 43U, 15, -42, 15, -43);
+ test_printf_one("%*.*u %*.*i", 14, 10, 13U, 14, 10, -14);
+ test_printf_one("%*x %*X %.*x %.*X", 15, 0x1AU, 15, 0x2BU, 15, 0x3CU, 15, 0x4DU);
+ test_printf_one("%#*x %#*X %#.*x %#.*X", 15, 0xA1U, 15, 0xB2U, 15, 0xC3U, 15, 0xD4U);
+ test_printf_one("%*.*x %*.*X", 14, 10, 0x1AU, 14, 10, 0x2BU);
+ test_printf_one("%#*.*x %#*.*X", 14, 10, 0x3CU, 14, 10, 0x4DU);
+
+ test_printf_one("%+.5i %+.5i % .7i % .7i", -15, 51, -15, 51);
+ test_printf_one("%+5.i %+5.i % 7.i % 7.i", -15, 51, -15, 51);
+
+ test_printf_one("%-10u %-10i %-10x %#-10X %- 10i", 1u, -2, 0xA2D2u, 0XB3F4u, -512);
+ test_printf_one("%-10.6u %-10.6i %-10.6x %#-10.6X %- 10.6i", 1u, -2, 0xA2D2u, 0XB3F4u, -512);
+ test_printf_one("%-6.10u %-6.10i %-6.10x %#-6.10X %- 6.10i", 3u, -4, 0x2A2Du, 0X3B4Fu, -215);
+ test_printf_one("%*.u %.*i %.*i", -4, 9u, -4, 8, -4, -6);
+
+ test_printf_one("%.0u %.0i %.0x %.0X", 0u, 0, 0u, 0u);
+ test_printf_one("%.*u %.*i %.*x %.*X", 0, 0u, 0, 0, 0, 0u, 0, 0u);
+ test_printf_one("%*u %*i %*x %*X", -1, 0u, -1, 0, -1, 0u, -1, 0u);
+
+ test_printf_one("%*s%*s%*s", 256, "", 256, "", 4096, ""); /* Test buf growing. */
+ test_printf_one("%0*i%0*i%0*i", 256, 0, 256, 0, 4096, 0); /* Test buf growing. */
+ test_printf_one("%0*i", INT16_MAX, 0); /* Poor programmer's memzero. */
+
+ /* Non printf-compatible behavior tests below. */
+ char16_t *s;
+
+ assert_se(s = xasprintf_status(0, "\n \r \r\n"));
+ assert_se(streq16(s, u"\r\n \r \r\r\n"));
+ s = mfree(s);
+
+ assert_se(s = xasprintf_status(EFI_SUCCESS, "%m"));
+ assert_se(streq16(s, u"Success"));
+ s = mfree(s);
+
+ assert_se(s = xasprintf_status(EFI_SUCCESS, "%15m"));
+ assert_se(streq16(s, u" Success"));
+ s = mfree(s);
+
+ assert_se(s = xasprintf_status(EFI_LOAD_ERROR, "%m"));
+ assert_se(streq16(s, u"Load error"));
+ s = mfree(s);
+
+ assert_se(s = xasprintf_status(0x42, "%m"));
+ assert_se(streq16(s, u"0x42"));
+ s = mfree(s);
+}
+
+TEST(efi_memchr) {
+ assert_se(streq8(efi_memchr("abcde", 'c', 5), "cde"));
+ assert_se(streq8(efi_memchr("abcde", 'c', 3), "cde"));
+ assert_se(streq8(efi_memchr("abcde", 'c', 2), NULL));
+ assert_se(streq8(efi_memchr("abcde", 'c', 7), "cde"));
+ assert_se(streq8(efi_memchr("abcde", 'q', 5), NULL));
+ assert_se(streq8(efi_memchr("abcde", 'q', 0), NULL));
+ /* Test that the character is interpreted as unsigned char. */
+ assert_se(streq8(efi_memchr("abcde", 'a', 6), efi_memchr("abcde", 'a' + 0x100, 6)));
+ assert_se(streq8(efi_memchr("abcde", 0, 6), ""));
+ assert_se(efi_memchr(NULL, 0, 0) == NULL);
+}
+
+TEST(efi_memcmp) {
+ assert_se(efi_memcmp(NULL, NULL, 0) == 0);
+ assert_se(efi_memcmp(NULL, NULL, 1) == 0);
+ assert_se(efi_memcmp(NULL, "", 1) < 0);
+ assert_se(efi_memcmp("", NULL, 1) > 0);
+ assert_se(efi_memcmp("", "", 0) == 0);
+ assert_se(efi_memcmp("", "", 1) == 0);
+ assert_se(efi_memcmp("1", "1", 1) == 0);
+ assert_se(efi_memcmp("1", "2", 1) < 0);
+ assert_se(efi_memcmp("A", "a", 1) < 0);
+ assert_se(efi_memcmp("a", "A", 1) > 0);
+ assert_se(efi_memcmp("abc", "ab", 2) == 0);
+ assert_se(efi_memcmp("ab", "abc", 3) < 0);
+ assert_se(efi_memcmp("abc", "ab", 3) > 0);
+ assert_se(efi_memcmp("ab\000bd", "ab\000bd", 6) == 0);
+ assert_se(efi_memcmp("ab\000b\0", "ab\000bd", 6) < 0);
+}
+
+TEST(efi_memcpy) {
+ char buf[10];
+
+ assert_se(!efi_memcpy(NULL, NULL, 0));
+ assert_se(!efi_memcpy(NULL, "", 1));
+ assert_se(efi_memcpy(buf, NULL, 0) == buf);
+ assert_se(efi_memcpy(buf, NULL, 1) == buf);
+ assert_se(efi_memcpy(buf, "a", 0) == buf);
+
+ assert_se(efi_memcpy(buf, "", 1) == buf);
+ assert_se(memcmp(buf, "", 1) == 0);
+ assert_se(efi_memcpy(buf, "1", 1) == buf);
+ assert_se(memcmp(buf, "1", 1) == 0);
+ assert_se(efi_memcpy(buf, "23", 3) == buf);
+ assert_se(memcmp(buf, "23", 3) == 0);
+ assert_se(efi_memcpy(buf, "45\0ab\0\0\0c", 9) == buf);
+ assert_se(memcmp(buf, "45\0ab\0\0\0c", 9) == 0);
+}
+
+TEST(efi_memset) {
+ char buf[10];
+
+ assert_se(!efi_memset(NULL, '1', 0));
+ assert_se(!efi_memset(NULL, '1', 1));
+ assert_se(efi_memset(buf, '1', 0) == buf);
+
+ assert_se(efi_memset(buf, '2', 1) == buf);
+ assert_se(memcmp(buf, "2", 1) == 0);
+ assert_se(efi_memset(buf, '4', 4) == buf);
+ assert_se(memcmp(buf, "4444", 4) == 0);
+ assert_se(efi_memset(buf, 'a', 10) == buf);
+ assert_se(memcmp(buf, "aaaaaaaaaa", 10) == 0);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
diff --git a/src/boot/efi/ticks.c b/src/boot/efi/ticks.c
new file mode 100644
index 0000000..873b9fe
--- /dev/null
+++ b/src/boot/efi/ticks.c
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "ticks.h"
+#include "util.h"
+#include "vmm.h"
+
+#if defined(__i386__) || defined(__x86_64__)
+# include <cpuid.h>
+
+static uint64_t ticks_read_arch(void) {
+ /* The TSC might or might not be virtualized in VMs (and thus might not be accurate or start at zero
+ * at boot), depending on hypervisor and CPU functionality. If it's not virtualized it's not useful
+ * for keeping time, hence don't attempt to use it. */
+ if (in_hypervisor())
+ return 0;
+
+ return __builtin_ia32_rdtsc();
+}
+
+static uint64_t ticks_freq_arch(void) {
+ /* Detect TSC frequency from CPUID information if available. */
+
+ unsigned max_leaf, ebx, ecx, edx;
+ if (__get_cpuid(0, &max_leaf, &ebx, &ecx, &edx) == 0)
+ return 0;
+
+ /* Leaf 0x15 is Intel only. */
+ if (max_leaf < 0x15 || ebx != signature_INTEL_ebx || ecx != signature_INTEL_ecx ||
+ edx != signature_INTEL_edx)
+ return 0;
+
+ unsigned denominator, numerator, crystal_hz;
+ __cpuid(0x15, denominator, numerator, crystal_hz, edx);
+ if (denominator == 0 || numerator == 0)
+ return 0;
+
+ uint64_t freq = crystal_hz;
+ if (crystal_hz == 0) {
+ /* If the crystal frequency is not available, try to deduce it from
+ * the processor frequency leaf if available. */
+ if (max_leaf < 0x16)
+ return 0;
+
+ unsigned core_mhz;
+ __cpuid(0x16, core_mhz, ebx, ecx, edx);
+ freq = core_mhz * 1000ULL * 1000ULL * denominator / numerator;
+ }
+
+ return freq * numerator / denominator;
+}
+
+#elif defined(__aarch64__)
+
+static uint64_t ticks_read_arch(void) {
+ uint64_t val;
+ asm volatile("mrs %0, cntvct_el0" : "=r"(val));
+ return val;
+}
+
+static uint64_t ticks_freq_arch(void) {
+ uint64_t freq;
+ asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
+ return freq;
+}
+
+#else
+
+static uint64_t ticks_read_arch(void) {
+ return 0;
+}
+
+static uint64_t ticks_freq_arch(void) {
+ return 0;
+}
+
+#endif
+
+static uint64_t ticks_freq(void) {
+ static uint64_t cache = 0;
+
+ if (cache != 0)
+ return cache;
+
+ cache = ticks_freq_arch();
+ if (cache != 0)
+ return cache;
+
+ /* As a fallback, count ticks during a millisecond delay. */
+ uint64_t ticks_start = ticks_read_arch();
+ BS->Stall(1000);
+ uint64_t ticks_end = ticks_read_arch();
+
+ if (ticks_end < ticks_start) /* Check for an overflow (which is not that unlikely, given on some
+ * archs the value is 32-bit) */
+ return 0;
+
+ cache = (ticks_end - ticks_start) * 1000UL;
+ return cache;
+}
+
+uint64_t time_usec(void) {
+ uint64_t ticks = ticks_read_arch();
+ if (ticks == 0)
+ return 0;
+
+ uint64_t freq = ticks_freq();
+ if (freq == 0)
+ return 0;
+
+ return 1000UL * 1000UL * ticks / freq;
+}
diff --git a/src/boot/efi/ticks.h b/src/boot/efi/ticks.h
new file mode 100644
index 0000000..fec3764
--- /dev/null
+++ b/src/boot/efi/ticks.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdint.h>
+
+uint64_t time_usec(void);
diff --git a/src/boot/efi/ubsan.c b/src/boot/efi/ubsan.c
new file mode 100644
index 0000000..9512046
--- /dev/null
+++ b/src/boot/efi/ubsan.c
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "log.h"
+
+typedef struct {
+ const char *filename;
+ uint32_t line;
+ uint32_t column;
+} SourceLocation;
+
+/* Note that all ubsan handlers have a pointer to a type-specific struct passed as first argument.
+ * Since we do not inspect the extra data in it we can just treat it as a SourceLocation struct
+ * directly to keep things simple. */
+
+#define HANDLER(name, ...) \
+ _used_ _noreturn_ void __ubsan_handle_##name(__VA_ARGS__); \
+ void __ubsan_handle_##name(__VA_ARGS__) { \
+ log_error("systemd-boot: %s in %s@%u:%u", \
+ __func__, \
+ location->filename, \
+ location->line, \
+ location->column); \
+ freeze(); \
+ }
+
+#define UNARY_HANDLER(name) HANDLER(name, SourceLocation *location, uintptr_t v)
+#define BINARY_HANDLER(name) HANDLER(name, SourceLocation *location, uintptr_t v1, uintptr_t v2)
+
+UNARY_HANDLER(load_invalid_value);
+UNARY_HANDLER(negate_overflow);
+UNARY_HANDLER(out_of_bounds);
+UNARY_HANDLER(type_mismatch_v1);
+UNARY_HANDLER(vla_bound_not_positive);
+
+BINARY_HANDLER(add_overflow);
+BINARY_HANDLER(divrem_overflow);
+BINARY_HANDLER(implicit_conversion);
+BINARY_HANDLER(mul_overflow);
+BINARY_HANDLER(pointer_overflow);
+BINARY_HANDLER(shift_out_of_bounds);
+BINARY_HANDLER(sub_overflow);
+
+HANDLER(builtin_unreachable, SourceLocation *location);
+HANDLER(invalid_builtin, SourceLocation *location);
+HANDLER(nonnull_arg, SourceLocation *location);
+HANDLER(nonnull_return_v1, SourceLocation *attr_location, SourceLocation *location);
diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c
new file mode 100644
index 0000000..e56ccfd
--- /dev/null
+++ b/src/boot/efi/util.c
@@ -0,0 +1,705 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "device-path-util.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"
+
+EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const char16_t *name, const void *buf, size_t size, uint32_t flags) {
+ assert(vendor);
+ assert(name);
+ assert(buf || size == 0);
+
+ flags |= EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS;
+ return RT->SetVariable((char16_t *) name, (EFI_GUID *) vendor, flags, size, (void *) buf);
+}
+
+EFI_STATUS efivar_set(const EFI_GUID *vendor, const char16_t *name, const char16_t *value, uint32_t flags) {
+ assert(vendor);
+ assert(name);
+
+ return efivar_set_raw(vendor, name, value, value ? strsize16(value) : 0, flags);
+}
+
+EFI_STATUS efivar_set_uint_string(const EFI_GUID *vendor, const char16_t *name, size_t i, uint32_t flags) {
+ assert(vendor);
+ assert(name);
+
+ _cleanup_free_ char16_t *str = xasprintf("%zu", i);
+ return efivar_set(vendor, name, str, flags);
+}
+
+EFI_STATUS efivar_set_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t value, uint32_t flags) {
+ uint8_t buf[4];
+
+ assert(vendor);
+ assert(name);
+
+ buf[0] = (uint8_t)(value >> 0U & 0xFF);
+ buf[1] = (uint8_t)(value >> 8U & 0xFF);
+ buf[2] = (uint8_t)(value >> 16U & 0xFF);
+ buf[3] = (uint8_t)(value >> 24U & 0xFF);
+
+ return efivar_set_raw(vendor, name, buf, sizeof(buf), flags);
+}
+
+EFI_STATUS efivar_set_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t value, uint32_t flags) {
+ uint8_t buf[8];
+
+ assert(vendor);
+ assert(name);
+
+ buf[0] = (uint8_t)(value >> 0U & 0xFF);
+ buf[1] = (uint8_t)(value >> 8U & 0xFF);
+ buf[2] = (uint8_t)(value >> 16U & 0xFF);
+ buf[3] = (uint8_t)(value >> 24U & 0xFF);
+ buf[4] = (uint8_t)(value >> 32U & 0xFF);
+ buf[5] = (uint8_t)(value >> 40U & 0xFF);
+ buf[6] = (uint8_t)(value >> 48U & 0xFF);
+ buf[7] = (uint8_t)(value >> 56U & 0xFF);
+
+ return efivar_set_raw(vendor, name, buf, sizeof(buf), flags);
+}
+
+EFI_STATUS efivar_unset(const EFI_GUID *vendor, const char16_t *name, uint32_t flags) {
+ EFI_STATUS err;
+
+ assert(vendor);
+ assert(name);
+
+ /* We could be wiping a non-volatile variable here and the spec makes no guarantees that won't incur
+ * in an extra write (and thus wear out). So check and clear only if needed. */
+ err = efivar_get_raw(vendor, name, NULL, NULL);
+ if (err == EFI_SUCCESS)
+ return efivar_set_raw(vendor, name, NULL, 0, flags);
+
+ return err;
+}
+
+EFI_STATUS efivar_get(const EFI_GUID *vendor, const char16_t *name, char16_t **ret) {
+ _cleanup_free_ char16_t *buf = NULL;
+ EFI_STATUS err;
+ char16_t *val;
+ size_t size;
+
+ assert(vendor);
+ assert(name);
+
+ err = efivar_get_raw(vendor, name, (char **) &buf, &size);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Make sure there are no incomplete characters in the buffer */
+ if ((size % sizeof(char16_t)) != 0)
+ return EFI_INVALID_PARAMETER;
+
+ if (!ret)
+ return EFI_SUCCESS;
+
+ /* Return buffer directly if it happens to be NUL terminated already */
+ if (size >= sizeof(char16_t) && buf[size / sizeof(char16_t) - 1] == 0) {
+ *ret = TAKE_PTR(buf);
+ return EFI_SUCCESS;
+ }
+
+ /* Make sure a terminating NUL is available at the end */
+ val = xmalloc(size + sizeof(char16_t));
+
+ memcpy(val, buf, size);
+ val[size / sizeof(char16_t) - 1] = 0; /* NUL terminate */
+
+ *ret = val;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS efivar_get_uint_string(const EFI_GUID *vendor, const char16_t *name, size_t *ret) {
+ _cleanup_free_ char16_t *val = NULL;
+ EFI_STATUS err;
+ uint64_t u;
+
+ assert(vendor);
+ assert(name);
+
+ err = efivar_get(vendor, name, &val);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (!parse_number16(val, &u, NULL) || u > SIZE_MAX)
+ return EFI_INVALID_PARAMETER;
+
+ if (ret)
+ *ret = u;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS efivar_get_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t *ret) {
+ _cleanup_free_ char *buf = NULL;
+ size_t size;
+ EFI_STATUS err;
+
+ assert(vendor);
+ assert(name);
+
+ err = efivar_get_raw(vendor, name, &buf, &size);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (size != sizeof(uint32_t))
+ return EFI_BUFFER_TOO_SMALL;
+
+ if (ret)
+ *ret = (uint32_t) buf[0] << 0U | (uint32_t) buf[1] << 8U | (uint32_t) buf[2] << 16U |
+ (uint32_t) buf[3] << 24U;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t *ret) {
+ _cleanup_free_ char *buf = NULL;
+ size_t size;
+ EFI_STATUS err;
+
+ assert(vendor);
+ assert(name);
+
+ err = efivar_get_raw(vendor, name, &buf, &size);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (size != sizeof(uint64_t))
+ return EFI_BUFFER_TOO_SMALL;
+
+ if (ret)
+ *ret = (uint64_t) buf[0] << 0U | (uint64_t) buf[1] << 8U | (uint64_t) buf[2] << 16U |
+ (uint64_t) buf[3] << 24U | (uint64_t) buf[4] << 32U | (uint64_t) buf[5] << 40U |
+ (uint64_t) buf[6] << 48U | (uint64_t) buf[7] << 56U;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, char **ret, size_t *ret_size) {
+ EFI_STATUS err;
+
+ assert(vendor);
+ assert(name);
+
+ size_t size = 0;
+ err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, NULL, &size, NULL);
+ if (err != EFI_BUFFER_TOO_SMALL)
+ return err;
+
+ _cleanup_free_ void *buf = xmalloc(size);
+ err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, NULL, &size, buf);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (ret)
+ *ret = TAKE_PTR(buf);
+ if (ret_size)
+ *ret_size = size;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS efivar_get_boolean_u8(const EFI_GUID *vendor, const char16_t *name, bool *ret) {
+ _cleanup_free_ char *b = NULL;
+ size_t size;
+ EFI_STATUS err;
+
+ assert(vendor);
+ assert(name);
+
+ err = efivar_get_raw(vendor, name, &b, &size);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (ret)
+ *ret = *b > 0;
+
+ return EFI_SUCCESS;
+}
+
+void efivar_set_time_usec(const EFI_GUID *vendor, const char16_t *name, uint64_t usec) {
+ assert(vendor);
+ assert(name);
+
+ if (usec == 0)
+ usec = time_usec();
+ if (usec == 0)
+ return;
+
+ _cleanup_free_ char16_t *str = xasprintf("%" PRIu64, usec);
+ efivar_set(vendor, name, str, 0);
+}
+
+void convert_efi_path(char16_t *path) {
+ assert(path);
+
+ for (size_t i = 0, fixed = 0;; i++) {
+ /* Fix device path node separator. */
+ path[fixed] = (path[i] == '/') ? '\\' : path[i];
+
+ /* Double '\' is not allowed in EFI file paths. */
+ if (fixed > 0 && path[fixed - 1] == '\\' && path[fixed] == '\\')
+ continue;
+
+ if (path[i] == '\0')
+ break;
+
+ fixed++;
+ }
+}
+
+char16_t *xstr8_to_path(const char *str8) {
+ assert(str8);
+ char16_t *path = xstr8_to_16(str8);
+ convert_efi_path(path);
+ return path;
+}
+
+static bool shall_be_whitespace(char16_t c) {
+ return c <= 0x20U || c == 0x7FU; /* All control characters + space */
+}
+
+char16_t* mangle_stub_cmdline(char16_t *cmdline) {
+ char16_t *p, *q, *e;
+
+ if (!cmdline)
+ return cmdline;
+
+ p = q = cmdline;
+
+ /* Skip initial whitespace */
+ while (shall_be_whitespace(*p))
+ p++;
+
+ /* Turn inner control characters into proper spaces */
+ for (e = p; *p != 0; p++) {
+ if (shall_be_whitespace(*p)) {
+ *(q++) = ' ';
+ continue;
+ }
+
+ *(q++) = *p;
+ e = q; /* remember last non-whitespace char */
+ }
+
+ /* Chop off trailing whitespace */
+ *e = 0;
+ return cmdline;
+}
+
+EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf) {
+ EFI_STATUS err;
+
+ assert(file);
+ assert(size);
+ assert(buf);
+
+ /* This is a drop-in replacement for EFI_FILE->Read() with the same API behavior.
+ * Some broken firmwares cannot handle large file reads and will instead return
+ * an error. As a workaround, read such files in small chunks.
+ * Note that we cannot just try reading the whole file first on such firmware as
+ * that will permanently break the handle even if it is re-opened.
+ *
+ * https://github.com/systemd/systemd/issues/25911 */
+
+ if (*size == 0)
+ return EFI_SUCCESS;
+
+ size_t read = 0, remaining = *size;
+ while (remaining > 0) {
+ size_t chunk = MIN(1024U * 1024U, remaining);
+
+ err = file->Read(file, &chunk, (uint8_t *) buf + read);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (chunk == 0)
+ /* Caller requested more bytes than are in file. */
+ break;
+
+ assert(chunk <= remaining);
+ read += chunk;
+ remaining -= chunk;
+ }
+
+ *size = read;
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t size, char **ret, size_t *ret_size) {
+ _cleanup_(file_closep) EFI_FILE *handle = NULL;
+ _cleanup_free_ char *buf = NULL;
+ EFI_STATUS err;
+
+ assert(dir);
+ assert(name);
+ assert(ret);
+
+ err = dir->Open(dir, &handle, (char16_t*) name, EFI_FILE_MODE_READ, 0ULL);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (size == 0) {
+ _cleanup_free_ EFI_FILE_INFO *info = NULL;
+
+ err = get_file_info(handle, &info, NULL);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ size = info->FileSize;
+ }
+
+ if (off > 0) {
+ err = handle->SetPosition(handle, off);
+ if (err != EFI_SUCCESS)
+ return err;
+ }
+
+ /* Allocate some extra bytes to guarantee the result is NUL-terminated for char and char16_t strings. */
+ size_t extra = size % sizeof(char16_t) + sizeof(char16_t);
+
+ buf = xmalloc(size + extra);
+ err = chunked_read(handle, &size, buf);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* Note that chunked_read() changes size to reflect the actual bytes read. */
+ memzero(buf + size, extra);
+
+ *ret = TAKE_PTR(buf);
+ if (ret_size)
+ *ret_size = size;
+
+ return err;
+}
+
+void print_at(size_t x, size_t y, size_t attr, const char16_t *str) {
+ assert(str);
+ ST->ConOut->SetCursorPosition(ST->ConOut, x, y);
+ ST->ConOut->SetAttribute(ST->ConOut, attr);
+ ST->ConOut->OutputString(ST->ConOut, (char16_t *) str);
+}
+
+void clear_screen(size_t attr) {
+ log_wait();
+ ST->ConOut->SetAttribute(ST->ConOut, attr);
+ ST->ConOut->ClearScreen(ST->ConOut);
+}
+
+void sort_pointer_array(
+ void **array,
+ size_t n_members,
+ compare_pointer_func_t compare) {
+
+ assert(array || n_members == 0);
+ assert(compare);
+
+ if (n_members <= 1)
+ return;
+
+ for (size_t i = 1; i < n_members; i++) {
+ size_t k;
+ void *entry = array[i];
+
+ for (k = i; k > 0; k--) {
+ if (compare(array[k - 1], entry) <= 0)
+ break;
+
+ array[k] = array[k - 1];
+ }
+
+ array[k] = entry;
+ }
+}
+
+EFI_STATUS get_file_info(EFI_FILE *handle, EFI_FILE_INFO **ret, size_t *ret_size) {
+ size_t size = EFI_FILE_INFO_MIN_SIZE;
+ _cleanup_free_ EFI_FILE_INFO *fi = NULL;
+ EFI_STATUS err;
+
+ assert(handle);
+ assert(ret);
+
+ fi = xmalloc(size);
+ err = handle->GetInfo(handle, MAKE_GUID_PTR(EFI_FILE_INFO), &size, fi);
+ if (err == EFI_BUFFER_TOO_SMALL) {
+ free(fi);
+ fi = xmalloc(size); /* GetInfo tells us the required size, let's use that now */
+ err = handle->GetInfo(handle, MAKE_GUID_PTR(EFI_FILE_INFO), &size, fi);
+ }
+
+ if (err != EFI_SUCCESS)
+ return err;
+
+ *ret = TAKE_PTR(fi);
+
+ if (ret_size)
+ *ret_size = size;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS readdir(
+ EFI_FILE *handle,
+ EFI_FILE_INFO **buffer,
+ size_t *buffer_size) {
+
+ EFI_STATUS err;
+ size_t sz;
+
+ assert(handle);
+ assert(buffer);
+ assert(buffer_size);
+
+ /* buffer/buffer_size are both in and output parameters. Should be zero-initialized initially, and
+ * the specified buffer needs to be freed by caller, after final use. */
+
+ if (!*buffer) {
+ sz = EFI_FILE_INFO_MIN_SIZE;
+ *buffer = xmalloc(sz);
+ *buffer_size = sz;
+ } else
+ sz = *buffer_size;
+
+ err = handle->Read(handle, &sz, *buffer);
+ if (err == EFI_BUFFER_TOO_SMALL) {
+ free(*buffer);
+ *buffer = xmalloc(sz);
+ *buffer_size = sz;
+ err = handle->Read(handle, &sz, *buffer);
+ }
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (sz == 0) {
+ /* End of directory */
+ free(*buffer);
+ *buffer = NULL;
+ *buffer_size = 0;
+ }
+
+ return EFI_SUCCESS;
+}
+
+bool is_ascii(const char16_t *f) {
+ if (!f)
+ return false;
+
+ for (; *f != 0; f++)
+ if (*f > 127)
+ return false;
+
+ return true;
+}
+
+char16_t **strv_free(char16_t **v) {
+ if (!v)
+ return NULL;
+
+ for (char16_t **i = v; *i; i++)
+ free(*i);
+
+ free(v);
+ return NULL;
+}
+
+EFI_STATUS open_directory(
+ EFI_FILE *root,
+ const char16_t *path,
+ EFI_FILE **ret) {
+
+ _cleanup_(file_closep) EFI_FILE *dir = NULL;
+ _cleanup_free_ EFI_FILE_INFO *file_info = NULL;
+ EFI_STATUS err;
+
+ assert(root);
+
+ /* Opens a file, and then verifies it is actually a directory */
+
+ err = root->Open(root, &dir, (char16_t *) path, EFI_FILE_MODE_READ, 0);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = get_file_info(dir, &file_info, NULL);
+ if (err != EFI_SUCCESS)
+ return err;
+ if (!FLAGS_SET(file_info->Attribute, EFI_FILE_DIRECTORY))
+ return EFI_LOAD_ERROR;
+
+ *ret = TAKE_PTR(dir);
+ return EFI_SUCCESS;
+}
+
+uint64_t get_os_indications_supported(void) {
+ uint64_t osind;
+ EFI_STATUS err;
+
+ /* Returns the supported OS indications. If we can't acquire it, returns a zeroed out mask, i.e. no
+ * supported features. */
+
+ err = efivar_get_uint64_le(MAKE_GUID_PTR(EFI_GLOBAL_VARIABLE), u"OsIndicationsSupported", &osind);
+ if (err != EFI_SUCCESS)
+ return 0;
+
+ return osind;
+}
+
+__attribute__((noinline)) void notify_debugger(const char *identity, volatile bool wait) {
+#ifdef EFI_DEBUG
+ printf("%s@%p %s\n", identity, __executable_start, GIT_VERSION);
+ if (wait)
+ printf("Waiting for debugger to attach...\n");
+
+ /* This is a poor programmer's breakpoint to wait until a debugger
+ * has attached to us. Just "set variable wait = 0" or "return" to continue. */
+ while (wait)
+ /* Prefer asm based stalling so that gdb has a source location to present. */
+# if defined(__i386__) || defined(__x86_64__)
+ asm volatile("pause");
+# elif defined(__aarch64__)
+ asm volatile("wfi");
+# else
+ BS->Stall(5000);
+# endif
+#endif
+}
+
+#if defined(__i386__) || defined(__x86_64__)
+static uint8_t inb(uint16_t port) {
+ uint8_t value;
+ asm volatile("inb %1, %0" : "=a"(value) : "Nd"(port));
+ return value;
+}
+
+static void outb(uint16_t port, uint8_t value) {
+ asm volatile("outb %0, %1" : : "a"(value), "Nd"(port));
+}
+
+void beep(unsigned beep_count) {
+ enum {
+ PITCH = 500,
+ BEEP_DURATION_USEC = 100 * 1000,
+ WAIT_DURATION_USEC = 400 * 1000,
+
+ PIT_FREQUENCY = 0x1234dd,
+ SPEAKER_CONTROL_PORT = 0x61,
+ SPEAKER_ON_MASK = 0x03,
+ TIMER_PORT_MAGIC = 0xB6,
+ TIMER_CONTROL_PORT = 0x43,
+ TIMER_CONTROL2_PORT = 0x42,
+ };
+
+ /* Set frequency. */
+ uint32_t counter = PIT_FREQUENCY / PITCH;
+ outb(TIMER_CONTROL_PORT, TIMER_PORT_MAGIC);
+ outb(TIMER_CONTROL2_PORT, counter & 0xFF);
+ outb(TIMER_CONTROL2_PORT, (counter >> 8) & 0xFF);
+
+ uint8_t value = inb(SPEAKER_CONTROL_PORT);
+
+ while (beep_count > 0) {
+ /* Turn speaker on. */
+ value |= SPEAKER_ON_MASK;
+ outb(SPEAKER_CONTROL_PORT, value);
+
+ BS->Stall(BEEP_DURATION_USEC);
+
+ /* Turn speaker off. */
+ value &= ~SPEAKER_ON_MASK;
+ outb(SPEAKER_CONTROL_PORT, value);
+
+ beep_count--;
+ if (beep_count > 0)
+ BS->Stall(WAIT_DURATION_USEC);
+ }
+}
+#endif
+
+EFI_STATUS open_volume(EFI_HANDLE device, EFI_FILE **ret_file) {
+ EFI_STATUS err;
+ EFI_FILE *file;
+ EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *volume;
+
+ assert(ret_file);
+
+ err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), (void **) &volume);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = volume->OpenVolume(volume, &file);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ *ret_file = file;
+ return EFI_SUCCESS;
+}
+
+void *find_configuration_table(const EFI_GUID *guid) {
+ for (size_t i = 0; i < ST->NumberOfTableEntries; i++)
+ if (efi_guid_equal(&ST->ConfigurationTable[i].VendorGuid, guid))
+ return ST->ConfigurationTable[i].VendorTable;
+
+ return NULL;
+}
+
+static void remove_boot_count(char16_t *path) {
+ char16_t *prefix_end;
+ const char16_t *tail;
+ uint64_t ignored;
+
+ assert(path);
+
+ prefix_end = strchr16(path, '+');
+ if (!prefix_end)
+ return;
+
+ tail = prefix_end + 1;
+
+ if (!parse_number16(tail, &ignored, &tail))
+ return;
+
+ if (*tail == '-') {
+ ++tail;
+ if (!parse_number16(tail, &ignored, &tail))
+ return;
+ }
+
+ if (!IN_SET(*tail, '\0', '.'))
+ return;
+
+ strcpy16(prefix_end, tail);
+}
+
+char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path) {
+ if (!file_path)
+ return NULL;
+
+ /* A device path is allowed to have more than one file path node. If that is the case they are
+ * supposed to be concatenated. Unfortunately, the device path to text protocol simply converts the
+ * nodes individually and then combines those with the usual '/' for device path nodes. But this does
+ * not create a legal EFI file path that the file protocol can use. */
+
+ /* Make sure we really only got file paths. */
+ for (const EFI_DEVICE_PATH *node = file_path; !device_path_is_end(node);
+ node = device_path_next_node(node))
+ if (node->Type != MEDIA_DEVICE_PATH || node->SubType != MEDIA_FILEPATH_DP)
+ return NULL;
+
+ _cleanup_free_ char16_t *file_path_str = NULL;
+ if (device_path_to_str(file_path, &file_path_str) != EFI_SUCCESS)
+ return NULL;
+
+ convert_efi_path(file_path_str);
+ remove_boot_count(file_path_str);
+ return xasprintf("%ls.extra.d", file_path_str);
+}
+
+void *xmalloc(size_t size) {
+ void *p = NULL;
+ assert_se(BS->AllocatePool(EfiLoaderData, size, &p) == EFI_SUCCESS);
+ return p;
+}
diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h
new file mode 100644
index 0000000..0306e32
--- /dev/null
+++ b/src/boot/efi/util.h
@@ -0,0 +1,213 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+#include "log.h"
+#include "proto/file-io.h"
+#include "string-util-fundamental.h"
+
+/* This is provided by the linker. */
+extern uint8_t __executable_start[];
+
+static inline void free(void *p) {
+ if (!p)
+ return;
+
+ /* Debugging an invalid free requires trace logging to find the call site or a debugger attached. For
+ * release builds it is not worth the bother to even warn when we cannot even print a call stack. */
+#ifdef EFI_DEBUG
+ assert_se(BS->FreePool(p) == EFI_SUCCESS);
+#else
+ (void) BS->FreePool(p);
+#endif
+}
+
+static inline void freep(void *p) {
+ free(*(void **) p);
+}
+
+#define _cleanup_free_ _cleanup_(freep)
+
+_malloc_ _alloc_(1) _returns_nonnull_ _warn_unused_result_
+void *xmalloc(size_t size);
+
+_malloc_ _alloc_(1, 2) _returns_nonnull_ _warn_unused_result_
+static inline void *xmalloc_multiply(size_t n, size_t size) {
+ assert_se(!__builtin_mul_overflow(size, n, &size));
+ return xmalloc(size);
+}
+
+/* Use malloc attribute as this never returns p like userspace realloc. */
+_malloc_ _alloc_(3) _returns_nonnull_ _warn_unused_result_
+static inline void *xrealloc(void *p, size_t old_size, size_t new_size) {
+ void *t = xmalloc(new_size);
+ new_size = MIN(old_size, new_size);
+ if (new_size > 0)
+ memcpy(t, p, new_size);
+ free(p);
+ return t;
+}
+
+_malloc_ _alloc_(2) _returns_nonnull_ _warn_unused_result_
+static inline void* xmemdup(const void *p, size_t l) {
+ return memcpy(xmalloc(l), p, l);
+}
+
+#define xnew(type, n) ((type *) xmalloc_multiply((n), sizeof(type)))
+
+typedef struct {
+ EFI_PHYSICAL_ADDRESS addr;
+ size_t n_pages;
+} Pages;
+
+static inline void cleanup_pages(Pages *p) {
+ if (p->n_pages == 0)
+ return;
+#ifdef EFI_DEBUG
+ assert_se(BS->FreePages(p->addr, p->n_pages) == EFI_SUCCESS);
+#else
+ (void) BS->FreePages(p->addr, p->n_pages);
+#endif
+}
+
+#define _cleanup_pages_ _cleanup_(cleanup_pages)
+
+static inline Pages xmalloc_pages(
+ EFI_ALLOCATE_TYPE type, EFI_MEMORY_TYPE memory_type, size_t n_pages, EFI_PHYSICAL_ADDRESS addr) {
+ assert_se(BS->AllocatePages(type, memory_type, n_pages, &addr) == EFI_SUCCESS);
+ return (Pages) {
+ .addr = addr,
+ .n_pages = n_pages,
+ };
+}
+
+EFI_STATUS efivar_set(const EFI_GUID *vendor, const char16_t *name, const char16_t *value, uint32_t flags);
+EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const char16_t *name, const void *buf, size_t size, uint32_t flags);
+EFI_STATUS efivar_set_uint_string(const EFI_GUID *vendor, const char16_t *name, size_t i, uint32_t flags);
+EFI_STATUS efivar_set_uint32_le(const EFI_GUID *vendor, const char16_t *NAME, uint32_t value, uint32_t flags);
+EFI_STATUS efivar_set_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t value, uint32_t flags);
+void efivar_set_time_usec(const EFI_GUID *vendor, const char16_t *name, uint64_t usec);
+
+EFI_STATUS efivar_unset(const EFI_GUID *vendor, const char16_t *name, uint32_t flags);
+
+EFI_STATUS efivar_get(const EFI_GUID *vendor, const char16_t *name, char16_t **ret);
+EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const char16_t *name, char **ret, size_t *ret_size);
+EFI_STATUS efivar_get_uint_string(const EFI_GUID *vendor, const char16_t *name, size_t *ret);
+EFI_STATUS efivar_get_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t *ret);
+EFI_STATUS efivar_get_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t *ret);
+EFI_STATUS efivar_get_boolean_u8(const EFI_GUID *vendor, const char16_t *name, bool *ret);
+
+void convert_efi_path(char16_t *path);
+char16_t *xstr8_to_path(const char *stra);
+char16_t *mangle_stub_cmdline(char16_t *cmdline);
+
+EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf);
+EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t size, char **content, size_t *content_size);
+
+static inline void file_closep(EFI_FILE **handle) {
+ if (!*handle)
+ return;
+
+ (*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\<vendor>-<revision>.conf convention and the
+ * associated EFI variables.
+ */
+#define LOADER_GUID \
+ { 0x4a67b082, 0x0a4c, 0x41cf, { 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f } }
+
+/* Note that GUID is evaluated multiple times! */
+#define GUID_FORMAT_STR "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"
+#define GUID_FORMAT_VAL(g) (g).Data1, (g).Data2, (g).Data3, (g).Data4[0], (g).Data4[1], \
+ (g).Data4[2], (g).Data4[3], (g).Data4[4], (g).Data4[5], (g).Data4[6], (g).Data4[7]
+
+void print_at(size_t x, size_t y, size_t attr, const char16_t *str);
+void clear_screen(size_t attr);
+
+typedef int (*compare_pointer_func_t)(const void *a, const void *b);
+void sort_pointer_array(void **array, size_t n_members, compare_pointer_func_t compare);
+
+EFI_STATUS get_file_info(EFI_FILE *handle, EFI_FILE_INFO **ret, size_t *ret_size);
+EFI_STATUS readdir(EFI_FILE *handle, EFI_FILE_INFO **buffer, size_t *buffer_size);
+
+bool is_ascii(const char16_t *f);
+
+char16_t **strv_free(char16_t **l);
+
+static inline void strv_freep(char16_t ***p) {
+ strv_free(*p);
+}
+
+EFI_STATUS open_directory(EFI_FILE *root_dir, const char16_t *path, EFI_FILE **ret);
+
+/* Conversion between EFI_PHYSICAL_ADDRESS and pointers is not obvious. The former is always 64-bit, even on
+ * 32-bit archs. And gcc complains if we cast a pointer to an integer of a different size. Hence let's do the
+ * conversion indirectly: first into uintptr_t and then extended to EFI_PHYSICAL_ADDRESS. */
+static inline EFI_PHYSICAL_ADDRESS POINTER_TO_PHYSICAL_ADDRESS(const void *p) {
+ return (EFI_PHYSICAL_ADDRESS) (uintptr_t) p;
+}
+
+static inline void *PHYSICAL_ADDRESS_TO_POINTER(EFI_PHYSICAL_ADDRESS addr) {
+ /* On 32-bit systems the address might not be convertible (as pointers are 32-bit but
+ * EFI_PHYSICAL_ADDRESS 64-bit) */
+ assert(addr <= UINTPTR_MAX);
+ return (void *) (uintptr_t) addr;
+}
+
+uint64_t get_os_indications_supported(void);
+
+/* If EFI_DEBUG, print our name and version and also report the address of the image base so a debugger can
+ * be attached. See debug-sd-boot.sh for how this can be done. */
+void notify_debugger(const char *identity, bool wait);
+
+/* On x86 the compiler assumes a different incoming stack alignment than what we get.
+ * This will cause long long variables to be misaligned when building with
+ * '-mlong-double' (for correct struct layouts). Normally, the compiler realigns the
+ * stack itself on entry, but we have to do this ourselves here as the compiler does
+ * not know that this is our entry point. */
+#ifdef __i386__
+# define _realign_stack_ __attribute__((force_align_arg_pointer))
+#else
+# define _realign_stack_
+#endif
+
+#define DEFINE_EFI_MAIN_FUNCTION(func, identity, wait_for_debugger) \
+ EFI_SYSTEM_TABLE *ST; \
+ EFI_BOOT_SERVICES *BS; \
+ EFI_RUNTIME_SERVICES *RT; \
+ _realign_stack_ \
+ EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table); \
+ EFIAPI EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) { \
+ ST = system_table; \
+ BS = system_table->BootServices; \
+ RT = system_table->RuntimeServices; \
+ __stack_chk_guard_init(); \
+ notify_debugger((identity), (wait_for_debugger)); \
+ EFI_STATUS err = func(image); \
+ log_wait(); \
+ return err; \
+ }
+
+#if defined(__i386__) || defined(__x86_64__)
+void beep(unsigned beep_count);
+#else
+static inline void beep(unsigned beep_count) {}
+#endif
+
+EFI_STATUS open_volume(EFI_HANDLE device, EFI_FILE **ret_file);
+
+static inline bool efi_guid_equal(const EFI_GUID *a, const EFI_GUID *b) {
+ return memcmp(a, b, sizeof(EFI_GUID)) == 0;
+}
+
+void *find_configuration_table(const EFI_GUID *guid);
+
+char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path);
diff --git a/src/boot/efi/vmm.c b/src/boot/efi/vmm.c
new file mode 100644
index 0000000..60e216d
--- /dev/null
+++ b/src/boot/efi/vmm.c
@@ -0,0 +1,426 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#if defined(__i386__) || defined(__x86_64__)
+# include <cpuid.h>
+#endif
+
+#include "confidential-virt-fundamental.h"
+#include "device-path-util.h"
+#include "drivers.h"
+#include "efi-string.h"
+#include "proto/device-path.h"
+#include "string-util-fundamental.h"
+#include "util.h"
+#include "vmm.h"
+
+#define QEMU_KERNEL_LOADER_FS_MEDIA_GUID \
+ { 0x1428f772, 0xb64a, 0x441e, { 0xb8, 0xc3, 0x9e, 0xbd, 0xd7, 0xf8, 0x93, 0xc7 } }
+
+#define VMM_BOOT_ORDER_GUID \
+ { 0x668f4529, 0x63d0, 0x4bb5, { 0xb6, 0x5d, 0x6f, 0xbb, 0x9d, 0x36, 0xa4, 0x4a } }
+
+/* detect direct boot */
+bool is_direct_boot(EFI_HANDLE device) {
+ EFI_STATUS err;
+ VENDOR_DEVICE_PATH *dp;
+
+ err = BS->HandleProtocol(device, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &dp);
+ if (err != EFI_SUCCESS)
+ return false;
+
+ /* 'qemu -kernel systemd-bootx64.efi' */
+ if (dp->Header.Type == MEDIA_DEVICE_PATH &&
+ dp->Header.SubType == MEDIA_VENDOR_DP &&
+ memcmp(&dp->Guid, MAKE_GUID_PTR(QEMU_KERNEL_LOADER_FS_MEDIA), sizeof(EFI_GUID)) == 0)
+ return true;
+
+ /* loaded from firmware volume (sd-boot added to ovmf) */
+ if (dp->Header.Type == MEDIA_DEVICE_PATH &&
+ dp->Header.SubType == MEDIA_PIWG_FW_VOL_DP)
+ return true;
+
+ return false;
+}
+
+/*
+ * Try find ESP when not loaded from ESP
+ *
+ * Inspect all filesystems known to the firmware, try find the ESP. In case VMMBootOrderNNNN variables are
+ * present they are used to inspect the filesystems in the specified order. When nothing was found or the
+ * variables are not present the function will do one final search pass over all filesystems.
+ *
+ * Recent OVMF builds store the qemu boot order (as specified using the bootindex property on the qemu
+ * command line) in VMMBootOrderNNNN. The variables contain a device path.
+ *
+ * Example qemu command line:
+ * qemu -virtio-scsi-pci,addr=14.0 -device scsi-cd,scsi-id=4,bootindex=1
+ *
+ * Resulting variable:
+ * VMMBootOrder0000 = PciRoot(0x0)/Pci(0x14,0x0)/Scsi(0x4,0x0)
+ */
+EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) {
+ _cleanup_free_ EFI_HANDLE *handles = NULL;
+ size_t n_handles;
+ EFI_STATUS err, dp_err;
+
+ assert(ret_vmm_dev);
+ assert(ret_vmm_dir);
+
+ /* Make sure all file systems have been initialized. Only do this in VMs as this is slow
+ * on some real firmwares. */
+ (void) reconnect_all_drivers();
+
+ /* find all file system handles */
+ err = BS->LocateHandleBuffer(
+ ByProtocol, MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), NULL, &n_handles, &handles);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ for (size_t order = 0;; order++) {
+ _cleanup_free_ EFI_DEVICE_PATH *dp = NULL;
+
+ _cleanup_free_ char16_t *order_str = xasprintf("VMMBootOrder%04zx", order);
+ dp_err = efivar_get_raw(MAKE_GUID_PTR(VMM_BOOT_ORDER), order_str, (char **) &dp, NULL);
+
+ for (size_t i = 0; i < n_handles; i++) {
+ _cleanup_(file_closep) EFI_FILE *root_dir = NULL, *efi_dir = NULL;
+ EFI_DEVICE_PATH *fs;
+
+ err = BS->HandleProtocol(
+ handles[i], MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void **) &fs);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* check against VMMBootOrderNNNN (if set) */
+ if (dp_err == EFI_SUCCESS && !device_path_startswith(fs, dp))
+ continue;
+
+ err = open_volume(handles[i], &root_dir);
+ if (err != EFI_SUCCESS)
+ continue;
+
+ /* simple ESP check */
+ err = root_dir->Open(root_dir, &efi_dir, (char16_t*) u"\\EFI",
+ EFI_FILE_MODE_READ,
+ EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY);
+ if (err != EFI_SUCCESS)
+ continue;
+
+ *ret_vmm_dev = handles[i];
+ *ret_vmm_dir = TAKE_PTR(root_dir);
+ return EFI_SUCCESS;
+ }
+
+ if (dp_err != EFI_SUCCESS)
+ return EFI_NOT_FOUND;
+ }
+ assert_not_reached();
+}
+
+static bool cpuid_in_hypervisor(void) {
+#if defined(__i386__) || defined(__x86_64__)
+ unsigned eax, ebx, ecx, edx;
+
+ /* This is a dumbed down version of src/basic/virt.c's detect_vm() that safely works in the UEFI
+ * environment. */
+
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0)
+ return false;
+
+ if (FLAGS_SET(ecx, 0x80000000U))
+ return true;
+#endif
+
+ return false;
+}
+
+#define SMBIOS_TABLE_GUID \
+ GUID_DEF(0xeb9d2d31, 0x2d88, 0x11d3, 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d)
+#define SMBIOS3_TABLE_GUID \
+ GUID_DEF(0xf2fd1544, 0x9794, 0x4a2c, 0x99, 0x2e, 0xe5, 0xbb, 0xcf, 0x20, 0xe3, 0x94)
+
+typedef struct {
+ uint8_t anchor_string[4];
+ uint8_t entry_point_structure_checksum;
+ uint8_t entry_point_length;
+ uint8_t major_version;
+ uint8_t minor_version;
+ uint16_t max_structure_size;
+ uint8_t entry_point_revision;
+ uint8_t formatted_area[5];
+ uint8_t intermediate_anchor_string[5];
+ uint8_t intermediate_checksum;
+ uint16_t table_length;
+ uint32_t table_address;
+ uint16_t number_of_smbios_structures;
+ uint8_t smbios_bcd_revision;
+} _packed_ SmbiosEntryPoint;
+
+typedef struct {
+ uint8_t anchor_string[5];
+ uint8_t entry_point_structure_checksum;
+ uint8_t entry_point_length;
+ uint8_t major_version;
+ uint8_t minor_version;
+ uint8_t docrev;
+ uint8_t entry_point_revision;
+ uint8_t reserved;
+ uint32_t table_maximum_size;
+ uint64_t table_address;
+} _packed_ Smbios3EntryPoint;
+
+typedef struct {
+ uint8_t type;
+ uint8_t length;
+ uint8_t handle[2];
+} _packed_ SmbiosHeader;
+
+typedef struct {
+ SmbiosHeader header;
+ uint8_t vendor;
+ uint8_t bios_version;
+ uint16_t bios_segment;
+ uint8_t bios_release_date;
+ uint8_t bios_size;
+ uint64_t bios_characteristics;
+ uint8_t bios_characteristics_ext[2];
+} _packed_ SmbiosTableType0;
+
+typedef struct {
+ SmbiosHeader header;
+ uint8_t count;
+ char contents[];
+} _packed_ SmbiosTableType11;
+
+static const void *find_smbios_configuration_table(uint64_t *ret_size) {
+ assert(ret_size);
+
+ const Smbios3EntryPoint *entry3 = find_configuration_table(MAKE_GUID_PTR(SMBIOS3_TABLE));
+ if (entry3 && memcmp(entry3->anchor_string, "_SM3_", 5) == 0 &&
+ entry3->entry_point_length <= sizeof(*entry3)) {
+ *ret_size = entry3->table_maximum_size;
+ return PHYSICAL_ADDRESS_TO_POINTER(entry3->table_address);
+ }
+
+ const SmbiosEntryPoint *entry = find_configuration_table(MAKE_GUID_PTR(SMBIOS_TABLE));
+ if (entry && memcmp(entry->anchor_string, "_SM_", 4) == 0 &&
+ entry->entry_point_length <= sizeof(*entry)) {
+ *ret_size = entry->table_length;
+ return PHYSICAL_ADDRESS_TO_POINTER(entry->table_address);
+ }
+
+ return NULL;
+}
+
+static const SmbiosHeader *get_smbios_table(uint8_t type, uint64_t *ret_size_left) {
+ uint64_t size = 0;
+ const uint8_t *p = find_smbios_configuration_table(&size);
+ if (!p)
+ return NULL;
+
+ for (;;) {
+ if (size < sizeof(SmbiosHeader))
+ return NULL;
+
+ const SmbiosHeader *header = (const SmbiosHeader *) p;
+
+ /* End of table. */
+ if (header->type == 127)
+ return NULL;
+
+ if (size < header->length)
+ return NULL;
+
+ if (header->type == type) {
+ if (ret_size_left)
+ *ret_size_left = size;
+ return header; /* Yay! */
+ }
+
+ /* Skip over formatted area. */
+ size -= header->length;
+ p += header->length;
+
+ /* Skip over string table. */
+ for (;;) {
+ const uint8_t *e = memchr(p, 0, size);
+ if (!e)
+ return NULL;
+
+ if (e == p) {/* Double NUL byte means we've reached the end of the string table. */
+ p++;
+ size--;
+ break;
+ }
+
+ size -= e + 1 - p;
+ p = e + 1;
+ }
+ }
+
+ return NULL;
+}
+
+static bool smbios_in_hypervisor(void) {
+ /* Look up BIOS Information (Type 0). */
+ const SmbiosTableType0 *type0 = (const SmbiosTableType0 *) get_smbios_table(0, NULL);
+ if (!type0 || type0->header.length < sizeof(SmbiosTableType0))
+ return false;
+
+ /* Bit 4 of 2nd BIOS characteristics extension bytes indicates virtualization. */
+ return FLAGS_SET(type0->bios_characteristics_ext[1], 1 << 4);
+}
+
+bool in_hypervisor(void) {
+ static int cache = -1;
+ if (cache >= 0)
+ return cache;
+
+ cache = cpuid_in_hypervisor() || smbios_in_hypervisor();
+ return cache;
+}
+
+const char* smbios_find_oem_string(const char *name) {
+ uint64_t left;
+
+ assert(name);
+
+ const SmbiosTableType11 *type11 = (const SmbiosTableType11 *) get_smbios_table(11, &left);
+ if (!type11 || type11->header.length < sizeof(SmbiosTableType11))
+ return NULL;
+
+ assert(left >= type11->header.length);
+
+ const char *s = type11->contents;
+ left -= type11->header.length;
+
+ for (const char *p = s; p < s + left; ) {
+ const char *e = memchr(p, 0, s + left - p);
+ if (!e || e == p) /* Double NUL byte means we've reached the end of the OEM strings. */
+ break;
+
+ const char *eq = startswith8(p, name);
+ if (eq && *eq == '=')
+ return eq + 1;
+
+ p = e + 1;
+ }
+
+ return NULL;
+}
+
+#if defined(__i386__) || defined(__x86_64__)
+static uint32_t cpuid_leaf(uint32_t eax, char ret_sig[static 13], bool swapped) {
+ /* zero-init as some queries explicitly require subleaf == 0 */
+ uint32_t sig[3] = {};
+
+ if (swapped)
+ __cpuid_count(eax, 0, eax, sig[0], sig[2], sig[1]);
+ else
+ __cpuid_count(eax, 0, eax, sig[0], sig[1], sig[2]);
+
+ memcpy(ret_sig, sig, sizeof(sig));
+ ret_sig[12] = 0; /* \0-terminate the string to make string comparison possible */
+
+ return eax;
+}
+
+static uint64_t msr(uint32_t index) {
+ uint64_t val;
+#ifdef __x86_64__
+ uint32_t low, high;
+ asm volatile ("rdmsr" : "=a"(low), "=d"(high) : "c"(index) : "memory");
+ val = ((uint64_t)high << 32) | low;
+#else
+ asm volatile ("rdmsr" : "=A"(val) : "c"(index) : "memory");
+#endif
+ return val;
+}
+
+static bool detect_hyperv_sev(void) {
+ uint32_t eax, ebx, ecx, edx, feat;
+ char sig[13] = {};
+
+ feat = cpuid_leaf(CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS, sig, false);
+
+ if (feat < CPUID_HYPERV_MIN || feat > CPUID_HYPERV_MAX)
+ return false;
+
+ if (memcmp(sig, CPUID_SIG_HYPERV, sizeof(sig)) != 0)
+ return false;
+
+ __cpuid(CPUID_HYPERV_FEATURES, eax, ebx, ecx, edx);
+
+ if (ebx & CPUID_HYPERV_ISOLATION && !(ebx & CPUID_HYPERV_CPU_MANAGEMENT)) {
+ __cpuid(CPUID_HYPERV_ISOLATION_CONFIG, eax, ebx, ecx, edx);
+
+ if ((ebx & CPUID_HYPERV_ISOLATION_TYPE_MASK) == CPUID_HYPERV_ISOLATION_TYPE_SNP)
+ return true;
+ }
+
+ return false;
+}
+
+static bool detect_sev(void) {
+ uint32_t eax, ebx, ecx, edx;
+ uint64_t msrval;
+
+ __cpuid(CPUID_GET_HIGHEST_FUNCTION, eax, ebx, ecx, edx);
+
+ if (eax < CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES)
+ return false;
+
+ __cpuid(CPUID_AMD_GET_ENCRYPTED_MEMORY_CAPABILITIES, eax, ebx, ecx, edx);
+
+ /* bit 1 == CPU supports SEV feature
+ *
+ * Note, Azure blocks this CPUID leaf from its SEV-SNP
+ * guests, so we must fallback to trying some HyperV
+ * specific CPUID checks.
+ */
+ if (!(eax & EAX_SEV))
+ return detect_hyperv_sev();
+
+ msrval = msr(MSR_AMD64_SEV);
+
+ if (msrval & (MSR_SEV_SNP | MSR_SEV_ES | MSR_SEV))
+ return true;
+
+ return false;
+}
+
+static bool detect_tdx(void) {
+ uint32_t eax, ebx, ecx, edx;
+ char sig[13] = {};
+
+ __cpuid(CPUID_GET_HIGHEST_FUNCTION, eax, ebx, ecx, edx);
+
+ if (eax < CPUID_INTEL_TDX_ENUMERATION)
+ return false;
+
+ cpuid_leaf(CPUID_INTEL_TDX_ENUMERATION, sig, true);
+
+ if (memcmp(sig, CPUID_SIG_INTEL_TDX, sizeof(sig)) == 0)
+ return true;
+
+ return false;
+}
+#endif /* ! __i386__ && ! __x86_64__ */
+
+bool is_confidential_vm(void) {
+#if defined(__i386__) || defined(__x86_64__)
+ char sig[13] = {};
+
+ if (!cpuid_in_hypervisor())
+ return false;
+
+ cpuid_leaf(0, sig, true);
+
+ if (memcmp(sig, CPUID_SIG_AMD, sizeof(sig)) == 0)
+ return detect_sev();
+ if (memcmp(sig, CPUID_SIG_INTEL, sizeof(sig)) == 0)
+ return detect_tdx();
+#endif /* ! __i386__ && ! __x86_64__ */
+
+ return false;
+}
diff --git a/src/boot/efi/vmm.h b/src/boot/efi/vmm.h
new file mode 100644
index 0000000..df48af3
--- /dev/null
+++ b/src/boot/efi/vmm.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+bool is_direct_boot(EFI_HANDLE device);
+EFI_STATUS vmm_open(EFI_HANDLE *ret_qemu_dev, EFI_FILE **ret_qemu_dir);
+
+bool in_hypervisor(void);
+
+bool is_confidential_vm(void);
+
+const char* smbios_find_oem_string(const char *name);
diff --git a/src/boot/measure.c b/src/boot/measure.c
new file mode 100644
index 0000000..5c5071e
--- /dev/null
+++ b/src/boot/measure.c
@@ -0,0 +1,1085 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "build.h"
+#include "efi-loader.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "hexdecoct.h"
+#include "json.h"
+#include "main-func.h"
+#include "memstream-util.h"
+#include "openssl-util.h"
+#include "parse-argument.h"
+#include "parse-util.h"
+#include "pretty-print.h"
+#include "sha256.h"
+#include "terminal-util.h"
+#include "tpm2-pcr.h"
+#include "tpm2-util.h"
+#include "uki.h"
+#include "verbs.h"
+
+/* Tool for pre-calculating expected TPM PCR values based on measured resources. This is intended to be used
+ * to pre-calculate suitable values for PCR 11, the way sd-stub measures into it. */
+
+static char *arg_sections[_UNIFIED_SECTION_MAX] = {};
+static char **arg_banks = NULL;
+static char *arg_tpm2_device = NULL;
+static char *arg_private_key = NULL;
+static char *arg_public_key = NULL;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_OFF;
+static PagerFlags arg_pager_flags = 0;
+static bool arg_current = false;
+static char **arg_phase = NULL;
+static char *arg_append = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_append, freep);
+
+static void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) {
+ for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++)
+ free((*sections)[c]);
+}
+
+STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections);
+
+static int help(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-measure", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...] COMMAND ...\n"
+ "\n%5$sPre-calculate and sign PCR hash for a unified kernel image (UKI).%6$s\n"
+ "\n%3$sCommands:%4$s\n"
+ " status Show current PCR values\n"
+ " calculate Calculate expected PCR values\n"
+ " sign Calculate and sign expected PCR values\n"
+ "\n%3$sOptions:%4$s\n"
+ " -h --help Show this help\n"
+ " --version Print version\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " -c --current Use current PCR values\n"
+ " --phase=PHASE Specify a boot phase to sign for\n"
+ " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n"
+ " --tpm2-device=PATH Use specified TPM2 device\n"
+ " --private-key=KEY Private key (PEM) to sign with\n"
+ " --public-key=KEY Public key (PEM) to validate against\n"
+ " --json=MODE Output as JSON\n"
+ " -j Same as --json=pretty on tty, --json=short otherwise\n"
+ " --append=PATH Load specified JSON signature, and append new signature to it\n"
+ "\n%3$sUKI PE Section Options:%4$s %3$sUKI PE Section%4$s\n"
+ " --linux=PATH Path to Linux kernel image file %7$s .linux\n"
+ " --osrel=PATH Path to os-release file %7$s .osrel\n"
+ " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n"
+ " --initrd=PATH Path to initrd image file %7$s .initrd\n"
+ " --splash=PATH Path to splash bitmap file %7$s .splash\n"
+ " --dtb=PATH Path to Devicetree file %7$s .dtb\n"
+ " --uname=PATH Path to 'uname -r' file %7$s .uname\n"
+ " --sbat=PATH Path to SBAT file %7$s .sbat\n"
+ " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal(),
+ special_glyph(SPECIAL_GLYPH_ARROW_RIGHT));
+
+ return 0;
+}
+
+static char *normalize_phase(const char *s) {
+ _cleanup_strv_free_ char **l = NULL;
+
+ /* Let's normalize phase expressions. We split the series of colon-separated words up, then remove
+ * all empty ones, and glue them back together again. In other words we remove duplicate ":", as well
+ * as leading and trailing ones. */
+
+ l = strv_split(s, ":"); /* Split series of words */
+ if (!l)
+ return NULL;
+
+ /* Remove all empty words and glue things back together */
+ return strv_join(strv_remove(l, ""), ":");
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ _ARG_SECTION_FIRST,
+ ARG_LINUX = _ARG_SECTION_FIRST,
+ ARG_OSREL,
+ ARG_CMDLINE,
+ ARG_INITRD,
+ ARG_SPLASH,
+ ARG_DTB,
+ ARG_UNAME,
+ ARG_SBAT,
+ _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */
+ _ARG_SECTION_LAST,
+ ARG_PCRPKEY = _ARG_SECTION_LAST,
+ ARG_BANK,
+ ARG_PRIVATE_KEY,
+ ARG_PUBLIC_KEY,
+ ARG_TPM2_DEVICE,
+ ARG_JSON,
+ ARG_PHASE,
+ ARG_APPEND,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "linux", required_argument, NULL, ARG_LINUX },
+ { "osrel", required_argument, NULL, ARG_OSREL },
+ { "cmdline", required_argument, NULL, ARG_CMDLINE },
+ { "initrd", required_argument, NULL, ARG_INITRD },
+ { "splash", required_argument, NULL, ARG_SPLASH },
+ { "dtb", required_argument, NULL, ARG_DTB },
+ { "uname", required_argument, NULL, ARG_UNAME },
+ { "sbat", required_argument, NULL, ARG_SBAT },
+ { "pcrpkey", required_argument, NULL, ARG_PCRPKEY },
+ { "current", no_argument, NULL, 'c' },
+ { "bank", required_argument, NULL, ARG_BANK },
+ { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
+ { "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
+ { "public-key", required_argument, NULL, ARG_PUBLIC_KEY },
+ { "json", required_argument, NULL, ARG_JSON },
+ { "phase", required_argument, NULL, ARG_PHASE },
+ { "append", required_argument, NULL, ARG_APPEND },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ /* Make sure the arguments list and the section list, stays in sync */
+ assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1);
+
+ while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ help(0, NULL, NULL);
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_NO_PAGER:
+ arg_pager_flags |= PAGER_DISABLE;
+ break;
+
+ case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: {
+ UnifiedSection section = c - _ARG_SECTION_FIRST;
+
+ r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section);
+ if (r < 0)
+ return r;
+ break;
+ }
+
+ case 'c':
+ arg_current = true;
+ break;
+
+ case ARG_BANK: {
+ const EVP_MD *implementation;
+
+ implementation = EVP_get_digestbyname(optarg);
+ if (!implementation)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg);
+
+ if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0)
+ return log_oom();
+
+ break;
+ }
+
+ case ARG_PRIVATE_KEY:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_private_key);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_PUBLIC_KEY:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_TPM2_DEVICE: {
+ _cleanup_free_ char *device = NULL;
+
+ if (streq(optarg, "list"))
+ return tpm2_list_devices();
+
+ if (!streq(optarg, "auto")) {
+ device = strdup(optarg);
+ if (!device)
+ return log_oom();
+ }
+
+ free_and_replace(arg_tpm2_device, device);
+ break;
+ }
+
+ case 'j':
+ arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
+ break;
+
+ case ARG_JSON:
+ r = parse_json_argument(optarg, &arg_json_format_flags);
+ if (r <= 0)
+ return r;
+
+ break;
+
+ case ARG_PHASE: {
+ char *n;
+
+ n = normalize_phase(optarg);
+ if (!n)
+ return log_oom();
+
+ r = strv_consume(&arg_phase, TAKE_PTR(n));
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ case ARG_APPEND:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (strv_isempty(arg_banks)) {
+ /* If no banks are specifically selected, pick all known banks */
+ arg_banks = strv_new("SHA1", "SHA256", "SHA384", "SHA512");
+ if (!arg_banks)
+ return log_oom();
+ }
+
+ strv_sort(arg_banks);
+ strv_uniq(arg_banks);
+
+ if (arg_current)
+ for (UnifiedSection us = 0; us < _UNIFIED_SECTION_MAX; us++)
+ if (arg_sections[us])
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "The --current switch cannot be used in combination with --linux= and related switches.");
+
+ if (strv_isempty(arg_phase)) {
+ /* If no phases are specifically selected, pick everything from the beginning of the initrd
+ * to the beginning of shutdown. */
+ if (strv_extend_strv(&arg_phase,
+ STRV_MAKE("enter-initrd",
+ "enter-initrd:leave-initrd",
+ "enter-initrd:leave-initrd:sysinit",
+ "enter-initrd:leave-initrd:sysinit:ready"),
+ /* filter_duplicates= */ false) < 0)
+ return log_oom();
+ } else {
+ strv_sort(arg_phase);
+ strv_uniq(arg_phase);
+ }
+
+ _cleanup_free_ char *j = NULL;
+ j = strv_join(arg_phase, ", ");
+ if (!j)
+ return log_oom();
+
+ log_debug("Measuring boot phases: %s", j);
+ return 1;
+}
+
+/* The PCR 11 state for one specific bank */
+typedef struct PcrState {
+ char *bank;
+ const EVP_MD *md;
+ void *value;
+ size_t value_size;
+ void *saved_value; /* A copy of the original value we calculated, used by pcr_states_save()/pcr_states_restore() to come later back to */
+} PcrState;
+
+static void pcr_state_free_all(PcrState **pcr_state) {
+ assert(pcr_state);
+
+ if (!*pcr_state)
+ return;
+
+ for (size_t i = 0; (*pcr_state)[i].value; i++) {
+ free((*pcr_state)[i].bank);
+ free((*pcr_state)[i].value);
+ free((*pcr_state)[i].saved_value);
+ }
+
+ *pcr_state = mfree(*pcr_state);
+}
+
+static void evp_md_ctx_free_all(EVP_MD_CTX **md[]) {
+ assert(md);
+
+ if (!*md)
+ return;
+
+ for (size_t i = 0; (*md)[i]; i++)
+ EVP_MD_CTX_free((*md)[i]);
+
+ *md = mfree(*md);
+}
+
+static int pcr_state_extend(PcrState *pcr_state, const void *data, size_t sz) {
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mc = NULL;
+ unsigned value_size;
+
+ assert(pcr_state);
+ assert(data || sz == 0);
+ assert(pcr_state->md);
+ assert(pcr_state->value);
+ assert(pcr_state->value_size > 0);
+
+ /* Extends a (virtual) PCR by the given data */
+
+ mc = EVP_MD_CTX_new();
+ if (!mc)
+ return log_oom();
+
+ if (EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", pcr_state->bank);
+
+ /* First thing we do, is hash the old PCR value */
+ if (EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest.");
+
+ /* Then, we hash the new data */
+ if (EVP_DigestUpdate(mc, data, sz) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest.");
+
+ if (EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context.");
+
+ assert(value_size == pcr_state->value_size);
+ return 0;
+}
+
+#define BUFFER_SIZE (16U * 1024U)
+
+static int measure_kernel(PcrState *pcr_states, size_t n) {
+ _cleanup_free_ void *buffer = NULL;
+ int r;
+
+ assert(n > 0);
+ assert(pcr_states);
+
+ /* Virtually measures the components of a unified kernel image into PCR 11 */
+
+ if (arg_current) {
+ /* Shortcut things, if we should just use the current PCR value */
+
+ for (size_t i = 0; i < n; i++) {
+ _cleanup_free_ char *p = NULL, *s = NULL;
+ _cleanup_free_ void *v = NULL;
+ size_t sz;
+
+ if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%i", pcr_states[i].bank, TPM2_PCR_KERNEL_BOOT) < 0)
+ return log_oom();
+
+ r = read_virtual_file(p, 4096, &s, NULL);
+ if (r == -ENOENT && access("/sys/class/tpm/tpm0/", F_OK) >= 0)
+ return log_error_errno(r, "TPM device exists, but cannot open '%s'; either the kernel is too old, or selected PCR bank is not supported: %m", p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read '%s': %m", p);
+
+ r = unhexmem(strstrip(s), SIZE_MAX, &v, &sz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to decode PCR value '%s': %m", s);
+
+ assert(pcr_states[i].value_size == sz);
+ memcpy(pcr_states[i].value, v, sz);
+ }
+
+ return 0;
+ }
+
+ buffer = malloc(BUFFER_SIZE);
+ if (!buffer)
+ return log_oom();
+
+ for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) {
+ _cleanup_(evp_md_ctx_free_all) EVP_MD_CTX **mdctx = NULL;
+ _cleanup_close_ int fd = -EBADF;
+ uint64_t m = 0;
+
+ if (!arg_sections[c])
+ continue;
+
+ fd = open(arg_sections[c], O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open '%s': %m", arg_sections[c]);
+
+ /* Allocate one message digest context per bank (NULL terminated) */
+ mdctx = new0(EVP_MD_CTX*, n + 1);
+ if (!mdctx)
+ return log_oom();
+
+ for (size_t i = 0; i < n; i++) {
+ mdctx[i] = EVP_MD_CTX_new();
+ if (!mdctx[i])
+ return log_oom();
+
+ if (EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Failed to initialize data %s context.", pcr_states[i].bank);
+ }
+
+ for (;;) {
+ ssize_t sz;
+
+ sz = read(fd, buffer, BUFFER_SIZE);
+ if (sz < 0)
+ return log_error_errno(errno, "Failed to read '%s': %m", arg_sections[c]);
+ if (sz == 0) /* EOF */
+ break;
+
+ for (size_t i = 0; i < n; i++)
+ if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest.");
+
+ m += sz;
+ }
+
+ fd = safe_close(fd);
+
+ if (m == 0) /* We skip over empty files, the stub does so too */
+ continue;
+
+ for (size_t i = 0; i < n; i++) {
+ _cleanup_free_ void *data_hash = NULL;
+ unsigned data_hash_size;
+
+ data_hash = malloc(pcr_states[i].value_size);
+ if (!data_hash)
+ return log_oom();
+
+ /* Measure name of section */
+ if (EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash section name with %s.", pcr_states[i].bank);
+
+ assert(data_hash_size == (unsigned) pcr_states[i].value_size);
+
+ r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size);
+ if (r < 0)
+ return r;
+
+ /* Retrieve hash of data and measure it */
+ if (EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context.");
+
+ assert(data_hash_size == (unsigned) pcr_states[i].value_size);
+
+ r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int measure_phase(PcrState *pcr_states, size_t n, const char *phase) {
+ _cleanup_strv_free_ char **l = NULL;
+ int r;
+
+ assert(pcr_states);
+ assert(n > 0);
+
+ /* Measure a phase string into PCR 11. This splits up the "phase" expression at colons, and then
+ * virtually extends each specified word into PCR 11, to model how during boot we measure a series of
+ * words into PCR 11, one for each phase. */
+
+ l = strv_split(phase, ":");
+ if (!l)
+ return log_oom();
+
+ STRV_FOREACH(word, l) {
+ size_t wl;
+
+ if (isempty(*word))
+ continue;
+
+ wl = strlen(*word);
+
+ for (size_t i = 0; i < n; i++) { /* For each bank */
+ _cleanup_free_ void *b = NULL;
+ int bsz;
+
+ bsz = EVP_MD_size(pcr_states[i].md);
+ assert(bsz > 0);
+
+ b = malloc(bsz);
+ if (!b)
+ return log_oom();
+
+ /* First hash the word itself */
+ if (EVP_Digest(*word, wl, b, NULL, pcr_states[i].md, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word '%s'.", *word);
+
+ /* And then extend the PCR with the resulting hash */
+ r = pcr_state_extend(pcr_states + i, b, bsz);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int pcr_states_allocate(PcrState **ret) {
+ _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
+ size_t n = 0;
+
+ pcr_states = new0(PcrState, strv_length(arg_banks) + 1);
+ if (!pcr_states)
+ return log_oom();
+
+ /* Allocate a PCR state structure, one for each bank */
+ STRV_FOREACH(d, arg_banks) {
+ const EVP_MD *implementation;
+ _cleanup_free_ void *v = NULL;
+ _cleanup_free_ char *b = NULL;
+ int sz;
+
+ assert_se(implementation = EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */
+
+ b = strdup(EVP_MD_name(implementation));
+ if (!b)
+ return log_oom();
+
+ sz = EVP_MD_size(implementation);
+ if (sz <= 0 || sz >= INT_MAX)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected digest size: %i", sz);
+
+ v = malloc0(sz); /* initial PCR state is all zeroes */
+ if (!v)
+ return log_oom();
+
+ pcr_states[n++] = (struct PcrState) {
+ .bank = ascii_strlower(TAKE_PTR(b)),
+ .md = implementation,
+ .value = TAKE_PTR(v),
+ .value_size = sz,
+ };
+ }
+
+ *ret = TAKE_PTR(pcr_states);
+ return (int) n;
+}
+
+static int pcr_states_save(PcrState *pcr_states, size_t n) {
+ assert(pcr_states);
+ assert(n > 0);
+
+ for (size_t i = 0; i < n; i++) {
+ _cleanup_free_ void *saved = NULL;
+
+ if (!pcr_states[i].value)
+ continue;
+
+ saved = memdup(pcr_states[i].value, pcr_states[i].value_size);
+ if (!saved)
+ return log_oom();
+
+ free_and_replace(pcr_states[i].saved_value, saved);
+ }
+
+ return 0;
+}
+
+static void pcr_states_restore(PcrState *pcr_states, size_t n) {
+ assert(pcr_states);
+ assert(n > 0);
+
+ for (size_t i = 0; i < n; i++) {
+
+ assert(pcr_states[i].value);
+ assert(pcr_states[i].saved_value);
+
+ memcpy(pcr_states[i].value, pcr_states[i].saved_value, pcr_states[i].value_size);
+ }
+}
+
+static int verb_calculate(int argc, char *argv[], void *userdata) {
+ _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+ _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
+ int r;
+
+ if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Either --linux= or --current must be specified, refusing.");
+ if (arg_append)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "The --append= switch is only supported for 'sign', not 'calculate'.");
+
+ assert(!strv_isempty(arg_banks));
+ assert(!strv_isempty(arg_phase));
+
+ r = pcr_states_allocate(&pcr_states);
+ if (r < 0)
+ return r;
+
+ size_t n = r;
+
+ r = measure_kernel(pcr_states, n);
+ if (r < 0)
+ return r;
+
+ /* Save the current state, so that we later can restore to it. This way we can measure the PCR values
+ * for multiple different boot phases without heaving to start from zero each time */
+ r = pcr_states_save(pcr_states, n);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(phase, arg_phase) {
+
+ r = measure_phase(pcr_states, n, *phase);
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < n; i++) {
+ if (arg_json_format_flags & JSON_FORMAT_OFF) {
+ _cleanup_free_ char *hd = NULL;
+
+ if (i == 0) {
+ fflush(stdout);
+ fprintf(stderr, "%s# PCR[%i] Phase <%s>%s\n",
+ ansi_grey(),
+ TPM2_PCR_KERNEL_BOOT,
+ isempty(*phase) ? ":" : *phase,
+ ansi_normal());
+ fflush(stderr);
+ }
+
+ hd = hexmem(pcr_states[i].value, pcr_states[i].value_size);
+ if (!hd)
+ return log_oom();
+
+ printf("%i:%s=%s\n", TPM2_PCR_KERNEL_BOOT, pcr_states[i].bank, hd);
+ } else {
+ _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+
+ array = json_variant_ref(json_variant_by_key(w, pcr_states[i].bank));
+
+ r = json_variant_append_arrayb(
+ &array,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_CONDITION(!isempty(*phase), "phase", JSON_BUILD_STRING(*phase)),
+ JSON_BUILD_PAIR("pcr", JSON_BUILD_INTEGER(TPM2_PCR_KERNEL_BOOT)),
+ JSON_BUILD_PAIR("hash", JSON_BUILD_HEX(pcr_states[i].value, pcr_states[i].value_size))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to append JSON object to array: %m");
+
+ r = json_variant_set_field(&w, pcr_states[i].bank, array);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add bank info to object: %m");
+ }
+ }
+
+ /* Return to the original kernel measurement for the next phase calculation */
+ pcr_states_restore(pcr_states, n);
+ }
+
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
+
+ if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
+ pager_open(arg_pager_flags);
+
+ json_variant_dump(w, arg_json_format_flags, stdout, NULL);
+ }
+
+ return 0;
+}
+
+static int verb_sign(int argc, char *argv[], void *userdata) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL;
+ _cleanup_fclose_ FILE *privkeyf = NULL;
+ size_t n;
+ int r;
+
+ if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Either --linux= or --current must be specified, refusing.");
+
+ if (!arg_private_key)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "No private key specified, use --private-key=.");
+
+ assert(!strv_isempty(arg_banks));
+ assert(!strv_isempty(arg_phase));
+
+ if (arg_append) {
+ r = json_parse_file(NULL, arg_append, 0, &v, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse '%s': %m", arg_append);
+
+ if (!json_variant_is_object(v))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "File '%s' is not a valid JSON object, refusing.", arg_append);
+ }
+
+ /* When signing we only support JSON output */
+ arg_json_format_flags &= ~JSON_FORMAT_OFF;
+
+ privkeyf = fopen(arg_private_key, "re");
+ if (!privkeyf)
+ return log_error_errno(errno, "Failed to open private key file '%s': %m", arg_private_key);
+
+ privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL);
+ if (!privkey)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", arg_private_key);
+
+ if (arg_public_key) {
+ _cleanup_fclose_ FILE *pubkeyf = NULL;
+
+ pubkeyf = fopen(arg_public_key, "re");
+ if (!pubkeyf)
+ return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key);
+
+ pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL);
+ if (!pubkey)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key);
+ } else {
+ _cleanup_(memstream_done) MemStream m = {};
+ FILE *tf;
+
+ /* No public key was specified, let's derive it automatically, if we can */
+
+ tf = memstream_init(&m);
+ if (!tf)
+ return log_oom();
+
+ if (i2d_PUBKEY_fp(tf, privkey) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to extract public key from private key file '%s'.", arg_private_key);
+
+ fflush(tf);
+ rewind(tf);
+
+ if (!d2i_PUBKEY_fp(tf, &pubkey))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to parse extracted public key of private key file '%s'.", arg_private_key);
+ }
+
+ r = pcr_states_allocate(&pcr_states);
+ if (r < 0)
+ return r;
+
+ n = (size_t) r;
+
+ r = measure_kernel(pcr_states, n);
+ if (r < 0)
+ return r;
+
+ r = pcr_states_save(pcr_states, n);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(phase, arg_phase) {
+
+ r = measure_phase(pcr_states, n, *phase);
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < n; i++) {
+ PcrState *p = pcr_states + i;
+
+ int tpmalg = tpm2_hash_alg_from_string(EVP_MD_name(p->md));
+ if (tpmalg < 0)
+ return log_error_errno(tpmalg, "Unsupported PCR bank");
+
+ Tpm2PCRValue pcr_value = TPM2_PCR_VALUE_MAKE(TPM2_PCR_KERNEL_BOOT,
+ tpmalg,
+ TPM2B_DIGEST_MAKE(p->value, p->value_size));
+
+ TPM2B_DIGEST pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
+
+ r = tpm2_calculate_policy_pcr(&pcr_value, 1, &pcr_policy_digest);
+ if (r < 0)
+ return log_error_errno(r, "Could not calculate PolicyPCR digest: %m");
+
+ _cleanup_free_ void *sig = NULL;
+ size_t ss;
+
+ r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss);
+ if (r < 0)
+ return log_error_errno(r, "Failed to sign PCR policy: %m");
+
+ _cleanup_free_ void *pubkey_fp = NULL;
+ size_t pubkey_fp_size = 0;
+ r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size);
+ if (r < 0)
+ return r;
+
+ _cleanup_(json_variant_unrefp) JsonVariant *a = NULL;
+ r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM2_PCR_KERNEL_BOOT, &a);
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON PCR mask array: %m");
+
+ _cleanup_(json_variant_unrefp) JsonVariant *bv = NULL;
+ r = json_build(&bv, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("pcrs", JSON_BUILD_VARIANT(a)), /* PCR mask */
+ JSON_BUILD_PAIR("pkfp", JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */
+ JSON_BUILD_PAIR("pol", JSON_BUILD_HEX(pcr_policy_digest.buffer, pcr_policy_digest.size)), /* TPM2 policy hash that is signed */
+ JSON_BUILD_PAIR("sig", JSON_BUILD_BASE64(sig, ss)))); /* signature data */
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON object: %m");
+
+ _cleanup_(json_variant_unrefp) JsonVariant *av = NULL;
+ av = json_variant_ref(json_variant_by_key(v, p->bank));
+
+ r = json_variant_append_array_nodup(&av, bv);
+ if (r < 0)
+ return log_error_errno(r, "Failed to append JSON object: %m");
+
+ r = json_variant_set_field(&v, p->bank, av);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add JSON field: %m");
+ }
+
+ /* Return to the original kernel measurement for the next phase calculation */
+ pcr_states_restore(pcr_states, n);
+ }
+
+ if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
+ pager_open(arg_pager_flags);
+
+ json_variant_dump(v, arg_json_format_flags, stdout, NULL);
+
+ return 0;
+}
+
+static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) {
+ _cleanup_free_ char *s = NULL;
+ uint32_t v;
+ int r;
+
+ r = efi_get_variable_string(varname, &s);
+ if (r == -ENOENT)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname);
+
+ r = safe_atou32(s, &v);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s);
+
+ if (pcr != v)
+ log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n"
+ "The measurements are likely inconsistent.", description, v, pcr);
+
+ return 0;
+}
+
+static int validate_stub(void) {
+ uint64_t features;
+ bool found = false;
+ int r;
+
+ if (tpm2_support() != TPM2_SUPPORT_FULL)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support.");
+
+ r = efi_stub_get_features(&features);
+ if (r < 0)
+ return log_error_errno(r, "Unable to get stub features: %m");
+
+ if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS))
+ log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n"
+ "The PCR measurements seen are unlikely to be valid.");
+
+ r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE(StubPcrKernelImage), "kernel image");
+ if (r < 0)
+ return r;
+
+ r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_CONFIG, EFI_LOADER_VARIABLE(StubPcrKernelParameters), "kernel parameters");
+ if (r < 0)
+ return r;
+
+ r = compare_reported_pcr_nr(TPM2_PCR_SYSEXTS, EFI_LOADER_VARIABLE(StubPcrInitRDSysExts), "initrd system extension images");
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(bank, arg_banks) {
+ _cleanup_free_ char *b = NULL, *p = NULL;
+
+ b = strdup(*bank);
+ if (!b)
+ return log_oom();
+
+ if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0)
+ return log_oom();
+
+ if (access(p, F_OK) < 0) {
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b);
+ } else
+ found = true;
+ }
+
+ if (!found)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist.");
+
+ return 0;
+}
+
+static int verb_status(int argc, char *argv[], void *userdata) {
+ static const uint32_t relevant_pcrs[] = {
+ TPM2_PCR_KERNEL_BOOT,
+ TPM2_PCR_KERNEL_CONFIG,
+ TPM2_PCR_SYSEXTS,
+ };
+
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ int r;
+
+ r = validate_stub();
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < ELEMENTSOF(relevant_pcrs); i++) {
+
+ STRV_FOREACH(bank, arg_banks) {
+ _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL;
+ _cleanup_free_ void *h = NULL;
+ size_t l;
+
+ b = strdup(*bank);
+ if (!b)
+ return log_oom();
+
+ if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), relevant_pcrs[i]) < 0)
+ return log_oom();
+
+ r = read_virtual_file(p, 4096, &s, NULL);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0)
+ return log_error_errno(r, "Failed to read '%s': %m", p);
+
+ r = unhexmem(strstrip(s), SIZE_MAX, &h, &l);
+ if (r < 0)
+ return log_error_errno(r, "Failed to decode PCR value '%s': %m", s);
+
+ if (arg_json_format_flags & JSON_FORMAT_OFF) {
+ _cleanup_free_ char *f = NULL;
+
+ f = hexmem(h, l);
+ if (!h)
+ return log_oom();
+
+ if (bank == arg_banks) {
+ /* before the first line for each PCR, write a short descriptive text to
+ * stderr, and leave the primary content on stdout */
+ fflush(stdout);
+ fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n",
+ ansi_grey(),
+ relevant_pcrs[i],
+ tpm2_pcr_index_to_string(relevant_pcrs[i]),
+ memeqzero(h, l) ? " (NOT SET!)" : "",
+ ansi_normal());
+ fflush(stderr);
+ }
+
+ printf("%" PRIu32 ":%s=%s\n", relevant_pcrs[i], b, f);
+
+ } else {
+ _cleanup_(json_variant_unrefp) JsonVariant *bv = NULL, *a = NULL;
+
+ r = json_build(&bv,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("pcr", JSON_BUILD_INTEGER(relevant_pcrs[i])),
+ JSON_BUILD_PAIR("hash", JSON_BUILD_HEX(h, l))
+ )
+ );
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON object: %m");
+
+ a = json_variant_ref(json_variant_by_key(v, b));
+
+ r = json_variant_append_array(&a, bv);
+ if (r < 0)
+ return log_error_errno(r, "Failed to append PCR entry to JSON array: %m");
+
+ r = json_variant_set_field(&v, b, a);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add bank info to object: %m");
+ }
+ }
+ }
+
+ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
+ if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
+ pager_open(arg_pager_flags);
+
+ json_variant_dump(v, arg_json_format_flags, stdout, NULL);
+ }
+
+ return 0;
+}
+
+static int measure_main(int argc, char *argv[]) {
+ static const Verb verbs[] = {
+ { "help", VERB_ANY, VERB_ANY, 0, help },
+ { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
+ { "calculate", VERB_ANY, 1, 0, verb_calculate },
+ { "sign", VERB_ANY, 1, 0, verb_sign },
+ {}
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_show_color(true);
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ return measure_main(argc, argv);
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/boot/meson.build b/src/boot/meson.build
new file mode 100644
index 0000000..55b9bd6
--- /dev/null
+++ b/src/boot/meson.build
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+bootctl_sources = files(
+ 'bootctl-install.c',
+ 'bootctl-random-seed.c',
+ 'bootctl-reboot-to-firmware.c',
+ 'bootctl-set-efivar.c',
+ 'bootctl-status.c',
+ 'bootctl-systemd-efi-options.c',
+ 'bootctl-uki.c',
+ 'bootctl-util.c',
+ 'bootctl.c',
+)
+
+if get_option('link-boot-shared')
+ boot_link_with = [libshared]
+else
+ boot_link_with = [
+ libshared_static,
+ libsystemd_static,
+ ]
+endif
+
+executables += [
+ executable_template + {
+ 'name' : 'bootctl',
+ 'public' : true,
+ 'conditions' : [
+ 'HAVE_BLKID',
+ ],
+ 'sources' : bootctl_sources,
+ 'link_with' : boot_link_with,
+ 'dependencies' : libblkid,
+ },
+ libexec_template + {
+ 'name' : 'systemd-bless-boot',
+ 'public' : true,
+ 'conditions' : [
+ 'HAVE_BLKID',
+ 'ENABLE_BOOTLOADER',
+ ],
+ 'sources' : files('bless-boot.c'),
+ 'link_with' : boot_link_with,
+ 'dependencies' : libblkid,
+ },
+ generator_template + {
+ 'name' : 'systemd-bless-boot-generator',
+ 'conditions' : [
+ 'HAVE_BLKID',
+ 'ENABLE_BOOTLOADER',
+ ],
+ 'sources' : files('bless-boot-generator.c'),
+ 'link_with' : boot_link_with,
+ },
+ libexec_template + {
+ 'name' : 'systemd-measure',
+ 'conditions' : [
+ 'HAVE_BLKID',
+ 'HAVE_OPENSSL',
+ 'HAVE_TPM2',
+ ],
+ 'sources' : files('measure.c'),
+ 'dependencies' : libopenssl,
+ },
+ libexec_template + {
+ 'name' : 'systemd-boot-check-no-failures',
+ 'sources' : files('boot-check-no-failures.c'),
+ },
+]