]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: Add support for formatting verity partitions
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 5 Sep 2022 22:45:32 +0000 (00:45 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 8 Sep 2022 06:43:07 +0000 (08:43 +0200)
This commit adds a new Verity= setting to repart definition files
with two possible values: "data" and "hash".

If Verity= is set to "data", repart works as before, and populates
the partition with the content from CopyBlocks= or CopyFiles=.

If Verity= is set to "hash", repart will try to find a matching
data partition with Verity=data and equal values for CopyBlocks=
or CopyFiles=, Format= and MakeDirectories=. If a matching data
partition is found, repart will generate verity hashes for that
data partition in the verity partition. The UUID of the data
partition is set to the first 128 bits of the verity root hash. The
UUID of the hashes partition is set to the final 128 bits of the
verity root hash.

Fixes #24559

man/repart.d.xml
src/partition/repart.c
test/TEST-58-REPART/test.sh
test/units/testsuite-58.sh

index 6a0a6b32b2e4705eb8d90915d566b2801a962b3d..bdbf054fcf699ca42df12b05df180abea1ef5e55 100644 (file)
         <para>This option has no effect if the partition already exists.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>Verity=</varname></term>
+
+        <listitem><para>Takes one of <literal>off</literal>, <literal>data</literal> or
+        <literal>hash</literal>. Defaults to <literal>off</literal>. If set to <literal>off</literal> or
+        <literal>data</literal>, the partition is populated with content as specified by
+        <varname>CopyBlocks=</varname> or <varname>CopyFiles=</varname>. If set to <literal>hash</literal>,
+        the partition will be populated with verity hashes from a matching verity data partition. A matching
+        data partition is a partition with <varname>Verity=</varname> set to <literal>data</literal> and the
+        same verity match key (as configured with <varname>VerityMatchKey=</varname>). If not explicitly
+        configured, the data partition's UUID will be set to the first 128 bits of the verity root hash.
+        Similarly, if not configured, the hash partition's UUID will be set to the final 128 bits of the
+        verity root hash. The verity root hash itself will be included in the output of
+        <command>systemd-repart</command>.</para>
+
+        <para>This option has no effect if the partition already exists.</para>
+
+        <para>Usage of this option in combination with <varname>Encrypt=</varname> is not supported.</para>
+
+        <para>For each unique <varname>VerityMatchKey=</varname> value, a single verity data partition
+        (<literal>Verity=data</literal>) and a single verity hash partition (<literal>Verity=hash</literal>)
+        must be defined.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>VerityMatchKey=</varname></term>
+
+        <listitem><para>Takes a short, user-chosen identifier string. This setting is used to find sibling
+        verity partitions for the current verity partition. See the description for
+        <varname>Verity=</varname>.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>FactoryReset=</varname></term>
 
@@ -746,6 +778,28 @@ SizeMaxBytes=64M
 
 <para><programlisting># ln -s 50-root.conf /usr/lib/repart.d/70-root-b.conf
 # ln -s 60-root-verity.conf /usr/lib/repart.d/80-root-verity-b.conf
+</programlisting></para>
+    </example>
+
+     <example>
+      <title>Create a data and verity partition from a OS tree</title>
+
+      <para>Assuming we have an OS tree at /var/tmp/os-tree that we want to package in a root partition
+      together with a matching verity partition, we can do so as follows:</para>
+
+      <para><programlisting># 50-root.conf
+[Partition]
+Type=root
+CopyFiles=/var/tmp/os-tree
+Verity=data
+VerityMatchKey=root
+</programlisting></para>
+
+      <para><programlisting># 60-root-verity.conf
+[Partition]
+Type=root-verity
+Verity=hash
+VerityMatchKey=root
 </programlisting></para>
     </example>
 
index 29524c205ee8e2e4023a2ff5574275a15ea07a92..889de712e2c8da6ae1a93916d05bb28a906d151f 100644 (file)
@@ -132,6 +132,14 @@ typedef enum EncryptMode {
         _ENCRYPT_MODE_INVALID = -EINVAL,
 } EncryptMode;
 
+typedef enum VerityMode {
+        VERITY_OFF,
+        VERITY_DATA,
+        VERITY_HASH,
+        _VERITY_MODE_MAX,
+        _VERITY_MODE_INVALID = -EINVAL,
+} VerityMode;
+
 struct Partition {
         char *definition_path;
         char **drop_in_files;
@@ -170,12 +178,19 @@ struct Partition {
         char **copy_files;
         char **make_directories;
         EncryptMode encrypt;
+        VerityMode verity;
+        char *verity_match_key;
 
         uint64_t gpt_flags;
         int no_auto;
         int read_only;
         int growfs;
 
+        uint8_t *roothash;
+        size_t roothash_size;
+
+        Partition *siblings[_VERITY_MODE_MAX];
+
         LIST_FIELDS(Partition, partitions);
 };
 
@@ -211,10 +226,18 @@ static const char *encrypt_mode_table[_ENCRYPT_MODE_MAX] = {
         [ENCRYPT_KEY_FILE_TPM2] = "key-file+tpm2",
 };
 
+static const char *verity_mode_table[_VERITY_MODE_MAX] = {
+        [VERITY_OFF]  = "off",
+        [VERITY_DATA] = "data",
+        [VERITY_HASH] = "hash",
+};
+
 #if HAVE_LIBCRYPTSETUP
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode);
 #else
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(verity_mode, VerityMode);
 #endif
 
 
@@ -282,6 +305,9 @@ static Partition* partition_free(Partition *p) {
         free(p->format);
         strv_free(p->copy_files);
         strv_free(p->make_directories);
+        free(p->verity_match_key);
+
+        free(p->roothash);
 
         return mfree(p);
 }
@@ -407,6 +433,21 @@ static bool context_drop_one_priority(Context *context) {
 
                 p->dropped = true;
                 log_info("Can't fit partition %s of priority %" PRIi32 ", dropping.", p->definition_path, p->priority);
+
+                /* We ensure that all verity sibling partitions have the same priority, so it's safe
+                 * to drop all siblings here as well. */
+
+                for (VerityMode mode = VERITY_OFF + 1; mode < _VERITY_MODE_MAX; mode++) {
+                        if (!p->siblings[mode])
+                                continue;
+
+                        if (p->siblings[mode]->dropped)
+                                continue;
+
+                        p->siblings[mode]->dropped = true;
+                        log_info("Also dropping sibling verity %s partition %s",
+                                 verity_mode_to_string(mode), p->siblings[mode]->definition_path);
+                }
         }
 
         return true;
@@ -1352,6 +1393,8 @@ static int config_parse_uuid(
         return 0;
 }
 
+static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF, "Invalid verity mode");
+
 static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) {
 
         ConfigTableItem table[] = {
@@ -1371,6 +1414,8 @@ static int partition_read_definition(Partition *p, const char *path, const char
                 { "Partition", "CopyFiles",       config_parse_copy_files,  0, p                    },
                 { "Partition", "MakeDirectories", config_parse_make_dirs,   0, p                    },
                 { "Partition", "Encrypt",         config_parse_encrypt,     0, &p->encrypt          },
+                { "Partition", "Verity",          config_parse_verity,      0, &p->verity           },
+                { "Partition", "VerityMatchKey",  config_parse_string,      0, &p->verity_match_key },
                 { "Partition", "Flags",           config_parse_gpt_flags,   0, &p->gpt_flags        },
                 { "Partition", "ReadOnly",        config_parse_tristate,    0, &p->read_only        },
                 { "Partition", "NoAuto",          config_parse_tristate,    0, &p->no_auto          },
@@ -1428,6 +1473,31 @@ static int partition_read_definition(Partition *p, const char *path, const char
                         return log_oom();
         }
 
+        if (p->verity != VERITY_OFF || p->encrypt != ENCRYPT_OFF) {
+                r = dlopen_cryptsetup();
+                if (r < 0)
+                        return log_syntax(NULL, LOG_ERR, path, 1, r,
+                                          "libcryptsetup not found, Verity=/Encrypt= are not supported: %m");
+        }
+
+        if (p->verity != VERITY_OFF && !p->verity_match_key)
+                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
+                                  "VerityMatchKey= must be set if Verity=%s", verity_mode_to_string(p->verity));
+
+        if (p->verity == VERITY_OFF && p->verity_match_key)
+                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
+                                  "VerityMatchKey= can only be set if Verity= is not \"%s\"",
+                                  verity_mode_to_string(p->verity));
+
+        if (p->verity == VERITY_HASH && (p->copy_files || p->copy_blocks_path || p->copy_blocks_auto || p->format || p->make_directories))
+                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
+                                  "CopyBlocks=/CopyFiles=/Format=/MakeDirectories= cannot be used with Verity=%s",
+                                  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),
+                                  "Encrypting verity hash/data partitions is not supported");
+
         /* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */
         if ((gpt_partition_type_is_root_verity(p->type_uuid) ||
              gpt_partition_type_is_usr_verity(p->type_uuid)) &&
@@ -1442,6 +1512,47 @@ static int partition_read_definition(Partition *p, const char *path, const char
         return 0;
 }
 
+static int find_verity_sibling(Context *context, Partition *p, VerityMode mode, Partition **ret) {
+        Partition *s = NULL;
+
+        assert(p);
+        assert(p->verity != VERITY_OFF);
+        assert(p->verity_match_key);
+        assert(mode != VERITY_OFF);
+        assert(p->verity != mode);
+        assert(ret);
+
+        /* Try to find the matching sibling partition of the given type for a verity partition. For a data
+         * partition, this is the corresponding hash partiton with the same verity name (and vice versa for
+         * the hash partition).
+         */
+
+        LIST_FOREACH(partitions, q, context->partitions) {
+                if (p == q)
+                        continue;
+
+                if (q->verity != mode)
+                        continue;
+
+                assert(q->verity_match_key);
+
+                if (!streq(p->verity_match_key, q->verity_match_key))
+                        continue;
+
+                if (s)
+                        return -ENOTUNIQ;
+
+                s = q;
+        }
+
+        if (!s)
+                return -ENXIO;
+
+        *ret = s;
+
+        return 0;
+}
+
 static int context_read_definitions(
                 Context *context,
                 char **directories,
@@ -1480,6 +1591,42 @@ static int context_read_definitions(
                 context->n_partitions++;
         }
 
+        /* Check that each configured verity hash/data partition has a matching verity data/hash partition. */
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                if (p->verity == VERITY_OFF)
+                        continue;
+
+                for (VerityMode mode = VERITY_OFF + 1; mode < _VERITY_MODE_MAX; mode++) {
+                        Partition *q;
+
+                        if (p->verity == mode)
+                                continue;
+
+                        if (p->siblings[mode])
+                                continue;
+
+                        r = find_verity_sibling(context, p, mode, &q);
+                        if (r == -ENXIO)
+                                return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
+                                                  "Missing verity %s partition for verity %s partition with VerityMatchKey=%s",
+                                                  verity_mode_to_string(mode), verity_mode_to_string(p->verity), p->verity_match_key);
+                        if (r == -ENOTUNIQ)
+                                return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
+                                                  "Multiple verity %s partitions found for verity %s partition with VerityMatchKey=%s",
+                                                  verity_mode_to_string(mode), verity_mode_to_string(p->verity), p->verity_match_key);
+                        if (r < 0)
+                                return r;
+
+                        if (q->priority != p->priority)
+                                return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
+                                                  "Priority mismatch (%i != %i) for verity sibling partitions with VerityMatchKey=%s",
+                                                  p->priority, q->priority, p->verity_match_key);
+
+                        p->siblings[mode] = q;
+                }
+        }
+
         return 0;
 }
 
@@ -2042,26 +2189,26 @@ static int context_dump_partitions(Context *context, const char *node) {
         _cleanup_(table_unrefp) Table *t = NULL;
         uint64_t sum_padding = 0, sum_size = 0;
         int r;
-        const size_t dropin_files_col = 13;
-        bool has_dropin_files = false;
+        const size_t roothash_col = 13, dropin_files_col = 14;
+        bool has_roothash = false, has_dropin_files = false;
 
         if ((arg_json_format_flags & JSON_FORMAT_OFF) && context->n_partitions == 0) {
                 log_info("Empty partition table.");
                 return 0;
         }
 
-        t = table_new("type", "label", "uuid", "file", "node", "offset", "old size", "raw size", "size", "old padding", "raw padding", "padding", "activity", "drop-in files");
+        t = table_new("type", "label", "uuid", "file", "node", "offset", "old size", "raw size", "size", "old padding", "raw padding", "padding", "activity", "roothash", "drop-in files");
         if (!t)
                 return log_oom();
 
         if (!DEBUG_LOGGING) {
                 if (arg_json_format_flags & JSON_FORMAT_OFF)
                         (void) table_set_display(t, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4,
-                                                    (size_t) 8, (size_t) 11, dropin_files_col);
+                                                    (size_t) 8, (size_t) 11, roothash_col, dropin_files_col);
                 else
                         (void) table_set_display(t, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4,
-                                                    (size_t) 5, (size_t) 6, (size_t) 7, (size_t) 9, (size_t) 10, (size_t) 12,
-                                                    dropin_files_col);
+                                                    (size_t) 5, (size_t) 6, (size_t) 7, (size_t) 9, (size_t) 10,
+                                                    (size_t) 12, roothash_col, dropin_files_col);
         }
 
         (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
@@ -2073,7 +2220,7 @@ static int context_dump_partitions(Context *context, const char *node) {
         (void) table_set_align_percent(t, table_get_cell(t, 0, 11), 100);
 
         LIST_FOREACH(partitions, p, context->partitions) {
-                _cleanup_free_ char *size_change = NULL, *padding_change = NULL, *partname = NULL;
+                _cleanup_free_ char *size_change = NULL, *padding_change = NULL, *partname = NULL, *rh = NULL;
                 char uuid_buffer[SD_ID128_UUID_STRING_MAX];
                 const char *label, *activity = NULL;
 
@@ -2101,6 +2248,12 @@ static int context_dump_partitions(Context *context, const char *node) {
                 if (p->new_padding != UINT64_MAX)
                         sum_padding += p->new_padding;
 
+                if (p->verity == VERITY_HASH) {
+                        rh = p->roothash ? hexmem(p->roothash, p->roothash_size) : strdup("TBD");
+                        if (!rh)
+                                return log_oom();
+                }
+
                 r = table_add_many(
                                 t,
                                 TABLE_STRING, gpt_partition_type_uuid_to_string_harder(p->type_uuid, uuid_buffer),
@@ -2116,10 +2269,12 @@ static int context_dump_partitions(Context *context, const char *node) {
                                 TABLE_UINT64, p->new_padding,
                                 TABLE_STRING, padding_change, TABLE_SET_COLOR, !p->partitions_next && sum_padding > 0 ? ansi_underline() : NULL,
                                 TABLE_STRING, activity ?: "unchanged",
+                                TABLE_STRING, rh,
                                 TABLE_STRV, p->drop_in_files);
                 if (r < 0)
                         return table_log_add_error(r);
 
+                has_roothash = has_roothash || !isempty(rh);
                 has_dropin_files = has_dropin_files || !strv_isempty(p->drop_in_files);
         }
 
@@ -2144,11 +2299,18 @@ static int context_dump_partitions(Context *context, const char *node) {
                                 TABLE_EMPTY,
                                 TABLE_STRING, b,
                                 TABLE_EMPTY,
+                                TABLE_EMPTY,
                                 TABLE_EMPTY);
                 if (r < 0)
                         return table_log_add_error(r);
         }
 
+        if (!has_roothash) {
+                r = table_hide_column_from_display(t, roothash_col);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set columns to display: %m");
+        }
+
         if (!has_dropin_files) {
                 r = table_hide_column_from_display(t, dropin_files_col);
                 if (r < 0)
@@ -2353,7 +2515,15 @@ static int context_dump_partition_bar(Context *context, const char *node) {
         return 0;
 }
 
-static int context_dump(Context *context, const char *node) {
+static bool context_has_roothash(Context *context) {
+        LIST_FOREACH(partitions, p, context->partitions)
+                if (p->roothash)
+                        return true;
+
+        return false;
+}
+
+static int context_dump(Context *context, const char *node, bool late) {
         int r;
 
         assert(context);
@@ -2362,11 +2532,23 @@ static int context_dump(Context *context, const char *node) {
         if (arg_pretty == 0 && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
                 return 0;
 
+        /* If we're outputting JSON, only dump after doing all operations so we can include the roothashes
+         * in the output.  */
+        if (!late && !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+                return 0;
+
+        /* If we're not outputting JSON, only dump again after doing all operations if there are any
+         * roothashes that we need to communicate to the user. */
+        if (late && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && !context_has_roothash(context))
+                return 0;
+
         r = context_dump_partitions(context, node);
         if (r < 0)
                 return r;
 
-        if (arg_json_format_flags & JSON_FORMAT_OFF) {
+        /* Make sure we only write the partition bar once, even if we're writing the partition table twice to
+         * communicate roothashes. */
+        if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && !late) {
                 putc('\n', stdout);
 
                 r = context_dump_partition_bar(context, node);
@@ -3172,6 +3354,130 @@ static int context_mkfs(Context *context) {
         return 0;
 }
 
+static int do_verity_format(
+                LoopDevice *data_device,
+                LoopDevice *hash_device,
+                uint64_t sector_size,
+                uint8_t **ret_roothash,
+                size_t *ret_roothash_size) {
+
+#if HAVE_LIBCRYPTSETUP
+        _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
+        _cleanup_free_ uint8_t *rh = NULL;
+        size_t rhs;
+        int r;
+
+        assert(data_device);
+        assert(hash_device);
+        assert(sector_size > 0);
+        assert(ret_roothash);
+        assert(ret_roothash_size);
+
+        r = dlopen_cryptsetup();
+        if (r < 0)
+                return log_error_errno(r, "libcryptsetup not found, cannot setup verity: %m");
+
+        r = sym_crypt_init(&cd, hash_device->node);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
+
+        r = sym_crypt_format(
+                        cd, CRYPT_VERITY, NULL, NULL, NULL, NULL, 0,
+                        &(struct crypt_params_verity){
+                                .data_device = data_device->node,
+                                .flags = CRYPT_VERITY_CREATE_HASH,
+                                .hash_name = "sha256",
+                                .hash_type = 1,
+                                .data_block_size = sector_size,
+                                .hash_block_size = sector_size,
+                                .salt_size = 32,
+                        });
+        if (r < 0)
+                return log_error_errno(r, "Failed to setup verity hash data: %m");
+
+        r = sym_crypt_get_volume_key_size(cd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine verity root hash size: %m");
+        rhs = (size_t) r;
+
+        rh = malloc(rhs);
+        if (!rh)
+                return log_oom();
+
+        r = sym_crypt_volume_key_get(cd, CRYPT_ANY_SLOT, (char *) rh, &rhs, NULL, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get verity root hash: %m");
+
+        *ret_roothash = TAKE_PTR(rh);
+        *ret_roothash_size = rhs;
+
+        return 0;
+#else
+        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity: %m");
+#endif
+}
+
+static int context_verity(Context *context) {
+        int fd = -1, r;
+
+        assert(context);
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                Partition *dp;
+                _cleanup_(loop_device_unrefp) LoopDevice *hash_device = NULL, *data_device = NULL;
+                _cleanup_free_ uint8_t *rh = NULL;
+                size_t rhs = 0; /* Initialize to work around for GCC false positive. */
+
+                if (p->dropped)
+                        continue;
+
+                if (PARTITION_EXISTS(p)) /* Never format existing partitions */
+                        continue;
+
+                if (p->verity != VERITY_HASH)
+                        continue;
+
+                assert_se(dp = p->siblings[VERITY_DATA]);
+                assert(!dp->dropped);
+
+                if (fd < 0)
+                        assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
+
+                r = loop_device_make(fd, O_RDONLY, dp->offset, dp->new_size, 0, LOCK_EX, &data_device);
+                if (r < 0)
+                        return log_error_errno(r,
+                                               "Failed to make loopback device of verity data partition %" PRIu64 ": %m",
+                                               p->partno);
+
+                r = loop_device_make(fd, O_RDWR, p->offset, p->new_size, 0, LOCK_EX, &hash_device);
+                if (r < 0)
+                        return log_error_errno(r,
+                                               "Failed to make loopback device of verity hash partition %" PRIu64 ": %m",
+                                               p->partno);
+
+                r = do_verity_format(data_device, hash_device, context->sector_size, &rh, &rhs);
+                if (r < 0)
+                        return r;
+
+                assert(rhs >= sizeof(sd_id128_t) * 2);
+
+                if (!dp->new_uuid_is_set) {
+                        memcpy_safe(dp->new_uuid.bytes, rh, sizeof(sd_id128_t));
+                        dp->new_uuid_is_set = true;
+                }
+
+                if (!p->new_uuid_is_set) {
+                        memcpy_safe(p->new_uuid.bytes, rh + rhs - sizeof(sd_id128_t), sizeof(sd_id128_t));
+                        p->new_uuid_is_set = true;
+                }
+
+                p->roothash = TAKE_PTR(rh);
+                p->roothash_size = rhs;
+        }
+
+        return 0;
+}
+
 static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *ret) {
         struct {
                 sd_id128_t type_uuid;
@@ -3317,7 +3623,7 @@ static int context_acquire_partition_uuids_and_labels(Context *context) {
 
                 if (!sd_id128_is_null(p->current_uuid))
                         p->new_uuid = p->current_uuid; /* Never change initialized UUIDs */
-                else if (!p->new_uuid_is_set) {
+                else if (!p->new_uuid_is_set && p->verity == VERITY_OFF) {
                         /* Not explicitly set by user! */
                         r = partition_acquire_uuid(context, p, &p->new_uuid);
                         if (r < 0)
@@ -3587,6 +3893,10 @@ static int context_write_partition_table(
         if (r < 0)
                 return r;
 
+        r = context_verity(context);
+        if (r < 0)
+                return r;
+
         r = context_mangle_partitions(context);
         if (r < 0)
                 return r;
@@ -5039,12 +5349,14 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
-        context_dump(context, node);
+        (void) context_dump(context, node, /*late=*/ false);
 
         r = context_write_partition_table(context, node, from_scratch);
         if (r < 0)
                 return r;
 
+        (void) context_dump(context, node, /*late=*/ true);
+
         return 0;
 }
 
index 4aff2788e433eecba9098b931946951866497792..063ce9425dcf8e324036807cf08ec9dc02d34419 100755 (executable)
@@ -10,6 +10,8 @@ TEST_DESCRIPTION="test systemd-repart"
 test_append_files() {
     if ! get_bool "${TEST_NO_QEMU:=}"; then
         install_dmevent
+        image_install jq
+        instmods dm_verity =md
         generate_module_dependencies
     fi
 }
index fddcda3872ad775f09ccb6713f8fe903a0b767ce..fd972a38b9621c3e8fae9ed9521b03c8d14df559 100755 (executable)
@@ -598,6 +598,51 @@ EOF
     assert_in "$imgs/zero1 : start=        2048, size=       20480, type=${root_guid}, uuid=00000000-0000-0000-0000-000000000000" "$output"
 }
 
+test_verity() {
+    local defs imgs output
+
+    if systemd-detect-virt --quiet --container; then
+        echo "Skipping verity test in container."
+        return
+    fi
+
+    defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")"
+    imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")"
+    # shellcheck disable=SC2064
+    trap "rm -rf '$defs' '$imgs'" RETURN
+
+    cat >"$defs/root.conf" <<EOF
+[Partition]
+Type=root-${architecture}
+CopyFiles=${defs}
+Verity=data
+VerityMatchKey=root
+EOF
+
+    cat >"$defs/verity.conf" <<EOF
+[Partition]
+Type=root-${architecture}-verity
+Verity=hash
+VerityMatchKey=root
+EOF
+
+    output=$(systemd-repart --definitions="$defs" \
+                            --seed="$seed" \
+                            --dry-run=no \
+                            --empty=create \
+                            --size=auto \
+                            --json=pretty \
+                            "$imgs/verity")
+
+    roothash=$(jq -r ".[] | select(.type == \"root-${architecture}-verity\") | .roothash" <<< "$output")
+
+     # Check that we can dissect, mount and unmount a repart verity image.
+
+    systemd-dissect "$imgs/verity" --root-hash "$roothash"
+    systemd-dissect "$imgs/verity" --root-hash "$roothash" -M "$imgs/mnt"
+    systemd-dissect -U "$imgs/mnt"
+}
+
 test_sector() {
     local defs imgs output loop
     local start size ratio
@@ -664,6 +709,7 @@ test_copy_blocks
 test_unaligned_partition
 test_issue_21817
 test_zero_uuid
+test_verity
 
 # Valid block sizes on the Linux block layer are >= 512 and <= PAGE_SIZE, and
 # must be powers of 2. Which leaves exactly four different ones to test on