]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/pcrphase.c
tpm2: whenever we measure, also write a tpm log record
[thirdparty/systemd.git] / src / boot / pcrphase.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4
5 #include <sd-device.h>
6 #include <sd-messages.h>
7
8 #include "blkid-util.h"
9 #include "blockdev-util.h"
10 #include "build.h"
11 #include "chase.h"
12 #include "efi-loader.h"
13 #include "efivars.h"
14 #include "escape.h"
15 #include "fd-util.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"
21 #include "tpm2-pcr.h"
22 #include "tpm2-util.h"
23
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;
29
30 STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
31 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
32 STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
33
34 static int help(int argc, char *argv[], void *userdata) {
35 _cleanup_free_ char *link = NULL;
36 int r;
37
38 r = terminal_urlify_man("systemd-pcrphase", "8", &link);
39 if (r < 0)
40 return log_oom();
41
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,
56 link,
57 ansi_underline(),
58 ansi_normal(),
59 ansi_highlight(),
60 ansi_normal());
61
62 return 0;
63 }
64
65 static int parse_argv(int argc, char *argv[]) {
66 enum {
67 ARG_VERSION = 0x100,
68 ARG_BANK,
69 ARG_TPM2_DEVICE,
70 ARG_GRACEFUL,
71 ARG_FILE_SYSTEM,
72 ARG_MACHINE_ID,
73 };
74
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 },
83 {}
84 };
85
86 int c, r;
87
88 assert(argc >= 0);
89 assert(argv);
90
91 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
92 switch (c) {
93
94 case 'h':
95 help(0, NULL, NULL);
96 return 0;
97
98 case ARG_VERSION:
99 return version();
100
101 case ARG_BANK: {
102 const EVP_MD *implementation;
103
104 implementation = EVP_get_digestbyname(optarg);
105 if (!implementation)
106 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg);
107
108 if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0)
109 return log_oom();
110
111 break;
112 }
113
114 case ARG_TPM2_DEVICE: {
115 _cleanup_free_ char *device = NULL;
116
117 if (streq(optarg, "list"))
118 return tpm2_list_devices();
119
120 if (!streq(optarg, "auto")) {
121 device = strdup(optarg);
122 if (!device)
123 return log_oom();
124 }
125
126 free_and_replace(arg_tpm2_device, device);
127 break;
128 }
129
130 case ARG_GRACEFUL:
131 arg_graceful = true;
132 break;
133
134 case ARG_FILE_SYSTEM:
135 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system);
136 if (r < 0)
137 return r;
138
139 break;
140
141 case ARG_MACHINE_ID:
142 arg_machine_id = true;
143 break;
144
145 case '?':
146 return -EINVAL;
147
148 default:
149 assert_not_reached();
150 }
151
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.");
154
155 return 1;
156 }
157
158 static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) {
159 _cleanup_strv_free_ char **l = NULL;
160 int r;
161
162 assert(c);
163
164 if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */
165 return 0;
166
167 r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << target_pcr_nr, &l);
168 if (r < 0)
169 return r;
170
171 strv_free_and_replace(arg_banks, l);
172 return 0;
173 }
174
175 static int get_file_system_word(
176 sd_device *d,
177 const char *prefix,
178 char **ret) {
179
180 int r;
181
182 assert(d);
183 assert(prefix);
184 assert(ret);
185
186 _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
187 if (block_fd < 0)
188 return block_fd;
189
190 _cleanup_(blkid_free_probep) blkid_probe b = blkid_new_probe();
191 if (!b)
192 return -ENOMEM;
193
194 errno = 0;
195 r = blkid_probe_set_device(b, block_fd, 0, 0);
196 if (r != 0)
197 return errno_or_else(ENOMEM);
198
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);
203
204 errno = 0;
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))
209 return -ENOPKG;
210
211 assert(r == _BLKID_SAFEPROBE_FOUND);
212
213 _cleanup_strv_free_ char **l = strv_new(prefix);
214 if (!l)
215 return log_oom();
216
217 FOREACH_STRING(field, "TYPE", "UUID", "LABEL", "PART_ENTRY_UUID", "PART_ENTRY_TYPE", "PART_ENTRY_NAME") {
218 const char *v = NULL;
219
220 (void) blkid_probe_lookup_value(b, field, &v, NULL);
221
222 _cleanup_free_ char *escaped = xescape(strempty(v), ":"); /* Avoid ambiguity around ":" */
223 if (!escaped)
224 return log_oom();
225
226 r = strv_consume(&l, TAKE_PTR(escaped));
227 if (r < 0)
228 return log_oom();
229
230 }
231
232 assert(strv_length(l) == 7); /* We always want 7 components, to avoid ambiguous strings */
233
234 _cleanup_free_ char *word = strv_join(l, ":");
235 if (!word)
236 return log_oom();
237
238 *ret = TAKE_PTR(word);
239 return 0;
240 }
241
242 static int run(int argc, char *argv[]) {
243 _cleanup_free_ char *joined = NULL, *word = NULL;
244 Tpm2UserspaceEventType event;
245 unsigned target_pcr_nr;
246 size_t length;
247 int r;
248
249 log_setup();
250
251 r = parse_argv(argc, argv);
252 if (r <= 0)
253 return r;
254
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;
259
260 if (optind != argc)
261 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
262
263 dfd = chase_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized);
264 if (dfd < 0)
265 return log_error_errno(dfd, "Failed to open path '%s': %m", arg_file_system);
266
267 r = fd_is_mount_point(dfd, NULL, 0);
268 if (r < 0)
269 return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized);
270 if (r == 0)
271 return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing: %m", normalized);
272
273 normalized_escaped = xescape(normalized, ":"); /* Avoid ambiguity around ":" */
274 if (!normalized_escaped)
275 return log_oom();
276
277 _cleanup_free_ char* prefix = strjoin("file-system:", normalized_escaped);
278 if (!prefix)
279 return log_oom();
280
281 r = block_device_new_from_fd(dfd, BLOCK_DEVICE_LOOKUP_BACKING, &d);
282 if (r < 0) {
283 log_notice_errno(r, "Unable to determine backing block device of '%s', measuring generic fallback file system identity string: %m", arg_file_system);
284
285 word = strjoin(prefix, "::::::");
286 if (!word)
287 return log_oom();
288 } else {
289 r = get_file_system_word(d, prefix, &word);
290 if (r < 0)
291 return log_error_errno(r, "Failed to get file system identifier string for '%s': %m", arg_file_system);
292 }
293
294 target_pcr_nr = TPM2_PCR_SYSTEM_IDENTITY; /* → PCR 15 */
295 event = TPM2_EVENT_FILESYSTEM;
296
297 } else if (arg_machine_id) {
298 sd_id128_t mid;
299
300 if (optind != argc)
301 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
302
303 r = sd_id128_get_machine(&mid);
304 if (r < 0)
305 return log_error_errno(r, "Failed to acquire machine ID: %m");
306
307 word = strjoin("machine-id:", SD_ID128_TO_STRING(mid));
308 if (!word)
309 return log_oom();
310
311 target_pcr_nr = TPM2_PCR_SYSTEM_IDENTITY; /* → PCR 15 */
312 event = TPM2_EVENT_MACHINE_ID;
313
314 } else {
315 if (optind+1 != argc)
316 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
317
318 word = strdup(argv[optind]);
319 if (!word)
320 return log_oom();
321
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. */
325 if (isempty(word))
326 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
327
328 target_pcr_nr = TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */
329 event = TPM2_EVENT_PHASE;
330 }
331
332 if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) {
333 log_notice("No complete TPM2 support detected, exiting gracefully.");
334 return EXIT_SUCCESS;
335 }
336
337 length = strlen(word);
338
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);
341 if (r < 0)
342 return r;
343 if (r == 0) {
344 log_info("Kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT);
345 return EXIT_SUCCESS;
346 }
347
348 _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
349 r = tpm2_context_new(arg_tpm2_device, &c);
350 if (r < 0)
351 return r;
352
353 r = determine_banks(c, target_pcr_nr);
354 if (r < 0)
355 return r;
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.");
358
359 joined = strv_join(arg_banks, ", ");
360 if (!joined)
361 return log_oom();
362
363 log_debug("Measuring '%s' into PCR index %u, banks %s.", word, target_pcr_nr, joined);
364
365 r = tpm2_extend_bytes(c, arg_banks, target_pcr_nr, word, length, NULL, 0, event, word);
366 if (r < 0)
367 return r;
368
369 log_struct(LOG_INFO,
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,
374 "BANKS=%s", joined);
375
376 return EXIT_SUCCESS;
377 }
378
379 DEFINE_MAIN_FUNCTION(run);