1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-12 13:18:14 +03:00

repart: Do online encryption when loop devices are available

Online encryption is much faster then offline encryption when using
cryptsetup. To take advantage of this, when loop devices are available,
let's use online encryption instead off offline encryption to encrypt
partitions.

The online encryption logic is the same logic as was removed in
48a09a8fff, except that it's now
integrated with PartitionTarget to ensure that logic outside of
partition_target_encrypt() has to minimally change regardless of
whether we're doing online or offline encryption.
This commit is contained in:
Daan De Meyer 2023-06-05 13:15:04 +02:00
parent 6aea5ce814
commit a09ae91538

View File

@ -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");
}