]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pcrphase: make tool more generic, reuse for measuring machine id/fs uuids
authorLennart Poettering <lennart@poettering.net>
Fri, 14 Oct 2022 21:29:48 +0000 (23:29 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 17 Jan 2023 08:42:16 +0000 (09:42 +0100)
See: #24503

meson.build
src/boot/pcrphase.c

index 921f7300b0797cf06b9e50a954593bd89fbce10a..f9409268942066c8e424d694f68f0fe104f1c7a3 100644 (file)
@@ -2677,6 +2677,7 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1
                         link_with : [libshared],
                         dependencies : [libopenssl,
                                         tpm2,
+                                        libblkid,
                                         versiondep],
                         install_rpath : rootpkglibdir,
                         install : true,
index 6d055e3499d258a1c5804653dff192e703f8f5d0..70c919b0f5df1e609d8fd6b344479f19b9e81308 100644 (file)
@@ -2,13 +2,21 @@
 
 #include <getopt.h>
 
+#include <sd-device.h>
 #include <sd-messages.h>
 
+#include "blkid-util.h"
+#include "blockdev-util.h"
 #include "build.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"
 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;
@@ -29,7 +40,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"
@@ -37,6 +50,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,
@@ -54,6 +69,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[] = {
@@ -62,10 +79,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);
@@ -113,6 +132,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;
 
@@ -120,10 +150,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;
 
@@ -132,7 +165,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;
 
@@ -140,11 +173,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;
 
@@ -154,16 +253,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.");
@@ -188,14 +350,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);
         }
@@ -208,7 +370,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? */
@@ -218,17 +380,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;