]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Support `CopyBlocks=` for `Verity={hash,sig}`
authorMichael Ferrari <nekkodroid404@gmail.com>
Fri, 27 Mar 2026 21:43:27 +0000 (22:43 +0100)
committerMichael Ferrari <nekkodroid404@gmail.com>
Thu, 2 Apr 2026 08:45:32 +0000 (10:45 +0200)
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=<path>` and of manually populating
partitions while hash/sig partitions are copied from existing sources.

src/repart/repart.c

index 7dbcfd6d5824ecca54eb3cdb148b835a6824431d..fd1f16d4de38ea2ed2452fd8e45fb964864a0373 100644 (file)
@@ -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;