From d2b974f55d80f09d544a3af6a2ef987df4284260 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 14 Oct 2022 23:29:48 +0200 Subject: [PATCH] pcrphase: make tool more generic, reuse for measuring machine id/fs uuids See: #24503 (cherry picked from commit 17984c55513fc18f9bd4878c37fa87d278ab1e1d) Related: RHEL-16182 --- meson.build | 4 +- src/boot/pcrphase.c | 210 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 189 insertions(+), 25 deletions(-) diff --git a/meson.build b/meson.build index fe7b47eef5..54155eee1f 100644 --- a/meson.build +++ b/meson.build @@ -2610,7 +2610,9 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1 'src/boot/pcrphase.c', include_directories : includes, link_with : [libshared], - dependencies : [libopenssl, tpm2], + dependencies : [libopenssl, + tpm2, + libblkid], install_rpath : rootpkglibdir, install : true, install_dir : rootlibexecdir) diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c index 1f3dc4ab3a..12629b2be3 100644 --- a/src/boot/pcrphase.c +++ b/src/boot/pcrphase.c @@ -2,12 +2,20 @@ #include +#include #include +#include "blkid-util.h" +#include "blockdev-util.h" +#include "chase-symlinks.h" #include "efivars.h" #include "env-util.h" +#include "escape.h" +#include "fd-util.h" #include "main-func.h" +#include "mountpoint-util.h" #include "openssl-util.h" +#include "parse-argument.h" #include "parse-util.h" #include "pretty-print.h" #include "tpm-pcr.h" @@ -16,9 +24,12 @@ static bool arg_graceful = false; static char *arg_tpm2_device = NULL; static char **arg_banks = NULL; +static char *arg_file_system = NULL; +static bool arg_machine_id = false; STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep); static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; @@ -28,7 +39,9 @@ static int help(int argc, char *argv[], void *userdata) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] WORD ...\n" + printf("%1$s [OPTIONS...] WORD\n" + "%1$s [OPTIONS...] --file-system=PATH\n" + "%1$s [OPTIONS...] --machine-id\n" "\n%5$sMeasure boot phase into TPM2 PCR 11.%6$s\n" "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" @@ -36,6 +49,8 @@ static int help(int argc, char *argv[], void *userdata) { " --bank=DIGEST Select TPM bank (SHA1, SHA256)\n" " --tpm2-device=PATH Use specified TPM2 device\n" " --graceful Exit gracefully if no TPM2 device is found\n" + " --file-system=PATH Measure UUID/labels of file system into PCR 15\n" + " --machine-id Measure machine ID into PCR 15\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -53,6 +68,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_BANK, ARG_TPM2_DEVICE, ARG_GRACEFUL, + ARG_FILE_SYSTEM, + ARG_MACHINE_ID, }; static const struct option options[] = { @@ -61,10 +78,12 @@ static int parse_argv(int argc, char *argv[]) { { "bank", required_argument, NULL, ARG_BANK }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "graceful", no_argument, NULL, ARG_GRACEFUL }, + { "file-system", required_argument, NULL, ARG_FILE_SYSTEM }, + { "machine-id", no_argument, NULL, ARG_MACHINE_ID }, {} }; - int c; + int c, r; assert(argc >= 0); assert(argv); @@ -112,6 +131,17 @@ static int parse_argv(int argc, char *argv[]) { arg_graceful = true; break; + case ARG_FILE_SYSTEM: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system); + if (r < 0) + return r; + + break; + + case ARG_MACHINE_ID: + arg_machine_id = true; + break; + case '?': return -EINVAL; @@ -119,10 +149,13 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (arg_file_system && arg_machine_id) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined."); + return 1; } -static int determine_banks(struct tpm2_context *c) { +static int determine_banks(struct tpm2_context *c, unsigned target_pcr_nr) { _cleanup_strv_free_ char **l = NULL; int r; @@ -131,7 +164,7 @@ static int determine_banks(struct tpm2_context *c) { if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */ return 0; - r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &l); + r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << target_pcr_nr, &l); if (r < 0) return r; @@ -139,11 +172,77 @@ static int determine_banks(struct tpm2_context *c) { return 0; } +static int get_file_system_word( + sd_device *d, + const char *prefix, + char **ret) { + + int r; + + assert(d); + assert(prefix); + assert(ret); + + _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (block_fd < 0) + return block_fd; + + _cleanup_(blkid_free_probep) blkid_probe b = blkid_new_probe(); + if (!b) + return -ENOMEM; + + errno = 0; + r = blkid_probe_set_device(b, block_fd, 0, 0); + if (r != 0) + return errno_or_else(ENOMEM); + + (void) blkid_probe_enable_superblocks(b, 1); + (void) blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_UUID|BLKID_SUBLKS_LABEL); + (void) blkid_probe_enable_partitions(b, 1); + (void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == _BLKID_SAFEPROBE_ERROR) + return errno_or_else(EIO); + if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND)) + return -ENOPKG; + + assert(r == _BLKID_SAFEPROBE_FOUND); + + _cleanup_strv_free_ char **l = strv_new(prefix); + if (!l) + return log_oom(); + + FOREACH_STRING(field, "TYPE", "UUID", "LABEL", "PART_ENTRY_UUID", "PART_ENTRY_TYPE", "PART_ENTRY_NAME") { + const char *v = NULL; + + (void) blkid_probe_lookup_value(b, field, &v, NULL); + + _cleanup_free_ char *escaped = xescape(strempty(v), ":"); /* Avoid ambiguity around ":" */ + if (!escaped) + return log_oom(); + + r = strv_consume(&l, TAKE_PTR(escaped)); + if (r < 0) + return log_oom(); + + } + + assert(strv_length(l) == 7); /* We always want 7 components, to avoid ambiguous strings */ + + _cleanup_free_ char *word = strv_join(l, ":"); + if (!word) + return log_oom(); + + *ret = TAKE_PTR(word); + return 0; +} + static int run(int argc, char *argv[]) { + _cleanup_free_ char *joined = NULL, *pcr_string = NULL, *word = NULL; _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; - _cleanup_free_ char *joined = NULL, *pcr_string = NULL; - const char *word; - unsigned pcr_nr; + unsigned target_pcr_nr, efi_pcr_nr; size_t length; int r; @@ -153,16 +252,79 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - if (optind+1 != argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument."); + if (arg_file_system) { + _cleanup_free_ char *normalized = NULL, *normalized_escaped = NULL; + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + _cleanup_close_ int dfd = -EBADF; + + if (optind != argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); - word = argv[optind]; + dfd = chase_symlinks_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized); + if (dfd < 0) + return log_error_errno(dfd, "Failed to open path '%s': %m", arg_file_system); - /* Refuse to measure an empty word. We want to be able to write the series of measured words - * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to - * disallow an empty word to avoid ambiguities. */ - if (isempty(word)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing."); + r = fd_is_mount_point(dfd, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing: %m", normalized); + + normalized_escaped = xescape(normalized, ":"); /* Avoid ambiguity around ":" */ + if (!normalized_escaped) + return log_oom(); + + _cleanup_free_ char* prefix = strjoin("file-system:", normalized_escaped); + if (!prefix) + return log_oom(); + + r = block_device_new_from_fd(dfd, BLOCK_DEVICE_LOOKUP_BACKING, &d); + if (r < 0) { + log_notice_errno(r, "Unable to determine backing block device of '%s', measuring generic fallback file system identity string: %m", arg_file_system); + + word = strjoin(prefix, "::::::"); + if (!word) + return log_oom(); + } else { + r = get_file_system_word(d, prefix, &word); + if (r < 0) + return log_error_errno(r, "Failed to get file system identifier string for '%s': %m", arg_file_system); + } + + target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */ + + } else if (arg_machine_id) { + sd_id128_t mid; + + if (optind != argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); + + r = sd_id128_get_machine(&mid); + if (r < 0) + return log_error_errno(r, "Failed to acquire machine ID: %m"); + + word = strjoin("machine-id:", SD_ID128_TO_STRING(mid)); + if (!word) + return log_oom(); + + target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */ + + } else { + if (optind+1 != argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument."); + + word = strdup(argv[optind]); + if (!word) + return log_oom(); + + /* Refuse to measure an empty word. We want to be able to write the series of measured words + * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to + * disallow an empty word to avoid ambiguities. */ + if (isempty(word)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing."); + + target_pcr_nr = TPM_PCR_INDEX_KERNEL_IMAGE; /* → PCR 11 */ + } if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) { log_notice("No complete TPM2 support detected, exiting gracefully."); @@ -187,14 +349,14 @@ static int run(int argc, char *argv[]) { return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m"); else { /* Let's validate that the stub announced PCR 11 as we expected. */ - r = safe_atou(pcr_string, &pcr_nr); + r = safe_atou(pcr_string, &efi_pcr_nr); if (r < 0) return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string); - if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) { + if (efi_pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) { if (b != 0) - return log_error_errno(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 log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", efi_pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); else - log_notice("Kernel stub measured kernel image into PCR %u, which is different than expected %u, but told to measure anyway, hence proceeding.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); + log_notice("Kernel stub measured kernel image into PCR %u, which is different than expected %u, but told to measure anyway, hence proceeding.", efi_pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); } else log_debug("Kernel stub reported same PCR %u as we want to use, proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE); } @@ -207,7 +369,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = determine_banks(&c); + r = determine_banks(&c, target_pcr_nr); if (r < 0) return r; if (strv_isempty(arg_banks)) /* Still none? */ @@ -217,17 +379,17 @@ static int run(int argc, char *argv[]) { if (!joined) return log_oom(); - log_debug("Measuring '%s' into PCR index %u, banks %s.", word, TPM_PCR_INDEX_KERNEL_IMAGE, joined); + log_debug("Measuring '%s' into PCR index %u, banks %s.", word, target_pcr_nr, joined); - r = tpm2_extend_bytes(c.esys_context, arg_banks, TPM_PCR_INDEX_KERNEL_IMAGE, word, length, NULL, 0); /* → PCR 11 */ + r = tpm2_extend_bytes(c.esys_context, arg_banks, target_pcr_nr, word, length, NULL, 0); if (r < 0) return r; log_struct(LOG_INFO, "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR, - LOG_MESSAGE("Successfully extended PCR index %u with '%s' (banks %s).", TPM_PCR_INDEX_KERNEL_IMAGE, word, joined), + LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", target_pcr_nr, word, joined), "MEASURING=%s", word, - "PCR=%u", TPM_PCR_INDEX_KERNEL_IMAGE, + "PCR=%u", target_pcr_nr, "BANKS=%s", joined); return EXIT_SUCCESS;