diff options
Diffstat (limited to '')
-rw-r--r-- | src/shared/efi-loader.c | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c new file mode 100644 index 0000000..7d6bda9 --- /dev/null +++ b/src/shared/efi-loader.c @@ -0,0 +1,363 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "efi-api.h" +#include "efi-loader.h" +#include "env-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "stat-util.h" +#include "strv.h" +#include "tpm2-pcr.h" +#include "utf8.h" + +#if ENABLE_EFI + +static int read_usec(const char *variable, usec_t *ret) { + _cleanup_free_ char *j = NULL; + uint64_t x = 0; + int r; + + assert(variable); + assert(ret); + + r = efi_get_variable_string(variable, &j); + if (r < 0) + return r; + + r = safe_atou64(j, &x); + if (r < 0) + return r; + + *ret = x; + return 0; +} + +int efi_loader_get_boot_usec(usec_t *ret_firmware, usec_t *ret_loader) { + uint64_t x, y; + int r; + + assert(ret_firmware); + assert(ret_loader); + + if (!is_efi_boot()) + return -EOPNOTSUPP; + + r = read_usec(EFI_LOADER_VARIABLE(LoaderTimeInitUSec), &x); + if (r < 0) + return log_debug_errno(r, "Failed to read LoaderTimeInitUSec: %m"); + + r = read_usec(EFI_LOADER_VARIABLE(LoaderTimeExecUSec), &y); + if (r < 0) + return log_debug_errno(r, "Failed to read LoaderTimeExecUSec: %m"); + + if (y == 0 || y < x || y - x > USEC_PER_HOUR) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Bad LoaderTimeInitUSec=%"PRIu64", LoaderTimeExecUSec=%" PRIu64"; refusing.", + x, y); + + *ret_firmware = x; + *ret_loader = y; + return 0; +} + +int efi_loader_get_device_part_uuid(sd_id128_t *ret) { + _cleanup_free_ char *p = NULL; + int r; + unsigned parsed[16]; + + if (!is_efi_boot()) + return -EOPNOTSUPP; + + r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderDevicePartUUID), &p); + if (r < 0) + return r; + + if (sscanf(p, SD_ID128_UUID_FORMAT_STR, + &parsed[0], &parsed[1], &parsed[2], &parsed[3], + &parsed[4], &parsed[5], &parsed[6], &parsed[7], + &parsed[8], &parsed[9], &parsed[10], &parsed[11], + &parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16) + return -EIO; + + if (ret) + for (unsigned i = 0; i < ELEMENTSOF(parsed); i++) + ret->bytes[i] = parsed[i]; + + return 0; +} + +int efi_loader_get_entries(char ***ret) { + _cleanup_free_ char16_t *entries = NULL; + _cleanup_strv_free_ char **l = NULL; + size_t size; + int r; + + assert(ret); + + if (!is_efi_boot()) + return -EOPNOTSUPP; + + r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntries), NULL, (void**) &entries, &size); + if (r < 0) + return r; + + /* The variable contains a series of individually NUL terminated UTF-16 strings. We gracefully + * consider the final NUL byte optional (i.e. the last string may or may not end in a NUL byte).*/ + + for (size_t i = 0, start = 0;; i++) { + _cleanup_free_ char *decoded = NULL; + bool end; + + /* Is this the end of the variable's data? */ + end = i * sizeof(char16_t) >= size; + + /* Are we in the middle of a string? (i.e. not at the end of the variable, nor at a NUL terminator?) If + * so, let's go to the next entry. */ + if (!end && entries[i] != 0) + continue; + + /* Empty string at the end of variable? That's the trailer, we are done (i.e. we have a final + * NUL terminator). */ + if (end && start == i) + break; + + /* We reached the end of a string, let's decode it into UTF-8 */ + decoded = utf16_to_utf8(entries + start, (i - start) * sizeof(char16_t)); + if (!decoded) + return -ENOMEM; + + if (efi_loader_entry_name_valid(decoded)) { + r = strv_consume(&l, TAKE_PTR(decoded)); + if (r < 0) + return r; + } else + log_debug("Ignoring invalid loader entry '%s'.", decoded); + + /* Exit the loop if we reached the end of the variable (i.e. we do not have a final NUL + * terminator) */ + if (end) + break; + + /* Continue after the NUL byte */ + start = i + 1; + } + + *ret = TAKE_PTR(l); + return 0; +} + +int efi_loader_get_features(uint64_t *ret) { + _cleanup_free_ void *v = NULL; + size_t s; + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = 0; + return 0; + } + + r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderFeatures), NULL, &v, &s); + if (r == -ENOENT) { + _cleanup_free_ char *info = NULL; + + /* The new (v240+) LoaderFeatures variable is not supported, let's see if it's systemd-boot at all */ + r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderInfo), &info); + if (r < 0) { + if (r != -ENOENT) + return r; + + /* Variable not set, definitely means not systemd-boot */ + + } else if (first_word(info, "systemd-boot")) { + + /* An older systemd-boot version. Let's hardcode the feature set, since it was pretty + * static in all its versions. */ + + *ret = EFI_LOADER_FEATURE_CONFIG_TIMEOUT | + EFI_LOADER_FEATURE_ENTRY_DEFAULT | + EFI_LOADER_FEATURE_ENTRY_ONESHOT; + + return 0; + } + + /* No features supported */ + *ret = 0; + return 0; + } + if (r < 0) + return r; + + if (s != sizeof(uint64_t)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "LoaderFeatures EFI variable doesn't have the right size."); + + memcpy(ret, v, sizeof(uint64_t)); + return 0; +} + +int efi_stub_get_features(uint64_t *ret) { + _cleanup_free_ void *v = NULL; + size_t s; + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = 0; + return 0; + } + + r = efi_get_variable(EFI_LOADER_VARIABLE(StubFeatures), NULL, &v, &s); + if (r == -ENOENT) { + _cleanup_free_ char *info = NULL; + + /* The new (v252+) StubFeatures variable is not supported, let's see if it's systemd-stub at all */ + r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubInfo), &info); + if (r < 0) { + if (r != -ENOENT) + return r; + + /* Variable not set, definitely means not systemd-stub */ + + } else if (first_word(info, "systemd-stub")) { + + /* An older systemd-stub version. Let's hardcode the feature set, since it was pretty + * static in all its versions. */ + + *ret = EFI_STUB_FEATURE_REPORT_BOOT_PARTITION; + return 0; + } + + /* No features supported */ + *ret = 0; + return 0; + } + if (r < 0) + return r; + + if (s != sizeof(uint64_t)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "StubFeatures EFI variable doesn't have the right size."); + + memcpy(ret, v, sizeof(uint64_t)); + return 0; +} + +int efi_measured_uki(int log_level) { + _cleanup_free_ char *pcr_string = NULL; + static int cached = -1; + unsigned pcr_nr; + int r; + + if (cached >= 0) + return cached; + + /* Checks if we are booted on a kernel with sd-stub which measured the kernel into PCR 11 on a TPM2 + * chip. Or in other words, if we are running on a TPM enabled UKI. (TPM 1.2 situations are ignored.) + * + * Returns == 0 and > 0 depending on the result of the test. Returns -EREMOTE if we detected a stub + * being used, but it measured things into a different PCR than we are configured for in + * userspace. (i.e. we expect PCR 11 being used for this by both sd-stub and us) */ + + r = getenv_bool_secure("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test, + * for debugging purposes */ + if (r >= 0) + return (cached = r); + if (r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_FORCE_MEASURE, ignoring: %m"); + + if (!efi_has_tpm2()) + return (cached = 0); + + r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string); + if (r == -ENOENT) + return (cached = 0); + if (r < 0) + return log_full_errno(log_level, r, + "Failed to get StubPcrKernelImage EFI variable: %m"); + + r = safe_atou(pcr_string, &pcr_nr); + if (r < 0) + return log_full_errno(log_level, r, + "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string); + if (pcr_nr != TPM2_PCR_KERNEL_BOOT) + return log_full_errno(log_level, SYNTHETIC_ERRNO(EREMOTE), + "Kernel stub measured kernel image into PCR %u, which is different than expected %i.", + pcr_nr, TPM2_PCR_KERNEL_BOOT); + + return (cached = 1); +} + +int efi_loader_get_config_timeout_one_shot(usec_t *ret) { + _cleanup_free_ char *v = NULL; + static struct stat cache_stat = {}; + struct stat new_stat; + static usec_t cache; + uint64_t sec; + int r; + + assert(ret); + + /* stat() the EFI variable, to see if the mtime changed. If it did, we need to cache again. */ + if (stat(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot)), &new_stat) < 0) + return -errno; + + if (stat_inode_unmodified(&new_stat, &cache_stat)) { + *ret = cache; + return 0; + } + + r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot), &v); + if (r < 0) + return r; + + r = safe_atou64(v, &sec); + if (r < 0) + return r; + if (sec > USEC_INFINITY / USEC_PER_SEC) + return -ERANGE; + + cache_stat = new_stat; + *ret = cache = sec * USEC_PER_SEC; /* return in μs */ + return 0; +} + +int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat) { + _cleanup_free_ char *v = NULL; + struct stat new_stat; + int r; + + assert(cache); + assert(cache_stat); + + /* stat() the EFI variable, to see if the mtime changed. If it did we need to cache again. */ + if (stat(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderEntryOneShot)), &new_stat) < 0) + return -errno; + + if (stat_inode_unmodified(&new_stat, cache_stat)) + return 0; + + r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot), &v); + if (r < 0) + return r; + + if (!efi_loader_entry_name_valid(v)) + return -EINVAL; + + *cache_stat = new_stat; + free_and_replace(*cache, v); + + return 0; +} + +#endif + +bool efi_loader_entry_name_valid(const char *s) { + if (!filename_is_valid(s)) /* Make sure entry names fit in filenames */ + return false; + + return in_charset(s, ALPHANUMERICAL "+-_."); +} |