Merge pull request #24572 from DaanDeMeyer/repart-verity

repart: Add support for formatting verity partitions
This commit is contained in:
Lennart Poettering 2022-09-08 12:02:27 +02:00 committed by GitHub
commit 3d83c3eacf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 460 additions and 25 deletions

2
TODO
View file

@ -117,6 +117,8 @@ Deprecations and removals:
Features:
* Add support for extra verity configuration options to systemd-reart (FEC, hash type, etc)
* measure credentials picked up from SMBIOS to some suitable PCR
* measure GPT and LUKS headers somewhere when we use them (i.e. in

View file

@ -580,6 +580,38 @@
<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>

View 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 no_dropin_files = true;
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,11 +2269,13 @@ 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);
no_dropin_files = no_dropin_files && strv_isempty(p->drop_in_files);
has_roothash = has_roothash || !isempty(rh);
has_dropin_files = has_dropin_files || !strv_isempty(p->drop_in_files);
}
if ((arg_json_format_flags & JSON_FORMAT_OFF) && (sum_padding > 0 || sum_size > 0)) {
@ -2144,12 +2299,19 @@ 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 (no_dropin_files) {
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)
return log_error_errno(r, "Failed to set columns to display: %m");
@ -2353,6 +2515,55 @@ static int context_dump_partition_bar(Context *context, const char *node) {
return 0;
}
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);
assert(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;
/* 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);
if (r < 0)
return r;
putc('\n', stdout);
}
fflush(stdout);
return 0;
}
static bool context_changed(const Context *context) {
assert(context);
@ -3143,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;
@ -3288,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)
@ -3512,21 +3847,6 @@ static int context_write_partition_table(
assert(context);
if (arg_pretty > 0 ||
(arg_pretty < 0 && isatty(STDOUT_FILENO) > 0) ||
!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
(void) context_dump_partitions(context, node);
if (arg_json_format_flags & JSON_FORMAT_OFF) {
putc('\n', stdout);
(void) context_dump_partition_bar(context, node);
putc('\n', stdout);
}
fflush(stdout);
}
if (!from_scratch && !context_changed(context)) {
log_info("No changes.");
return 0;
@ -3573,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;
@ -4420,6 +4744,9 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
if (arg_pretty < 0 && isatty(STDOUT_FILENO))
arg_pretty = true;
return 1;
}
@ -5022,10 +5349,14 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
(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;
}

View file

@ -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
}

View file

@ -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