/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.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 "tpm-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. */ 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; /* 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); /* We reached the end of the variable */ 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_stub_measured(int log_level) { _cleanup_free_ char *pcr_string = NULL; unsigned pcr_nr; int r; /* Checks if we are booted on a kernel with sd-stub which measured the kernel into PCR 11. Or in * other words, if we are running on a TPM enabled UKI. * * 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 r; if (r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_FORCE_MEASURE, ignoring: %m"); if (!is_efi_boot()) return 0; r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string); if (r == -ENOENT) return 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 != TPM_PCR_INDEX_KERNEL_IMAGE) return log_full_errno(log_level, SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); return 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 "+-_."); }