]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/pcrextend/pcrextend.c
man/systemd.mount: tmpfs automatically gains After=swap.target dep
[thirdparty/systemd.git] / src / pcrextend / pcrextend.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4
5 #include <sd-messages.h>
6
7 #include "build.h"
8 #include "efi-loader.h"
9 #include "escape.h"
10 #include "main-func.h"
11 #include "openssl-util.h"
12 #include "parse-argument.h"
13 #include "parse-util.h"
14 #include "pcrextend-util.h"
15 #include "pretty-print.h"
16 #include "strv.h"
17 #include "tpm2-pcr.h"
18 #include "tpm2-util.h"
19 #include "varlink.h"
20 #include "varlink-io.systemd.PCRExtend.h"
21
22 static bool arg_graceful = false;
23 static char *arg_tpm2_device = NULL;
24 static char **arg_banks = NULL;
25 static char *arg_file_system = NULL;
26 static bool arg_machine_id = false;
27 static unsigned arg_pcr_index = UINT_MAX;
28 static bool arg_varlink = 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 #define EXTENSION_STRING_SAFE_LIMIT 1024
35
36 static int help(int argc, char *argv[], void *userdata) {
37 _cleanup_free_ char *link = NULL;
38 int r;
39
40 r = terminal_urlify_man("systemd-pcrextend", "8", &link);
41 if (r < 0)
42 return log_oom();
43
44 printf("%1$s [OPTIONS...] WORD\n"
45 "%1$s [OPTIONS...] --file-system=PATH\n"
46 "%1$s [OPTIONS...] --machine-id\n"
47 "\n%5$sExtend a TPM2 PCR with boot phase, machine ID, or file system ID.%6$s\n"
48 "\n%3$sOptions:%4$s\n"
49 " -h --help Show this help\n"
50 " --version Print version\n"
51 " --bank=DIGEST Select TPM PCR bank (SHA1, SHA256)\n"
52 " --pcr=INDEX Select TPM PCR index (0…23)\n"
53 " --tpm2-device=PATH Use specified TPM2 device\n"
54 " --graceful Exit gracefully if no TPM2 device is found\n"
55 " --file-system=PATH Measure UUID/labels of file system into PCR 15\n"
56 " --machine-id Measure machine ID into PCR 15\n"
57 "\nSee the %2$s for details.\n",
58 program_invocation_short_name,
59 link,
60 ansi_underline(),
61 ansi_normal(),
62 ansi_highlight(),
63 ansi_normal());
64
65 return 0;
66 }
67
68 static int parse_argv(int argc, char *argv[]) {
69 enum {
70 ARG_VERSION = 0x100,
71 ARG_BANK,
72 ARG_PCR,
73 ARG_TPM2_DEVICE,
74 ARG_GRACEFUL,
75 ARG_FILE_SYSTEM,
76 ARG_MACHINE_ID,
77 };
78
79 static const struct option options[] = {
80 { "help", no_argument, NULL, 'h' },
81 { "version", no_argument, NULL, ARG_VERSION },
82 { "bank", required_argument, NULL, ARG_BANK },
83 { "pcr", required_argument, NULL, ARG_PCR },
84 { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
85 { "graceful", no_argument, NULL, ARG_GRACEFUL },
86 { "file-system", required_argument, NULL, ARG_FILE_SYSTEM },
87 { "machine-id", no_argument, NULL, ARG_MACHINE_ID },
88 {}
89 };
90
91 int c, r;
92
93 assert(argc >= 0);
94 assert(argv);
95
96 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
97 switch (c) {
98
99 case 'h':
100 help(0, NULL, NULL);
101 return 0;
102
103 case ARG_VERSION:
104 return version();
105
106 case ARG_BANK: {
107 const EVP_MD *implementation;
108
109 implementation = EVP_get_digestbyname(optarg);
110 if (!implementation)
111 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg);
112
113 if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0)
114 return log_oom();
115
116 break;
117 }
118
119 case ARG_PCR:
120 r = tpm2_pcr_index_from_string(optarg);
121 if (r < 0)
122 return log_error_errno(r, "Failed to parse PCR index: %s", optarg);
123
124 arg_pcr_index = r;
125 break;
126
127 case ARG_TPM2_DEVICE: {
128 _cleanup_free_ char *device = NULL;
129
130 if (streq(optarg, "list"))
131 return tpm2_list_devices();
132
133 if (!streq(optarg, "auto")) {
134 device = strdup(optarg);
135 if (!device)
136 return log_oom();
137 }
138
139 free_and_replace(arg_tpm2_device, device);
140 break;
141 }
142
143 case ARG_GRACEFUL:
144 arg_graceful = true;
145 break;
146
147 case ARG_FILE_SYSTEM:
148 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system);
149 if (r < 0)
150 return r;
151
152 break;
153
154 case ARG_MACHINE_ID:
155 arg_machine_id = true;
156 break;
157
158 case '?':
159 return -EINVAL;
160
161 default:
162 assert_not_reached();
163 }
164
165 if (arg_file_system && arg_machine_id)
166 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined.");
167
168 r = varlink_invocation(VARLINK_ALLOW_ACCEPT);
169 if (r < 0)
170 return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
171 if (r > 0)
172 arg_varlink = true;
173 else if (arg_pcr_index == UINT_MAX)
174 arg_pcr_index = (arg_file_system || arg_machine_id) ?
175 TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */
176 TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */
177
178 return 1;
179 }
180
181 static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) {
182 _cleanup_strv_free_ char **l = NULL;
183 int r;
184
185 assert(c);
186
187 if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */
188 return 0;
189
190 r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << target_pcr_nr, &l);
191 if (r < 0)
192 return log_error_errno(r, "Could not verify pcr banks: %m");
193
194 strv_free_and_replace(arg_banks, l);
195 return 0;
196 }
197
198 static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2UserspaceEventType event) {
199 _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
200 int r;
201
202 r = tpm2_context_new(arg_tpm2_device, &c);
203 if (r < 0)
204 return r;
205
206 r = determine_banks(c, pcr);
207 if (r < 0)
208 return r;
209 if (strv_isempty(arg_banks)) /* Still none? */
210 return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate.");
211
212 _cleanup_free_ char *joined_banks = NULL;
213 joined_banks = strv_join(arg_banks, ", ");
214 if (!joined_banks)
215 return log_oom();
216
217 _cleanup_free_ char *safe = NULL;
218 if (size > EXTENSION_STRING_SAFE_LIMIT) {
219 safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT);
220 if (!safe)
221 return log_oom();
222
223 if (!strextend(&safe, "..."))
224 return log_oom();
225 } else {
226 safe = cescape_length(data, size);
227 if (!safe)
228 return log_oom();
229 }
230
231 log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks);
232
233 r = tpm2_extend_bytes(c, arg_banks, pcr, data, size, /* secret= */ NULL, /* secret_size= */ 0, event, safe);
234 if (r < 0)
235 return log_error_errno(r, "Could not extend PCR: %m");
236
237 log_struct(LOG_INFO,
238 "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR,
239 LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", pcr, safe, joined_banks),
240 "MEASURING=%s", safe,
241 "PCR=%u", pcr,
242 "BANKS=%s", joined_banks);
243
244 return 0;
245 }
246
247 typedef struct MethodExtendParameters {
248 unsigned pcr;
249 const char *text;
250 struct iovec data;
251 } MethodExtendParameters;
252
253 static void method_extend_parameters_done(MethodExtendParameters *p) {
254 assert(p);
255
256 iovec_done(&p->data);
257 }
258
259 static int vl_method_extend(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
260
261 static const JsonDispatch dispatch_table[] = {
262 { "pcr", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(MethodExtendParameters, pcr), JSON_MANDATORY },
263 { "text", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 },
264 { "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodExtendParameters, data), 0 },
265 {}
266 };
267 _cleanup_(method_extend_parameters_done) MethodExtendParameters p = {
268 .pcr = UINT_MAX,
269 };
270 int r;
271
272 assert(link);
273
274 r = varlink_dispatch(link, parameters, dispatch_table, &p);
275 if (r != 0)
276 return r;
277
278 if (!TPM2_PCR_INDEX_VALID(p.pcr))
279 return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "pcr")));
280
281 if (p.text) {
282 /* Specifying both the text string and the binary data is not allowed */
283 if (p.data.iov_base)
284 return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "data")));
285
286 r = extend_now(p.pcr, p.text, strlen(p.text), _TPM2_USERSPACE_EVENT_TYPE_INVALID);
287 } else if (p.data.iov_base)
288 r = extend_now(p.pcr, p.data.iov_base, p.data.iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
289 else
290 return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "text")));
291 if (r < 0)
292 return r;
293
294 return varlink_reply(link, NULL);
295 }
296
297 static int run(int argc, char *argv[]) {
298 _cleanup_free_ char *word = NULL;
299 Tpm2UserspaceEventType event;
300 int r;
301
302 log_setup();
303
304 r = parse_argv(argc, argv);
305 if (r <= 0)
306 return r;
307
308 if (arg_varlink) {
309 _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
310
311 /* Invocation as Varlink service */
312
313 r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY);
314 if (r < 0)
315 return log_error_errno(r, "Failed to allocate Varlink server: %m");
316
317 r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_PCRExtend);
318 if (r < 0)
319 return log_error_errno(r, "Failed to add Varlink interface: %m");
320
321 r = varlink_server_bind_method(varlink_server, "io.systemd.PCRExtend.Extend", vl_method_extend);
322 if (r < 0)
323 return log_error_errno(r, "Failed to bind Varlink method: %m");
324
325 r = varlink_server_loop_auto(varlink_server);
326 if (r < 0)
327 return log_error_errno(r, "Failed to run Varlink event loop: %m");
328
329 return EXIT_SUCCESS;
330 }
331
332 if (arg_file_system) {
333 if (optind != argc)
334 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
335
336 r = pcrextend_file_system_word(arg_file_system, &word, NULL);
337 if (r < 0)
338 return r;
339
340 event = TPM2_EVENT_FILESYSTEM;
341
342 } else if (arg_machine_id) {
343
344 if (optind != argc)
345 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
346
347 r = pcrextend_machine_id_word(&word);
348 if (r < 0)
349 return r;
350
351 event = TPM2_EVENT_MACHINE_ID;
352
353 } else {
354 if (optind+1 != argc)
355 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
356
357 word = strdup(argv[optind]);
358 if (!word)
359 return log_oom();
360
361 /* Refuse to measure an empty word. We want to be able to write the series of measured words
362 * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
363 * disallow an empty word to avoid ambiguities. */
364 if (isempty(word))
365 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
366
367 event = TPM2_EVENT_PHASE;
368 }
369
370 if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) {
371 log_notice("No complete TPM2 support detected, exiting gracefully.");
372 return EXIT_SUCCESS;
373 }
374
375 /* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
376 r = efi_measured_uki(LOG_ERR);
377 if (r < 0)
378 return r;
379 if (r == 0) {
380 log_info("Kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT);
381 return EXIT_SUCCESS;
382 }
383
384 r = extend_now(arg_pcr_index, word, strlen(word), event);
385 if (r < 0)
386 return log_error_errno(r, "Failed to create TPM2 context: %m");
387
388 return EXIT_SUCCESS;
389 }
390
391 DEFINE_MAIN_FUNCTION(run);