Home | History | Annotate | Download | only in fsverity-utils
      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, &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);
    550 out:
    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 
    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