_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;
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);
};
[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
free(p->format);
strv_free(p->copy_files);
strv_free(p->make_directories);
+ free(p->verity_match_key);
+
+ free(p->roothash);
return mfree(p);
}
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;
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[] = {
{ "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 },
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)) &&
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,
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;
}
_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);
(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;
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),
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);
}
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)
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);
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);
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;
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)
if (r < 0)
return r;
+ r = context_verity(context);
+ if (r < 0)
+ return r;
+
r = context_mangle_partitions(context);
if (r < 0)
return r;
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;
}