blob: c598a71582f09690f217cfe71078ca08d06d25ab [file] [log] [blame]
Eric Biggers431c67b2018-06-27 15:01:06 -07001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * The 'fsverity setup' command
4 *
Eric Biggers8387ad32018-08-21 12:37:56 -07005 * Copyright (C) 2018 Google LLC
Eric Biggers431c67b2018-06-27 15:01:06 -07006 *
Eric Biggers8387ad32018-08-21 12:37:56 -07007 * Written by Eric Biggers.
Eric Biggers431c67b2018-06-27 15:01:06 -07008 */
9
10#include <fcntl.h>
11#include <getopt.h>
12#include <stdlib.h>
13#include <string.h>
14#include <unistd.h>
15
16#include "commands.h"
Eric Biggers25b59452018-07-27 10:47:02 -070017#include "fsverity_uapi.h"
Eric Biggers431c67b2018-06-27 15:01:06 -070018#include "fsveritysetup.h"
19#include "hash_algs.h"
20
21enum {
22 OPT_HASH,
23 OPT_SALT,
24 OPT_BLOCKSIZE,
25 OPT_SIGNING_KEY,
26 OPT_SIGNING_CERT,
27 OPT_SIGNATURE,
28 OPT_ELIDE,
29 OPT_PATCH,
30};
31
32static const struct option longopts[] = {
33 {"hash", required_argument, NULL, OPT_HASH},
34 {"salt", required_argument, NULL, OPT_SALT},
35 {"blocksize", required_argument, NULL, OPT_BLOCKSIZE},
36 {"signing-key", required_argument, NULL, OPT_SIGNING_KEY},
37 {"signing-cert", required_argument, NULL, OPT_SIGNING_CERT},
38 {"signature", required_argument, NULL, OPT_SIGNATURE},
39 {"elide", required_argument, NULL, OPT_ELIDE},
40 {"patch", required_argument, NULL, OPT_PATCH},
41 {NULL, 0, NULL, 0}
42};
43
44/* Parse the --blocksize=BLOCKSIZE option */
45static bool parse_blocksize_option(const char *opt, int *blocksize_ret)
46{
47 char *end;
48 unsigned long n = strtoul(opt, &end, 10);
49
50 if (n <= 0 || n >= INT32_MAX || *end || !is_power_of_2(n)) {
51 error_msg("Invalid block size: %s. Must be power of 2", opt);
52 return false;
53 }
54 *blocksize_ret = n;
55 return true;
56}
57
58#define FS_VERITY_MAX_LEVELS 64
59
60/*
61 * Calculate the depth of the Merkle tree, then create a map from level to the
62 * block offset at which that level's hash blocks start. Level 'depth - 1' is
63 * the root and is stored first in the file, in the first block following the
64 * original data. Level 0 is the "leaf" level: it's directly "above" the data
Eric Biggers25b59452018-07-27 10:47:02 -070065 * blocks and is stored last in the file.
Eric Biggers431c67b2018-06-27 15:01:06 -070066 */
67static void compute_tree_layout(u64 data_size, u64 tree_offset, int blockbits,
68 unsigned int hashes_per_block,
69 u64 hash_lvl_region_idx[FS_VERITY_MAX_LEVELS],
70 int *depth_ret, u64 *tree_end_ret)
71{
72 u64 blocks = data_size >> blockbits;
73 u64 offset = tree_offset >> blockbits;
74 int depth = 0;
75 int i;
76
77 ASSERT(data_size > 0);
78 ASSERT(data_size % (1 << blockbits) == 0);
79 ASSERT(tree_offset % (1 << blockbits) == 0);
80 ASSERT(hashes_per_block >= 2);
81
82 while (blocks > 1) {
83 ASSERT(depth < FS_VERITY_MAX_LEVELS);
84 blocks = DIV_ROUND_UP(blocks, hashes_per_block);
85 hash_lvl_region_idx[depth++] = blocks;
86 }
87 for (i = depth - 1; i >= 0; i--) {
88 u64 next_count = hash_lvl_region_idx[i];
89
90 hash_lvl_region_idx[i] = offset;
91 offset += next_count;
92 }
93 *depth_ret = depth;
94 *tree_end_ret = offset << blockbits;
95}
96
97/*
98 * Build a Merkle tree (hash tree) over the data of a file.
99 *
100 * @params: Block size, hashes per block, and salt
101 * @hash: Handle for the hash algorithm
102 * @data_file: input data file
103 * @data_size: size of data file in bytes; must be aligned to ->blocksize
104 * @tree_file: output tree file
105 * @tree_offset: byte offset in tree file at which to write the tree;
106 * must be aligned to ->blocksize
107 * @tree_end_ret: On success, the byte offset in the tree file of the end of the
108 * tree is written here
109 * @root_hash_ret: On success, the Merkle tree root hash is written here
110 *
111 * Return: exit status code (0 on success, nonzero on failure)
112 */
113static int build_merkle_tree(const struct fsveritysetup_params *params,
114 struct hash_ctx *hash,
115 struct filedes *data_file, u64 data_size,
116 struct filedes *tree_file, u64 tree_offset,
117 u64 *tree_end_ret, u8 *root_hash_ret)
118{
119 const unsigned int digest_size = hash->alg->digest_size;
120 int depth;
121 u64 hash_lvl_region_idx[FS_VERITY_MAX_LEVELS];
122 u8 *data_to_hash = NULL;
123 u8 *pending_hashes = NULL;
124 unsigned int pending_hash_bytes;
125 u64 nr_hashes_at_this_lvl;
126 int lvl;
127 int status;
128
129 compute_tree_layout(data_size, tree_offset, params->blockbits,
130 params->hashes_per_block, hash_lvl_region_idx,
131 &depth, tree_end_ret);
132
133 /* Allocate block buffers */
134 data_to_hash = xmalloc(params->blocksize);
135 pending_hashes = xmalloc(params->blocksize);
136 pending_hash_bytes = 0;
137 nr_hashes_at_this_lvl = data_size >> params->blockbits;
138
139 /*
140 * Generate each level of the Merkle tree, starting at the leaf level
141 * ('lvl == 0') and ascending to the root node ('lvl == depth - 1').
142 * Then at the end ('lvl == depth'), calculate the root node's hash.
143 */
144 for (lvl = 0; lvl <= depth; lvl++) {
145 u64 i;
146
147 for (i = 0; i < nr_hashes_at_this_lvl; i++) {
148 struct filedes *file;
149 u64 blk_idx;
150
151 hash_init(hash);
152 hash_update(hash, params->salt, params->saltlen);
153
154 if (lvl == 0) {
155 /* Leaf: hashing a data block */
156 file = data_file;
157 blk_idx = i;
158 } else {
159 /* Non-leaf: hashing a hash block */
160 file = tree_file;
161 blk_idx = hash_lvl_region_idx[lvl - 1] + i;
162 }
163 if (!full_pread(file, data_to_hash, params->blocksize,
164 blk_idx << params->blockbits))
165 goto out_err;
166 hash_update(hash, data_to_hash, params->blocksize);
167
168 hash_final(hash, &pending_hashes[pending_hash_bytes]);
169 pending_hash_bytes += digest_size;
170
171 if (lvl == depth) {
172 /* Root hash */
173 ASSERT(nr_hashes_at_this_lvl == 1);
174 ASSERT(pending_hash_bytes == digest_size);
175 memcpy(root_hash_ret, pending_hashes,
176 digest_size);
177 status = 0;
178 goto out;
179 }
180
181 if (pending_hash_bytes + digest_size > params->blocksize
182 || i + 1 == nr_hashes_at_this_lvl) {
183 /* Flush the pending hash block */
184 memset(&pending_hashes[pending_hash_bytes], 0,
185 params->blocksize - pending_hash_bytes);
186 blk_idx = hash_lvl_region_idx[lvl] +
187 (i / params->hashes_per_block);
188 if (!full_pwrite(tree_file,
189 pending_hashes,
190 params->blocksize,
191 blk_idx << params->blockbits))
192 goto out_err;
193 pending_hash_bytes = 0;
194 }
195 }
196
197 nr_hashes_at_this_lvl = DIV_ROUND_UP(nr_hashes_at_this_lvl,
198 params->hashes_per_block);
199 }
200 ASSERT(0); /* unreachable; should exit via "Root hash" case above */
201out_err:
202 status = 1;
203out:
204 free(data_to_hash);
205 free(pending_hashes);
206 return status;
207}
208
209/*
210 * Append to the buffer @*buf_p an extension (variable-length metadata) item of
211 * type @type, containing the data @ext of length @extlen bytes.
212 */
213void fsverity_append_extension(void **buf_p, int type,
214 const void *ext, size_t extlen)
215{
216 void *buf = *buf_p;
217 struct fsverity_extension *hdr = buf;
218
219 hdr->type = cpu_to_le16(type);
220 hdr->length = cpu_to_le32(sizeof(*hdr) + extlen);
221 hdr->reserved = 0;
222 buf += sizeof(*hdr);
223 memcpy(buf, ext, extlen);
224 buf += extlen;
225 memset(buf, 0, -extlen & 7);
226 buf += -extlen & 7;
227 ASSERT(buf - *buf_p == FSVERITY_EXTLEN(extlen));
228 *buf_p = buf;
229}
230
231/*
Eric Biggers25b59452018-07-27 10:47:02 -0700232 * Append the authenticated portion of the fs-verity descriptor to 'out', in the
Eric Biggers431c67b2018-06-27 15:01:06 -0700233 * process updating 'hash' with the data written.
234 */
Eric Biggers25b59452018-07-27 10:47:02 -0700235static int append_fsverity_descriptor(const struct fsveritysetup_params *params,
236 u64 filesize, const u8 *root_hash,
237 struct filedes *out,
238 struct hash_ctx *hash)
Eric Biggers431c67b2018-06-27 15:01:06 -0700239{
Eric Biggers25b59452018-07-27 10:47:02 -0700240 size_t desc_auth_len;
Eric Biggers431c67b2018-06-27 15:01:06 -0700241 void *buf;
Eric Biggers25b59452018-07-27 10:47:02 -0700242 struct fsverity_descriptor *desc;
243 u16 auth_ext_count;
Eric Biggers431c67b2018-06-27 15:01:06 -0700244 int status;
245
Eric Biggers25b59452018-07-27 10:47:02 -0700246 desc_auth_len = sizeof(*desc);
247 desc_auth_len += FSVERITY_EXTLEN(params->hash_alg->digest_size);
Eric Biggers431c67b2018-06-27 15:01:06 -0700248 if (params->saltlen)
Eric Biggers25b59452018-07-27 10:47:02 -0700249 desc_auth_len += FSVERITY_EXTLEN(params->saltlen);
250 desc_auth_len += total_elide_patch_ext_length(params);
251 desc = buf = xzalloc(desc_auth_len);
Eric Biggers431c67b2018-06-27 15:01:06 -0700252
Eric Biggers25b59452018-07-27 10:47:02 -0700253 memcpy(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic));
254 desc->major_version = 1;
255 desc->minor_version = 0;
256 desc->log_data_blocksize = params->blockbits;
257 desc->log_tree_blocksize = params->blockbits;
258 desc->data_algorithm = cpu_to_le16(params->hash_alg -
259 fsverity_hash_algs);
260 desc->tree_algorithm = desc->data_algorithm;
261 desc->orig_file_size = cpu_to_le64(filesize);
Eric Biggers431c67b2018-06-27 15:01:06 -0700262
Eric Biggers25b59452018-07-27 10:47:02 -0700263 auth_ext_count = 1; /* root hash */
Eric Biggers431c67b2018-06-27 15:01:06 -0700264 if (params->saltlen)
Eric Biggers25b59452018-07-27 10:47:02 -0700265 auth_ext_count++;
266 auth_ext_count += params->num_elisions_and_patches;
267 desc->auth_ext_count = cpu_to_le16(auth_ext_count);
Eric Biggers431c67b2018-06-27 15:01:06 -0700268
Eric Biggers25b59452018-07-27 10:47:02 -0700269 buf += sizeof(*desc);
270 fsverity_append_extension(&buf, FS_VERITY_EXT_ROOT_HASH,
271 root_hash, params->hash_alg->digest_size);
Eric Biggers431c67b2018-06-27 15:01:06 -0700272 if (params->saltlen)
273 fsverity_append_extension(&buf, FS_VERITY_EXT_SALT,
274 params->salt, params->saltlen);
275 append_elide_patch_exts(&buf, params);
Eric Biggers25b59452018-07-27 10:47:02 -0700276 ASSERT(buf - (void *)desc == desc_auth_len);
Eric Biggers431c67b2018-06-27 15:01:06 -0700277
Eric Biggers25b59452018-07-27 10:47:02 -0700278 hash_update(hash, desc, desc_auth_len);
279 if (!full_write(out, desc, desc_auth_len))
Eric Biggers431c67b2018-06-27 15:01:06 -0700280 goto out_err;
281 status = 0;
282out:
Eric Biggers25b59452018-07-27 10:47:02 -0700283 free(desc);
Eric Biggers431c67b2018-06-27 15:01:06 -0700284 return status;
285
286out_err:
287 status = 1;
288 goto out;
289}
290
Eric Biggers25b59452018-07-27 10:47:02 -0700291/*
292 * Append any needed unauthenticated extension items: currently, just possibly a
293 * PKCS7_SIGNATURE item containing the signed file measurement.
294 */
295static int
296append_unauthenticated_extensions(struct filedes *out,
297 const struct fsveritysetup_params *params,
298 const u8 *measurement)
Eric Biggers431c67b2018-06-27 15:01:06 -0700299{
Eric Biggers25b59452018-07-27 10:47:02 -0700300 u16 unauth_ext_count = 0;
301 struct {
302 __le16 unauth_ext_count;
303 __le16 pad[3];
304 } hdr;
305 bool have_sig = params->signing_key_file || params->signature_file;
Eric Biggers431c67b2018-06-27 15:01:06 -0700306
Eric Biggers25b59452018-07-27 10:47:02 -0700307 if (have_sig)
308 unauth_ext_count++;
309
310 ASSERT(sizeof(hdr) % 8 == 0);
311 memset(&hdr, 0, sizeof(hdr));
312 hdr.unauth_ext_count = cpu_to_le16(unauth_ext_count);
313
314 if (!full_write(out, &hdr, sizeof(hdr)))
315 return 1;
316
317 if (have_sig)
318 return append_signed_measurement(out, params, measurement);
319
320 return 0;
321}
322
323static int append_footer(struct filedes *out, u64 desc_offset)
324{
325 struct fsverity_footer ftr;
326 u32 offset = (out->pos + sizeof(ftr)) - desc_offset;
327
328 ftr.desc_reverse_offset = cpu_to_le32(offset);
329 memcpy(ftr.magic, FS_VERITY_MAGIC, sizeof(ftr.magic));
330
331 if (!full_write(out, &ftr, sizeof(ftr)))
Eric Biggers431c67b2018-06-27 15:01:06 -0700332 return 1;
333 return 0;
334}
335
336static int fsveritysetup(const char *infile, const char *outfile,
337 const struct fsveritysetup_params *params)
338{
339 struct filedes _in = { .fd = -1 };
340 struct filedes _out = { .fd = -1 };
341 struct filedes _tmp = { .fd = -1 };
342 struct hash_ctx *hash = NULL;
Eric Biggers00c0ca72018-06-28 18:10:43 -0700343 struct filedes *in = &_in, *out = &_out, *src;
Eric Biggers431c67b2018-06-27 15:01:06 -0700344 u64 filesize;
345 u64 aligned_filesize;
346 u64 src_filesize;
347 u64 tree_end_offset;
348 u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE];
349 u8 measurement[FS_VERITY_MAX_DIGEST_SIZE];
350 char hash_hex[FS_VERITY_MAX_DIGEST_SIZE * 2 + 1];
351 int status;
352
353 if (!open_file(in, infile, (infile == outfile ? O_RDWR : O_RDONLY), 0))
354 goto out_err;
355
356 if (!get_file_size(in, &filesize))
357 goto out_err;
358
359 if (filesize <= 0) {
360 error_msg("input file is empty: '%s'", infile);
361 goto out_err;
362 }
363
364 if (infile == outfile) {
365 /*
366 * Invoked with one file argument: we're appending verity
367 * metadata to an existing file.
368 */
369 out = in;
370 if (!filedes_seek(out, filesize, SEEK_SET))
371 goto out_err;
372 } else {
373 /*
374 * Invoked with two file arguments: we're copying the first file
375 * to the second file, then appending verity metadata to it.
376 */
Eric Biggers431c67b2018-06-27 15:01:06 -0700377 if (!open_file(out, outfile, O_RDWR|O_CREAT|O_TRUNC, 0644))
378 goto out_err;
379 if (!copy_file_data(in, out, filesize))
380 goto out_err;
381 }
382
383 /* Zero-pad the output file to the next block boundary */
384 aligned_filesize = ALIGN(filesize, params->blocksize);
385 if (!write_zeroes(out, aligned_filesize - filesize))
386 goto out_err;
387
388 if (params->num_elisions_and_patches) {
389 /* Merkle tree is built over temporary elided/patched file */
390 src = &_tmp;
391 if (!apply_elisions_and_patches(params, in, filesize,
392 src, &src_filesize))
393 goto out_err;
394 } else {
395 /* Merkle tree is built over original file */
396 src = out;
397 src_filesize = aligned_filesize;
398 }
399
400 hash = hash_create(params->hash_alg);
401
402 /* Build the file's Merkle tree and calculate its root hash */
403 status = build_merkle_tree(params, hash, src, src_filesize,
404 out, aligned_filesize,
405 &tree_end_offset, root_hash);
406 if (status)
407 goto out;
Eric Biggers431c67b2018-06-27 15:01:06 -0700408 if (!filedes_seek(out, tree_end_offset, SEEK_SET))
409 goto out_err;
Eric Biggers25b59452018-07-27 10:47:02 -0700410
411 /* Append the additional needed metadata */
412
Eric Biggers431c67b2018-06-27 15:01:06 -0700413 hash_init(hash);
Eric Biggers25b59452018-07-27 10:47:02 -0700414 status = append_fsverity_descriptor(params, filesize, root_hash,
415 out, hash);
Eric Biggers431c67b2018-06-27 15:01:06 -0700416 if (status)
417 goto out;
Eric Biggers431c67b2018-06-27 15:01:06 -0700418 hash_final(hash, measurement);
419
Eric Biggers25b59452018-07-27 10:47:02 -0700420 status = append_unauthenticated_extensions(out, params, measurement);
Eric Biggers431c67b2018-06-27 15:01:06 -0700421 if (status)
422 goto out;
423
Eric Biggers25b59452018-07-27 10:47:02 -0700424 status = append_footer(out, tree_end_offset);
Eric Biggers431c67b2018-06-27 15:01:06 -0700425 if (status)
426 goto out;
427
Eric Biggers431c67b2018-06-27 15:01:06 -0700428 bin2hex(measurement, params->hash_alg->digest_size, hash_hex);
Eric Biggers25b59452018-07-27 10:47:02 -0700429 printf("File measurement: %s:%s\n", params->hash_alg->name, hash_hex);
Eric Biggers431c67b2018-06-27 15:01:06 -0700430 status = 0;
431out:
432 hash_free(hash);
Eric Biggers00c0ca72018-06-28 18:10:43 -0700433 if (status != 0 && out->fd >= 0) {
434 /* Error occurred; undo what we wrote */
435 if (in == out)
436 (void)ftruncate(out->fd, filesize);
437 else
438 out->autodelete = true;
439 }
Eric Biggers431c67b2018-06-27 15:01:06 -0700440 filedes_close(&_in);
441 filedes_close(&_tmp);
442 if (!filedes_close(&_out) && status == 0)
443 status = 1;
444 return status;
445
446out_err:
447 status = 1;
448 goto out;
449}
450
451int fsverity_cmd_setup(const struct fsverity_command *cmd,
452 int argc, char *argv[])
453{
454 struct fsveritysetup_params params = {
455 .hash_alg = DEFAULT_HASH_ALG,
456 };
457 STRING_LIST(elide_opts);
458 STRING_LIST(patch_opts);
459 int c;
460 int status;
461
462 while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
463 switch (c) {
464 case OPT_HASH:
Eric Biggers25b59452018-07-27 10:47:02 -0700465 params.hash_alg = find_hash_alg_by_name(optarg);
Eric Biggers431c67b2018-06-27 15:01:06 -0700466 if (!params.hash_alg)
467 goto out_usage;
468 break;
469 case OPT_SALT:
470 if (params.salt) {
471 error_msg("--salt can only be specified once");
472 goto out_usage;
473 }
474 params.saltlen = strlen(optarg) / 2;
475 params.salt = xmalloc(params.saltlen);
476 if (!hex2bin(optarg, params.salt, params.saltlen)) {
477 error_msg("salt is not a valid hex string");
478 goto out_usage;
479 }
480 break;
481 case OPT_BLOCKSIZE:
482 if (!parse_blocksize_option(optarg, &params.blocksize))
483 goto out_usage;
484 break;
485 case OPT_SIGNING_KEY:
486 params.signing_key_file = optarg;
487 break;
488 case OPT_SIGNING_CERT:
489 params.signing_cert_file = optarg;
490 break;
491 case OPT_SIGNATURE:
492 params.signature_file = optarg;
493 break;
494 case OPT_ELIDE:
495 string_list_append(&elide_opts, optarg);
496 break;
497 case OPT_PATCH:
498 string_list_append(&patch_opts, optarg);
499 break;
500 default:
501 goto out_usage;
502 }
503 }
504
505 argv += optind;
506 argc -= optind;
507
508 if (argc != 1 && argc != 2)
509 goto out_usage;
510
511 ASSERT(params.hash_alg->digest_size <= FS_VERITY_MAX_DIGEST_SIZE);
512
513 if (params.blocksize == 0) {
514 params.blocksize = sysconf(_SC_PAGESIZE);
515 if (params.blocksize <= 0 || !is_power_of_2(params.blocksize)) {
516 fprintf(stderr,
517 "Warning: invalid _SC_PAGESIZE (%d). Assuming 4K blocks.\n",
518 params.blocksize);
519 params.blocksize = 4096;
520 }
521 }
522 params.blockbits = ilog2(params.blocksize);
523
524 params.hashes_per_block = params.blocksize /
525 params.hash_alg->digest_size;
526 if (params.hashes_per_block < 2) {
527 error_msg("block size of %d bytes is too small for %s hash",
528 params.blocksize, params.hash_alg->name);
529 goto out_err;
530 }
531
532 if (params.signing_cert_file && !params.signing_key_file) {
533 error_msg("--signing-cert was given, but --signing-key was not.\n"
534" You must provide the certificate's private key file using --signing-key.");
535 goto out_err;
536 }
537
538 if ((params.signing_key_file || params.signature_file) &&
539 !params.hash_alg->cryptographic) {
540 error_msg("Signing a file using '%s' checksums does not make sense\n"
541 " because '%s' is not a cryptographically secure hash algorithm.",
542 params.hash_alg->name, params.hash_alg->name);
543 goto out_err;
544 }
545
546 if (!load_elisions_and_patches(&elide_opts, &patch_opts, &params))
547 goto out_err;
548
549 status = fsveritysetup(argv[0], argv[argc - 1], &params);
550out:
551 free(params.salt);
552 free_elisions_and_patches(&params);
553 string_list_destroy(&elide_opts);
554 string_list_destroy(&patch_opts);
555 return status;
556
557out_err:
558 status = 1;
559 goto out;
560
561out_usage:
562 usage(cmd, stderr);
563 status = 2;
564 goto out;
565}