1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * The 'fsverity setup' command 4 * 5 * Copyright (C) 2018 Google LLC 6 * 7 * Written by Eric Biggers. 8 */ 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" 17 #include "fsverity_uapi.h" 18 #include "fsveritysetup.h" 19 #include "hash_algs.h" 20 21 enum { 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 32 static 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 */ 45 static 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 65 * blocks and is stored last in the file. 66 */ 67 static 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 */ 113 static 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 */ 201 out_err: 202 status = 1; 203 out: 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 */ 213 void 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 /* 232 * Append the authenticated portion of the fs-verity descriptor to 'out', in the 233 * process updating 'hash' with the data written. 234 */ 235 static 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) 239 { 240 size_t desc_auth_len; 241 void *buf; 242 struct fsverity_descriptor *desc; 243 u16 auth_ext_count; 244 int status; 245 246 desc_auth_len = sizeof(*desc); 247 desc_auth_len += FSVERITY_EXTLEN(params->hash_alg->digest_size); 248 if (params->saltlen) 249 desc_auth_len += FSVERITY_EXTLEN(params->saltlen); 250 desc_auth_len += total_elide_patch_ext_length(params); 251 desc = buf = xzalloc(desc_auth_len); 252 253 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); 262 263 auth_ext_count = 1; /* root hash */ 264 if (params->saltlen) 265 auth_ext_count++; 266 auth_ext_count += params->num_elisions_and_patches; 267 desc->auth_ext_count = cpu_to_le16(auth_ext_count); 268 269 buf += sizeof(*desc); 270 fsverity_append_extension(&buf, FS_VERITY_EXT_ROOT_HASH, 271 root_hash, params->hash_alg->digest_size); 272 if (params->saltlen) 273 fsverity_append_extension(&buf, FS_VERITY_EXT_SALT, 274 params->salt, params->saltlen); 275 append_elide_patch_exts(&buf, params); 276 ASSERT(buf - (void *)desc == desc_auth_len); 277 278 hash_update(hash, desc, desc_auth_len); 279 if (!full_write(out, desc, desc_auth_len)) 280 goto out_err; 281 status = 0; 282 out: 283 free(desc); 284 return status; 285 286 out_err: 287 status = 1; 288 goto out; 289 } 290 291 /* 292 * Append any needed unauthenticated extension items: currently, just possibly a 293 * PKCS7_SIGNATURE item containing the signed file measurement. 294 */ 295 static int 296 append_unauthenticated_extensions(struct filedes *out, 297 const struct fsveritysetup_params *params, 298 const u8 *measurement) 299 { 300 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; 306 307 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 323 static 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))) 332 return 1; 333 return 0; 334 } 335 336 static 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; 343 struct filedes *in = &_in, *out = &_out, *src; 344 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 */ 377 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; 408 if (!filedes_seek(out, tree_end_offset, SEEK_SET)) 409 goto out_err; 410 411 /* Append the additional needed metadata */ 412 413 hash_init(hash); 414 status = append_fsverity_descriptor(params, filesize, root_hash, 415 out, hash); 416 if (status) 417 goto out; 418 hash_final(hash, measurement); 419 420 status = append_unauthenticated_extensions(out, params, measurement); 421 if (status) 422 goto out; 423 424 status = append_footer(out, tree_end_offset); 425 if (status) 426 goto out; 427 428 bin2hex(measurement, params->hash_alg->digest_size, hash_hex); 429 printf("File measurement: %s:%s\n", params->hash_alg->name, hash_hex); 430 status = 0; 431 out: 432 hash_free(hash); 433 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 } 440 filedes_close(&_in); 441 filedes_close(&_tmp); 442 if (!filedes_close(&_out) && status == 0) 443 status = 1; 444 return status; 445 446 out_err: 447 status = 1; 448 goto out; 449 } 450 451 int 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: 465 params.hash_alg = find_hash_alg_by_name(optarg); 466 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, ¶ms.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, ¶ms)) 547 goto out_err; 548 549 status = fsveritysetup(argv[0], argv[argc - 1], ¶ms); 550 out: 551 free(params.salt); 552 free_elisions_and_patches(¶ms); 553 string_list_destroy(&elide_opts); 554 string_list_destroy(&patch_opts); 555 return status; 556 557 out_err: 558 status = 1; 559 goto out; 560 561 out_usage: 562 usage(cmd, stderr); 563 status = 2; 564 goto out; 565 } 566