/* 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_STR("LoaderTimeInitUSec"), &x); if (r < 0) return log_debug_errno(r, "Failed to read LoaderTimeInitUSec: %m"); r = read_usec(EFI_LOADER_VARIABLE_STR("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; } static int get_device_part_uuid(const char *variable, sd_id128_t *ret) { if (!is_efi_boot()) return -EOPNOTSUPP; return efi_get_variable_id128(variable, ret); } int efi_loader_get_device_part_uuid(sd_id128_t *ret) { return get_device_part_uuid(EFI_LOADER_VARIABLE_STR("LoaderDevicePartUUID"), ret); } int efi_stub_get_device_part_uuid(sd_id128_t *ret) { return get_device_part_uuid(EFI_LOADER_VARIABLE_STR("StubDevicePartUUID"), ret); } 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_STR("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_STR("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_STR("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_STR("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_STR("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 = secure_getenv_bool("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_STR("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_STR("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_STR("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_STR("LoaderEntryOneShot")), &new_stat) < 0) return -errno; if (stat_inode_unmodified(&new_stat, cache_stat)) return 0; r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("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; } int efi_get_variable_id128(const char *variable, sd_id128_t *ret) { int r; assert(variable); /* This is placed here (rather than in basic/efivars.c) because code in basic/ is not allowed to link * against libsystemd.so */ _cleanup_free_ char *p = NULL; r = efi_get_variable_string(variable, &p); if (r < 0) return r; return sd_id128_from_string(p, ret); } #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 "+-_.@"); }