repart,dissect: explicitly support DDIs that are both signed *and* encrypted (#43009)

This is a pretty relevant usecase: preparing an image on some trusted
host, submitting it to some other host that authenticates it and
decrypts it, and consumes it only then.

Let's support this explicitly.
This commit is contained in:
Lennart Poettering
2026-07-16 13:40:49 +02:00
committed by GitHub
4 changed files with 233 additions and 14 deletions
+31 -2
View File
@@ -3138,9 +3138,9 @@ static int partition_read_definition(
verity_mode_to_string(p->verity));
}
if (p->verity != VERITY_OFF && p->encrypt != ENCRYPT_OFF)
if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG) && p->encrypt != ENCRYPT_OFF)
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"Encrypting verity hash/data partitions is not supported.");
"Encrypting verity hash/signature partitions is not supported.");
if (p->verity == VERITY_SIG && (p->size_min != UINT64_MAX || p->size_max != UINT64_MAX))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
@@ -3667,6 +3667,13 @@ static int context_read_definitions(Context *context) {
if (dp->minimize == MINIMIZE_OFF && !(dp->copy_blocks_path || dp->copy_blocks_auto))
return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
"Minimize= set for verity hash partition but data partition does not set CopyBlocks= or Minimize=.");
/* The verity hash of an encrypted data partition covers the ciphertext, which is generated
* with a fresh volume key only when the final image is built, hence it cannot be
* precalculated for minimizing purposes. */
if (dp->encrypt != ENCRYPT_OFF)
return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
"Minimize= cannot be set for verity hash partitions whose data partition is encrypted, use SizeMaxBytes= on the data partition instead.");
}
LIST_FOREACH(partitions, p, context->partitions) {
@@ -5323,6 +5330,15 @@ static int partition_target_prepare(
return 0;
}
static void partition_target_drop_decrypted(PartitionTarget *t) {
assert(t);
/* Deactivate the dm-crypt device again, so that subsequent access to the target reaches the
* encrypted data as it is stored on disk (i.e. the ciphertext). Requires the target to have been
* sync'ed first. */
t->decrypted = decrypted_partition_target_free(t->decrypted);
}
static int partition_target_grow(PartitionTarget *t, uint64_t size) {
int r;
@@ -5990,6 +6006,9 @@ static int partition_format_verity_hash(
if (r < 0)
return r;
if (p->partno != UINT64_MAX)
log_info("Calculating Verity protection data for future partition %" PRIu64 "...", p->partno);
if (!node) {
r = partition_target_prepare(context, p, p->new_size, /* need_path= */ true, &t);
if (r < 0)
@@ -6411,6 +6430,11 @@ static int context_copy_blocks(Context *context) {
p->partno, FORMAT_TIMESPAN(time_spent, 0));
if (p->siblings[VERITY_HASH] && !partition_defer(context, p->siblings[VERITY_HASH])) {
/* The verity hash must cover the partition contents as stored on disk, i.e. the
* ciphertext if the partition is encrypted, hence tear down the dm-crypt device
* first. */
partition_target_drop_decrypted(t);
r = partition_format_verity_hash(context, p->siblings[VERITY_HASH],
/* node= */ NULL, partition_target_path(t));
if (r < 0)
@@ -7600,6 +7624,11 @@ static int context_mkfs(Context *context) {
return r;
if (p->siblings[VERITY_HASH] && !partition_defer(context, p->siblings[VERITY_HASH])) {
/* The verity hash must cover the partition contents as stored on disk, i.e. the
* ciphertext if the partition is encrypted, hence tear down the dm-crypt device
* first. */
partition_target_drop_decrypted(t);
r = partition_format_verity_hash(context, p->siblings[VERITY_HASH],
/* node= */ NULL, partition_target_path(t));
if (r < 0)
+23 -11
View File
@@ -2868,8 +2868,9 @@ static DecryptedImage* decrypted_image_free(DecryptedImage *d) {
if (!d)
return NULL;
for (size_t i = 0; i < d->n_decrypted; i++) {
DecryptedPartition *p = d->decrypted + i;
/* Detach in reverse order so that nested dm-verity and dm-crypt are detached in the right order. */
for (size_t i = d->n_decrypted; i > 0; i--) {
DecryptedPartition *p = d->decrypted + i - 1;
if (p->device && p->name && !p->relinquished) {
_cleanup_free_ char *node = NULL;
@@ -3005,6 +3006,13 @@ static int decrypt_partition(
if (!FLAGS_SET(policy_flags, PARTITION_POLICY_ENCRYPTED))
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Attempted to unlock partition via LUKS, but it's prohibited.");
/* If a dm-verity device was already set up on this partition, then the partition is both Verity
* protected and LUKS encrypted, with Verity being the outer envelope and LUKS the inner. In that
* case open the LUKS device on top of the verity device, so that everything we read is
* authenticated. Since verity devices are read-only, the LUKS layer must then be read-only, too. */
const char *source_node = m->decrypted_node ?: m->node;
bool on_verity = !!m->decrypted_node;
r = dlopen_cryptsetup(LOG_DEBUG);
if (r < 0)
return r;
@@ -3016,7 +3024,7 @@ static int decrypt_partition(
if (!GREEDY_REALLOC0(d->decrypted, d->n_decrypted + 1))
return -ENOMEM;
r = sym_crypt_init(&cd, m->node);
r = sym_crypt_init(&cd, source_node);
if (r < 0)
return log_debug_errno(r, "Failed to initialize dm-crypt: %m");
@@ -3027,8 +3035,8 @@ static int decrypt_partition(
return log_debug_errno(r, "Failed to load LUKS metadata: %m");
r = sym_crypt_activate_by_passphrase(cd, name, CRYPT_ANY_SLOT, passphrase, strlen(passphrase),
((flags & DISSECT_IMAGE_DEVICE_READ_ONLY) ? CRYPT_ACTIVATE_READONLY : 0) |
((flags & DISSECT_IMAGE_DISCARD_ON_CRYPTO) ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0));
((flags & DISSECT_IMAGE_DEVICE_READ_ONLY) || on_verity ? CRYPT_ACTIVATE_READONLY : 0) |
((flags & DISSECT_IMAGE_DISCARD_ON_CRYPTO) && !on_verity ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0));
if (r < 0) {
log_debug_errno(r, "Failed to activate LUKS device: %m");
return r == -EPERM ? -EKEYREJECTED : r;
@@ -3043,7 +3051,7 @@ static int decrypt_partition(
.device = TAKE_PTR(cd),
};
m->decrypted_node = TAKE_PTR(node);
free_and_replace(m->decrypted_node, node);
close_and_replace(m->mount_node_fd, fd);
return 0;
@@ -3542,7 +3550,7 @@ success:
.device = TAKE_PTR(cd),
};
m->decrypted_node = TAKE_PTR(node);
free_and_replace(m->decrypted_node, node);
close_and_replace(m->mount_node_fd, mount_node_fd);
return 0;
@@ -3602,10 +3610,10 @@ int dissected_image_decrypt(
PartitionPolicyFlags fl = image_policy_get_exhaustively(policy, i);
r = decrypt_partition(m, p, passphrase, flags, fl, d);
if (r < 0)
return r;
/* Set up Verity first, and only then LUKS: if a partition is both Verity protected and
* encrypted, Verity is the outer envelope and LUKS the inner, i.e. the Verity hash data
* covers the LUKS ciphertext, and decrypt_partition() then stacks the LUKS device on top of
* the verity device. */
k = partition_verity_hash_of(i);
if (k >= 0) {
r = verity_partition(m, i, p, m->partitions + k, root, verity, flags, fl, d);
@@ -3613,6 +3621,10 @@ int dissected_image_decrypt(
return r;
}
r = decrypt_partition(m, p, passphrase, flags, fl, d);
if (r < 0)
return r;
if (!p->decrypted_fstype && p->mount_node_fd >= 0 && p->decrypted_node) {
r = probe_filesystem_full(p->mount_node_fd, p->decrypted_node, 0, UINT64_MAX, /* bool restrict_fstypes= */ true, &p->decrypted_fstype);
if (r < 0 && r != -EUCLEAN)