| /* 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 "+-_."); |
| } |