1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include <sd-messages.h>
8 #include "blkid-util.h"
9 #include "blockdev-util.h"
12 #include "efi-loader.h"
16 #include "main-func.h"
17 #include "mountpoint-util.h"
18 #include "openssl-util.h"
19 #include "parse-argument.h"
20 #include "pretty-print.h"
22 #include "tpm2-util.h"
24 static bool arg_graceful
= false;
25 static char *arg_tpm2_device
= NULL
;
26 static char **arg_banks
= NULL
;
27 static char *arg_file_system
= NULL
;
28 static bool arg_machine_id
= false;
30 STATIC_DESTRUCTOR_REGISTER(arg_banks
, strv_freep
);
31 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device
, freep
);
32 STATIC_DESTRUCTOR_REGISTER(arg_file_system
, freep
);
34 static int help(int argc
, char *argv
[], void *userdata
) {
35 _cleanup_free_
char *link
= NULL
;
38 r
= terminal_urlify_man("systemd-pcrphase", "8", &link
);
42 printf("%1$s [OPTIONS...] WORD\n"
43 "%1$s [OPTIONS...] --file-system=PATH\n"
44 "%1$s [OPTIONS...] --machine-id\n"
45 "\n%5$sMeasure boot phase into TPM2 PCR 11.%6$s\n"
46 "\n%3$sOptions:%4$s\n"
47 " -h --help Show this help\n"
48 " --version Print version\n"
49 " --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
50 " --tpm2-device=PATH Use specified TPM2 device\n"
51 " --graceful Exit gracefully if no TPM2 device is found\n"
52 " --file-system=PATH Measure UUID/labels of file system into PCR 15\n"
53 " --machine-id Measure machine ID into PCR 15\n"
54 "\nSee the %2$s for details.\n",
55 program_invocation_short_name
,
65 static int parse_argv(int argc
, char *argv
[]) {
75 static const struct option options
[] = {
76 { "help", no_argument
, NULL
, 'h' },
77 { "version", no_argument
, NULL
, ARG_VERSION
},
78 { "bank", required_argument
, NULL
, ARG_BANK
},
79 { "tpm2-device", required_argument
, NULL
, ARG_TPM2_DEVICE
},
80 { "graceful", no_argument
, NULL
, ARG_GRACEFUL
},
81 { "file-system", required_argument
, NULL
, ARG_FILE_SYSTEM
},
82 { "machine-id", no_argument
, NULL
, ARG_MACHINE_ID
},
91 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
102 const EVP_MD
*implementation
;
104 implementation
= EVP_get_digestbyname(optarg
);
106 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Unknown bank '%s', refusing.", optarg
);
108 if (strv_extend(&arg_banks
, EVP_MD_name(implementation
)) < 0)
114 case ARG_TPM2_DEVICE
: {
115 _cleanup_free_
char *device
= NULL
;
117 if (streq(optarg
, "list"))
118 return tpm2_list_devices();
120 if (!streq(optarg
, "auto")) {
121 device
= strdup(optarg
);
126 free_and_replace(arg_tpm2_device
, device
);
134 case ARG_FILE_SYSTEM
:
135 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_file_system
);
142 arg_machine_id
= true;
149 assert_not_reached();
152 if (arg_file_system
&& arg_machine_id
)
153 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "--file-system= and --machine-id may not be combined.");
158 static int determine_banks(Tpm2Context
*c
, unsigned target_pcr_nr
) {
159 _cleanup_strv_free_
char **l
= NULL
;
164 if (!strv_isempty(arg_banks
)) /* Explicitly configured? Then use that */
167 r
= tpm2_get_good_pcr_banks_strv(c
, UINT32_C(1) << target_pcr_nr
, &l
);
171 strv_free_and_replace(arg_banks
, l
);
175 static int get_file_system_word(
186 _cleanup_close_
int block_fd
= sd_device_open(d
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
);
190 _cleanup_(blkid_free_probep
) blkid_probe b
= blkid_new_probe();
195 r
= blkid_probe_set_device(b
, block_fd
, 0, 0);
197 return errno_or_else(ENOMEM
);
199 (void) blkid_probe_enable_superblocks(b
, 1);
200 (void) blkid_probe_set_superblocks_flags(b
, BLKID_SUBLKS_TYPE
|BLKID_SUBLKS_UUID
|BLKID_SUBLKS_LABEL
);
201 (void) blkid_probe_enable_partitions(b
, 1);
202 (void) blkid_probe_set_partitions_flags(b
, BLKID_PARTS_ENTRY_DETAILS
);
205 r
= blkid_do_safeprobe(b
);
206 if (r
== _BLKID_SAFEPROBE_ERROR
)
207 return errno_or_else(EIO
);
208 if (IN_SET(r
, _BLKID_SAFEPROBE_AMBIGUOUS
, _BLKID_SAFEPROBE_NOT_FOUND
))
211 assert(r
== _BLKID_SAFEPROBE_FOUND
);
213 _cleanup_strv_free_
char **l
= strv_new(prefix
);
217 FOREACH_STRING(field
, "TYPE", "UUID", "LABEL", "PART_ENTRY_UUID", "PART_ENTRY_TYPE", "PART_ENTRY_NAME") {
218 const char *v
= NULL
;
220 (void) blkid_probe_lookup_value(b
, field
, &v
, NULL
);
222 _cleanup_free_
char *escaped
= xescape(strempty(v
), ":"); /* Avoid ambiguity around ":" */
226 r
= strv_consume(&l
, TAKE_PTR(escaped
));
232 assert(strv_length(l
) == 7); /* We always want 7 components, to avoid ambiguous strings */
234 _cleanup_free_
char *word
= strv_join(l
, ":");
238 *ret
= TAKE_PTR(word
);
242 static int run(int argc
, char *argv
[]) {
243 _cleanup_free_
char *joined
= NULL
, *word
= NULL
;
244 Tpm2UserspaceEventType event
;
245 unsigned target_pcr_nr
;
251 r
= parse_argv(argc
, argv
);
255 if (arg_file_system
) {
256 _cleanup_free_
char *normalized
= NULL
, *normalized_escaped
= NULL
;
257 _cleanup_(sd_device_unrefp
) sd_device
*d
= NULL
;
258 _cleanup_close_
int dfd
= -EBADF
;
261 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Expected no argument.");
263 dfd
= chase_and_open(arg_file_system
, NULL
, 0, O_DIRECTORY
|O_CLOEXEC
, &normalized
);
265 return log_error_errno(dfd
, "Failed to open path '%s': %m", arg_file_system
);
267 r
= fd_is_mount_point(dfd
, NULL
, 0);
269 return log_error_errno(r
, "Failed to determine if path '%s' is mount point: %m", normalized
);
271 return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR
), "Specified path '%s' is not a mount point, refusing: %m", normalized
);
273 normalized_escaped
= xescape(normalized
, ":"); /* Avoid ambiguity around ":" */
274 if (!normalized_escaped
)
277 _cleanup_free_
char* prefix
= strjoin("file-system:", normalized_escaped
);
281 r
= block_device_new_from_fd(dfd
, BLOCK_DEVICE_LOOKUP_BACKING
, &d
);
283 log_notice_errno(r
, "Unable to determine backing block device of '%s', measuring generic fallback file system identity string: %m", arg_file_system
);
285 word
= strjoin(prefix
, "::::::");
289 r
= get_file_system_word(d
, prefix
, &word
);
291 return log_error_errno(r
, "Failed to get file system identifier string for '%s': %m", arg_file_system
);
294 target_pcr_nr
= TPM2_PCR_SYSTEM_IDENTITY
; /* → PCR 15 */
295 event
= TPM2_EVENT_FILESYSTEM
;
297 } else if (arg_machine_id
) {
301 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Expected no argument.");
303 r
= sd_id128_get_machine(&mid
);
305 return log_error_errno(r
, "Failed to acquire machine ID: %m");
307 word
= strjoin("machine-id:", SD_ID128_TO_STRING(mid
));
311 target_pcr_nr
= TPM2_PCR_SYSTEM_IDENTITY
; /* → PCR 15 */
312 event
= TPM2_EVENT_MACHINE_ID
;
315 if (optind
+1 != argc
)
316 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Expected a single argument.");
318 word
= strdup(argv
[optind
]);
322 /* Refuse to measure an empty word. We want to be able to write the series of measured words
323 * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
324 * disallow an empty word to avoid ambiguities. */
326 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "String to measure cannot be empty, refusing.");
328 target_pcr_nr
= TPM2_PCR_KERNEL_BOOT
; /* → PCR 11 */
329 event
= TPM2_EVENT_PHASE
;
332 if (arg_graceful
&& tpm2_support() != TPM2_SUPPORT_FULL
) {
333 log_notice("No complete TPM2 support detected, exiting gracefully.");
337 length
= strlen(word
);
339 /* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
340 r
= efi_stub_measured(LOG_ERR
);
344 log_info("Kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT
);
348 _cleanup_(tpm2_context_unrefp
) Tpm2Context
*c
= NULL
;
349 r
= tpm2_context_new(arg_tpm2_device
, &c
);
353 r
= determine_banks(c
, target_pcr_nr
);
356 if (strv_isempty(arg_banks
)) /* Still none? */
357 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "Found a TPM2 without enabled PCR banks. Can't operate.");
359 joined
= strv_join(arg_banks
, ", ");
363 log_debug("Measuring '%s' into PCR index %u, banks %s.", word
, target_pcr_nr
, joined
);
365 r
= tpm2_extend_bytes(c
, arg_banks
, target_pcr_nr
, word
, length
, NULL
, 0, event
, word
);
370 "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR
,
371 LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", target_pcr_nr
, word
, joined
),
372 "MEASURING=%s", word
,
373 "PCR=%u", target_pcr_nr
,
379 DEFINE_MAIN_FUNCTION(run
);