diff --git a/src/partition/repart.c b/src/partition/repart.c index 78bd06a537..fe471d601c 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3155,29 +3155,85 @@ static int context_wipe_and_discard(Context *context) { return 0; } +typedef struct DecryptedPartitionTarget { + int fd; + char *volume; + struct crypt_device *device; +} DecryptedPartitionTarget; + +static DecryptedPartitionTarget* decrypted_partition_target_free(DecryptedPartitionTarget *t) { +#ifdef HAVE_LIBCRYPTSETUP + _cleanup_free_ char *name = NULL; + int r; + + if (!t) + return NULL; + + r = path_extract_filename(t->volume, &name); + if (r < 0) { + assert(r == -ENOMEM); + log_oom(); + } + + safe_close(t->fd); + + if (name) { + /* udev or so might access out block device in the background while we are done. Let's hence + * force detach the volume. We sync'ed before, hence this should be safe. */ + r = sym_crypt_deactivate_by_name(t->device, name, CRYPT_DEACTIVATE_FORCE); + if (r < 0) + log_error_errno(r, "Failed to deactivate LUKS device: %m"); + } + + sym_crypt_free(t->device); + free(t->volume); + free(t); +#endif + return NULL; +} + typedef struct { LoopDevice *loop; int fd; char *path; int whole_fd; + DecryptedPartitionTarget *decrypted; } PartitionTarget; static int partition_target_fd(PartitionTarget *t) { assert(t); assert(t->loop || t->fd >= 0 || t->whole_fd >= 0); - return t->loop ? t->loop->fd : t->fd >= 0 ? t->fd : t->whole_fd; + + if (t->decrypted) + return t->decrypted->fd; + + if (t->loop) + return t->loop->fd; + + if (t->fd >= 0) + return t->fd; + + return t->whole_fd; } static const char* partition_target_path(PartitionTarget *t) { assert(t); assert(t->loop || t->path); - return t->loop ? t->loop->node : t->path; + + if (t->decrypted) + return t->decrypted->volume; + + if (t->loop) + return t->loop->node; + + return t->path; } static PartitionTarget *partition_target_free(PartitionTarget *t) { if (!t) return NULL; + decrypted_partition_target_free(t->decrypted); loop_device_unref(t->loop); safe_close(t->fd); unlink_and_free(t->path); @@ -3285,6 +3341,7 @@ static int partition_target_grow(PartitionTarget *t, uint64_t size) { int r; assert(t); + assert(!t->decrypted); if (t->loop) { r = loop_device_refresh_size(t->loop, UINT64_MAX, size); @@ -3308,6 +3365,9 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + if (t->decrypted && fsync(t->decrypted->fd) < 0) + return log_error_errno(errno, "Failed to sync changes to '%s': %m", t->decrypted->volume); + if (t->loop) { r = loop_device_sync(t->loop); if (r < 0) @@ -3340,12 +3400,13 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget return 0; } -static int partition_encrypt(Context *context, Partition *p, const char *node) { +static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline) { #if HAVE_LIBCRYPTSETUP && HAVE_CRYPT_SET_DATA_OFFSET && HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE && HAVE_CRYPT_REENCRYPT + const char *node = partition_target_path(target); struct crypt_params_luks2 luks_params = { .label = strempty(ASSERT_PTR(p)->new_label), .sector_size = ASSERT_PTR(context)->sector_size, - .data_device = node, + .data_device = offline ? node : NULL, }; struct crypt_params_reencrypt reencrypt_params = { .mode = CRYPT_REENCRYPT_ENCRYPT, @@ -3358,7 +3419,7 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) { _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_fclose_ FILE *h = NULL; - _cleanup_free_ char *hp = NULL; + _cleanup_free_ char *hp = NULL, *vol = NULL, *dm_name = NULL; const char *passphrase = NULL; size_t passphrase_size = 0; const char *vt; @@ -3374,39 +3435,51 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) { log_info("Encrypting future partition %" PRIu64 "...", p->partno); - r = var_tmp_dir(&vt); - if (r < 0) - return log_error_errno(r, "Failed to determine temporary files directory: %m"); + if (offline) { + r = var_tmp_dir(&vt); + if (r < 0) + return log_error_errno(r, "Failed to determine temporary files directory: %m"); - r = fopen_temporary_child(vt, &h, &hp); - if (r < 0) - return log_error_errno(r, "Failed to create temporary LUKS header file: %m"); + r = fopen_temporary_child(vt, &h, &hp); + if (r < 0) + return log_error_errno(r, "Failed to create temporary LUKS header file: %m"); - /* Weird cryptsetup requirement which requires the header file to be the size of at least one sector. */ - r = ftruncate(fileno(h), context->sector_size); - if (r < 0) - return log_error_errno(r, "Failed to grow temporary LUKS header file: %m"); + /* Weird cryptsetup requirement which requires the header file to be the size of at least one + * sector. */ + r = ftruncate(fileno(h), context->sector_size); + if (r < 0) + return log_error_errno(r, "Failed to grow temporary LUKS header file: %m"); + } else { + if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0) + return log_oom(); - r = sym_crypt_init(&cd, hp); + vol = path_join("/dev/mapper/", dm_name); + if (!vol) + return log_oom(); + } + + r = sym_crypt_init(&cd, offline ? hp : node); if (r < 0) return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", hp); cryptsetup_enable_logging(cd); - /* Disable kernel keyring usage by libcryptsetup as a workaround for - * https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/273. This makes sure that we can do - * offline encryption even when repart is running in a container. */ - r = sym_crypt_volume_key_keyring(cd, false); - if (r < 0) - return log_error_errno(r, "Failed to disable kernel keyring: %m"); + if (offline) { + /* Disable kernel keyring usage by libcryptsetup as a workaround for + * https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/273. This makes sure that we can + * do offline encryption even when repart is running in a container. */ + r = sym_crypt_volume_key_keyring(cd, false); + if (r < 0) + return log_error_errno(r, "Failed to disable kernel keyring: %m"); - r = sym_crypt_metadata_locking(cd, false); - if (r < 0) - return log_error_errno(r, "Failed to disable metadata locking: %m"); + r = sym_crypt_metadata_locking(cd, false); + if (r < 0) + return log_error_errno(r, "Failed to disable metadata locking: %m"); - r = sym_crypt_set_data_offset(cd, LUKS2_METADATA_SIZE / 512); - if (r < 0) - return log_error_errno(r, "Failed to set data offset: %m"); + r = sym_crypt_set_data_offset(cd, LUKS2_METADATA_SIZE / 512); + if (r < 0) + return log_error_errno(r, "Failed to set data offset: %m"); + } r = sym_crypt_format(cd, CRYPT_LUKS2, @@ -3517,51 +3590,84 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) { #endif } - r = sym_crypt_reencrypt_init_by_passphrase( - cd, - NULL, - passphrase, - passphrase_size, - CRYPT_ANY_SLOT, - 0, - sym_crypt_get_cipher(cd), - sym_crypt_get_cipher_mode(cd), - &reencrypt_params); - if (r < 0) - return log_error_errno(r, "Failed to prepare for reencryption: %m"); + if (offline) { + r = sym_crypt_reencrypt_init_by_passphrase( + cd, + NULL, + passphrase, + passphrase_size, + CRYPT_ANY_SLOT, + 0, + sym_crypt_get_cipher(cd), + sym_crypt_get_cipher_mode(cd), + &reencrypt_params); + if (r < 0) + return log_error_errno(r, "Failed to prepare for reencryption: %m"); - /* crypt_reencrypt_init_by_passphrase() doesn't actually put the LUKS header at the front, we have - * to do that ourselves. */ + /* crypt_reencrypt_init_by_passphrase() doesn't actually put the LUKS header at the front, we + * have to do that ourselves. */ - sym_crypt_free(cd); - cd = NULL; + sym_crypt_free(cd); + cd = NULL; - r = sym_crypt_init(&cd, node); - if (r < 0) - return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", node); + r = sym_crypt_init(&cd, node); + if (r < 0) + return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", node); - r = sym_crypt_header_restore(cd, CRYPT_LUKS2, hp); - if (r < 0) - return log_error_errno(r, "Failed to place new LUKS header at head of %s: %m", node); + r = sym_crypt_header_restore(cd, CRYPT_LUKS2, hp); + if (r < 0) + return log_error_errno(r, "Failed to place new LUKS header at head of %s: %m", node); - reencrypt_params.flags &= ~CRYPT_REENCRYPT_INITIALIZE_ONLY; + reencrypt_params.flags &= ~CRYPT_REENCRYPT_INITIALIZE_ONLY; - r = sym_crypt_reencrypt_init_by_passphrase( - cd, - NULL, - passphrase, - passphrase_size, - CRYPT_ANY_SLOT, - 0, - NULL, - NULL, - &reencrypt_params); - if (r < 0) - return log_error_errno(r, "Failed to load reencryption context: %m"); + r = sym_crypt_reencrypt_init_by_passphrase( + cd, + NULL, + passphrase, + passphrase_size, + CRYPT_ANY_SLOT, + 0, + NULL, + NULL, + &reencrypt_params); + if (r < 0) + return log_error_errno(r, "Failed to load reencryption context: %m"); - r = sym_crypt_reencrypt(cd, NULL); - if (r < 0) - return log_error_errno(r, "Failed to encrypt %s: %m", node); + r = sym_crypt_reencrypt(cd, NULL); + if (r < 0) + return log_error_errno(r, "Failed to encrypt %s: %m", node); + } else { + _cleanup_free_ DecryptedPartitionTarget *t = NULL; + _cleanup_close_ int dev_fd = -1; + + r = sym_crypt_activate_by_volume_key( + cd, + dm_name, + NULL, + VOLUME_KEY_SIZE, + arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0); + if (r < 0) + return log_error_errno(r, "Failed to activate LUKS superblock: %m"); + + dev_fd = open(vol, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (dev_fd < 0) + return log_error_errno(errno, "Failed to open LUKS volume '%s': %m", vol); + + if (flock(dev_fd, LOCK_EX) < 0) + return log_error_errno(errno, "Failed to lock '%s': %m", vol); + + t = new(DecryptedPartitionTarget, 1); + if (!t) + return log_oom(); + + *t = (DecryptedPartitionTarget) { + .fd = TAKE_FD(dev_fd), + .volume = TAKE_PTR(vol), + .device = TAKE_PTR(cd), + }; + + target->decrypted = TAKE_PTR(t); + } log_info("Successfully encrypted future partition %" PRIu64 ".", p->partno); @@ -3833,6 +3939,12 @@ static int context_copy_blocks(Context *context) { if (r < 0) return r; + if (p->encrypt != ENCRYPT_OFF && t->loop) { + r = partition_encrypt(context, p, t, /* offline = */ false); + if (r < 0) + return r; + } + log_info("Copying in '%s' (%s) on block level into future partition %" PRIu64 ".", p->copy_blocks_path, FORMAT_BYTES(p->copy_blocks_size), p->partno); @@ -3840,8 +3952,10 @@ static int context_copy_blocks(Context *context) { if (r < 0) return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path); - if (p->encrypt != ENCRYPT_OFF) { - r = partition_encrypt(context, p, partition_target_path(t)); + log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); + + if (p->encrypt != ENCRYPT_OFF && !t->loop) { + r = partition_encrypt(context, p, t, /* offline = */ true); if (r < 0) return r; } @@ -3850,8 +3964,6 @@ static int context_copy_blocks(Context *context) { if (r < 0) return r; - log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); - if (p->siblings[VERITY_HASH] && !partition_defer(p->siblings[VERITY_HASH])) { r = partition_format_verity_hash(context, p->siblings[VERITY_HASH], /* node = */ NULL, partition_target_path(t)); @@ -4270,6 +4382,16 @@ static int context_mkfs(Context *context) { if (r < 0) return r; + if (p->encrypt != ENCRYPT_OFF && t->loop) { + r = partition_target_grow(t, p->new_size); + if (r < 0) + return r; + + r = partition_encrypt(context, p, t, /* offline = */ false); + if (r < 0) + return log_error_errno(r, "Failed to encrypt device: %m"); + } + log_info("Formatting future partition %" PRIu64 ".", p->partno); /* If we're not writing to a loop device or if we're populating a read-only filesystem, we @@ -4305,17 +4427,17 @@ static int context_mkfs(Context *context) { if (partition_needs_populate(p) && !root) { assert(t->loop); - r = partition_populate_filesystem(context, p, t->loop->node); + r = partition_populate_filesystem(context, p, partition_target_path(t)); if (r < 0) return r; } - if (p->encrypt != ENCRYPT_OFF) { + if (p->encrypt != ENCRYPT_OFF && !t->loop) { r = partition_target_grow(t, p->new_size); if (r < 0) return r; - r = partition_encrypt(context, p, partition_target_path(t)); + r = partition_encrypt(context, p, t, /* offline = */ true); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); }