From 189d5c020b9705ccc7215dacd5bb262e96e7dcc3 Mon Sep 17 00:00:00 2001 From: Michael Ferrari Date: Fri, 27 Mar 2026 22:43:27 +0100 Subject: [PATCH 1/2] Support `CopyBlocks=` for `Verity={hash,sig}` This enables deriving the minimum size of the `Verity=hash` partition using the `Verity=` logic when the size of the `Verity=data` partition is bigger than the `CopyBlocks=` target. This enables using `Minimize=true` for an "installer image" and later using sd-repart to install to a system with reserve space for future updates by specifying `Size{Min,Max}Bytes=` only in the `Verity=data` partition, without needing to hardcode the corresponding size for the `Verity=hash` partition. While not strictly necessary for `Verity=signature` partitions (since they have a fixed size) there isn't too much reason to not support it, since then you can still specify `VerityMatchKey=` to indicate that the partition is logically still part of that group of partitions. We ensure that if one of the hash uses `CopyBlocks=` that the data partition does so as well. Similarly if the signature partition does it checks that the hash and data partition do so as well. This is to minimize the chance of accidental misconfiguration of mixing `CopyBlocks=auto` and `CopyBlocks=` and of manually populating partitions while hash/sig partitions are copied from existing sources. --- src/repart/repart.c | 48 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 7dbcfd6d58..fd1f16d4de 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -2998,10 +2998,20 @@ static int partition_read_definition( "VerityMatchKey= can only be set if Verity= is not \"%s\".", verity_mode_to_string(p->verity)); - if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG) && (p->copy_blocks_path || p->copy_blocks_auto || p->format || partition_needs_populate(p))) - return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "CopyBlocks=/CopyFiles=/Format=/MakeDirectories=/MakeSymlinks= cannot be used with Verity=%s.", - verity_mode_to_string(p->verity)); + if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG)) { + if (p->format || partition_needs_populate(p)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyFiles=/Format=/MakeDirectories=/MakeSymlinks= cannot be used with Verity=%s.", + verity_mode_to_string(p->verity)); + + /* Later we check that the same CopyBlocks= type (auto vs path) is used for the entire verity set. + * So we assume that CopyBlocks=auto is going to be correct and just path based blocks might result in + * a broken setup */ + if (p->copy_blocks_path) + log_syntax(NULL, LOG_DEBUG, path, 1, 0, + "CopyBlocks= with Verity=%s bypasses dm-verity hash/signature computation; repart cannot verify the resulting setup is correct.", + verity_mode_to_string(p->verity)); + } if (p->verity != VERITY_OFF && p->encrypt != ENCRYPT_OFF) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), @@ -3494,6 +3504,27 @@ static int context_read_definitions(Context *context) { } } + LIST_FOREACH(partitions, p, context->partitions) { + if (!IN_SET(p->verity, VERITY_HASH, VERITY_SIG)) + continue; + + /* We check all verity siblings up until our current type and ensure that if we are using CopyBlocks= + * the previous ones are using the same type of CopyBlocks=. */ + for (VerityMode mode = VERITY_DATA; mode < p->verity; mode++) { + Partition *q = ASSERT_PTR(p->siblings[mode]); + + if (p->copy_blocks_auto && !q->copy_blocks_auto) + return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyBlocks=auto set with Verity=%s but Verity=%s partition does not set CopyBlocks=auto.", + verity_mode_to_string(p->verity), verity_mode_to_string(mode)); + + if (p->copy_blocks_path && !q->copy_blocks_path) + return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyBlocks= set with Verity=%s but Verity=%s partition does not set CopyBlocks=.", + verity_mode_to_string(p->verity), verity_mode_to_string(mode)); + } + } + LIST_FOREACH(partitions, p, context->partitions) { Partition *dp; @@ -5620,7 +5651,7 @@ static int partition_format_verity_hash( if (PARTITION_EXISTS(p)) /* Never format existing partitions */ return 0; - /* Minimized partitions will use the copy blocks logic so skip those here. */ + /* Either we are minimizing the partition or we were instructed to copy an existing hash block directly. */ if (p->copy_blocks_fd >= 0) return 0; @@ -5794,6 +5825,10 @@ static int partition_format_verity_sig(Context *context, Partition *p) { if (PARTITION_EXISTS(p)) return 0; + /* We were instructed to copy an existing signature block directly */ + if (p->copy_blocks_fd >= 0) + return 0; + assert_se(hp = p->siblings[VERITY_HASH]); assert(!hp->dropped); assert_se(rp = p->siblings[VERITY_DATA]); @@ -8884,6 +8919,9 @@ static int context_minimize(Context *context) { if (PARTITION_EXISTS(p)) /* Never format existing partitions */ continue; + if (p->copy_blocks_fd >= 0) + continue; + if (p->minimize == MINIMIZE_OFF) continue; From ecf2329e14f1c554e2a3c240290cb9116546597f Mon Sep 17 00:00:00 2001 From: Michael Ferrari Date: Mon, 30 Mar 2026 02:48:48 +0200 Subject: [PATCH 2/2] Initialize roothash when populating sig partition This allows one to specify `CopyBlocks=` on both the `Verity=` data and hash partition and the signature is recreated correctly. --- src/repart/repart.c | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index fd1f16d4de..1512c1a0d6 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -5788,21 +5788,41 @@ static int sign_verity_roothash( #endif } -static const VeritySettings *lookup_verity_settings_by_uuid_pair(sd_id128_t data_uuid, sd_id128_t hash_uuid) { - uint8_t root_hash_key[sizeof(sd_id128_t) * 2]; +static int iovec_roothash_from_uuid_pair( + sd_id128_t data_uuid, + sd_id128_t hash_uuid, + struct iovec *ret_roothash) { + + uint8_t roothash_bytes[sizeof(sd_id128_t) * 2]; + + assert(ret_roothash); if (sd_id128_is_null(data_uuid) || sd_id128_is_null(hash_uuid)) - return NULL; + return -EINVAL; /* As per the https://uapi-group.org/specifications/specs/discoverable_partitions_specification/ the * UUIDs of the data and verity partitions are respectively the first and second halves of the * dm-verity roothash, so we can use them to match the signature to the right partition. */ - memcpy(root_hash_key, data_uuid.bytes, sizeof(sd_id128_t)); - memcpy(root_hash_key + sizeof(sd_id128_t), hash_uuid.bytes, sizeof(sd_id128_t)); + memcpy(roothash_bytes, data_uuid.bytes, sizeof(sd_id128_t)); + memcpy(roothash_bytes + sizeof(sd_id128_t), hash_uuid.bytes, sizeof(sd_id128_t)); + + if (!iovec_memdup(&IOVEC_MAKE(roothash_bytes, sizeof(roothash_bytes)), ret_roothash)) + return -ENOMEM; + + return 0; +} + +static const VeritySettings *lookup_verity_settings_by_uuid_pair(sd_id128_t data_uuid, sd_id128_t hash_uuid) { + _cleanup_(iovec_done) struct iovec roothash = {}; + int r; + + r = iovec_roothash_from_uuid_pair(data_uuid, hash_uuid, &roothash); + if (r < 0) + return NULL; VeritySettings key = { - .root_hash = IOVEC_MAKE(root_hash_key, sizeof(root_hash_key)), + .root_hash = roothash, }; return set_get(arg_verity_settings, &key); @@ -5834,6 +5854,14 @@ static int partition_format_verity_sig(Context *context, Partition *p) { assert_se(rp = p->siblings[VERITY_DATA]); assert(!rp->dropped); + /* Currently only set while formatting the hash partition. But if this is skipped via CopyBlocks= + * we just derive the roothash from the UUIDs from the data + hash partition. */ + if (!iovec_is_set(&hp->roothash)) { + r = iovec_roothash_from_uuid_pair(rp->new_uuid, hp->new_uuid, &hp->roothash); + if (r < 0) + return log_error_errno(r, "Unable to derive roothash: %m"); + } + verity_settings = lookup_verity_settings_by_uuid_pair(rp->current_uuid, hp->current_uuid); if (!verity_settings) {