diff --git a/src/partition/repart.c b/src/partition/repart.c index 8b51dc8ec5..e4eb2c1bb0 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -479,6 +479,54 @@ static uint64_t round_up_size(uint64_t v, uint64_t p) { return v * p; } +/* calculates the size of a dm-verity hash partition's contents */ +static int calculate_verity_hash_size( + uint64_t data_bytes, + uint64_t hash_block_size, + uint64_t data_block_size, + uint64_t *ret_bytes) { + + /* The calculation here is based on the documented on-disk format of the dm-verity + * https://docs.kernel.org/admin-guide/device-mapper/verity.html#hash-tree + * + * Upstream implementation: + * https://gitlab.com/cryptsetup/cryptsetup/-/blob/v2.7.5/lib/verity/verity_hash.c */ + + uint64_t data_blocks = DIV_ROUND_UP(data_bytes, data_block_size); + if (data_blocks > UINT64_MAX / data_block_size) + return -EOVERFLOW; + + /* hashes that fit in one hash block (node in the merkle tree) */ + uint64_t hashes_per_hash_block = hash_block_size / SHA256_DIGEST_SIZE; + + /* initialize with 2 for the root of the merkle tree + the superblock */ + uint64_t hash_blocks = 2; + + /* iterate through the levels of the merkle tree bottom up */ + uint64_t remaining_blocks = data_blocks; + + while (remaining_blocks > hashes_per_hash_block) { + uint64_t hash_blocks_for_level; + /* number of hash blocks required to reference the underlying blocks */ + hash_blocks_for_level = DIV_ROUND_UP(remaining_blocks, hashes_per_hash_block); + + if (hash_blocks > UINT64_MAX - hash_blocks_for_level) + return -EOVERFLOW; + + /* add current layer to the total number of hash blocks */ + hash_blocks += hash_blocks_for_level; + /* hashes on this level serve as the blocks on which the next level is built */ + remaining_blocks = hash_blocks_for_level; + } + + if (hash_blocks > UINT64_MAX / hash_block_size) + return -EOVERFLOW; + + *ret_bytes = hash_blocks * hash_block_size; + + return 0; +} + static Partition *partition_new(void) { Partition *p; @@ -4858,11 +4906,6 @@ static int partition_format_verity_hash( node = partition_target_path(t); } - if (p->verity_data_block_size == UINT64_MAX) - p->verity_data_block_size = context->fs_sector_size; - if (p->verity_hash_block_size == UINT64_MAX) - p->verity_hash_block_size = context->fs_sector_size; - r = sym_crypt_init(&cd, node); if (r < 0) return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", node); @@ -7392,6 +7435,60 @@ static int context_crypttab(Context *context) { return 0; } +/* update block sizes for verity siblings, calculate hash partition size if requested */ +static int context_update_verity_size(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + Partition *dp; + + if (p->verity != VERITY_HASH) + continue; + + if (p->dropped) + continue; + + if (PARTITION_EXISTS(p)) /* Never format existing partitions */ + continue; + + assert_se(dp = p->siblings[VERITY_DATA]); + + if (p->verity_data_block_size == UINT64_MAX) + p->verity_data_block_size = context->fs_sector_size; + + if (p->verity_hash_block_size == UINT64_MAX) + p->verity_hash_block_size = context->fs_sector_size; + + uint64_t sz; + if (dp->size_max != UINT64_MAX) { + r = calculate_verity_hash_size( + dp->size_max, + p->verity_hash_block_size, + p->verity_data_block_size, + &sz); + if (r < 0) + return log_error_errno(r, "Failed to caculate size of dm-verity hash partition: %m"); + + if (sz > p->size_min || sz > p->size_max) + log_warning("The dm-verity hash partition %s may be too small for a data partition " + "with SizeMaxBytes=%s. The hash partition would require %s for a data " + "partition of specified max size. Consider increasing the size of the " + "hash partition, or decreasing SizeMaxBytes= of the data partition.", + p->definition_path, FORMAT_BYTES(dp->size_max), FORMAT_BYTES(sz)); + else if (p->size_min == UINT64_MAX) { + log_debug("Derived size %s of verity hash partition %s from verity data partition %s.", + FORMAT_BYTES(sz), p->definition_path, dp->definition_path); + + p->size_min = sz; + } + } + } + + return 0; +} + static int context_minimize(Context *context) { const char *vt = NULL; unsigned attrs = 0; @@ -9046,6 +9143,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_update_verity_size(context); + if (r < 0) + return r; + r = context_minimize(context); if (r < 0) return r;