1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <stdbool.h>
5 #include <stdlib.h>
6 #include <sys/stat.h>
7 #include <unistd.h>
8
9 #include "alloc-util.h"
10 #include "fd-util.h"
11 #include "fileio.h"
12 #include "fstab-util.h"
13 #include "generator.h"
14 #include "hexdecoct.h"
15 #include "id128-util.h"
16 #include "main-func.h"
17 #include "mkdir.h"
18 #include "parse-util.h"
19 #include "path-util.h"
20 #include "proc-cmdline.h"
21 #include "specifier.h"
22 #include "string-util.h"
23 #include "unit-name.h"
24
25 #define SYSTEMD_VERITYSETUP_SERVICE_ROOT "systemd-veritysetup@root.service"
26 #define SYSTEMD_VERITYSETUP_SERVICE_USR "systemd-veritysetup@usr.service"
27
28 static const char *arg_dest = NULL;
29 static bool arg_enabled = true;
30 static bool arg_read_veritytab = true;
31 static const char *arg_veritytab = NULL;
32 static char *arg_root_hash = NULL;
33 static char *arg_root_data_what = NULL;
34 static char *arg_root_hash_what = NULL;
35 static char *arg_root_options = NULL;
36 static char *arg_usr_hash = NULL;
37 static char *arg_usr_data_what = NULL;
38 static char *arg_usr_hash_what = NULL;
39 static char *arg_usr_options = NULL;
40
41 STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep);
42 STATIC_DESTRUCTOR_REGISTER(arg_root_data_what, freep);
43 STATIC_DESTRUCTOR_REGISTER(arg_root_hash_what, freep);
44 STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep);
45 STATIC_DESTRUCTOR_REGISTER(arg_usr_hash, freep);
46 STATIC_DESTRUCTOR_REGISTER(arg_usr_data_what, freep);
47 STATIC_DESTRUCTOR_REGISTER(arg_usr_hash_what, freep);
48 STATIC_DESTRUCTOR_REGISTER(arg_usr_options, freep);
49
create_device(const char * name,const char * service,const char * hash,const char * data_what,const char * hash_what,const char * options)50 static int create_device(
51 const char *name,
52 const char *service,
53 const char *hash,
54 const char *data_what,
55 const char *hash_what,
56 const char *options) {
57
58 _cleanup_free_ char *u = NULL, *v = NULL, *d = NULL, *e = NULL, *u_escaped = NULL, *v_escaped = NULL,
59 *hash_escaped = NULL, *options_escaped = NULL;
60 _cleanup_fclose_ FILE *f = NULL;
61 const char *to, *from;
62 int r;
63
64 assert(name);
65 assert(service);
66
67 /* If all three pieces of information are missing, then verity is turned off */
68 if (!hash && !data_what && !hash_what)
69 return 0;
70
71 /* if one of them is missing however, the data is simply incomplete and this is an error */
72 if (!hash)
73 log_error("Verity information for %s incomplete, hash unspecified.", name);
74 if (!data_what)
75 log_error("Verity information for %s incomplete, data device unspecified.", name);
76 if (!hash_what)
77 log_error("Verity information for %s incomplete, hash device unspecified.", name);
78
79 if (!hash || !data_what || !hash_what)
80 return -EINVAL;
81
82 log_debug("Using %s verity data device %s, hash device %s, options %s, and hash %s.", name, data_what, hash_what, options, hash);
83
84 u = fstab_node_to_udev_node(data_what);
85 if (!u)
86 return log_oom();
87 v = fstab_node_to_udev_node(hash_what);
88 if (!v)
89 return log_oom();
90
91 u_escaped = specifier_escape(u);
92 if (!u_escaped)
93 return log_oom();
94 v_escaped = specifier_escape(v);
95 if (!v_escaped)
96 return log_oom();
97
98 r = unit_name_from_path(u, ".device", &d);
99 if (r < 0)
100 return log_error_errno(r, "Failed to generate unit name: %m");
101 r = unit_name_from_path(v, ".device", &e);
102 if (r < 0)
103 return log_error_errno(r, "Failed to generate unit name: %m");
104
105 options_escaped = specifier_escape(strempty(options));
106 if (!options_escaped)
107 return log_oom();
108
109 hash_escaped = specifier_escape(hash);
110 if (!hash_escaped)
111 return log_oom();
112
113 r = generator_open_unit_file(arg_dest, NULL, service, &f);
114 if (r < 0)
115 return r;
116
117 fprintf(f,
118 "[Unit]\n"
119 "Description=Verity Protection Setup for %%I\n"
120 "Documentation=man:systemd-veritysetup-generator(8) man:systemd-veritysetup@.service(8)\n"
121 "SourcePath=/proc/cmdline\n"
122 "DefaultDependencies=no\n"
123 "Conflicts=umount.target\n"
124 "BindsTo=%s %s\n"
125 "IgnoreOnIsolate=true\n"
126 "After=veritysetup-pre.target systemd-udevd-kernel.socket %s %s\n"
127 "Before=veritysetup.target umount.target\n"
128 "\n[Service]\n"
129 "Type=oneshot\n"
130 "RemainAfterExit=yes\n"
131 "ExecStart=" ROOTLIBEXECDIR "/systemd-veritysetup attach '%s' '%s' '%s' '%s' '%s'\n"
132 "ExecStop=" ROOTLIBEXECDIR "/systemd-veritysetup detach '%s' \n",
133 d, e,
134 d, e,
135 name, u_escaped, v_escaped, hash_escaped, options_escaped,
136 name);
137
138 r = fflush_and_check(f);
139 if (r < 0)
140 return log_error_errno(r, "Failed to write file unit %s: %m", service);
141
142 to = strjoina(arg_dest, "/veritysetup.target.requires/", service);
143 from = strjoina("../", service);
144
145 (void) mkdir_parents(to, 0755);
146 if (symlink(from, to) < 0)
147 return log_error_errno(errno, "Failed to create symlink %s: %m", to);
148
149 return 0;
150 }
151
create_root_device(void)152 static int create_root_device(void) {
153 return create_device("root", SYSTEMD_VERITYSETUP_SERVICE_ROOT, arg_root_hash, arg_root_data_what, arg_root_hash_what, arg_root_options);
154 }
155
create_usr_device(void)156 static int create_usr_device(void) {
157 return create_device("usr", SYSTEMD_VERITYSETUP_SERVICE_USR, arg_usr_hash, arg_usr_data_what, arg_usr_hash_what, arg_usr_options);
158 }
159
parse_proc_cmdline_item(const char * key,const char * value,void * data)160 static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
161 int r;
162
163 if (proc_cmdline_key_streq(key, "systemd.verity")) {
164
165 r = value ? parse_boolean(value) : 1;
166 if (r < 0)
167 log_warning("Failed to parse verity= kernel command line switch %s. Ignoring.", value);
168 else
169 arg_enabled = r;
170
171 } else if (streq(key, "veritytab")) {
172
173 r = value ? parse_boolean(value) : 1;
174 if (r < 0)
175 log_warning("Failed to parse veritytab= kernel command line switch %s. Ignoring.", value);
176 else
177 arg_read_veritytab = r;
178
179 } else if (proc_cmdline_key_streq(key, "roothash")) {
180
181 if (proc_cmdline_value_missing(key, value))
182 return 0;
183
184 r = free_and_strdup(&arg_root_hash, value);
185 if (r < 0)
186 return log_oom();
187
188 } else if (proc_cmdline_key_streq(key, "systemd.verity_root_data")) {
189
190 if (proc_cmdline_value_missing(key, value))
191 return 0;
192
193 r = free_and_strdup(&arg_root_data_what, value);
194 if (r < 0)
195 return log_oom();
196
197 } else if (proc_cmdline_key_streq(key, "systemd.verity_root_hash")) {
198
199 if (proc_cmdline_value_missing(key, value))
200 return 0;
201
202 r = free_and_strdup(&arg_root_hash_what, value);
203 if (r < 0)
204 return log_oom();
205
206 } else if (proc_cmdline_key_streq(key, "systemd.verity_root_options")) {
207
208 if (proc_cmdline_value_missing(key, value))
209 return 0;
210
211 r = free_and_strdup(&arg_root_options, value);
212 if (r < 0)
213 return log_oom();
214
215 } else if (proc_cmdline_key_streq(key, "usrhash")) {
216
217 if (proc_cmdline_value_missing(key, value))
218 return 0;
219
220 r = free_and_strdup(&arg_usr_hash, value);
221 if (r < 0)
222 return log_oom();
223
224 } else if (proc_cmdline_key_streq(key, "systemd.verity_usr_data")) {
225
226 if (proc_cmdline_value_missing(key, value))
227 return 0;
228
229 r = free_and_strdup(&arg_usr_data_what, value);
230 if (r < 0)
231 return log_oom();
232
233 } else if (proc_cmdline_key_streq(key, "systemd.verity_usr_hash")) {
234
235 if (proc_cmdline_value_missing(key, value))
236 return 0;
237
238 r = free_and_strdup(&arg_usr_hash_what, value);
239 if (r < 0)
240 return log_oom();
241
242 } else if (proc_cmdline_key_streq(key, "systemd.verity_usr_options")) {
243
244 if (proc_cmdline_value_missing(key, value))
245 return 0;
246
247 r = free_and_strdup(&arg_usr_options, value);
248 if (r < 0)
249 return log_oom();
250
251 }
252
253 return 0;
254 }
255
determine_device(const char * name,const char * hash,char ** data_what,char ** hash_what)256 static int determine_device(
257 const char *name,
258 const char *hash,
259 char **data_what,
260 char **hash_what) {
261
262 sd_id128_t data_uuid, verity_uuid;
263 _cleanup_free_ void *m = NULL;
264 size_t l;
265 int r;
266
267 assert(name);
268 assert(data_what);
269 assert(hash_what);
270
271 if (!hash)
272 return 0;
273
274 if (*data_what && *hash_what)
275 return 0;
276
277 r = unhexmem(hash, strlen(hash), &m, &l);
278 if (r < 0)
279 return log_error_errno(r, "Failed to parse hash: %s", hash);
280 if (l < sizeof(sd_id128_t)) {
281 log_debug("Root hash for %s is shorter than 128 bits (32 characters), ignoring for discovering verity partition.", name);
282 return 0;
283 }
284
285 if (!*data_what) {
286 memcpy(&data_uuid, m, sizeof(data_uuid));
287
288 *data_what = path_join("/dev/disk/by-partuuid", SD_ID128_TO_UUID_STRING(data_uuid));
289 if (!*data_what)
290 return log_oom();
291 }
292
293 if (!*hash_what) {
294 memcpy(&verity_uuid, (uint8_t*) m + l - sizeof(verity_uuid), sizeof(verity_uuid));
295
296 *hash_what = path_join("/dev/disk/by-partuuid", SD_ID128_TO_UUID_STRING(verity_uuid));
297 if (!*hash_what)
298 return log_oom();
299 }
300
301 log_info("Using data device %s and hash device %s for %s.", *data_what, *hash_what, name);
302
303 return 1;
304 }
305
determine_devices(void)306 static int determine_devices(void) {
307 int r;
308
309 r = determine_device("root", arg_root_hash, &arg_root_data_what, &arg_root_hash_what);
310 if (r < 0)
311 return r;
312
313 return determine_device("usr", arg_usr_hash, &arg_usr_data_what, &arg_usr_hash_what);
314 }
315
create_disk(const char * name,const char * data_device,const char * hash_device,const char * roothash,const char * options,const char * source)316 static int create_disk(
317 const char *name,
318 const char *data_device,
319 const char *hash_device,
320 const char *roothash,
321 const char *options,
322 const char *source) {
323
324 _cleanup_free_ char *n = NULL, *dd = NULL, *du = NULL, *hd = NULL, *hu = NULL, *e = NULL,
325 *du_escaped = NULL, *hu_escaped = NULL, *name_escaped = NULL;
326 _cleanup_fclose_ FILE *f = NULL;
327 const char *dmname;
328 bool noauto, nofail, netdev, attach_in_initrd;
329 int r;
330
331 assert(name);
332 assert(data_device);
333 assert(hash_device);
334 assert(roothash);
335
336 noauto = fstab_test_yes_no_option(options, "noauto\0" "auto\0");
337 nofail = fstab_test_yes_no_option(options, "nofail\0" "fail\0");
338 netdev = fstab_test_option(options, "_netdev\0");
339 attach_in_initrd = fstab_test_option(options, "x-initrd.attach\0");
340
341 name_escaped = specifier_escape(name);
342 if (!name_escaped)
343 return log_oom();
344
345 e = unit_name_escape(name);
346 if (!e)
347 return log_oom();
348
349 du = fstab_node_to_udev_node(data_device);
350 if (!du)
351 return log_oom();
352
353 hu = fstab_node_to_udev_node(hash_device);
354 if (!hu)
355 return log_oom();
356
357 r = unit_name_build("systemd-veritysetup", e, ".service", &n);
358 if (r < 0)
359 return log_error_errno(r, "Failed to generate unit name: %m");
360
361 du_escaped = specifier_escape(du);
362 if (!du_escaped)
363 return log_oom();
364
365 hu_escaped = specifier_escape(hu);
366 if (!hu_escaped)
367 return log_oom();
368
369 r = unit_name_from_path(du, ".device", &dd);
370 if (r < 0)
371 return log_error_errno(r, "Failed to generate unit name: %m");
372
373 r = unit_name_from_path(hu, ".device", &hd);
374 if (r < 0)
375 return log_error_errno(r, "Failed to generate unit name: %m");
376
377 r = generator_open_unit_file(arg_dest, NULL, n, &f);
378 if (r < 0)
379 return r;
380
381 r = generator_write_veritysetup_unit_section(f, source);
382 if (r < 0)
383 return r;
384
385 if (netdev)
386 fprintf(f, "After=remote-fs-pre.target\n");
387
388 /* If initrd takes care of attaching the disk then it should also detach it during shutdown. */
389 if (!attach_in_initrd)
390 fprintf(f, "Conflicts=umount.target\n");
391
392 if (!nofail)
393 fprintf(f,
394 "Before=%s\n",
395 netdev ? "remote-veritysetup.target" : "veritysetup.target");
396
397 if (path_startswith(du, "/dev/"))
398 fprintf(f,
399 "BindsTo=%s\n"
400 "After=%s\n"
401 "Before=umount.target\n",
402 dd, dd);
403 else
404 /* For loopback devices, add systemd-tmpfiles-setup-dev.service
405 dependency to ensure that loopback support is available in
406 the kernel (/dev/loop-control needs to exist) */
407 fprintf(f,
408 "RequiresMountsFor=%s\n"
409 "Requires=systemd-tmpfiles-setup-dev.service\n"
410 "After=systemd-tmpfiles-setup-dev.service\n",
411 du_escaped);
412
413 if (path_startswith(hu, "/dev/"))
414 fprintf(f,
415 "BindsTo=%s\n"
416 "After=%s\n"
417 "Before=umount.target\n",
418 hd, hd);
419 else
420 /* For loopback devices, add systemd-tmpfiles-setup-dev.service
421 dependency to ensure that loopback support is available in
422 the kernel (/dev/loop-control needs to exist) */
423 fprintf(f,
424 "RequiresMountsFor=%s\n"
425 "Requires=systemd-tmpfiles-setup-dev.service\n"
426 "After=systemd-tmpfiles-setup-dev.service\n",
427 hu_escaped);
428
429 r = generator_write_veritysetup_service_section(f, name, du_escaped, hu_escaped, roothash, options);
430 if (r < 0)
431 return r;
432
433 r = fflush_and_check(f);
434 if (r < 0)
435 return log_error_errno(r, "Failed to write unit file %s: %m", n);
436
437 if (!noauto) {
438 r = generator_add_symlink(arg_dest,
439 netdev ? "remote-veritysetup.target" : "veritysetup.target",
440 nofail ? "wants" : "requires", n);
441 if (r < 0)
442 return r;
443 }
444
445 dmname = strjoina("dev-mapper-", e, ".device");
446 return generator_add_symlink(arg_dest, dmname, "requires", n);
447 }
448
add_veritytab_devices(void)449 static int add_veritytab_devices(void) {
450 _cleanup_fclose_ FILE *f = NULL;
451 unsigned veritytab_line = 0;
452 int r;
453
454 if (!arg_read_veritytab)
455 return 0;
456
457 r = fopen_unlocked(arg_veritytab, "re", &f);
458 if (r < 0) {
459 if (errno != ENOENT)
460 log_error_errno(errno, "Failed to open %s: %m", arg_veritytab);
461 return 0;
462 }
463
464 for (;;) {
465 _cleanup_free_ char *line = NULL, *name = NULL, *data_device = NULL, *hash_device = NULL,
466 *roothash = NULL, *options = NULL;
467 char *l, *data_uuid, *hash_uuid;
468
469 r = read_line(f, LONG_LINE_MAX, &line);
470 if (r < 0)
471 return log_error_errno(r, "Failed to read %s: %m", arg_veritytab);
472 if (r == 0)
473 break;
474
475 veritytab_line++;
476
477 l = strstrip(line);
478 if (IN_SET(l[0], 0, '#'))
479 continue;
480
481 r = sscanf(l, "%ms %ms %ms %ms %ms", &name, &data_device, &hash_device, &roothash, &options);
482 if (!IN_SET(r, 4, 5)) {
483 log_error("Failed to parse %s:%u, ignoring.", arg_veritytab, veritytab_line);
484 continue;
485 }
486
487 data_uuid = startswith(data_device, "UUID=");
488 if (!data_uuid)
489 data_uuid = path_startswith(data_device, "/dev/disk/by-uuid/");
490
491 hash_uuid = startswith(hash_device, "UUID=");
492 if (!hash_uuid)
493 hash_uuid = path_startswith(hash_device, "/dev/disk/by-uuid/");
494
495 r = create_disk(name,
496 data_device,
497 hash_device,
498 roothash,
499 options,
500 arg_veritytab);
501 if (r < 0)
502 return r;
503 }
504
505 return 0;
506 }
507
run(const char * dest,const char * dest_early,const char * dest_late)508 static int run(const char *dest, const char *dest_early, const char *dest_late) {
509 int r;
510
511 assert_se(arg_dest = dest);
512
513 arg_veritytab = getenv("SYSTEMD_VERITYTAB") ?: "/etc/veritytab";
514
515 r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX);
516 if (r < 0)
517 return log_warning_errno(r, "Failed to parse kernel command line: %m");
518
519 if (!arg_enabled)
520 return 0;
521
522 r = add_veritytab_devices();
523 if (r < 0)
524 return r;
525
526 r = determine_devices();
527 if (r < 0)
528 return r;
529
530 r = create_root_device();
531 if (r < 0)
532 return r;
533
534 return create_usr_device();
535 }
536
537 DEFINE_MAIN_GENERATOR_FUNCTION(run);
538