diff --git a/meson.build b/meson.build index 2d41ff8799d..55ed9687985 100644 --- a/meson.build +++ b/meson.build @@ -1325,7 +1325,10 @@ if want_libcryptsetup != 'false' and not skip_deps foreach ident : ['crypt_set_metadata_size', 'crypt_activate_by_signed_key', - 'crypt_token_max'] + 'crypt_token_max', + 'crypt_reencrypt_init_by_passphrase', + 'crypt_reencrypt', + 'crypt_set_data_offset'] have_ident = have and cc.has_function( ident, prefix : '#include ', diff --git a/mkosi.conf.d/10-systemd.conf b/mkosi.conf.d/10-systemd.conf index 5bc13f919ab..f9e4d086160 100644 --- a/mkosi.conf.d/10-systemd.conf +++ b/mkosi.conf.d/10-systemd.conf @@ -21,6 +21,8 @@ Packages= coreutils diffutils dnsmasq + dosfstools + e2fsprogs findutils gcc # For sanitizer libraries gdb @@ -29,6 +31,7 @@ Packages= kexec-tools kmod less + mtools nano nftables openssl @@ -40,6 +43,7 @@ Packages= util-linux valgrind wireguard-tools + xfsprogs zsh BuildPackages= diff --git a/mkosi.conf.d/arch/10-mkosi.arch b/mkosi.conf.d/arch/10-mkosi.arch index 883dc1fcd5c..993e3dd3441 100644 --- a/mkosi.conf.d/arch/10-mkosi.arch +++ b/mkosi.conf.d/arch/10-mkosi.arch @@ -11,8 +11,10 @@ Distribution=arch [Content] Packages= alsa-lib + btrfs-progs compsize dhcp + f2fs-tools fuse2 gnutls iproute diff --git a/mkosi.conf.d/debian/10-mkosi.debian b/mkosi.conf.d/debian/10-mkosi.debian index b2da9b62327..c52d2e9e965 100644 --- a/mkosi.conf.d/debian/10-mkosi.debian +++ b/mkosi.conf.d/debian/10-mkosi.debian @@ -9,7 +9,9 @@ Release=testing [Content] Packages= + btrfs-progs cryptsetup-bin + f2fs-tools fdisk fuse gcc # Provides libasan/libubsan diff --git a/mkosi.conf.d/fedora/10-mkosi.fedora b/mkosi.conf.d/fedora/10-mkosi.fedora index c76f479956c..5f92aab95cd 100644 --- a/mkosi.conf.d/fedora/10-mkosi.fedora +++ b/mkosi.conf.d/fedora/10-mkosi.fedora @@ -10,9 +10,11 @@ Release=37 [Content] Packages= alsa-lib + btrfs-progs compsize cryptsetup dhcp-server + f2fs-tools fuse glib2 glibc-minimal-langpack diff --git a/mkosi.conf.d/opensuse/10-mkosi.opensuse b/mkosi.conf.d/opensuse/10-mkosi.opensuse index 7a212237f2f..417827f7c0d 100644 --- a/mkosi.conf.d/opensuse/10-mkosi.opensuse +++ b/mkosi.conf.d/opensuse/10-mkosi.opensuse @@ -9,7 +9,9 @@ Release=tumbleweed [Content] Packages= + btrfs-progs dbus-1 + f2fs-tools fuse gcc # Provides libasan/libubsan glibc-32bit diff --git a/mkosi.conf.d/ubuntu/10-mkosi.ubuntu b/mkosi.conf.d/ubuntu/10-mkosi.ubuntu index c7badf5742a..346b129e521 100644 --- a/mkosi.conf.d/ubuntu/10-mkosi.ubuntu +++ b/mkosi.conf.d/ubuntu/10-mkosi.ubuntu @@ -10,7 +10,9 @@ Repositories=main,universe [Content] Packages= + btrfs-progs cryptsetup-bin + f2fs-tools fdisk fuse gcc # Provides libasan/libubsan diff --git a/src/partition/repart.c b/src/partition/repart.c index f4c37087662..5d4cdbe6892 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -90,6 +90,12 @@ /* LUKS2 takes off 16M of the partition size with its metadata by default */ #define LUKS2_METADATA_SIZE (16ULL*1024ULL*1024ULL) +/* To do LUKS2 offline encryption, we need to keep some extra free space at the end of the partition. */ +#define LUKS2_METADATA_KEEP_FREE (LUKS2_METADATA_SIZE*2ULL) + +/* LUKS2 volume key size. */ +#define VOLUME_KEY_SIZE (512ULL/8ULL) + /* Note: When growing and placing new partitions we always align to 4K sector size. It's how newer hard disks * are designed, and if everything is aligned to that performance is best. And for older hard disks with 512B * sector size devices were generally assumed to have an even number of sectors, hence at the worst we'll @@ -271,12 +277,7 @@ static const char *verity_mode_table[_VERITY_MODE_MAX] = { [VERITY_SIG] = "signature", }; -#if HAVE_LIBCRYPTSETUP -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); -#else DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); -#endif - DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode); static uint64_t round_down_size(uint64_t v, uint64_t p) { @@ -563,7 +564,7 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) { uint64_t d = 0; if (p->encrypt != ENCRYPT_OFF) - d += round_up_size(LUKS2_METADATA_SIZE, context->grain_size); + d += round_up_size(LUKS2_METADATA_KEEP_FREE, context->grain_size); if (p->copy_blocks_size != UINT64_MAX) d += round_up_size(p->copy_blocks_size, context->grain_size); @@ -1593,6 +1594,15 @@ static int partition_read_definition(Partition *p, const char *path, const char return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Minimize= can only be enabled if Format= is set"); + if ((!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)) && !mkfs_supports_root_option(p->format) && geteuid() != 0) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EPERM), + "Need to be root to populate %s filesystems with CopyFiles=/MakeDirectories=", + p->format); + + if (p->format && fstype_is_ro(p->format) && strv_isempty(p->copy_files) && strv_isempty(p->make_directories)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "Cannot format %s filesystem without source files, refusing", p->format); + if (p->verity != VERITY_OFF || p->encrypt != ENCRYPT_OFF) { r = dlopen_cryptsetup(); if (r < 0) @@ -2982,18 +2992,191 @@ static int context_wipe_and_discard(Context *context, bool from_scratch) { return 0; } -static int partition_encrypt( +typedef struct { + LoopDevice *loop; + int fd; + char *path; + int whole_fd; +} 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; +} + +static const char* partition_target_path(PartitionTarget *t) { + assert(t); + assert(t->loop || t->path); + return t->loop ? t->loop->node : t->path; +} + +static PartitionTarget *partition_target_free(PartitionTarget *t) { + if (!t) + return NULL; + + loop_device_unref(t->loop); + safe_close(t->fd); + unlink_and_free(t->path); + + return mfree(t); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(PartitionTarget*, partition_target_free); + +static int partition_target_prepare( Context *context, Partition *p, - const char *node, - struct crypt_device **ret_cd, - char **ret_volume, - int *ret_fd) { -#if HAVE_LIBCRYPTSETUP + uint64_t size, + bool need_path, + PartitionTarget **ret) { + + _cleanup_(partition_target_freep) PartitionTarget *t = NULL; + struct stat st; + int whole_fd; + int r; + + assert(context); + assert(p); + assert(ret); + + assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + + if (fstat(whole_fd, &st) < 0) + return -errno; + + /* If we're operating on a block device, we definitely need privileges to access block devices so we + * can just use loop devices as our target. Otherwise, we're operating on a regular file, in that + * case, let's write to regular files and copy those into the final image so we can run without root + * privileges. On filesystems with reflinking support, we can take advantage of this and just reflink + * the result into the image. + */ + + t = new0(PartitionTarget, 1); + if (!t) + return log_oom(); + + if (S_ISBLK(st.st_mode) || (p->format && !mkfs_supports_root_option(p->format))) { + _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; + + /* Loopback block devices are not only useful to turn regular files into block devices, but + * also to cut out sections of block devices into new block devices. */ + + r = loop_device_make(whole_fd, O_RDWR, p->offset, size, 0, 0, LOCK_EX, &d); + if (r < 0) + return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); + + *t = (PartitionTarget) { + .loop = TAKE_PTR(d), + .fd = -1, + }; + } else if (need_path) { + _cleanup_(unlink_and_freep) char *temp = NULL; + _cleanup_close_ int fd = -1; + const char *vt; + + r = var_tmp_dir(&vt); + if (r < 0) + return log_error_errno(r, "Could not determine temporary directory: %m"); + + temp = path_join(vt, "repart-XXXXXX"); + if (!temp) + return log_oom(); + + fd = mkostemp_safe(temp); + if (fd < 0) + return log_error_errno(fd, "Failed to create temporary file: %m"); + + if (ftruncate(fd, size) < 0) + return log_error_errno(errno, "Failed to truncate temporary file to %s: %m", + FORMAT_BYTES(size)); + + *t = (PartitionTarget) { + .fd = TAKE_FD(fd), + .path = TAKE_PTR(temp), + }; + } else { + if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to seek to partition offset: %m"); + + *t = (PartitionTarget) { + .fd = -1, + .whole_fd = whole_fd, + }; + } + + *ret = TAKE_PTR(t); + + return 0; +} + +static int partition_target_grow(PartitionTarget *t, uint64_t size) { + int r; + + assert(t); + + if (t->loop) { + r = loop_device_refresh_size(t->loop, UINT64_MAX, size); + if (r < 0) + return log_error_errno(r, "Failed to refresh loopback device size: %m"); + } else if (t->fd >= 0) { + if (ftruncate(t->fd, size) < 0) + return log_error_errno(errno, "Failed to grow '%s' to %s by truncation: %m", + t->path, FORMAT_BYTES(size)); + } + + return 0; +} + +static int partition_target_sync(Context *context, Partition *p, PartitionTarget *t) { + int whole_fd, r; + + assert(context); + assert(p); + assert(t); + + assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + + if (t->loop) { + r = loop_device_sync(t->loop); + if (r < 0) + return log_error_errno(r, "Failed to sync loopback device: %m"); + } else if (t->fd >= 0) { + if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to seek to partition offset: %m"); + + r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes to partition: %m"); + } else { + if (fsync(t->whole_fd) < 0) + return log_error_errno(errno, "Failed to sync changes: %m"); + } + + return 0; +} + +static int partition_encrypt(Context *context, Partition *p, const char *node) { +#if HAVE_LIBCRYPTSETUP && HAVE_CRYPT_SET_DATA_OFFSET && HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE && HAVE_CRYPT_REENCRYPT + struct crypt_params_luks2 luks_params = { + .label = strempty(p->new_label), + .sector_size = context->sector_size, + .data_device = node, + }; + struct crypt_params_reencrypt reencrypt_params = { + .mode = CRYPT_REENCRYPT_ENCRYPT, + .direction = CRYPT_REENCRYPT_BACKWARD, + .resilience = "datashift", + .data_shift = LUKS2_METADATA_SIZE / 512, + .luks2 = &luks_params, + .flags = CRYPT_REENCRYPT_INITIALIZE_ONLY|CRYPT_REENCRYPT_MOVE_FIRST_SEGMENT, + }; _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; - _cleanup_(erase_and_freep) void *volume_key = NULL; - _cleanup_free_ char *dm_name = NULL, *vol = NULL; - size_t volume_key_size = 256 / 8; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_fclose_ FILE *h = NULL; + _cleanup_free_ char *hp = NULL; + const char *passphrase = NULL; + size_t passphrase_size = 0; sd_id128_t uuid; int r; @@ -3001,52 +3184,54 @@ static int partition_encrypt( assert(p); assert(p->encrypt != ENCRYPT_OFF); - log_debug("Encryption mode for partition %" PRIu64 ": %s", p->partno, encrypt_mode_to_string(p->encrypt)); - r = dlopen_cryptsetup(); if (r < 0) return log_error_errno(r, "libcryptsetup not found, cannot encrypt: %m"); - if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0) - return log_oom(); - - if (ret_volume) { - vol = path_join("/dev/mapper/", dm_name); - if (!vol) - return log_oom(); - } - r = derive_uuid(p->new_uuid, "luks-uuid", &uuid); if (r < 0) return r; log_info("Encrypting future partition %" PRIu64 "...", p->partno); - volume_key = malloc(volume_key_size); - if (!volume_key) - return log_oom(); - - r = crypto_random_bytes(volume_key, volume_key_size); + r = fopen_temporary(NULL, &h, &hp); if (r < 0) - return log_error_errno(r, "Failed to generate volume key: %m"); + return log_error_errno(r, "Failed to create temporary LUKS header file: %m"); - r = sym_crypt_init(&cd, node); + /* Weird cryptsetup requirement which requires the header file to be the size of at least one sector. */ + r = posix_fallocate(fileno(h), 0, context->sector_size); if (r < 0) - return log_error_errno(r, "Failed to allocate libcryptsetup context: %m"); + return log_error_errno(r, "Failed to grow temporary LUKS header file: %m"); + + r = sym_crypt_init(&cd, hp); + 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"); + + 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_format(cd, CRYPT_LUKS2, "aes", "xts-plain64", SD_ID128_TO_UUID_STRING(uuid), - volume_key, - volume_key_size, - &(struct crypt_params_luks2) { - .label = strempty(p->new_label), - .sector_size = context->sector_size, - }); + NULL, + VOLUME_KEY_SIZE, + &luks_params); if (r < 0) return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); @@ -3054,17 +3239,19 @@ static int partition_encrypt( r = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, - volume_key, - volume_key_size, + NULL, + VOLUME_KEY_SIZE, strempty(arg_key), arg_key_size); if (r < 0) return log_error_errno(r, "Failed to add LUKS2 key: %m"); + + passphrase = strempty(arg_key); + passphrase_size = arg_key_size; } if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) { #if HAVE_TPM2 - _cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(erase_and_freep) void *secret = NULL; _cleanup_free_ void *pubkey = NULL; @@ -3108,12 +3295,12 @@ static int partition_encrypt( keyslot = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, - volume_key, - volume_key_size, + NULL, + VOLUME_KEY_SIZE, base64_encoded, strlen(base64_encoded)); if (keyslot < 0) - return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node); + return log_error_errno(keyslot, "Failed to add new TPM2 key: %m"); r = tpm2_make_luks2_json( keyslot, @@ -3132,79 +3319,292 @@ static int partition_encrypt( r = cryptsetup_add_token_json(cd, v); if (r < 0) return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m"); + + passphrase = base64_encoded; + passphrase_size = strlen(base64_encoded); #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for TPM2 enrollment not enabled."); #endif } - r = sym_crypt_activate_by_volume_key( + r = sym_crypt_reencrypt_init_by_passphrase( cd, - dm_name, - volume_key, - volume_key_size, - arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0); + 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 activate LUKS superblock: %m"); + 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. */ + + 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_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; + + 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); log_info("Successfully encrypted future partition %" PRIu64 ".", p->partno); - if (ret_fd) { - _cleanup_close_ int dev_fd = -1; - - 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); - - *ret_fd = TAKE_FD(dev_fd); - } - - if (ret_cd) - *ret_cd = TAKE_PTR(cd); - if (ret_volume) - *ret_volume = TAKE_PTR(vol); - return 0; #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot encrypt: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcryptsetup is not supported or is missing required symbols, cannot encrypt: %m"); #endif } -static int deactivate_luks(struct crypt_device *cd, const char *node) { +static int partition_format_verity_hash( + Context *context, + Partition *p, + const char *data_node) { + #if HAVE_LIBCRYPTSETUP + Partition *dp; + _cleanup_(partition_target_freep) PartitionTarget *t = NULL; + _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_free_ uint8_t *rh = NULL; + size_t rhs; int r; - if (!cd) + assert(context); + assert(p); + assert(data_node); + + if (p->dropped) return 0; - assert(node); + if (PARTITION_EXISTS(p)) /* Never format existing partitions */ + return 0; - /* 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. */ + if (p->verity != VERITY_HASH) + return 0; - r = sym_crypt_deactivate_by_name(cd, basename(node), CRYPT_DEACTIVATE_FORCE); + if (partition_skip(p)) + return 0; + + assert_se(dp = p->siblings[VERITY_DATA]); + assert(!dp->dropped); + + r = dlopen_cryptsetup(); if (r < 0) - return log_error_errno(r, "Failed to deactivate LUKS device: %m"); + return log_error_errno(r, "libcryptsetup not found, cannot setup verity: %m"); + + r = partition_target_prepare(context, p, p->new_size, /*need_path=*/ true, &t); + if (r < 0) + return r; + + r = sym_crypt_init(&cd, partition_target_path(t)); + 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_node, + .flags = CRYPT_VERITY_CREATE_HASH, + .hash_name = "sha256", + .hash_type = 1, + .data_block_size = context->sector_size, + .hash_block_size = context->sector_size, + .salt_size = 32, + }); + if (r < 0) + return log_error_errno(r, "Failed to setup verity hash data: %m"); + + r = partition_target_sync(context, p, t); + if (r < 0) + return r; + + 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"); + + 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 1; -#else return 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity hashes: %m"); #endif } +static int sign_verity_roothash( + const uint8_t *roothash, + size_t roothash_size, + uint8_t **ret_signature, + size_t *ret_signature_size) { + +#if HAVE_OPENSSL + _cleanup_(BIO_freep) BIO *rb = NULL; + _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL; + _cleanup_free_ char *hex = NULL; + _cleanup_free_ uint8_t *sig = NULL; + int sigsz; + + assert(roothash); + assert(roothash_size > 0); + assert(ret_signature); + assert(ret_signature_size); + + hex = hexmem(roothash, roothash_size); + if (!hex) + return log_oom(); + + rb = BIO_new_mem_buf(hex, -1); + if (!rb) + return log_oom(); + + p7 = PKCS7_sign(arg_certificate, arg_private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY); + if (!p7) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s", + ERR_error_string(ERR_get_error(), NULL)); + + sigsz = i2d_PKCS7(p7, &sig); + if (sigsz < 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", + ERR_error_string(ERR_get_error(), NULL)); + + *ret_signature = TAKE_PTR(sig); + *ret_signature_size = sigsz; + + return 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "openssl is not supported, cannot setup verity signature: %m"); +#endif +} + +static int partition_format_verity_sig(Context *context, Partition *p) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ uint8_t *sig = NULL; + _cleanup_free_ char *text = NULL; + Partition *hp; + uint8_t fp[X509_FINGERPRINT_SIZE]; + size_t sigsz = 0, padsz; /* avoid false maybe-uninitialized warning */ + int whole_fd, r; + + assert(p->verity == VERITY_SIG); + + if (p->dropped) + return 0; + + if (PARTITION_EXISTS(p)) + return 0; + + if (partition_skip(p)) + return 0; + + assert_se(hp = p->siblings[VERITY_HASH]); + assert(!hp->dropped); + + assert(arg_certificate); + + assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + + r = sign_verity_roothash(hp->roothash, hp->roothash_size, &sig, &sigsz); + if (r < 0) + return r; + + r = x509_fingerprint(arg_certificate, fp); + if (r < 0) + return log_error_errno(r, "Unable to calculate X509 certificate fingerprint: %m"); + + r = json_build(&v, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("rootHash", JSON_BUILD_HEX(hp->roothash, hp->roothash_size)), + JSON_BUILD_PAIR( + "certificateFingerprint", + JSON_BUILD_HEX(fp, sizeof(fp)) + ), + JSON_BUILD_PAIR("signature", JSON_BUILD_BASE64(sig, sigsz)) + ) + ); + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + r = json_variant_format(v, 0, &text); + if (r < 0) + return log_error_errno(r, "Failed to format JSON object: %m"); + + padsz = round_up_size(strlen(text), 4096); + assert_se(padsz <= p->new_size); + + r = strgrowpad0(&text, padsz); + if (r < 0) + return log_error_errno(r, "Failed to pad string to %s", FORMAT_BYTES(padsz)); + + if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to seek to partition offset: %m"); + + r = loop_write(whole_fd, text, padsz, /*do_poll=*/ false); + if (r < 0) + return log_error_errno(r, "Failed to write verity signature to partition: %m"); + + if (fsync(whole_fd) < 0) + return log_error_errno(errno, "Failed to synchronize verity signature JSON: %m"); + + return 0; +} + static int context_copy_blocks(Context *context) { - int whole_fd = -1, r; + int r; assert(context); /* Copy in file systems on the block level */ LIST_FOREACH(partitions, p, context->partitions) { - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; - _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; - _cleanup_free_ char *encrypted = NULL; - _cleanup_close_ int encrypted_dev_fd = -1; - int target_fd; + _cleanup_(partition_target_freep) PartitionTarget *t = NULL; if (p->copy_blocks_fd < 0) continue; @@ -3220,63 +3620,52 @@ static int context_copy_blocks(Context *context) { assert(p->new_size != UINT64_MAX); assert(p->copy_blocks_size != UINT64_MAX); - assert(p->new_size >= p->copy_blocks_size); + assert(p->new_size >= p->copy_blocks_size + (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0)); - if (whole_fd < 0) - assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); - - if (p->encrypt != ENCRYPT_OFF) { - r = loop_device_make(whole_fd, O_RDWR, p->offset, p->new_size, 0, 0, LOCK_EX, &d); - if (r < 0) - return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); - - r = partition_encrypt(context, p, d->node, &cd, &encrypted, &encrypted_dev_fd); - if (r < 0) - return log_error_errno(r, "Failed to encrypt device: %m"); - - if (flock(encrypted_dev_fd, LOCK_EX) < 0) - return log_error_errno(errno, "Failed to lock LUKS device: %m"); - - target_fd = encrypted_dev_fd; - } else { - if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1) - return log_error_errno(errno, "Failed to seek to partition offset: %m"); - - target_fd = whole_fd; - } + r = partition_target_prepare(context, p, p->new_size, + /*need_path=*/ p->encrypt != ENCRYPT_OFF || p->siblings[VERITY_HASH], + &t); + 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); - r = copy_bytes(p->copy_blocks_fd, target_fd, p->copy_blocks_size, COPY_REFLINK); + r = copy_bytes(p->copy_blocks_fd, partition_target_fd(t), p->copy_blocks_size, COPY_REFLINK); if (r < 0) return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path); - if (fsync(target_fd) < 0) - return log_error_errno(errno, "Failed to synchronize copied data blocks: %m"); - if (p->encrypt != ENCRYPT_OFF) { - encrypted_dev_fd = safe_close(encrypted_dev_fd); - - r = deactivate_luks(cd, encrypted); + r = partition_encrypt(context, p, partition_target_path(t)); if (r < 0) return r; - - sym_crypt_free(cd); - cd = NULL; - - r = loop_device_sync(d); - if (r < 0) - return log_error_errno(r, "Failed to sync loopback device: %m"); } + r = partition_target_sync(context, p, t); + if (r < 0) + return r; + log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); + + if (p->siblings[VERITY_HASH]) { + r = partition_format_verity_hash(context, p->siblings[VERITY_HASH], + partition_target_path(t)); + if (r < 0) + return r; + } + + if (p->siblings[VERITY_SIG]) { + r = partition_format_verity_sig(context, p->siblings[VERITY_SIG]); + if (r < 0) + return r; + } } return 0; } static int do_copy_files(Partition *p, const char *root, const Set *denylist) { + int r; assert(p); @@ -3321,18 +3710,19 @@ static int do_copy_files(Partition *p, const char *root, const Set *denylist) { r = copy_tree_at( sfd, ".", pfd, fn, - UID_INVALID, GID_INVALID, + getuid(), getgid(), COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS, denylist); } else r = copy_tree_at( sfd, ".", tfd, ".", - UID_INVALID, GID_INVALID, + getuid(), getgid(), COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS, denylist); if (r < 0) - return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", *source, strempty(arg_root), *target); + return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m", + strempty(arg_root), *source, strempty(root), *target); } else { _cleanup_free_ char *dn = NULL, *fn = NULL; @@ -3382,7 +3772,7 @@ static int do_make_directories(Partition *p, const char *root) { STRV_FOREACH(d, p->make_directories) { - r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755); + r = mkdir_p_root(root, *d, getuid(), getgid(), 0755); if (r < 0) return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d); } @@ -3390,28 +3780,14 @@ static int do_make_directories(Partition *p, const char *root) { return 0; } -static int partition_populate_directory(Partition *p, const Set *denylist, char **ret_root, char **ret_tmp_root) { +static int partition_populate_directory(Partition *p, const Set *denylist, char **ret) { _cleanup_(rm_rf_physical_and_freep) char *root = NULL; int r; - assert(ret_root); - assert(ret_tmp_root); + assert(ret); - /* If we only have a single directory that's meant to become the root directory of the filesystem, - * we can shortcut this function and just use that directory as the root directory instead. If we - * allocate a temporary directory, it's stored in "ret_tmp_root" to indicate it should be removed. - * Otherwise, we return the directory to use in "root" to indicate it should not be removed. */ - - if (strv_length(p->copy_files) == 2 && strv_length(p->make_directories) == 0 && - streq(p->copy_files[1], "/") && set_isempty(denylist)) { - _cleanup_free_ char *s = NULL; - - r = chase_symlinks(p->copy_files[0], arg_root, CHASE_PREFIX_ROOT, &s, NULL); - if (r < 0) - return log_error_errno(r, "Failed to resolve source '%s%s': %m", strempty(arg_root), p->copy_files[0]); - - *ret_root = TAKE_PTR(s); - *ret_tmp_root = NULL; + if ((strv_isempty(p->copy_files) && strv_isempty(p->make_directories))) { + *ret = NULL; return 0; } @@ -3419,6 +3795,13 @@ static int partition_populate_directory(Partition *p, const Set *denylist, char if (r < 0) return log_error_errno(r, "Failed to create temporary directory: %m"); + if (chmod(root, 0755) < 0) + return log_error_errno(errno, "Failed to change mode of temporary directory: %m"); + + /* Make sure everything is owned by the user running repart so that make_filesystem() can map the + * user running repart to "root" in a user namespace to have the files owned by root in the final + * image. */ + r = do_copy_files(p, root, denylist); if (r < 0) return r; @@ -3427,8 +3810,7 @@ static int partition_populate_directory(Partition *p, const Set *denylist, char if (r < 0) return r; - *ret_root = NULL; - *ret_tmp_root = TAKE_PTR(root); + *ret = TAKE_PTR(root); return 0; } @@ -3537,7 +3919,7 @@ static int make_copy_files_denylist(Context *context, Set **ret) { static int context_mkfs(Context *context) { _cleanup_set_free_ Set *denylist = NULL; - int fd = -1, r; + int r; assert(context); @@ -3548,12 +3930,8 @@ static int context_mkfs(Context *context) { return r; LIST_FOREACH(partitions, p, context->partitions) { - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; - _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; - _cleanup_(rm_rf_physical_and_freep) char *tmp_root = NULL; - _cleanup_free_ char *encrypted = NULL, *root = NULL; - _cleanup_close_ int encrypted_dev_fd = -1; - const char *fsdev; + _cleanup_(rm_rf_physical_and_freep) char *root = NULL; + _cleanup_(partition_target_freep) PartitionTarget *t = NULL; if (p->dropped) continue; @@ -3573,214 +3951,73 @@ static int context_mkfs(Context *context) { assert(p->offset != UINT64_MAX); assert(p->new_size != UINT64_MAX); + assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0)); - if (fd < 0) - assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0); - - /* Loopback block devices are not only useful to turn regular files into block devices, but - * also to cut out sections of block devices into new block devices. */ - - r = loop_device_make(fd, O_RDWR, p->offset, p->new_size, 0, 0, LOCK_EX, &d); + /* If we're doing encryption, we make sure we keep free space at the end which is required + * for cryptsetup's offline encryption. */ + r = partition_target_prepare(context, p, + p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0), + /*need_path=*/ true, + &t); if (r < 0) - return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); - - if (p->encrypt != ENCRYPT_OFF) { - r = partition_encrypt(context, p, d->node, &cd, &encrypted, &encrypted_dev_fd); - if (r < 0) - return log_error_errno(r, "Failed to encrypt device: %m"); - - if (flock(encrypted_dev_fd, LOCK_EX) < 0) - return log_error_errno(errno, "Failed to lock LUKS device: %m"); - - fsdev = encrypted; - } else - fsdev = d->node; + return r; log_info("Formatting future partition %" PRIu64 ".", p->partno); - /* Ideally, we populate filesystems using our own code after creating the filesystem to - * ensure consistent handling of chattrs, xattrs and other similar things. However, when - * using read-only filesystems such as squashfs, we can't populate after creating the - * filesystem because it's read-only, so instead we create a temporary root to use as the - * source tree when generating the read-only filesystem. */ + /* We prefer (or are required in the case of read-only filesystems) to populate filesystems + * directly via the corresponding mkfs binary if it supports a --rootdir (or equivalent) + * option. To do that, we need to setup the final directory tree beforehand. */ - if (fstype_is_ro(p->format)) { - r = partition_populate_directory(p, denylist, &root, &tmp_root); + if (mkfs_supports_root_option(p->format)) { + r = partition_populate_directory(p, denylist, &root); if (r < 0) return r; } - r = make_filesystem(fsdev, p->format, strempty(p->new_label), root ?: tmp_root, p->fs_uuid, arg_discard); - if (r < 0) { - encrypted_dev_fd = safe_close(encrypted_dev_fd); - (void) deactivate_luks(cd, encrypted); + r = make_filesystem(partition_target_path(t), p->format, strempty(p->new_label), root, + p->fs_uuid, arg_discard); + if (r < 0) return r; - } log_info("Successfully formatted future partition %" PRIu64 ".", p->partno); - /* The file system is now created, no need to delay udev further */ - if (p->encrypt != ENCRYPT_OFF) - if (flock(encrypted_dev_fd, LOCK_UN) < 0) - return log_error_errno(errno, "Failed to unlock LUKS device: %m"); - - /* Now, we can populate all the other filesystems that aren't read-only. */ - if (!fstype_is_ro(p->format)) { - r = partition_populate_filesystem(p, fsdev, denylist); - if (r < 0) { - encrypted_dev_fd = safe_close(encrypted_dev_fd); - (void) deactivate_luks(cd, encrypted); + /* Now, we can populate all the other filesystems that we couldn't populate earlier. */ + if (!mkfs_supports_root_option(p->format)) { + r = partition_populate_filesystem(p, partition_target_path(t), denylist); + if (r < 0) return r; - } + } + + if (p->encrypt != ENCRYPT_OFF) { + r = partition_target_grow(t, p->new_size); + if (r < 0) + return r; + + r = partition_encrypt(context, p, partition_target_path(t)); + if (r < 0) + return log_error_errno(r, "Failed to encrypt device: %m"); } /* Note that we always sync explicitly here, since mkfs.fat doesn't do that on its own, and * if we don't sync before detaching a block device the in-flight sectors possibly won't hit * the disk. */ - if (p->encrypt != ENCRYPT_OFF) { - if (fsync(encrypted_dev_fd) < 0) - return log_error_errno(errno, "Failed to synchronize LUKS volume: %m"); - encrypted_dev_fd = safe_close(encrypted_dev_fd); - - r = deactivate_luks(cd, encrypted); - if (r < 0) - return r; - - sym_crypt_free(cd); - cd = NULL; - } - - r = loop_device_sync(d); - if (r < 0) - return log_error_errno(r, "Failed to sync loopback device: %m"); - } - - 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 hashes: %m"); -#endif -} - -static int context_verity_hash(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; - - if (partition_skip(p)) - 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, 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, 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); + r = partition_target_sync(context, p, t); 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->siblings[VERITY_HASH]) { + r = partition_format_verity_hash(context, p->siblings[VERITY_HASH], + partition_target_path(t)); + if (r < 0) + return r; } - 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; + if (p->siblings[VERITY_SIG]) { + r = partition_format_verity_sig(context, p->siblings[VERITY_SIG]); + if (r < 0) + return r; } - - p->roothash = TAKE_PTR(rh); - p->roothash_size = rhs; } return 0; @@ -3840,130 +4077,6 @@ static int parse_private_key(const char *key, size_t key_size, EVP_PKEY **ret) { #endif } -static int sign_verity_roothash( - const uint8_t *roothash, - size_t roothash_size, - uint8_t **ret_signature, - size_t *ret_signature_size) { - -#if HAVE_OPENSSL - _cleanup_(BIO_freep) BIO *rb = NULL; - _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL; - _cleanup_free_ char *hex = NULL; - _cleanup_free_ uint8_t *sig = NULL; - int sigsz; - - assert(roothash); - assert(roothash_size > 0); - assert(ret_signature); - assert(ret_signature_size); - - hex = hexmem(roothash, roothash_size); - if (!hex) - return log_oom(); - - rb = BIO_new_mem_buf(hex, -1); - if (!rb) - return log_oom(); - - p7 = PKCS7_sign(arg_certificate, arg_private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY); - if (!p7) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s", - ERR_error_string(ERR_get_error(), NULL)); - - sigsz = i2d_PKCS7(p7, &sig); - if (sigsz < 0) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", - ERR_error_string(ERR_get_error(), NULL)); - - *ret_signature = TAKE_PTR(sig); - *ret_signature_size = sigsz; - - return 0; -#else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "openssl is not supported, cannot setup verity signature: %m"); -#endif -} - -static int context_verity_sig(Context *context) { - int fd = -1, r; - - assert(context); - - LIST_FOREACH(partitions, p, context->partitions) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - _cleanup_free_ uint8_t *sig = NULL; - _cleanup_free_ char *text = NULL; - Partition *hp; - uint8_t fp[X509_FINGERPRINT_SIZE]; - size_t sigsz = 0, padsz; /* avoid false maybe-uninitialized warning */ - - if (p->dropped) - continue; - - if (PARTITION_EXISTS(p)) - continue; - - if (p->verity != VERITY_SIG) - continue; - - if (partition_skip(p)) - continue; - - assert_se(hp = p->siblings[VERITY_HASH]); - assert(!hp->dropped); - - assert(arg_certificate); - - if (fd < 0) - assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0); - - r = sign_verity_roothash(hp->roothash, hp->roothash_size, &sig, &sigsz); - if (r < 0) - return r; - - r = x509_fingerprint(arg_certificate, fp); - if (r < 0) - return log_error_errno(r, "Unable to calculate X509 certificate fingerprint: %m"); - - r = json_build(&v, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR("rootHash", JSON_BUILD_HEX(hp->roothash, hp->roothash_size)), - JSON_BUILD_PAIR( - "certificateFingerprint", - JSON_BUILD_HEX(fp, sizeof(fp)) - ), - JSON_BUILD_PAIR("signature", JSON_BUILD_BASE64(sig, sigsz)) - ) - ); - if (r < 0) - return log_error_errno(r, "Failed to build JSON object: %m"); - - r = json_variant_format(v, 0, &text); - if (r < 0) - return log_error_errno(r, "Failed to format JSON object: %m"); - - padsz = round_up_size(strlen(text), 4096); - assert_se(padsz <= p->new_size); - - r = strgrowpad0(&text, padsz); - if (r < 0) - return log_error_errno(r, "Failed to pad string to %s", FORMAT_BYTES(padsz)); - - if (lseek(fd, p->offset, SEEK_SET) == (off_t) -1) - return log_error_errno(errno, "Failed to seek to partition offset: %m"); - - r = loop_write(fd, text, padsz, /*do_poll=*/ false); - if (r < 0) - return log_error_errno(r, "Failed to write verity signature to partition: %m"); - - if (fsync(fd) < 0) - return log_error_errno(errno, "Failed to synchronize verity signature JSON: %m"); - } - - return 0; -} - static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *ret) { struct { sd_id128_t type_uuid; @@ -4535,14 +4648,6 @@ static int context_write_partition_table( if (r < 0) return r; - r = context_verity_hash(context); - if (r < 0) - return r; - - r = context_verity_sig(context); - if (r < 0) - return r; - r = context_mangle_partitions(context); if (r < 0) return r; @@ -5119,9 +5224,8 @@ static int context_minimize(Context *context) { return log_error_errno(r, "Could not determine temporary directory: %m"); LIST_FOREACH(partitions, p, context->partitions) { - _cleanup_(rm_rf_physical_and_freep) char *tmp_root = NULL; + _cleanup_(rm_rf_physical_and_freep) char *root = NULL; _cleanup_(unlink_and_freep) char *temp = NULL; - _cleanup_free_ char *root = NULL; _cleanup_close_ int fd = -1; sd_id128_t fs_uuid; uint64_t fsz; @@ -5144,7 +5248,9 @@ static int context_minimize(Context *context) { if (r < 0) return log_error_errno(r, "Failed to generate temporary file path: %m"); - if (!fstype_is_ro(p->format)) { + if (fstype_is_ro(p->format)) + fs_uuid = p->fs_uuid; + else { fd = open(temp, O_CREAT|O_EXCL|O_CLOEXEC|O_RDWR|O_NOCTTY, 0600); if (fd < 0) return log_error_errno(errno, "Failed to open temporary file %s: %m", temp); @@ -5160,16 +5266,15 @@ static int context_minimize(Context *context) { r = sd_id128_randomize(&fs_uuid); if (r < 0) return r; - } else { - r = partition_populate_directory(p, denylist, &root, &tmp_root); - if (r < 0) - return r; - - fs_uuid = p->fs_uuid; } - r = make_filesystem(temp, p->format, strempty(p->new_label), root ?: tmp_root, fs_uuid, - arg_discard); + if (mkfs_supports_root_option(p->format)) { + r = partition_populate_directory(p, denylist, &root); + if (r < 0) + return r; + } + + r = make_filesystem(temp, p->format, strempty(p->new_label), root, fs_uuid, arg_discard); if (r < 0) return r; @@ -5180,9 +5285,11 @@ static int context_minimize(Context *context) { continue; } - r = partition_populate_filesystem(p, temp, denylist); - if (r < 0) - return r; + if (!mkfs_supports_root_option(p->format)) { + r = partition_populate_filesystem(p, temp, denylist); + if (r < 0) + return r; + } /* Other filesystems need to be provided with a pre-sized loopback file and will adapt to * fully occupy it. Because we gave the filesystem a 1T sparse file, we need to shrink the @@ -5211,14 +5318,15 @@ static int context_minimize(Context *context) { if (ftruncate(fd, fsz)) return log_error_errno(errno, "Failed to truncate temporary file to %s: %m", FORMAT_BYTES(fsz)); - r = make_filesystem(temp, p->format, strempty(p->new_label), root ?: tmp_root, p->fs_uuid, - arg_discard); + r = make_filesystem(temp, p->format, strempty(p->new_label), root, p->fs_uuid, arg_discard); if (r < 0) return r; - r = partition_populate_filesystem(p, temp, denylist); - if (r < 0) - return r; + if (!mkfs_supports_root_option(p->format)) { + r = partition_populate_filesystem(p, temp, denylist); + if (r < 0) + return r; + } p->copy_blocks_path = TAKE_PTR(temp); } diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 92a9bcde4f7..4574a7899e1 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -245,7 +245,6 @@ int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offs assert(infd >= 0); assert(outfd >= 0); - assert(sz > 0); r = fd_verify_regular(outfd); if (r < 0) diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 401e7a3f9c7..7437cbed6ba 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -49,6 +49,18 @@ int (*sym_crypt_token_max)(const char *type); #endif crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type); int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size); +#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE +int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params); +#endif +#if HAVE_CRYPT_REENCRYPT +int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr)); +#endif +int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable); +#if HAVE_CRYPT_SET_DATA_OFFSET +int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset); +#endif +int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file); +int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable); static void cryptsetup_log_glue(int level, const char *msg, void *usrptr) { @@ -234,7 +246,19 @@ int dlopen_cryptsetup(void) { DLSYM_ARG(crypt_token_max), #endif DLSYM_ARG(crypt_token_status), - DLSYM_ARG(crypt_volume_key_get)); + DLSYM_ARG(crypt_volume_key_get), +#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE + DLSYM_ARG(crypt_reencrypt_init_by_passphrase), +#endif +#if HAVE_CRYPT_REENCRYPT + DLSYM_ARG(crypt_reencrypt), +#endif + DLSYM_ARG(crypt_metadata_locking), +#if HAVE_CRYPT_SET_DATA_OFFSET + DLSYM_ARG(crypt_set_data_offset), +#endif + DLSYM_ARG(crypt_header_restore), + DLSYM_ARG(crypt_volume_key_keyring)); if (r <= 0) return r; diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index b390dc9a5cb..5ff439d9c29 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -64,6 +64,18 @@ static inline int crypt_token_max(_unused_ const char *type) { #endif extern crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type); extern int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size); +#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE +extern int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params); +#endif +#if HAVE_CRYPT_REENCRYPT +extern int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr)); +#endif +extern int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable); +#if HAVE_CRYPT_SET_DATA_OFFSET +extern int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset); +#endif +extern int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file); +extern int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL); diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index 8161dbf825f..b1c8fe7bc49 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -2,11 +2,15 @@ #include +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" #include "id128-util.h" #include "mkfs-util.h" #include "mountpoint-util.h" #include "path-util.h" #include "process-util.h" +#include "stat-util.h" #include "stdio-util.h" #include "string-util.h" #include "utf8.h" @@ -33,6 +37,10 @@ int mkfs_exists(const char *fstype) { return true; } +int mkfs_supports_root_option(const char *fstype) { + return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "vfat"); +} + static int mangle_linux_fs_label(const char *s, size_t max_len, char **ret) { /* Not more than max_len bytes (12 or 16) */ @@ -87,6 +95,96 @@ static int mangle_fat_label(const char *s, char **ret) { return 0; } +static int setup_userns(uid_t uid, gid_t gid) { + int r; + + /* mkfs programs tend to keep ownership intact when bootstrapping themselves from a root directory. + * However, we'd like for the files to be owned by root instead, so we fork off a user namespace and + * inside of it, map the uid/gid of the root directory to root in the user namespace. mkfs programs + * will pick up on this and the files will be owned by root in the generated filesystem. */ + + r = write_string_filef("/proc/self/uid_map", WRITE_STRING_FILE_DISABLE_BUFFER, + UID_FMT " " UID_FMT " " UID_FMT, 0u, uid, 1u); + if (r < 0) + return log_error_errno(r, + "Failed to write mapping for "UID_FMT" to /proc/self/uid_map: %m", + uid); + + r = write_string_file("/proc/self/setgroups", "deny", WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_error_errno(r, "Failed to write 'deny' to /proc/self/setgroups: %m"); + + r = write_string_filef("/proc/self/gid_map", WRITE_STRING_FILE_DISABLE_BUFFER, + UID_FMT " " UID_FMT " " UID_FMT, 0u, gid, 1u); + if (r < 0) + return log_error_errno(r, + "Failed to write mapping for "UID_FMT" to /proc/self/gid_map: %m", + gid); + + return 0; +} + +static int do_mcopy(const char *node, const char *root) { + _cleanup_strv_free_ char **argv = NULL; + _cleanup_closedir_ DIR *rootdir = NULL; + struct stat st; + int r; + + assert(node); + assert(root); + + /* Return early if there's nothing to copy. */ + if (dir_is_empty(root, /*ignore_hidden_or_backup=*/ false)) + return 0; + + argv = strv_new("mcopy", "-b", "-s", "-p", "-Q", "-n", "-m", "-i", node); + if (!argv) + return log_oom(); + + /* mcopy copies the top level directory instead of everything in it so we have to pass all + * the subdirectories to mcopy instead to end up with the correct directory structure. */ + + rootdir = opendir(root); + if (!rootdir) + return log_error_errno(errno, "Failed to open directory '%s'", root); + + FOREACH_DIRENT(de, rootdir, return -errno) { + char *p = path_join(root, de->d_name); + if (!p) + return log_oom(); + + r = strv_consume(&argv, TAKE_PTR(p)); + if (r < 0) + return log_oom(); + } + + r = strv_extend(&argv, "::"); + if (r < 0) + return log_oom(); + + if (stat(root, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", root); + + r = safe_fork("(mcopy)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_NEW_USERNS, NULL); + if (r < 0) + return r; + if (r == 0) { + r = setup_userns(st.st_uid, st.st_gid); + if (r < 0) + _exit(EXIT_FAILURE); + + /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling + * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */ + execvpe("mcopy", argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1")); + + log_error_errno(errno, "Failed to execute mcopy: %m"); + + _exit(EXIT_FAILURE); + } + + return 0; +} + int make_filesystem( const char *node, const char *fstype, @@ -96,7 +194,9 @@ int make_filesystem( bool discard) { _cleanup_free_ char *mkfs = NULL, *mangled_label = NULL; + _cleanup_strv_free_ char **argv = NULL; char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {}; + struct stat st; int r; assert(node); @@ -128,9 +228,9 @@ int make_filesystem( "Don't know how to create read-only file system '%s', refusing.", fstype); } else { - if (root) + if (root && !mkfs_supports_root_option(fstype)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Populating with source tree is only supported for read-only filesystems"); + "Populating with source tree is not supported for %s", fstype); r = mkfs_exists(fstype); if (r < 0) return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype); @@ -169,101 +269,151 @@ int make_filesystem( if (isempty(vol_id)) assert_se(sd_id128_to_uuid_string(uuid, vol_id)); - r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR, NULL); + /* When changing this conditional, also adjust the log statement below. */ + if (streq(fstype, "ext2")) { + argv = strv_new(mkfs, + "-q", + "-L", label, + "-U", vol_id, + "-I", "256", + "-m", "0", + "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1", + node); + if (!argv) + return log_oom(); + + if (root) { + r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false); + if (r < 0) + return log_oom(); + } + + } else if (STR_IN_SET(fstype, "ext3", "ext4")) { + argv = strv_new(mkfs, + "-q", + "-L", label, + "-U", vol_id, + "-I", "256", + "-O", "has_journal", + "-m", "0", + "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1", + node); + + if (root) { + r = strv_extend_strv(&argv, STRV_MAKE("-d", root), false); + if (r < 0) + return log_oom(); + } + + } else if (streq(fstype, "btrfs")) { + argv = strv_new(mkfs, + "-q", + "-L", label, + "-U", vol_id, + node); + if (!argv) + return log_oom(); + + if (!discard) { + r = strv_extend(&argv, "--nodiscard"); + if (r < 0) + return log_oom(); + } + + if (root) { + r = strv_extend_strv(&argv, STRV_MAKE("-r", root), false); + if (r < 0) + return log_oom(); + } + + } else if (streq(fstype, "f2fs")) { + argv = strv_new(mkfs, + "-q", + "-g", /* "default options" */ + "-f", /* force override, without this it doesn't seem to want to write to an empty partition */ + "-l", label, + "-U", vol_id, + "-t", one_zero(discard), + node); + + } else if (streq(fstype, "xfs")) { + const char *j; + + j = strjoina("uuid=", vol_id); + + argv = strv_new(mkfs, + "-q", + "-L", label, + "-m", j, + "-m", "reflink=1", + node); + if (!argv) + return log_oom(); + + if (!discard) { + r = strv_extend(&argv, "-K"); + if (r < 0) + return log_oom(); + } + + } else if (streq(fstype, "vfat")) + + argv = strv_new(mkfs, + "-i", vol_id, + "-n", label, + "-F", "32", /* yes, we force FAT32 here */ + node); + + else if (streq(fstype, "swap")) + /* TODO: add --quiet here if + * https://github.com/util-linux/util-linux/issues/1499 resolved. */ + + argv = strv_new(mkfs, + "-L", label, + "-U", vol_id, + node); + + else if (streq(fstype, "squashfs")) + + argv = strv_new(mkfs, + root, node, + "-quiet", + "-noappend"); + else + /* Generic fallback for all other file systems */ + argv = strv_new(mkfs, node); + + if (!argv) + return log_oom(); + + if (root && stat(root, &st) < 0) + return log_error_errno(errno, "Failed to stat %s: %m", root); + + r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|(root ? FORK_NEW_USERNS : 0), NULL); if (r < 0) return r; if (r == 0) { /* Child */ - /* When changing this conditional, also adjust the log statement below. */ - if (streq(fstype, "ext2")) - (void) execlp(mkfs, mkfs, - "-q", - "-L", label, - "-U", vol_id, - "-I", "256", - "-m", "0", - "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1", - node, NULL); + if (root) { + r = setup_userns(st.st_uid, st.st_gid); + if (r < 0) + _exit(EXIT_FAILURE); + } - else if (STR_IN_SET(fstype, "ext3", "ext4")) - (void) execlp(mkfs, mkfs, - "-q", - "-L", label, - "-U", vol_id, - "-I", "256", - "-O", "has_journal", - "-m", "0", - "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1", - node, NULL); - - else if (streq(fstype, "btrfs")) { - (void) execlp(mkfs, mkfs, - "-q", - "-L", label, - "-U", vol_id, - node, - discard ? NULL : "--nodiscard", - NULL); - - } else if (streq(fstype, "f2fs")) { - (void) execlp(mkfs, mkfs, - "-q", - "-g", /* "default options" */ - "-f", /* force override, without this it doesn't seem to want to write to an empty partition */ - "-l", label, - "-U", vol_id, - "-t", one_zero(discard), - node, - NULL); - - } else if (streq(fstype, "xfs")) { - const char *j; - - j = strjoina("uuid=", vol_id); - - (void) execlp(mkfs, mkfs, - "-q", - "-L", label, - "-m", j, - "-m", "reflink=1", - node, - discard ? NULL : "-K", - NULL); - - } else if (streq(fstype, "vfat")) - - (void) execlp(mkfs, mkfs, - "-i", vol_id, - "-n", label, - "-F", "32", /* yes, we force FAT32 here */ - node, NULL); - - else if (streq(fstype, "swap")) - /* TODO: add --quiet here if - * https://github.com/util-linux/util-linux/issues/1499 resolved. */ - - (void) execlp(mkfs, mkfs, - "-L", label, - "-U", vol_id, - node, NULL); - - else if (streq(fstype, "squashfs")) - - (void) execlp(mkfs, mkfs, - root, node, - "-quiet", - "-noappend", - NULL); - else - /* Generic fallback for all other file systems */ - (void) execlp(mkfs, mkfs, node, NULL); + execvp(mkfs, argv); log_error_errno(errno, "Failed to execute %s: %m", mkfs); _exit(EXIT_FAILURE); } + if (root && streq(fstype, "vfat")) { + r = do_mcopy(node, root); + if (r < 0) + return r; + } + if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "f2fs", "xfs", "vfat", "swap")) log_info("%s successfully formatted as %s (label \"%s\", uuid %s)", node, fstype, label, vol_id); diff --git a/src/shared/mkfs-util.h b/src/shared/mkfs-util.h index 3125ef176d6..62061c6647d 100644 --- a/src/shared/mkfs-util.h +++ b/src/shared/mkfs-util.h @@ -9,4 +9,6 @@ int mkfs_exists(const char *fstype); +int mkfs_supports_root_option(const char *fstype); + int make_filesystem(const char *node, const char *fstype, const char *label, const char *root, sd_id128_t uuid, bool discard); diff --git a/test/TEST-58-REPART/test.sh b/test/TEST-58-REPART/test.sh index 1638cb2b163..0d513cf85bf 100755 --- a/test/TEST-58-REPART/test.sh +++ b/test/TEST-58-REPART/test.sh @@ -12,13 +12,15 @@ TEST_FORCE_NEWIMAGE=1 test_append_files() { if ! get_bool "${TEST_NO_QEMU:=}"; then install_dmevent - if command -v openssl >/dev/null 2>&1; then - inst_binary openssl - fi instmods dm_verity =md generate_module_dependencies image_install -o /sbin/mksquashfs fi + + inst_binary mcopy + if command -v openssl >/dev/null 2>&1; then + inst_binary openssl + fi } do_test "$@" diff --git a/test/test-functions b/test/test-functions index 18c76a69abd..e59a20f091e 100644 --- a/test/test-functions +++ b/test/test-functions @@ -1310,6 +1310,11 @@ install_missing_libraries() { inst_simple "${path}/engines-3/capi.so" || true inst_simple "${path}/engines-3/loader_attic.so" || true inst_simple "${path}/engines-3/padlock.so" || true + + # Binaries from mtools depend on the gconv modules to translate between codepages. Because there's no + # pkg-config file for these, we copy every gconv/ directory we can find in /usr/lib and /usr/lib64. + # shellcheck disable=SC2046 + inst_recursive $(find /usr/lib* -name gconv 2>/dev/null) } cleanup_loopdev() { diff --git a/test/units/testsuite-58.sh b/test/units/testsuite-58.sh index 121fabc0afe..a225ac8beec 100755 --- a/test/units/testsuite-58.sh +++ b/test/units/testsuite-58.sh @@ -3,6 +3,13 @@ set -eux set -o pipefail +runas() { + declare userid=$1 + shift + # shellcheck disable=SC2016 + su "$userid" -s /bin/sh -c 'XDG_RUNTIME_DIR=/run/user/$UID exec "$@"' -- sh "$@" +} + if ! command -v systemd-repart &>/dev/null; then echo "no systemd-repart" >/skipped exit 0 @@ -89,17 +96,17 @@ test_basic() { local defs imgs output local loop volume - defs="$(mktemp --directory "/tmp/test-repart.XXXXXXXXXX")" - imgs="$(mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")" + defs="$(runas testuser mktemp --directory "/tmp/test-repart.XXXXXXXXXX")" + imgs="$(runas testuser mktemp --directory "/var/tmp/test-repart.XXXXXXXXXX")" # shellcheck disable=SC2064 trap "rm -rf '$defs' '$imgs'" RETURN # 1. create an empty image - systemd-repart --empty=create \ - --size=1G \ - --seed="$seed" \ - "$imgs/zzz" + runas testuser systemd-repart --empty=create \ + --size=1G \ + --seed="$seed" \ + "$imgs/zzz" output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') @@ -133,11 +140,11 @@ SizeMaxBytes=64M PaddingMinBytes=92M EOF - systemd-repart --definitions="$defs" \ - --dry-run=no \ - --seed="$seed" \ - --include-partitions=home,swap \ - "$imgs/zzz" + runas testuser systemd-repart --definitions="$defs" \ + --dry-run=no \ + --seed="$seed" \ + --include-partitions=home,swap \ + "$imgs/zzz" output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') @@ -150,11 +157,11 @@ last-lba: 2097118 $imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\" $imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\"" - systemd-repart --definitions="$defs" \ - --dry-run=no \ - --seed="$seed" \ - --exclude-partitions=root \ - "$imgs/zzz" + runas testuser systemd-repart --definitions="$defs" \ + --dry-run=no \ + --seed="$seed" \ + --exclude-partitions=root \ + "$imgs/zzz" output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') @@ -167,10 +174,10 @@ last-lba: 2097118 $imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\" $imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\"" - systemd-repart --definitions="$defs" \ - --dry-run=no \ - --seed="$seed" \ - "$imgs/zzz" + runas testuser systemd-repart --definitions="$defs" \ + --dry-run=no \ + --seed="$seed" \ + "$imgs/zzz" output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') @@ -203,10 +210,10 @@ EOF echo "Label=ignored_label" >>"$defs/home.conf" echo "UUID=b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" >>"$defs/home.conf" - systemd-repart --definitions="$defs" \ - --dry-run=no \ - --seed="$seed" \ - "$imgs/zzz" + runas testuser systemd-repart --definitions="$defs" \ + --dry-run=no \ + --seed="$seed" \ + "$imgs/zzz" output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') @@ -224,11 +231,11 @@ $imgs/zzz5 : start= 1908696, size= 188416, type=0FC63DAF-8483-4772-8E79 # 4. Resizing to 2G - systemd-repart --definitions="$defs" \ - --size=2G \ - --dry-run=no \ - --seed="$seed" \ - "$imgs/zzz" + runas testuser systemd-repart --definitions="$defs" \ + --size=2G \ + --dry-run=no \ + --seed="$seed" \ + "$imgs/zzz" output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') @@ -256,11 +263,11 @@ UUID=2a1d97e1d0a346cca26eadc643926617 CopyBlocks=$imgs/block-copy EOF - systemd-repart --definitions="$defs" \ - --size=3G \ - --dry-run=no \ - --seed="$seed" \ - "$imgs/zzz" + runas testuser systemd-repart --definitions="$defs" \ + --size=3G \ + --dry-run=no \ + --seed="$seed" \ + "$imgs/zzz" output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') @@ -279,11 +286,6 @@ $imgs/zzz6 : start= 4194264, size= 2097152, type=0FC63DAF-8483-4772-8E79 cmp --bytes=$((4096*10240)) --ignore-initial=0:$((512*4194264)) "$imgs/block-copy" "$imgs/zzz" - if systemd-detect-virt --quiet --container; then - echo "Skipping encrypt tests in container." - return - fi - # 6. Testing Format=/Encrypt=/CopyFiles= cat >"$defs/extra3.conf" <"$defs/esp.conf" <