Merge pull request #27925 from DaanDeMeyer/repart-encrypt

repart: Do online encryption when loop devices are available
This commit is contained in:
Daan De Meyer
2023-06-06 21:36:09 +02:00
committed by GitHub

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