mirror of
https://github.com/systemd/systemd.git
synced 2025-03-02 12:58:35 +03:00
Merge pull request #34636 from WilliButz/repart/verity-hash-max-data-size
repart: support verity hash partitions sized for custom data size
This commit is contained in:
commit
fa3faf8abb
@ -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;
|
||||
|
@ -971,6 +971,83 @@ EOF
|
||||
veritysetup dump "${loop}p2" | grep 'Hash block size:' | grep -q '1024'
|
||||
}
|
||||
|
||||
testcase_verity_hash_size_from_data_size() {
|
||||
local defs imgs loop
|
||||
|
||||
if systemd-detect-virt --quiet --container; then
|
||||
echo "Skipping verity hash size from data size test in container."
|
||||
return
|
||||
fi
|
||||
|
||||
defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")"
|
||||
imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
|
||||
|
||||
# shellcheck disable=SC2064
|
||||
trap "rm -rf '$defs' '$imgs'" RETURN
|
||||
chmod 0755 "$defs"
|
||||
|
||||
echo "*** dm-verity-hash-size-from-data-size ***"
|
||||
|
||||
# create minimized data partition with SizeMaxBytes=
|
||||
tee "$defs/verity-data.conf" <<EOF
|
||||
[Partition]
|
||||
Type=root-${architecture}
|
||||
CopyFiles=${defs}
|
||||
Verity=data
|
||||
VerityMatchKey=root
|
||||
Minimize=guess
|
||||
SizeMaxBytes=10G
|
||||
EOF
|
||||
|
||||
# create hash partition, its size will be derived from SizeMaxBytes= of the data partition
|
||||
tee "$defs/verity-hash.conf" <<EOF
|
||||
[Partition]
|
||||
Type=root-${architecture}-verity
|
||||
Verity=hash
|
||||
VerityMatchKey=root
|
||||
VerityHashBlockSizeBytes=4096
|
||||
VerityDataBlockSizeBytes=4096
|
||||
EOF
|
||||
|
||||
systemd-repart --offline="$OFFLINE" \
|
||||
--definitions="$defs" \
|
||||
--seed="$seed" \
|
||||
--dry-run=no \
|
||||
--empty=create \
|
||||
--size=auto \
|
||||
--json=pretty \
|
||||
"$imgs/verity"
|
||||
|
||||
loop="$(losetup --partscan --show --find "$imgs/verity")"
|
||||
|
||||
# Make sure the loopback device gets cleaned up
|
||||
# shellcheck disable=SC2064
|
||||
trap "rm -rf '$defs' '$imgs' ; losetup -d '$loop'" RETURN ERR
|
||||
|
||||
udevadm wait --timeout 60 --settle "${loop:?}p1" "${loop:?}p2"
|
||||
|
||||
output=$(sfdisk -J "$loop")
|
||||
|
||||
# size of the hash partition, as determined by calculate_verity_hash_size()
|
||||
# for 10GiB data partition and hash / data block size of 4096B
|
||||
hash_bytes=84557824
|
||||
hash_sectors_expected=$((hash_bytes / 512))
|
||||
|
||||
hash_sectors_actual=$(jq -r ".partitiontable.partitions | map(select(.name == \"root-${architecture}-verity\")) | .[].size" <<<"$output")
|
||||
|
||||
assert_eq "$hash_sectors_expected" "$hash_sectors_actual"
|
||||
|
||||
data_sectors=$(jq -r ".partitiontable.partitions | map(select(.name == \"root-${architecture}\")) | .[].size" <<<"$output")
|
||||
data_bytes=$((data_sectors * 512))
|
||||
data_verity_blocks=$((data_bytes / 4096))
|
||||
|
||||
# The actual data partition is much smaller than 10GiB, i.e. also smaller than 100MiB
|
||||
assert_rc 0 test $data_bytes -lt $((100 * 1024 * 1024))
|
||||
|
||||
# Check that the verity hash tree is created from the actual on-disk data, not the custom size
|
||||
veritysetup dump "${loop}p2" | grep 'Data blocks:' | grep -q "$data_verity_blocks"
|
||||
}
|
||||
|
||||
testcase_exclude_files() {
|
||||
local defs imgs root output
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user