mirror of
https://github.com/systemd/systemd
synced 2024-10-04 15:21:01 +00:00
Merge pull request #24572 from DaanDeMeyer/repart-verity
repart: Add support for formatting verity partitions
This commit is contained in:
commit
3d83c3eacf
2
TODO
2
TODO
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue