]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2f3dfc6f | 2 | |
dccca82b | 3 | #include <errno.h> |
2f3dfc6f LP |
4 | #include <stdio.h> |
5 | #include <sys/stat.h> | |
6 | ||
dccca82b | 7 | #include "alloc-util.h" |
1e2f3230 | 8 | #include "cryptsetup-util.h" |
035e8e50 | 9 | #include "fileio.h" |
21c60c76 | 10 | #include "fstab-util.h" |
2f3dfc6f | 11 | #include "hexdecoct.h" |
dccca82b | 12 | #include "log.h" |
6b9306b2 | 13 | #include "main-func.h" |
14de7ef9 | 14 | #include "parse-util.h" |
035e8e50 | 15 | #include "path-util.h" |
294bf0c3 | 16 | #include "pretty-print.h" |
542bb9be | 17 | #include "process-util.h" |
2f3dfc6f | 18 | #include "string-util.h" |
37ec0fdd | 19 | #include "terminal-util.h" |
2f3dfc6f | 20 | |
0bbf7a84 GP |
21 | static char *arg_hash = NULL; |
22 | static bool arg_superblock = true; | |
23 | static int arg_format = 1; | |
24 | static uint64_t arg_data_block_size = 4096; | |
25 | static uint64_t arg_hash_block_size = 4096; | |
26 | static uint64_t arg_data_blocks = 0; | |
14de7ef9 | 27 | static uint64_t arg_hash_offset = 0; |
0bbf7a84 GP |
28 | static void *arg_salt = NULL; |
29 | static uint64_t arg_salt_size = 32; | |
30 | static char *arg_uuid = NULL; | |
cb0198a1 | 31 | static uint32_t arg_activate_flags = CRYPT_ACTIVATE_READONLY; |
21c60c76 GP |
32 | static char *arg_fec_what = NULL; |
33 | static uint64_t arg_fec_offset = 0; | |
34 | static uint64_t arg_fec_roots = 2; | |
bd4dea76 LP |
35 | static char *arg_root_hash_signature = NULL; |
36 | ||
0bbf7a84 GP |
37 | STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); |
38 | STATIC_DESTRUCTOR_REGISTER(arg_salt, freep); | |
39 | STATIC_DESTRUCTOR_REGISTER(arg_uuid, freep); | |
21c60c76 | 40 | STATIC_DESTRUCTOR_REGISTER(arg_fec_what, freep); |
bd4dea76 | 41 | STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep); |
6b9306b2 | 42 | |
2f3dfc6f | 43 | static int help(void) { |
37ec0fdd LP |
44 | _cleanup_free_ char *link = NULL; |
45 | int r; | |
46 | ||
47 | r = terminal_urlify_man("systemd-veritysetup@.service", "8", &link); | |
48 | if (r < 0) | |
49 | return log_oom(); | |
50 | ||
cb0198a1 | 51 | printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH [OPTIONS]\n" |
2f3dfc6f | 52 | "%s detach VOLUME\n\n" |
9a2a6ec4 | 53 | "Attach or detach a verity protected block device.\n" |
bc556335 DDM |
54 | "\nSee the %s for details.\n", |
55 | program_invocation_short_name, | |
56 | program_invocation_short_name, | |
57 | link); | |
2f3dfc6f LP |
58 | |
59 | return 0; | |
60 | } | |
61 | ||
2fe8749d | 62 | static int save_roothashsig_option(const char *option, bool strict) { |
bd4dea76 | 63 | int r; |
cb0198a1 | 64 | |
2fe8749d ZJS |
65 | if (path_is_absolute(option) || startswith(option, "base64:")) { |
66 | if (!HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY) | |
67 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
68 | "Activation of verity device with signature requested, but cryptsetup does not support crypt_activate_by_signed_key()."); | |
cb0198a1 | 69 | |
bd4dea76 LP |
70 | r = free_and_strdup_warn(&arg_root_hash_signature, option); |
71 | if (r < 0) | |
72 | return r; | |
73 | ||
2fe8749d | 74 | return true; |
cb0198a1 GP |
75 | } |
76 | ||
2fe8749d ZJS |
77 | if (!strict) |
78 | return false; | |
79 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
80 | "root-hash-signature= expects either full path to signature file or " | |
81 | "base64 string encoding signature prefixed by base64:."); | |
cb0198a1 GP |
82 | } |
83 | ||
0bbf7a84 GP |
84 | static int parse_block_size(const char *t, uint64_t *size) { |
85 | uint64_t u; | |
86 | int r; | |
87 | ||
88 | r = parse_size(t, 1024, &u); | |
89 | if (r < 0) | |
90 | return r; | |
91 | ||
92 | if (u < 512 || u > (512 * 1024)) | |
93 | return -ERANGE; | |
94 | ||
95 | if ((u % 512) != 0 || !ISPOWEROF2(u)) | |
96 | return -EINVAL; | |
97 | ||
98 | *size = u; | |
99 | ||
100 | return 0; | |
101 | } | |
102 | ||
cb0198a1 GP |
103 | static int parse_options(const char *options) { |
104 | int r; | |
105 | ||
106 | /* backward compatibility with the obsolete ROOTHASHSIG positional argument */ | |
bd4dea76 | 107 | r = save_roothashsig_option(options, /* strict= */ false); |
cb0198a1 GP |
108 | if (r < 0) |
109 | return r; | |
bd4dea76 | 110 | if (r > 0) { |
cb0198a1 GP |
111 | log_warning("Usage of ROOTHASHSIG positional argument is deprecated. " |
112 | "Please use the option root-hash-signature=%s instead.", options); | |
113 | return 0; | |
114 | } | |
115 | ||
116 | for (;;) { | |
117 | _cleanup_free_ char *word = NULL; | |
118 | char *val; | |
119 | ||
120 | r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS); | |
121 | if (r < 0) | |
122 | return log_error_errno(r, "Failed to parse options: %m"); | |
123 | if (r == 0) | |
124 | break; | |
125 | ||
08b04ec7 GP |
126 | if (STR_IN_SET(word, "noauto", "auto", "nofail", "fail", "_netdev")) |
127 | continue; | |
128 | ||
cb0198a1 GP |
129 | if (isempty(word)) |
130 | continue; | |
131 | else if (streq(word, "ignore-corruption")) | |
132 | arg_activate_flags |= CRYPT_ACTIVATE_IGNORE_CORRUPTION; | |
133 | else if (streq(word, "restart-on-corruption")) | |
134 | arg_activate_flags |= CRYPT_ACTIVATE_RESTART_ON_CORRUPTION; | |
135 | else if (streq(word, "ignore-zero-blocks")) | |
136 | arg_activate_flags |= CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS; | |
137 | #ifdef CRYPT_ACTIVATE_CHECK_AT_MOST_ONCE | |
138 | else if (streq(word, "check-at-most-once")) | |
139 | arg_activate_flags |= CRYPT_ACTIVATE_CHECK_AT_MOST_ONCE; | |
140 | #endif | |
141 | #ifdef CRYPT_ACTIVATE_PANIC_ON_CORRUPTION | |
142 | else if (streq(word, "panic-on-corruption")) | |
143 | arg_activate_flags |= CRYPT_ACTIVATE_PANIC_ON_CORRUPTION; | |
144 | #endif | |
0bbf7a84 GP |
145 | else if ((val = startswith(word, "superblock="))) { |
146 | ||
147 | r = parse_boolean(val); | |
148 | if (r < 0) | |
149 | return log_error_errno(r, "Failed to parse boolean '%s': %m", word); | |
150 | ||
151 | arg_superblock = r; | |
152 | } else if ((val = startswith(word, "format="))) { | |
153 | ||
154 | if (!STR_IN_SET(val, "0", "1")) | |
155 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "format= expects either 0 (original Chrome OS version) or " | |
156 | "1 (modern version)."); | |
157 | ||
158 | arg_format = val[0] - '0'; | |
159 | } else if ((val = startswith(word, "data-block-size="))) { | |
160 | uint64_t sz; | |
161 | ||
162 | r = parse_block_size(val, &sz); | |
163 | if (r < 0) | |
164 | return log_error_errno(r, "Failed to parse size '%s': %m", word); | |
165 | ||
166 | arg_data_block_size = sz; | |
167 | } else if ((val = startswith(word, "hash-block-size="))) { | |
168 | uint64_t sz; | |
169 | ||
170 | r = parse_block_size(val, &sz); | |
171 | if (r < 0) | |
172 | return log_error_errno(r, "Failed to parse size '%s': %m", word); | |
173 | ||
174 | arg_hash_block_size = sz; | |
175 | } else if ((val = startswith(word, "data-blocks="))) { | |
176 | uint64_t u; | |
177 | ||
178 | r = safe_atou64(val, &u); | |
179 | if (r < 0) | |
180 | return log_error_errno(r, "Failed to parse number '%s': %m", word); | |
181 | ||
182 | arg_data_blocks = u; | |
183 | } else if ((val = startswith(word, "hash-offset="))) { | |
14de7ef9 GP |
184 | uint64_t off; |
185 | ||
186 | r = parse_size(val, 1024, &off); | |
187 | if (r < 0) | |
188 | return log_error_errno(r, "Failed to parse offset '%s': %m", word); | |
189 | if (off % 512 != 0) | |
190 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "hash-offset= expects a 512-byte aligned value."); | |
191 | ||
192 | arg_hash_offset = off; | |
0bbf7a84 GP |
193 | } else if ((val = startswith(word, "salt="))) { |
194 | ||
195 | if (!string_is_safe(val)) | |
196 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "salt= is not valid."); | |
197 | ||
198 | if (isempty(val)) { | |
199 | arg_salt = mfree(arg_salt); | |
200 | arg_salt_size = 32; | |
201 | } else if (streq(val, "-")) { | |
202 | arg_salt = mfree(arg_salt); | |
203 | arg_salt_size = 0; | |
204 | } else { | |
205 | size_t l; | |
206 | void *m; | |
207 | ||
bdd2036e | 208 | r = unhexmem(val, &m, &l); |
0bbf7a84 GP |
209 | if (r < 0) |
210 | return log_error_errno(r, "Failed to parse salt '%s': %m", word); | |
211 | ||
212 | free_and_replace(arg_salt, m); | |
213 | arg_salt_size = l; | |
214 | } | |
215 | } else if ((val = startswith(word, "uuid="))) { | |
216 | ||
217 | r = sd_id128_from_string(val, NULL); | |
218 | if (r < 0) | |
219 | return log_error_errno(r, "Failed to parse UUID '%s': %m", word); | |
220 | ||
221 | r = free_and_strdup(&arg_uuid, val); | |
222 | if (r < 0) | |
223 | return log_oom(); | |
224 | } else if ((val = startswith(word, "hash="))) { | |
225 | ||
226 | r = free_and_strdup(&arg_hash, val); | |
227 | if (r < 0) | |
228 | return log_oom(); | |
21c60c76 GP |
229 | } else if ((val = startswith(word, "fec-device="))) { |
230 | _cleanup_free_ char *what = NULL; | |
231 | ||
232 | what = fstab_node_to_udev_node(val); | |
233 | if (!what) | |
234 | return log_oom(); | |
235 | ||
236 | if (!path_is_absolute(what)) | |
237 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an absolute path."); | |
238 | ||
239 | if (!path_is_normalized(what)) | |
240 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an normalized path."); | |
241 | ||
242 | r = free_and_strdup(&arg_fec_what, what); | |
243 | if (r < 0) | |
244 | return log_oom(); | |
245 | } else if ((val = startswith(word, "fec-offset="))) { | |
246 | uint64_t off; | |
247 | ||
248 | r = parse_size(val, 1024, &off); | |
249 | if (r < 0) | |
250 | return log_error_errno(r, "Failed to parse offset '%s': %m", word); | |
251 | if (off % 512 != 0) | |
252 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-offset= expects a 512-byte aligned value."); | |
253 | ||
254 | arg_fec_offset = off; | |
255 | } else if ((val = startswith(word, "fec-roots="))) { | |
256 | uint64_t u; | |
257 | ||
258 | r = safe_atou64(val, &u); | |
259 | if (r < 0) | |
260 | return log_error_errno(r, "Failed to parse number '%s', ignoring: %m", word); | |
261 | if (u < 2 || u > 24) | |
262 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-rootfs= expects a value between 2 and 24 (including)."); | |
263 | ||
264 | arg_fec_roots = u; | |
14de7ef9 | 265 | } else if ((val = startswith(word, "root-hash-signature="))) { |
bd4dea76 | 266 | r = save_roothashsig_option(val, /* strict= */ true); |
cb0198a1 GP |
267 | if (r < 0) |
268 | return r; | |
cb0198a1 | 269 | |
cb0198a1 GP |
270 | } else |
271 | log_warning("Encountered unknown option '%s', ignoring.", word); | |
272 | } | |
273 | ||
274 | return r; | |
275 | } | |
276 | ||
6b9306b2 | 277 | static int run(int argc, char *argv[]) { |
294bd454 | 278 | _cleanup_(crypt_freep) struct crypt_device *cd = NULL; |
49a4a81d | 279 | const char *verb; |
2f3dfc6f LP |
280 | int r; |
281 | ||
542bb9be | 282 | if (argv_looks_like_help(argc, argv)) |
6b9306b2 | 283 | return help(); |
2f3dfc6f | 284 | |
6b9306b2 YW |
285 | if (argc < 3) |
286 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires at least two arguments."); | |
2f3dfc6f | 287 | |
d2acb93d | 288 | log_setup(); |
2f3dfc6f | 289 | |
2f678640 LP |
290 | cryptsetup_enable_logging(NULL); |
291 | ||
2f3dfc6f LP |
292 | umask(0022); |
293 | ||
49a4a81d LP |
294 | verb = argv[1]; |
295 | ||
296 | if (streq(verb, "attach")) { | |
297 | const char *volume, *data_device, *verity_device, *root_hash, *options; | |
2f3dfc6f | 298 | _cleanup_free_ void *m = NULL; |
14de7ef9 | 299 | struct crypt_params_verity p = {}; |
2f3dfc6f LP |
300 | crypt_status_info status; |
301 | size_t l; | |
302 | ||
6b9306b2 | 303 | if (argc < 6) |
f3b3cab2 | 304 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least four arguments."); |
2f3dfc6f | 305 | |
49a4a81d LP |
306 | volume = argv[2]; |
307 | data_device = argv[3]; | |
308 | verity_device = argv[4]; | |
309 | root_hash = argv[5]; | |
bb3ff7a9 | 310 | options = mangle_none(argc > 6 ? argv[6] : NULL); |
49a4a81d | 311 | |
9959d782 LP |
312 | if (!filename_is_valid(volume)) |
313 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); | |
314 | ||
bdd2036e | 315 | r = unhexmem(root_hash, &m, &l); |
6b9306b2 YW |
316 | if (r < 0) |
317 | return log_error_errno(r, "Failed to parse root hash: %m"); | |
2f3dfc6f | 318 | |
49a4a81d | 319 | r = crypt_init(&cd, verity_device); |
6b9306b2 | 320 | if (r < 0) |
49a4a81d | 321 | return log_error_errno(r, "Failed to open verity device %s: %m", verity_device); |
2f3dfc6f | 322 | |
efc3b12f | 323 | cryptsetup_enable_logging(cd); |
2f3dfc6f | 324 | |
49a4a81d | 325 | status = crypt_status(cd, volume); |
3742095b | 326 | if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { |
49a4a81d | 327 | log_info("Volume %s already active.", volume); |
6b9306b2 | 328 | return 0; |
2f3dfc6f LP |
329 | } |
330 | ||
bb3ff7a9 | 331 | if (options) { |
49a4a81d | 332 | r = parse_options(options); |
cb0198a1 GP |
333 | if (r < 0) |
334 | return log_error_errno(r, "Failed to parse options: %m"); | |
335 | } | |
336 | ||
0bbf7a84 GP |
337 | if (arg_superblock) { |
338 | p = (struct crypt_params_verity) { | |
21c60c76 | 339 | .fec_device = arg_fec_what, |
0bbf7a84 | 340 | .hash_area_offset = arg_hash_offset, |
21c60c76 GP |
341 | .fec_area_offset = arg_fec_offset, |
342 | .fec_roots = arg_fec_roots, | |
0bbf7a84 GP |
343 | }; |
344 | ||
345 | r = crypt_load(cd, CRYPT_VERITY, &p); | |
346 | if (r < 0) | |
347 | return log_error_errno(r, "Failed to load verity superblock: %m"); | |
348 | } else { | |
349 | p = (struct crypt_params_verity) { | |
350 | .hash_name = arg_hash, | |
351 | .data_device = data_device, | |
21c60c76 | 352 | .fec_device = arg_fec_what, |
0bbf7a84 GP |
353 | .salt = arg_salt, |
354 | .salt_size = arg_salt_size, | |
355 | .hash_type = arg_format, | |
356 | .data_block_size = arg_data_block_size, | |
357 | .hash_block_size = arg_hash_block_size, | |
358 | .data_size = arg_data_blocks, | |
359 | .hash_area_offset = arg_hash_offset, | |
21c60c76 GP |
360 | .fec_area_offset = arg_fec_offset, |
361 | .fec_roots = arg_fec_roots, | |
0bbf7a84 GP |
362 | .flags = CRYPT_VERITY_NO_HEADER, |
363 | }; | |
364 | ||
365 | r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); | |
366 | if (r < 0) | |
367 | return log_error_errno(r, "Failed to format verity superblock: %m"); | |
368 | } | |
2f3dfc6f | 369 | |
49a4a81d | 370 | r = crypt_set_data_device(cd, data_device); |
6b9306b2 YW |
371 | if (r < 0) |
372 | return log_error_errno(r, "Failed to configure data device: %m"); | |
2f3dfc6f | 373 | |
2fe8749d | 374 | if (arg_root_hash_signature) { |
035e8e50 LB |
375 | #if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY |
376 | _cleanup_free_ char *hash_sig = NULL; | |
377 | size_t hash_sig_size; | |
378 | char *value; | |
379 | ||
cb0198a1 | 380 | if ((value = startswith(arg_root_hash_signature, "base64:"))) { |
bdd2036e | 381 | r = unbase64mem(value, (void*) &hash_sig, &hash_sig_size); |
035e8e50 | 382 | if (r < 0) |
cb0198a1 | 383 | return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature); |
035e8e50 | 384 | } else { |
986311c2 | 385 | r = read_full_file_full( |
cb0198a1 | 386 | AT_FDCWD, arg_root_hash_signature, UINT64_MAX, SIZE_MAX, |
986311c2 LP |
387 | READ_FULL_FILE_CONNECT_SOCKET, |
388 | NULL, | |
389 | &hash_sig, &hash_sig_size); | |
035e8e50 LB |
390 | if (r < 0) |
391 | return log_error_errno(r, "Failed to read root hash signature: %m"); | |
392 | } | |
393 | ||
49a4a81d | 394 | r = crypt_activate_by_signed_key(cd, volume, m, l, hash_sig, hash_sig_size, arg_activate_flags); |
035e8e50 | 395 | #else |
2fe8749d | 396 | assert_not_reached(); |
035e8e50 LB |
397 | #endif |
398 | } else | |
49a4a81d | 399 | r = crypt_activate_by_volume_key(cd, volume, m, l, arg_activate_flags); |
6b9306b2 | 400 | if (r < 0) |
ffe0da29 | 401 | return log_error_errno(r, "Failed to set up verity device '%s': %m", volume); |
2f3dfc6f | 402 | |
49a4a81d LP |
403 | } else if (streq(verb, "detach")) { |
404 | const char *volume; | |
405 | ||
406 | volume = argv[2]; | |
2f3dfc6f | 407 | |
9959d782 LP |
408 | if (!filename_is_valid(volume)) |
409 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); | |
410 | ||
49a4a81d | 411 | r = crypt_init_by_name(&cd, volume); |
2f3dfc6f | 412 | if (r == -ENODEV) { |
ffe0da29 | 413 | log_info("Volume %s 'already' inactive.", volume); |
6b9306b2 | 414 | return 0; |
2f3dfc6f | 415 | } |
6b9306b2 | 416 | if (r < 0) |
ffe0da29 | 417 | return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); |
2f3dfc6f | 418 | |
efc3b12f | 419 | cryptsetup_enable_logging(cd); |
2f3dfc6f | 420 | |
49a4a81d | 421 | r = crypt_deactivate(cd, volume); |
6b9306b2 | 422 | if (r < 0) |
ffe0da29 | 423 | return log_error_errno(r, "Failed to deactivate volume '%s': %m", volume); |
2f3dfc6f | 424 | |
6b9306b2 | 425 | } else |
49a4a81d | 426 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb); |
2f3dfc6f | 427 | |
6b9306b2 | 428 | return 0; |
2f3dfc6f | 429 | } |
6b9306b2 YW |
430 | |
431 | DEFINE_MAIN_FUNCTION(run); |