From 3e09ad57c6269eac8d34d5b443757dd81490922e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 22 Mar 2022 16:30:34 +0100 Subject: [PATCH 1/7] efivars: cache ENOENT as no efi secure boot On systems lacking EFI or the SecureBoot efi var the caching of this info didn#t work, since we'd see ENOENT when reading the var, and cache that, which we then use as reason to retry next time. Let's fix that and convert ENOENT to "secure boot", because that's what it really means. All other errors are left as is (and reason to retry). But let's add some debug logging for that case. --- src/basic/efivars.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/basic/efivars.c b/src/basic/efivars.c index 7a9d1bf6412..957e73a7bf1 100644 --- a/src/basic/efivars.c +++ b/src/basic/efivars.c @@ -310,9 +310,17 @@ static int read_flag(const char *variable) { bool is_efi_secure_boot(void) { static int cache = -1; + int r; - if (cache < 0) - cache = read_flag(EFI_GLOBAL_VARIABLE(SecureBoot)); + if (cache < 0) { + r = read_flag(EFI_GLOBAL_VARIABLE(SecureBoot)); + if (r == -ENOENT) + cache = false; + else if (r < 0) + log_debug_errno(r, "Error reading SecureBoot EFI variable, assuming not in SecureBoot mode: %m"); + else + cache = r; + } return cache > 0; } From bc5eb90015c596483d0f7a5bae135e185902b73f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 22 Mar 2022 16:32:35 +0100 Subject: [PATCH 2/7] efivars: tweak debug log message in efi_get_secure_boot_mode() mention what we'll do as effect of the error we are seeing and eat up. --- src/basic/efivars.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/basic/efivars.c b/src/basic/efivars.c index 957e73a7bf1..470369c0cbb 100644 --- a/src/basic/efivars.c +++ b/src/basic/efivars.c @@ -334,7 +334,8 @@ SecureBootMode efi_get_secure_boot_mode(void) { int secure = read_flag(EFI_GLOBAL_VARIABLE(SecureBoot)); if (secure < 0) { if (secure != -ENOENT) - log_debug_errno(secure, "Error reading SecureBoot EFI variable: %m"); + log_debug_errno(secure, "Error reading SecureBoot EFI variable, assuming not in SecureBoot mode: %m"); + return (cache = SECURE_BOOT_UNSUPPORTED); } From 83fe0be1706c453f2f9d6be5259031a70021f610 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 22 Mar 2022 16:33:09 +0100 Subject: [PATCH 3/7] =?UTF-8?q?efivars:=20no=20need=20to=20convert=20ENOEN?= =?UTF-8?q?T=20=E2=86=92=20ENODATA=20twice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit read_efi_options_variable() already does this, don#t do it again. --- src/basic/efivars.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/basic/efivars.c b/src/basic/efivars.c index 470369c0cbb..92b11cb2c57 100644 --- a/src/basic/efivars.c +++ b/src/basic/efivars.c @@ -439,9 +439,7 @@ int systemd_efi_options_efivarfs_if_newer(char **line) { r = read_efi_options_variable(line); if (r < 0) - log_warning_errno(r, "Failed to read SystemdOptions EFI variable: %m"); - if (r == -ENOENT) - return -ENODATA; - return r; + return log_warning_errno(r, "Failed to read SystemdOptions EFI variable: %m"); + return 0; } #endif From afd1a45a163eb0e50b7a3ff74376ff97392e9192 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 22 Mar 2022 16:34:39 +0100 Subject: [PATCH 4/7] efivars: downgrade log level in systemd_efi_options_efivarfs_if_newer() The only caller logs anyway, let's avoid duplicate logging above LOG_DEBUG. --- src/basic/efivars.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/basic/efivars.c b/src/basic/efivars.c index 92b11cb2c57..4dc9dbba6a9 100644 --- a/src/basic/efivars.c +++ b/src/basic/efivars.c @@ -439,7 +439,8 @@ int systemd_efi_options_efivarfs_if_newer(char **line) { r = read_efi_options_variable(line); if (r < 0) - return log_warning_errno(r, "Failed to read SystemdOptions EFI variable: %m"); + return log_debug_errno(r, "Failed to read SystemdOptions EFI variable: %m"); + return 0; } #endif From 187513fd3a7164f618e67a9da66ec57f637e761e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 22 Mar 2022 16:38:25 +0100 Subject: [PATCH 5/7] efivarfs: rename a couple of return params to ret_xyz/ret --- src/basic/efivars.c | 22 +++++++++++----------- src/basic/efivars.h | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/basic/efivars.c b/src/basic/efivars.c index 4dc9dbba6a9..847b6da1eef 100644 --- a/src/basic/efivars.c +++ b/src/basic/efivars.c @@ -142,7 +142,7 @@ int efi_get_variable( return 0; } -int efi_get_variable_string(const char *variable, char **p) { +int efi_get_variable_string(const char *variable, char **ret) { _cleanup_free_ void *s = NULL; size_t ss = 0; int r; @@ -156,7 +156,7 @@ int efi_get_variable_string(const char *variable, char **p) { if (!x) return -ENOMEM; - *p = x; + *ret = x; return 0; } @@ -350,7 +350,7 @@ SecureBootMode efi_get_secure_boot_mode(void) { return (cache = decode_secure_boot_mode(secure, audit > 0, deployed > 0, setup > 0)); } -static int read_efi_options_variable(char **line) { +static int read_efi_options_variable(char **ret) { int r; /* In SecureBoot mode this is probably not what you want. As your cmdline is cryptographically signed @@ -370,7 +370,7 @@ static int read_efi_options_variable(char **line) { return -EPERM; } - r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE(SystemdOptions), line); + r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE(SystemdOptions), ret); if (r == -ENOENT) return -ENODATA; return r; @@ -388,13 +388,13 @@ int cache_efi_options_variable(void) { WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755); } -int systemd_efi_options_variable(char **line) { +int systemd_efi_options_variable(char **ret) { const char *e; int r; /* Returns the contents of the variable for current boot from the cache. */ - assert(line); + assert(ret); /* For testing purposes it is sometimes useful to be able to override this */ e = secure_getenv("SYSTEMD_EFI_OPTIONS"); @@ -405,11 +405,11 @@ int systemd_efi_options_variable(char **line) { if (!m) return -ENOMEM; - *line = m; + *ret = m; return 0; } - r = read_one_line_file(EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE(SystemdOptions)), line); + r = read_one_line_file(EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE(SystemdOptions)), ret); if (r == -ENOENT) return -ENODATA; return r; @@ -419,7 +419,7 @@ static inline int compare_stat_mtime(const struct stat *a, const struct stat *b) return CMP(timespec_load(&a->st_mtim), timespec_load(&b->st_mtim)); } -int systemd_efi_options_efivarfs_if_newer(char **line) { +int systemd_efi_options_efivarfs_if_newer(char **ret) { struct stat a = {}, b; int r; @@ -433,11 +433,11 @@ int systemd_efi_options_efivarfs_if_newer(char **line) { log_debug("Variable SystemdOptions in evifarfs is newer than in cache."); else { log_debug("Variable SystemdOptions in cache is up to date."); - *line = NULL; + *ret = NULL; return 0; } - r = read_efi_options_variable(line); + r = read_efi_options_variable(ret); if (r < 0) return log_debug_errno(r, "Failed to read SystemdOptions EFI variable: %m"); diff --git a/src/basic/efivars.h b/src/basic/efivars.h index 494154b3618..bd40272ba46 100644 --- a/src/basic/efivars.h +++ b/src/basic/efivars.h @@ -42,8 +42,8 @@ #if ENABLE_EFI -int efi_get_variable(const char *variable, uint32_t *attribute, void **value, size_t *size); -int efi_get_variable_string(const char *variable, char **p); +int efi_get_variable(const char *variable, uint32_t *attribute, void **ret_value, size_t *ret_size); +int efi_get_variable_string(const char *variable, char **ret); int efi_set_variable(const char *variable, const void *value, size_t size); int efi_set_variable_string(const char *variable, const char *p); @@ -52,8 +52,8 @@ bool is_efi_secure_boot(void); SecureBootMode efi_get_secure_boot_mode(void); int cache_efi_options_variable(void); -int systemd_efi_options_variable(char **line); -int systemd_efi_options_efivarfs_if_newer(char **line); +int systemd_efi_options_variable(char **ret); +int systemd_efi_options_efivarfs_if_newer(char **ret); #else @@ -61,7 +61,7 @@ static inline int efi_get_variable(const char *variable, uint32_t *attribute, vo return -EOPNOTSUPP; } -static inline int efi_get_variable_string(const char *variable, char **p) { +static inline int efi_get_variable_string(const char *variable, char **ret) { return -EOPNOTSUPP; } From 8fc5c444268be03329164991c3f1cbe9b35a81e9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 22 Mar 2022 16:43:42 +0100 Subject: [PATCH 6/7] efivars: define efi variable flags less weirdly The flags are actually 32bit values, but aligned with zeroes befitting a 64bit value. Let's fix that. --- src/basic/efivars.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/basic/efivars.h b/src/basic/efivars.h index bd40272ba46..bafe2d3128b 100644 --- a/src/basic/efivars.h +++ b/src/basic/efivars.h @@ -19,9 +19,10 @@ #define EFI_VENDOR_GLOBAL_STR SD_ID128_MAKE_UUID_STR(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c) #define EFI_VENDOR_SYSTEMD SD_ID128_MAKE(8c,f2,64,4b,4b,0b,42,8f,93,87,6d,87,60,50,dc,67) #define EFI_VENDOR_SYSTEMD_STR SD_ID128_MAKE_UUID_STR(8c,f2,64,4b,4b,0b,42,8f,93,87,6d,87,60,50,dc,67) -#define EFI_VARIABLE_NON_VOLATILE 0x0000000000000001 -#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x0000000000000002 -#define EFI_VARIABLE_RUNTIME_ACCESS 0x0000000000000004 + +#define EFI_VARIABLE_NON_VOLATILE UINT32_C(0x00000001) +#define EFI_VARIABLE_BOOTSERVICE_ACCESS UINT32_C(0x00000002) +#define EFI_VARIABLE_RUNTIME_ACCESS UINT32_C(0x00000004) /* Note that the - naming scheme is an efivarfs convention, i.e. part of the Linux * API file system implementation for EFI. EFI itself processes UIDS in binary form. From 7be4b23649c02df33e4292f37ffc8aecf512955a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 23 Mar 2022 12:07:06 +0100 Subject: [PATCH 7/7] efi-loader: split efi-api.[ch] from efi-loader.[ch] Some refactoring: split efi-loader.[ch] in two: isolate the calls that implement out boot loader interface spec, and those which implement access to upstream UEFI firmware features. They are quite different in nature and behaviour, and even semantically it makes to keep these two separate. At the very least because the previous name "efi-loader.[ch]" suggests all was about loader-specific APIs, but much of it is generic uefi stuff... While we are at it, I renamed a bunch of return parameters to follow our usual ret_xyz naming. But besides renaming no real code changes. --- src/boot/bless-boot.c | 1 + src/boot/bootctl.c | 1 + src/cryptsetup/cryptsetup.c | 2 +- src/login/logind-dbus.c | 1 + src/shared/condition.c | 2 +- src/shared/efi-api.c | 556 ++++++++++++++++++++++++++++++++++ src/shared/efi-api.h | 68 +++++ src/shared/efi-loader.c | 579 ++---------------------------------- src/shared/efi-loader.h | 61 +--- src/shared/meson.build | 2 + 10 files changed, 653 insertions(+), 620 deletions(-) create mode 100644 src/shared/efi-api.c create mode 100644 src/shared/efi-api.h diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c index 259ae33bb56..c8bc205d1b0 100644 --- a/src/boot/bless-boot.c +++ b/src/boot/bless-boot.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "bootspec.h" +#include "efi-api.h" #include "efi-loader.h" #include "efivars.h" #include "fd-util.h" diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 5dd92a6ca0f..cc79f51655f 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -17,6 +17,7 @@ #include "bootspec.h" #include "copy.h" #include "dirent-util.h" +#include "efi-api.h" #include "efi-loader.h" #include "efivars.h" #include "env-file.h" diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index bdb381eecf4..22c91c3ffc6 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -17,7 +17,7 @@ #include "cryptsetup-tpm2.h" #include "cryptsetup-util.h" #include "device-util.h" -#include "efi-loader.h" +#include "efi-api.h" #include "env-util.h" #include "escape.h" #include "fileio.h" diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index a1c7d0a8836..77670966199 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -20,6 +20,7 @@ #include "cgroup-util.h" #include "device-util.h" #include "dirent-util.h" +#include "efi-api.h" #include "efi-loader.h" #include "efivars.h" #include "env-file.h" diff --git a/src/shared/condition.c b/src/shared/condition.c index 0a4072c9bef..b9fca7bf0b6 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -22,7 +22,7 @@ #include "cgroup-util.h" #include "condition.h" #include "cpu-set-util.h" -#include "efi-loader.h" +#include "efi-api.h" #include "env-file.h" #include "env-util.h" #include "extract-word.h" diff --git a/src/shared/efi-api.c b/src/shared/efi-api.c new file mode 100644 index 00000000000..b2c6af8754d --- /dev/null +++ b/src/shared/efi-api.c @@ -0,0 +1,556 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "efi-api.h" +#include "efivars.h" +#include "fd-util.h" +#include "sort-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "utf8.h" + +#if ENABLE_EFI + +#define LOAD_OPTION_ACTIVE 0x00000001 +#define MEDIA_DEVICE_PATH 0x04 +#define MEDIA_HARDDRIVE_DP 0x01 +#define MEDIA_FILEPATH_DP 0x04 +#define SIGNATURE_TYPE_GUID 0x02 +#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02 +#define END_DEVICE_PATH_TYPE 0x7f +#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff + +#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI UINT64_C(0x0000000000000001) + +#define boot_option__contents \ + { \ + uint32_t attr; \ + uint16_t path_len; \ + uint16_t title[]; \ + } + +struct boot_option boot_option__contents; +struct boot_option__packed boot_option__contents _packed_; +assert_cc(offsetof(struct boot_option, title) == offsetof(struct boot_option__packed, title)); +/* sizeof(struct boot_option) != sizeof(struct boot_option__packed), so + * the *size* of the structure should not be used anywhere below. */ + +struct drive_path { + uint32_t part_nr; + uint64_t part_start; + uint64_t part_size; + char signature[16]; + uint8_t mbr_type; + uint8_t signature_type; +} _packed_; + +#define device_path__contents \ + { \ + uint8_t type; \ + uint8_t sub_type; \ + uint16_t length; \ + union { \ + uint16_t path[0]; \ + struct drive_path drive; \ + }; \ + } + +struct device_path device_path__contents; +struct device_path__packed device_path__contents _packed_; +assert_cc(sizeof(struct device_path) == sizeof(struct device_path__packed)); + +int efi_reboot_to_firmware_supported(void) { + _cleanup_free_ void *v = NULL; + static int cache = -1; + uint64_t b; + size_t s; + int r; + + if (cache > 0) + return 0; + if (cache == 0) + return -EOPNOTSUPP; + + if (!is_efi_boot()) + goto not_supported; + + r = efi_get_variable(EFI_GLOBAL_VARIABLE(OsIndicationsSupported), NULL, &v, &s); + if (r == -ENOENT) + goto not_supported; /* variable doesn't exist? it's not supported then */ + if (r < 0) + return r; + if (s != sizeof(uint64_t)) + return -EINVAL; + + b = *(uint64_t*) v; + if (!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) + goto not_supported; /* bit unset? it's not supported then */ + + cache = 1; + return 0; + +not_supported: + cache = 0; + return -EOPNOTSUPP; +} + +static int get_os_indications(uint64_t *ret) { + static struct stat cache_stat = {}; + _cleanup_free_ void *v = NULL; + static uint64_t cache; + struct stat new_stat; + size_t s; + int r; + + assert(ret); + + /* Let's verify general support first */ + r = efi_reboot_to_firmware_supported(); + if (r < 0) + return r; + + /* stat() the EFI variable, to see if the mtime changed. If it did we need to cache again. */ + if (stat(EFIVAR_PATH(EFI_GLOBAL_VARIABLE(OsIndications)), &new_stat) < 0) { + if (errno != ENOENT) + return -errno; + + /* Doesn't exist? Then we can exit early (also see below) */ + *ret = 0; + return 0; + + } else if (stat_inode_unmodified(&new_stat, &cache_stat)) { + /* inode didn't change, we can return the cached value */ + *ret = cache; + return 0; + } + + r = efi_get_variable(EFI_GLOBAL_VARIABLE(OsIndications), NULL, &v, &s); + if (r == -ENOENT) { + /* Some firmware implementations that do support OsIndications and report that with + * OsIndicationsSupported will remove the OsIndications variable when it is unset. Let's + * pretend it's 0 then, to hide this implementation detail. Note that this call will return + * -ENOENT then only if the support for OsIndications is missing entirely, as determined by + * efi_reboot_to_firmware_supported() above. */ + *ret = 0; + return 0; + } + if (r < 0) + return r; + if (s != sizeof(uint64_t)) + return -EINVAL; + + cache_stat = new_stat; + *ret = cache = *(uint64_t *)v; + return 0; +} + +int efi_get_reboot_to_firmware(void) { + int r; + uint64_t b; + + r = get_os_indications(&b); + if (r < 0) + return r; + + return !!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI); +} + +int efi_set_reboot_to_firmware(bool value) { + int r; + uint64_t b, b_new; + + r = get_os_indications(&b); + if (r < 0) + return r; + + b_new = UPDATE_FLAG(b, EFI_OS_INDICATIONS_BOOT_TO_FW_UI, value); + + /* Avoid writing to efi vars store if we can due to firmware bugs. */ + if (b != b_new) + return efi_set_variable(EFI_GLOBAL_VARIABLE(OsIndications), &b_new, sizeof(uint64_t)); + + return 0; +} + +static ssize_t utf16_size(const uint16_t *s, size_t buf_len_bytes) { + size_t l = 0; + + /* Returns the size of the string in bytes without the terminating two zero bytes */ + + if (buf_len_bytes % sizeof(uint16_t) != 0) + return -EINVAL; + + while (l < buf_len_bytes / sizeof(uint16_t)) { + if (s[l] == 0) + return (l + 1) * sizeof(uint16_t); + l++; + } + + return -EINVAL; /* The terminator was not found */ +} + +struct guid { + uint32_t u1; + uint16_t u2; + uint16_t u3; + uint8_t u4[8]; +} _packed_; + +static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) { + uint32_t u1; + uint16_t u2, u3; + const struct guid *uuid = guid; + + memcpy(&u1, &uuid->u1, sizeof(uint32_t)); + id128->bytes[0] = (u1 >> 24) & 0xff; + id128->bytes[1] = (u1 >> 16) & 0xff; + id128->bytes[2] = (u1 >> 8) & 0xff; + id128->bytes[3] = u1 & 0xff; + memcpy(&u2, &uuid->u2, sizeof(uint16_t)); + id128->bytes[4] = (u2 >> 8) & 0xff; + id128->bytes[5] = u2 & 0xff; + memcpy(&u3, &uuid->u3, sizeof(uint16_t)); + id128->bytes[6] = (u3 >> 8) & 0xff; + id128->bytes[7] = u3 & 0xff; + memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4)); +} + +int efi_get_boot_option( + uint16_t id, + char **ret_title, + sd_id128_t *ret_part_uuid, + char **ret_path, + bool *ret_active) { + + char variable[STRLEN(EFI_GLOBAL_VARIABLE_STR("Boot")) + 4 + 1]; + _cleanup_free_ uint8_t *buf = NULL; + size_t l; + struct boot_option *header; + ssize_t title_size; + _cleanup_free_ char *s = NULL, *p = NULL; + sd_id128_t p_uuid = SD_ID128_NULL; + int r; + + if (!is_efi_boot()) + return -EOPNOTSUPP; + + xsprintf(variable, EFI_GLOBAL_VARIABLE_STR("Boot%04X"), id); + r = efi_get_variable(variable, NULL, (void **)&buf, &l); + if (r < 0) + return r; + if (l < offsetof(struct boot_option, title)) + return -ENOENT; + + header = (struct boot_option *)buf; + title_size = utf16_size(header->title, l - offsetof(struct boot_option, title)); + if (title_size < 0) + return title_size; + + if (ret_title) { + s = utf16_to_utf8(header->title, title_size); + if (!s) + return -ENOMEM; + } + + if (header->path_len > 0) { + uint8_t *dbuf; + size_t dnext, doff; + + doff = offsetof(struct boot_option, title) + title_size; + dbuf = buf + doff; + if (header->path_len > l - doff) + return -EINVAL; + + dnext = 0; + while (dnext < header->path_len) { + struct device_path *dpath; + + dpath = (struct device_path *)(dbuf + dnext); + if (dpath->length < 4) + break; + + /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ + if (dpath->type == END_DEVICE_PATH_TYPE && dpath->sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE) + break; + + dnext += dpath->length; + + /* Type 0x04 – Media Device Path */ + if (dpath->type != MEDIA_DEVICE_PATH) + continue; + + /* Sub-Type 1 – Hard Drive */ + if (dpath->sub_type == MEDIA_HARDDRIVE_DP) { + /* 0x02 – GUID Partition Table */ + if (dpath->drive.mbr_type != MBR_TYPE_EFI_PARTITION_TABLE_HEADER) + continue; + + /* 0x02 – GUID signature */ + if (dpath->drive.signature_type != SIGNATURE_TYPE_GUID) + continue; + + if (ret_part_uuid) + efi_guid_to_id128(dpath->drive.signature, &p_uuid); + continue; + } + + /* Sub-Type 4 – File Path */ + if (dpath->sub_type == MEDIA_FILEPATH_DP && !p && ret_path) { + p = utf16_to_utf8(dpath->path, dpath->length-4); + if (!p) + return -ENOMEM; + + efi_tilt_backslashes(p); + continue; + } + } + } + + if (ret_title) + *ret_title = TAKE_PTR(s); + if (ret_part_uuid) + *ret_part_uuid = p_uuid; + if (ret_path) + *ret_path = TAKE_PTR(p); + if (ret_active) + *ret_active = header->attr & LOAD_OPTION_ACTIVE; + + return 0; +} + +static void to_utf16(uint16_t *dest, const char *src) { + int i; + + for (i = 0; src[i] != '\0'; i++) + dest[i] = src[i]; + dest[i] = '\0'; +} + +static void id128_to_efi_guid(sd_id128_t id, void *guid) { + struct guid uuid = { + .u1 = id.bytes[0] << 24 | id.bytes[1] << 16 | id.bytes[2] << 8 | id.bytes[3], + .u2 = id.bytes[4] << 8 | id.bytes[5], + .u3 = id.bytes[6] << 8 | id.bytes[7], + }; + memcpy(uuid.u4, id.bytes+8, sizeof(uuid.u4)); + memcpy(guid, &uuid, sizeof(uuid)); +} + +static uint16_t *tilt_slashes(uint16_t *s) { + for (uint16_t *p = s; *p; p++) + if (*p == '/') + *p = '\\'; + + return s; +} + +int efi_add_boot_option( + uint16_t id, + const char *title, + uint32_t part, + uint64_t pstart, + uint64_t psize, + sd_id128_t part_uuid, + const char *path) { + + size_t size, title_len, path_len; + _cleanup_free_ char *buf = NULL; + struct boot_option *option; + struct device_path *devicep; + char variable[STRLEN(EFI_GLOBAL_VARIABLE_STR("Boot")) + 4 + 1]; + + if (!is_efi_boot()) + return -EOPNOTSUPP; + + title_len = (strlen(title)+1) * 2; + path_len = (strlen(path)+1) * 2; + + buf = malloc0(offsetof(struct boot_option, title) + title_len + + sizeof(struct drive_path) + + sizeof(struct device_path) + path_len); + if (!buf) + return -ENOMEM; + + /* header */ + option = (struct boot_option *)buf; + option->attr = LOAD_OPTION_ACTIVE; + option->path_len = offsetof(struct device_path, drive) + sizeof(struct drive_path) + + offsetof(struct device_path, path) + path_len + + offsetof(struct device_path, path); + to_utf16(option->title, title); + size = offsetof(struct boot_option, title) + title_len; + + /* partition info */ + devicep = (struct device_path *)(buf + size); + devicep->type = MEDIA_DEVICE_PATH; + devicep->sub_type = MEDIA_HARDDRIVE_DP; + devicep->length = offsetof(struct device_path, drive) + sizeof(struct drive_path); + memcpy(&devicep->drive.part_nr, &part, sizeof(uint32_t)); + memcpy(&devicep->drive.part_start, &pstart, sizeof(uint64_t)); + memcpy(&devicep->drive.part_size, &psize, sizeof(uint64_t)); + id128_to_efi_guid(part_uuid, devicep->drive.signature); + devicep->drive.mbr_type = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; + devicep->drive.signature_type = SIGNATURE_TYPE_GUID; + size += devicep->length; + + /* path to loader */ + devicep = (struct device_path *)(buf + size); + devicep->type = MEDIA_DEVICE_PATH; + devicep->sub_type = MEDIA_FILEPATH_DP; + devicep->length = offsetof(struct device_path, path) + path_len; + to_utf16(devicep->path, path); + tilt_slashes(devicep->path); + size += devicep->length; + + /* end of path */ + devicep = (struct device_path *)(buf + size); + devicep->type = END_DEVICE_PATH_TYPE; + devicep->sub_type = END_ENTIRE_DEVICE_PATH_SUBTYPE; + devicep->length = offsetof(struct device_path, path); + size += devicep->length; + + xsprintf(variable, EFI_GLOBAL_VARIABLE_STR("Boot%04X"), id); + return efi_set_variable(variable, buf, size); +} + +int efi_remove_boot_option(uint16_t id) { + char variable[STRLEN(EFI_GLOBAL_VARIABLE_STR("Boot")) + 4 + 1]; + + if (!is_efi_boot()) + return -EOPNOTSUPP; + + xsprintf(variable, EFI_GLOBAL_VARIABLE_STR("Boot%04X"), id); + return efi_set_variable(variable, NULL, 0); +} + +int efi_get_boot_order(uint16_t **ret_order) { + _cleanup_free_ void *buf = NULL; + size_t l; + int r; + + assert(ret_order); + + if (!is_efi_boot()) + return -EOPNOTSUPP; + + r = efi_get_variable(EFI_GLOBAL_VARIABLE(BootOrder), NULL, &buf, &l); + if (r < 0) + return r; + + if (l <= 0) + return -ENOENT; + + if (l % sizeof(uint16_t) > 0 || + l / sizeof(uint16_t) > INT_MAX) + return -EINVAL; + + *ret_order = TAKE_PTR(buf); + return (int) (l / sizeof(uint16_t)); +} + +int efi_set_boot_order(const uint16_t *order, size_t n) { + + if (!is_efi_boot()) + return -EOPNOTSUPP; + + return efi_set_variable(EFI_GLOBAL_VARIABLE(BootOrder), order, n * sizeof(uint16_t)); +} + +static int boot_id_hex(const char s[static 4]) { + int id = 0; + + assert(s); + + for (int i = 0; i < 4; i++) + if (s[i] >= '0' && s[i] <= '9') + id |= (s[i] - '0') << (3 - i) * 4; + else if (s[i] >= 'A' && s[i] <= 'F') + id |= (s[i] - 'A' + 10) << (3 - i) * 4; + else + return -EINVAL; + + return id; +} + +static int cmp_uint16(const uint16_t *a, const uint16_t *b) { + return CMP(*a, *b); +} + +int efi_get_boot_options(uint16_t **ret_options) { + _cleanup_closedir_ DIR *dir = NULL; + _cleanup_free_ uint16_t *list = NULL; + int count = 0; + + assert(ret_options); + + if (!is_efi_boot()) + return -EOPNOTSUPP; + + dir = opendir(EFIVAR_PATH(".")); + if (!dir) + return -errno; + + FOREACH_DIRENT(de, dir, return -errno) { + int id; + + if (strncmp(de->d_name, "Boot", 4) != 0) + continue; + + if (strlen(de->d_name) != 45) + continue; + + if (strcmp(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR("")) != 0) /* generate variable suffix using macro */ + continue; + + id = boot_id_hex(de->d_name + 4); + if (id < 0) + continue; + + if (!GREEDY_REALLOC(list, count + 1)) + return -ENOMEM; + + list[count++] = id; + } + + typesafe_qsort(list, count, cmp_uint16); + + *ret_options = TAKE_PTR(list); + + return count; +} + +bool efi_has_tpm2(void) { + static int cache = -1; + + /* Returns whether the system has a TPM2 chip which is known to the EFI firmware. */ + + if (cache < 0) { + + /* First, check if we are on an EFI boot at all. */ + if (!is_efi_boot()) + cache = false; + else { + /* Then, check if the ACPI table "TPM2" exists, which is the TPM2 event log table, see: + * https://trustedcomputinggroup.org/wp-content/uploads/TCG_ACPIGeneralSpecification_v1.20_r8.pdf + * This table exists whenever the firmware is hooked up to TPM2. */ + cache = access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0; + if (!cache && errno != ENOENT) + log_debug_errno(errno, "Unable to test whether /sys/firmware/acpi/tables/TPM2 exists, assuming it doesn't: %m"); + } + } + + return cache; +} + +#endif + +char *efi_tilt_backslashes(char *s) { + for (char *p = s; *p; p++) + if (*p == '\\') + *p = '/'; + + return s; +} diff --git a/src/shared/efi-api.h b/src/shared/efi-api.h new file mode 100644 index 00000000000..5acc9e83ce9 --- /dev/null +++ b/src/shared/efi-api.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efivars-fundamental.h" +#include "efivars.h" + +/* Various calls for interfacing with EFI variables from the official UEFI specs. */ + +#if ENABLE_EFI + +int efi_reboot_to_firmware_supported(void); +int efi_get_reboot_to_firmware(void); +int efi_set_reboot_to_firmware(bool value); + +int efi_get_boot_option(uint16_t nr, char **ret_title, sd_id128_t *ret_part_uuid, char **ret_path, bool *ret_active); +int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path); +int efi_remove_boot_option(uint16_t id); +int efi_get_boot_order(uint16_t **ret_order); +int efi_set_boot_order(const uint16_t *order, size_t n); +int efi_get_boot_options(uint16_t **ret_options); + +bool efi_has_tpm2(void); + +#else + +static inline int efi_reboot_to_firmware_supported(void) { + return -EOPNOTSUPP; +} + +static inline int efi_get_reboot_to_firmware(void) { + return -EOPNOTSUPP; +} + +static inline int efi_set_reboot_to_firmware(bool value) { + return -EOPNOTSUPP; +} + +static inline int efi_get_boot_option(uint16_t nr, char **ret_title, sd_id128_t *ret_part_uuid, char **ret_path, bool *ret_active) { + return -EOPNOTSUPP; +} + +static inline int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path) { + return -EOPNOTSUPP; +} + +static inline int efi_remove_boot_option(uint16_t id) { + return -EOPNOTSUPP; +} + +static inline int efi_get_boot_order(uint16_t **ret_order) { + return -EOPNOTSUPP; +} + +static inline int efi_set_boot_order(const uint16_t *order, size_t n) { + return -EOPNOTSUPP; +} + +static inline int efi_get_boot_options(uint16_t **ret_options) { + return -EOPNOTSUPP; +} + +static inline bool efi_has_tpm2(void) { + return false; +} + +#endif + +char *efi_tilt_backslashes(char *s); diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index b14a2c32aa8..182a1f0222c 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -1,537 +1,22 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include -#include - #include "alloc-util.h" -#include "dirent-util.h" #include "efi-loader.h" -#include "efivars.h" -#include "fd-util.h" -#include "io-util.h" #include "parse-util.h" -#include "sort-util.h" +#include "path-util.h" #include "stat-util.h" -#include "stdio-util.h" -#include "string-util.h" +#include "strv.h" #include "utf8.h" -#include "virt.h" #if ENABLE_EFI -#define LOAD_OPTION_ACTIVE 0x00000001 -#define MEDIA_DEVICE_PATH 0x04 -#define MEDIA_HARDDRIVE_DP 0x01 -#define MEDIA_FILEPATH_DP 0x04 -#define SIGNATURE_TYPE_GUID 0x02 -#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02 -#define END_DEVICE_PATH_TYPE 0x7f -#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff -#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001 - -#define boot_option__contents \ - { \ - uint32_t attr; \ - uint16_t path_len; \ - uint16_t title[]; \ - } - -struct boot_option boot_option__contents; -struct boot_option__packed boot_option__contents _packed_; -assert_cc(offsetof(struct boot_option, title) == offsetof(struct boot_option__packed, title)); -/* sizeof(struct boot_option) != sizeof(struct boot_option__packed), so - * the *size* of the structure should not be used anywhere below. */ - -struct drive_path { - uint32_t part_nr; - uint64_t part_start; - uint64_t part_size; - char signature[16]; - uint8_t mbr_type; - uint8_t signature_type; -} _packed_; - -#define device_path__contents \ - { \ - uint8_t type; \ - uint8_t sub_type; \ - uint16_t length; \ - union { \ - uint16_t path[0]; \ - struct drive_path drive; \ - }; \ - } - -struct device_path device_path__contents; -struct device_path__packed device_path__contents _packed_; -assert_cc(sizeof(struct device_path) == sizeof(struct device_path__packed)); - -int efi_reboot_to_firmware_supported(void) { - _cleanup_free_ void *v = NULL; - static int cache = -1; - uint64_t b; - size_t s; - int r; - - if (cache > 0) - return 0; - if (cache == 0) - return -EOPNOTSUPP; - - if (!is_efi_boot()) - goto not_supported; - - r = efi_get_variable(EFI_GLOBAL_VARIABLE(OsIndicationsSupported), NULL, &v, &s); - if (r == -ENOENT) - goto not_supported; /* variable doesn't exist? it's not supported then */ - if (r < 0) - return r; - if (s != sizeof(uint64_t)) - return -EINVAL; - - b = *(uint64_t*) v; - if (!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI)) - goto not_supported; /* bit unset? it's not supported then */ - - cache = 1; - return 0; - -not_supported: - cache = 0; - return -EOPNOTSUPP; -} - -static int get_os_indications(uint64_t *ret) { - static struct stat cache_stat = {}; - _cleanup_free_ void *v = NULL; - static uint64_t cache; - struct stat new_stat; - size_t s; - int r; - - assert(ret); - - /* Let's verify general support first */ - r = efi_reboot_to_firmware_supported(); - if (r < 0) - return r; - - /* stat() the EFI variable, to see if the mtime changed. If it did we need to cache again. */ - if (stat(EFIVAR_PATH(EFI_GLOBAL_VARIABLE(OsIndications)), &new_stat) < 0) { - if (errno != ENOENT) - return -errno; - - /* Doesn't exist? Then we can exit early (also see below) */ - *ret = 0; - return 0; - - } else if (stat_inode_unmodified(&new_stat, &cache_stat)) { - /* inode didn't change, we can return the cached value */ - *ret = cache; - return 0; - } - - r = efi_get_variable(EFI_GLOBAL_VARIABLE(OsIndications), NULL, &v, &s); - if (r == -ENOENT) { - /* Some firmware implementations that do support OsIndications and report that with - * OsIndicationsSupported will remove the OsIndications variable when it is unset. Let's - * pretend it's 0 then, to hide this implementation detail. Note that this call will return - * -ENOENT then only if the support for OsIndications is missing entirely, as determined by - * efi_reboot_to_firmware_supported() above. */ - *ret = 0; - return 0; - } - if (r < 0) - return r; - if (s != sizeof(uint64_t)) - return -EINVAL; - - cache_stat = new_stat; - *ret = cache = *(uint64_t *)v; - return 0; -} - -int efi_get_reboot_to_firmware(void) { - int r; - uint64_t b; - - r = get_os_indications(&b); - if (r < 0) - return r; - - return !!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI); -} - -int efi_set_reboot_to_firmware(bool value) { - int r; - uint64_t b, b_new; - - r = get_os_indications(&b); - if (r < 0) - return r; - - b_new = UPDATE_FLAG(b, EFI_OS_INDICATIONS_BOOT_TO_FW_UI, value); - - /* Avoid writing to efi vars store if we can due to firmware bugs. */ - if (b != b_new) - return efi_set_variable(EFI_GLOBAL_VARIABLE(OsIndications), &b_new, sizeof(uint64_t)); - - return 0; -} - -static ssize_t utf16_size(const uint16_t *s, size_t buf_len_bytes) { - size_t l = 0; - - /* Returns the size of the string in bytes without the terminating two zero bytes */ - - if (buf_len_bytes % sizeof(uint16_t) != 0) - return -EINVAL; - - while (l < buf_len_bytes / sizeof(uint16_t)) { - if (s[l] == 0) - return (l + 1) * sizeof(uint16_t); - l++; - } - - return -EINVAL; /* The terminator was not found */ -} - -struct guid { - uint32_t u1; - uint16_t u2; - uint16_t u3; - uint8_t u4[8]; -} _packed_; - -static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) { - uint32_t u1; - uint16_t u2, u3; - const struct guid *uuid = guid; - - memcpy(&u1, &uuid->u1, sizeof(uint32_t)); - id128->bytes[0] = (u1 >> 24) & 0xff; - id128->bytes[1] = (u1 >> 16) & 0xff; - id128->bytes[2] = (u1 >> 8) & 0xff; - id128->bytes[3] = u1 & 0xff; - memcpy(&u2, &uuid->u2, sizeof(uint16_t)); - id128->bytes[4] = (u2 >> 8) & 0xff; - id128->bytes[5] = u2 & 0xff; - memcpy(&u3, &uuid->u3, sizeof(uint16_t)); - id128->bytes[6] = (u3 >> 8) & 0xff; - id128->bytes[7] = u3 & 0xff; - memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4)); -} - -int efi_get_boot_option( - uint16_t id, - char **title, - sd_id128_t *part_uuid, - char **path, - bool *active) { - - char variable[STRLEN(EFI_GLOBAL_VARIABLE_STR("Boot")) + 4 + 1]; - _cleanup_free_ uint8_t *buf = NULL; - size_t l; - struct boot_option *header; - ssize_t title_size; - _cleanup_free_ char *s = NULL, *p = NULL; - sd_id128_t p_uuid = SD_ID128_NULL; - int r; - - if (!is_efi_boot()) - return -EOPNOTSUPP; - - xsprintf(variable, EFI_GLOBAL_VARIABLE_STR("Boot%04X"), id); - r = efi_get_variable(variable, NULL, (void **)&buf, &l); - if (r < 0) - return r; - if (l < offsetof(struct boot_option, title)) - return -ENOENT; - - header = (struct boot_option *)buf; - title_size = utf16_size(header->title, l - offsetof(struct boot_option, title)); - if (title_size < 0) - return title_size; - - if (title) { - s = utf16_to_utf8(header->title, title_size); - if (!s) - return -ENOMEM; - } - - if (header->path_len > 0) { - uint8_t *dbuf; - size_t dnext, doff; - - doff = offsetof(struct boot_option, title) + title_size; - dbuf = buf + doff; - if (header->path_len > l - doff) - return -EINVAL; - - dnext = 0; - while (dnext < header->path_len) { - struct device_path *dpath; - - dpath = (struct device_path *)(dbuf + dnext); - if (dpath->length < 4) - break; - - /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ - if (dpath->type == END_DEVICE_PATH_TYPE && dpath->sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE) - break; - - dnext += dpath->length; - - /* Type 0x04 – Media Device Path */ - if (dpath->type != MEDIA_DEVICE_PATH) - continue; - - /* Sub-Type 1 – Hard Drive */ - if (dpath->sub_type == MEDIA_HARDDRIVE_DP) { - /* 0x02 – GUID Partition Table */ - if (dpath->drive.mbr_type != MBR_TYPE_EFI_PARTITION_TABLE_HEADER) - continue; - - /* 0x02 – GUID signature */ - if (dpath->drive.signature_type != SIGNATURE_TYPE_GUID) - continue; - - if (part_uuid) - efi_guid_to_id128(dpath->drive.signature, &p_uuid); - continue; - } - - /* Sub-Type 4 – File Path */ - if (dpath->sub_type == MEDIA_FILEPATH_DP && !p && path) { - p = utf16_to_utf8(dpath->path, dpath->length-4); - if (!p) - return -ENOMEM; - - efi_tilt_backslashes(p); - continue; - } - } - } - - if (title) - *title = TAKE_PTR(s); - if (part_uuid) - *part_uuid = p_uuid; - if (path) - *path = TAKE_PTR(p); - if (active) - *active = header->attr & LOAD_OPTION_ACTIVE; - - return 0; -} - -static void to_utf16(uint16_t *dest, const char *src) { - int i; - - for (i = 0; src[i] != '\0'; i++) - dest[i] = src[i]; - dest[i] = '\0'; -} - -static void id128_to_efi_guid(sd_id128_t id, void *guid) { - struct guid uuid = { - .u1 = id.bytes[0] << 24 | id.bytes[1] << 16 | id.bytes[2] << 8 | id.bytes[3], - .u2 = id.bytes[4] << 8 | id.bytes[5], - .u3 = id.bytes[6] << 8 | id.bytes[7], - }; - memcpy(uuid.u4, id.bytes+8, sizeof(uuid.u4)); - memcpy(guid, &uuid, sizeof(uuid)); -} - -static uint16_t *tilt_slashes(uint16_t *s) { - for (uint16_t *p = s; *p; p++) - if (*p == '/') - *p = '\\'; - - return s; -} - -int efi_add_boot_option( - uint16_t id, - const char *title, - uint32_t part, - uint64_t pstart, - uint64_t psize, - sd_id128_t part_uuid, - const char *path) { - - size_t size, title_len, path_len; - _cleanup_free_ char *buf = NULL; - struct boot_option *option; - struct device_path *devicep; - char variable[STRLEN(EFI_GLOBAL_VARIABLE_STR("Boot")) + 4 + 1]; - - if (!is_efi_boot()) - return -EOPNOTSUPP; - - title_len = (strlen(title)+1) * 2; - path_len = (strlen(path)+1) * 2; - - buf = malloc0(offsetof(struct boot_option, title) + title_len + - sizeof(struct drive_path) + - sizeof(struct device_path) + path_len); - if (!buf) - return -ENOMEM; - - /* header */ - option = (struct boot_option *)buf; - option->attr = LOAD_OPTION_ACTIVE; - option->path_len = offsetof(struct device_path, drive) + sizeof(struct drive_path) + - offsetof(struct device_path, path) + path_len + - offsetof(struct device_path, path); - to_utf16(option->title, title); - size = offsetof(struct boot_option, title) + title_len; - - /* partition info */ - devicep = (struct device_path *)(buf + size); - devicep->type = MEDIA_DEVICE_PATH; - devicep->sub_type = MEDIA_HARDDRIVE_DP; - devicep->length = offsetof(struct device_path, drive) + sizeof(struct drive_path); - memcpy(&devicep->drive.part_nr, &part, sizeof(uint32_t)); - memcpy(&devicep->drive.part_start, &pstart, sizeof(uint64_t)); - memcpy(&devicep->drive.part_size, &psize, sizeof(uint64_t)); - id128_to_efi_guid(part_uuid, devicep->drive.signature); - devicep->drive.mbr_type = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; - devicep->drive.signature_type = SIGNATURE_TYPE_GUID; - size += devicep->length; - - /* path to loader */ - devicep = (struct device_path *)(buf + size); - devicep->type = MEDIA_DEVICE_PATH; - devicep->sub_type = MEDIA_FILEPATH_DP; - devicep->length = offsetof(struct device_path, path) + path_len; - to_utf16(devicep->path, path); - tilt_slashes(devicep->path); - size += devicep->length; - - /* end of path */ - devicep = (struct device_path *)(buf + size); - devicep->type = END_DEVICE_PATH_TYPE; - devicep->sub_type = END_ENTIRE_DEVICE_PATH_SUBTYPE; - devicep->length = offsetof(struct device_path, path); - size += devicep->length; - - xsprintf(variable, EFI_GLOBAL_VARIABLE_STR("Boot%04X"), id); - return efi_set_variable(variable, buf, size); -} - -int efi_remove_boot_option(uint16_t id) { - char variable[STRLEN(EFI_GLOBAL_VARIABLE_STR("Boot")) + 4 + 1]; - - if (!is_efi_boot()) - return -EOPNOTSUPP; - - xsprintf(variable, EFI_GLOBAL_VARIABLE_STR("Boot%04X"), id); - return efi_set_variable(variable, NULL, 0); -} - -int efi_get_boot_order(uint16_t **order) { - _cleanup_free_ void *buf = NULL; - size_t l; - int r; - - if (!is_efi_boot()) - return -EOPNOTSUPP; - - r = efi_get_variable(EFI_GLOBAL_VARIABLE(BootOrder), NULL, &buf, &l); - if (r < 0) - return r; - - if (l <= 0) - return -ENOENT; - - if (l % sizeof(uint16_t) > 0 || - l / sizeof(uint16_t) > INT_MAX) - return -EINVAL; - - *order = TAKE_PTR(buf); - return (int) (l / sizeof(uint16_t)); -} - -int efi_set_boot_order(uint16_t *order, size_t n) { - - if (!is_efi_boot()) - return -EOPNOTSUPP; - - return efi_set_variable(EFI_GLOBAL_VARIABLE(BootOrder), order, n * sizeof(uint16_t)); -} - -static int boot_id_hex(const char s[static 4]) { - int id = 0; - - assert(s); - - for (int i = 0; i < 4; i++) - if (s[i] >= '0' && s[i] <= '9') - id |= (s[i] - '0') << (3 - i) * 4; - else if (s[i] >= 'A' && s[i] <= 'F') - id |= (s[i] - 'A' + 10) << (3 - i) * 4; - else - return -EINVAL; - - return id; -} - -static int cmp_uint16(const uint16_t *a, const uint16_t *b) { - return CMP(*a, *b); -} - -int efi_get_boot_options(uint16_t **options) { - _cleanup_closedir_ DIR *dir = NULL; - _cleanup_free_ uint16_t *list = NULL; - int count = 0; - - assert(options); - - if (!is_efi_boot()) - return -EOPNOTSUPP; - - dir = opendir(EFIVAR_PATH(".")); - if (!dir) - return -errno; - - FOREACH_DIRENT(de, dir, return -errno) { - int id; - - if (strncmp(de->d_name, "Boot", 4) != 0) - continue; - - if (strlen(de->d_name) != 45) - continue; - - if (strcmp(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR("")) != 0) /* generate variable suffix using macro */ - continue; - - id = boot_id_hex(de->d_name + 4); - if (id < 0) - continue; - - if (!GREEDY_REALLOC(list, count + 1)) - return -ENOMEM; - - list[count++] = id; - } - - typesafe_qsort(list, count, cmp_uint16); - - *options = TAKE_PTR(list); - - return count; -} - -static int read_usec(const char *variable, usec_t *u) { +static int read_usec(const char *variable, usec_t *ret) { _cleanup_free_ char *j = NULL; - int r; uint64_t x = 0; + int r; assert(variable); - assert(u); + assert(ret); r = efi_get_variable_string(variable, &j); if (r < 0) @@ -541,16 +26,16 @@ static int read_usec(const char *variable, usec_t *u) { if (r < 0) return r; - *u = x; + *ret = x; return 0; } -int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { +int efi_loader_get_boot_usec(usec_t *ret_firmware, usec_t *ret_loader) { uint64_t x, y; int r; - assert(firmware); - assert(loader); + assert(ret_firmware); + assert(ret_loader); if (!is_efi_boot()) return -EOPNOTSUPP; @@ -568,16 +53,17 @@ int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { "Bad LoaderTimeInitUSec=%"PRIu64", LoaderTimeExecUSec=%" PRIu64"; refusing.", x, y); - *firmware = x; - *loader = y; - + *ret_firmware = x; + *ret_loader = y; return 0; } -int efi_loader_get_device_part_uuid(sd_id128_t *u) { +int efi_loader_get_device_part_uuid(sd_id128_t *ret) { _cleanup_free_ char *p = NULL; int r, parsed[16]; + assert(ret); + if (!is_efi_boot()) return -EOPNOTSUPP; @@ -592,9 +78,9 @@ int efi_loader_get_device_part_uuid(sd_id128_t *u) { &parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16) return -EIO; - if (u) + if (ret) for (unsigned i = 0; i < ELEMENTSOF(parsed); i++) - u->bytes[i] = parsed[i]; + ret->bytes[i] = parsed[i]; return 0; } @@ -657,6 +143,8 @@ int efi_loader_get_features(uint64_t *ret) { size_t s; int r; + assert(ret); + if (!is_efi_boot()) { *ret = 0; return 0; @@ -763,29 +251,6 @@ int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat return 0; } -bool efi_has_tpm2(void) { - static int cache = -1; - - /* Returns whether the system has a TPM2 chip which is known to the EFI firmware. */ - - if (cache < 0) { - - /* First, check if we are on an EFI boot at all. */ - if (!is_efi_boot()) - cache = false; - else { - /* Then, check if the ACPI table "TPM2" exists, which is the TPM2 event log table, see: - * https://trustedcomputinggroup.org/wp-content/uploads/TCG_ACPIGeneralSpecification_v1.20_r8.pdf - * This table exists whenever the firmware is hooked up to TPM2. */ - cache = access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0; - if (!cache && errno != ENOENT) - log_debug_errno(errno, "Unable to test whether /sys/firmware/acpi/tables/TPM2 exists, assuming it doesn't: %m"); - } - } - - return cache; -} - #endif bool efi_loader_entry_name_valid(const char *s) { @@ -794,11 +259,3 @@ bool efi_loader_entry_name_valid(const char *s) { return in_charset(s, ALPHANUMERICAL "+-_."); } - -char *efi_tilt_backslashes(char *s) { - for (char *p = s; *p; p++) - if (*p == '\\') - *p = '/'; - - return s; -} diff --git a/src/shared/efi-loader.h b/src/shared/efi-loader.h index 7a100e53642..507160e50de 100644 --- a/src/shared/efi-loader.h +++ b/src/shared/efi-loader.h @@ -6,21 +6,12 @@ #include "efivars-fundamental.h" #include "efivars.h" +/* Various calls that interface with EFI variables implementing https://systemd.io/BOOT_LOADER_INTERFACE */ + #if ENABLE_EFI -int efi_reboot_to_firmware_supported(void); -int efi_get_reboot_to_firmware(void); -int efi_set_reboot_to_firmware(bool value); - -int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active); -int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path); -int efi_remove_boot_option(uint16_t id); -int efi_get_boot_order(uint16_t **order); -int efi_set_boot_order(uint16_t *order, size_t n); -int efi_get_boot_options(uint16_t **options); - -int efi_loader_get_device_part_uuid(sd_id128_t *u); -int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader); +int efi_loader_get_device_part_uuid(sd_id128_t *ret); +int efi_loader_get_boot_usec(usec_t *ret_firmware, usec_t *ret_loader); int efi_loader_get_entries(char ***ret); @@ -29,46 +20,8 @@ int efi_loader_get_features(uint64_t *ret); int efi_loader_get_config_timeout_one_shot(usec_t *ret); int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat); -bool efi_has_tpm2(void); - #else -static inline int efi_reboot_to_firmware_supported(void) { - return -EOPNOTSUPP; -} - -static inline int efi_get_reboot_to_firmware(void) { - return -EOPNOTSUPP; -} - -static inline int efi_set_reboot_to_firmware(bool value) { - return -EOPNOTSUPP; -} - -static inline int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active) { - return -EOPNOTSUPP; -} - -static inline int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path) { - return -EOPNOTSUPP; -} - -static inline int efi_remove_boot_option(uint16_t id) { - return -EOPNOTSUPP; -} - -static inline int efi_get_boot_order(uint16_t **order) { - return -EOPNOTSUPP; -} - -static inline int efi_set_boot_order(uint16_t *order, size_t n) { - return -EOPNOTSUPP; -} - -static inline int efi_get_boot_options(uint16_t **options) { - return -EOPNOTSUPP; -} - static inline int efi_loader_get_device_part_uuid(sd_id128_t *u) { return -EOPNOTSUPP; } @@ -93,12 +46,6 @@ static inline int efi_loader_update_entry_one_shot_cache(char **cache, struct st return -EOPNOTSUPP; } -static inline bool efi_has_tpm2(void) { - return false; -} - #endif bool efi_loader_entry_name_valid(const char *s); - -char *efi_tilt_backslashes(char *s); diff --git a/src/shared/meson.build b/src/shared/meson.build index 668462f1731..4333c9a0a90 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -105,6 +105,8 @@ shared_sources = files( 'dns-domain.h', 'dropin.c', 'dropin.h', + 'efi-api.c', + 'efi-api.h', 'efi-loader.c', 'efi-loader.h', 'elf-util.c',