1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-09 01:18:19 +03:00

pcrphase: make tool more generic, reuse for measuring machine id/fs uuids

See: #24503
This commit is contained in:
Lennart Poettering 2022-10-14 23:29:48 +02:00
parent ff386f985b
commit 17984c5551
2 changed files with 187 additions and 24 deletions

View File

@ -2677,6 +2677,7 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1
link_with : [libshared], link_with : [libshared],
dependencies : [libopenssl, dependencies : [libopenssl,
tpm2, tpm2,
libblkid,
versiondep], versiondep],
install_rpath : rootpkglibdir, install_rpath : rootpkglibdir,
install : true, install : true,

View File

@ -2,13 +2,21 @@
#include <getopt.h> #include <getopt.h>
#include <sd-device.h>
#include <sd-messages.h> #include <sd-messages.h>
#include "blkid-util.h"
#include "blockdev-util.h"
#include "build.h" #include "build.h"
#include "chase-symlinks.h"
#include "efivars.h" #include "efivars.h"
#include "env-util.h" #include "env-util.h"
#include "escape.h"
#include "fd-util.h"
#include "main-func.h" #include "main-func.h"
#include "mountpoint-util.h"
#include "openssl-util.h" #include "openssl-util.h"
#include "parse-argument.h"
#include "parse-util.h" #include "parse-util.h"
#include "pretty-print.h" #include "pretty-print.h"
#include "tpm-pcr.h" #include "tpm-pcr.h"
@ -17,9 +25,12 @@
static bool arg_graceful = false; static bool arg_graceful = false;
static char *arg_tpm2_device = NULL; static char *arg_tpm2_device = NULL;
static char **arg_banks = 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_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
static int help(int argc, char *argv[], void *userdata) { static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL; _cleanup_free_ char *link = NULL;
@ -29,7 +40,9 @@ static int help(int argc, char *argv[], void *userdata) {
if (r < 0) if (r < 0)
return log_oom(); 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%5$sMeasure boot phase into TPM2 PCR 11.%6$s\n"
"\n%3$sOptions:%4$s\n" "\n%3$sOptions:%4$s\n"
" -h --help Show this help\n" " -h --help Show this help\n"
@ -37,6 +50,8 @@ static int help(int argc, char *argv[], void *userdata) {
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n" " --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
" --tpm2-device=PATH Use specified TPM2 device\n" " --tpm2-device=PATH Use specified TPM2 device\n"
" --graceful Exit gracefully if no TPM2 device is found\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", "\nSee the %2$s for details.\n",
program_invocation_short_name, program_invocation_short_name,
link, link,
@ -54,6 +69,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_BANK, ARG_BANK,
ARG_TPM2_DEVICE, ARG_TPM2_DEVICE,
ARG_GRACEFUL, ARG_GRACEFUL,
ARG_FILE_SYSTEM,
ARG_MACHINE_ID,
}; };
static const struct option options[] = { static const struct option options[] = {
@ -62,10 +79,12 @@ static int parse_argv(int argc, char *argv[]) {
{ "bank", required_argument, NULL, ARG_BANK }, { "bank", required_argument, NULL, ARG_BANK },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "graceful", no_argument, NULL, ARG_GRACEFUL }, { "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(argc >= 0);
assert(argv); assert(argv);
@ -113,6 +132,17 @@ static int parse_argv(int argc, char *argv[]) {
arg_graceful = true; arg_graceful = true;
break; 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 '?': case '?':
return -EINVAL; return -EINVAL;
@ -120,10 +150,13 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached(); 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; 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; _cleanup_strv_free_ char **l = NULL;
int r; int r;
@ -132,7 +165,7 @@ static int determine_banks(struct tpm2_context *c) {
if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */ if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */
return 0; 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) if (r < 0)
return r; return r;
@ -140,11 +173,77 @@ static int determine_banks(struct tpm2_context *c) {
return 0; 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[]) { 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_(tpm2_context_destroy) struct tpm2_context c = {};
_cleanup_free_ char *joined = NULL, *pcr_string = NULL; unsigned target_pcr_nr, efi_pcr_nr;
const char *word;
unsigned pcr_nr;
size_t length; size_t length;
int r; int r;
@ -154,16 +253,79 @@ static int run(int argc, char *argv[]) {
if (r <= 0) if (r <= 0)
return r; return r;
if (optind+1 != argc) if (arg_file_system) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument."); _cleanup_free_ char *normalized = NULL, *normalized_escaped = NULL;
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
_cleanup_close_ int dfd = -EBADF;
word = argv[optind]; if (optind != argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
/* Refuse to measure an empty word. We want to be able to write the series of measured words dfd = chase_symlinks_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized);
* separated by colons, where multiple separating colons are collapsed. Thus it makes sense to if (dfd < 0)
* disallow an empty word to avoid ambiguities. */ return log_error_errno(dfd, "Failed to open path '%s': %m", arg_file_system);
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) { if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) {
log_notice("No complete TPM2 support detected, exiting gracefully."); log_notice("No complete TPM2 support detected, exiting gracefully.");
@ -188,14 +350,14 @@ static int run(int argc, char *argv[]) {
return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m"); return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m");
else { else {
/* Let's validate that the stub announced PCR 11 as we expected. */ /* 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) if (r < 0)
return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string); 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) 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 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 } else
log_debug("Kernel stub reported same PCR %u as we want to use, proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE); log_debug("Kernel stub reported same PCR %u as we want to use, proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE);
} }
@ -208,7 +370,7 @@ static int run(int argc, char *argv[]) {
if (r < 0) if (r < 0)
return r; return r;
r = determine_banks(&c); r = determine_banks(&c, target_pcr_nr);
if (r < 0) if (r < 0)
return r; return r;
if (strv_isempty(arg_banks)) /* Still none? */ if (strv_isempty(arg_banks)) /* Still none? */
@ -218,17 +380,17 @@ static int run(int argc, char *argv[]) {
if (!joined) if (!joined)
return log_oom(); 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) if (r < 0)
return r; return r;
log_struct(LOG_INFO, log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR, "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, "MEASURING=%s", word,
"PCR=%u", TPM_PCR_INDEX_KERNEL_IMAGE, "PCR=%u", target_pcr_nr,
"BANKS=%s", joined); "BANKS=%s", joined);
return EXIT_SUCCESS; return EXIT_SUCCESS;