From c93b0efdb9ee8e8a68ba323785a69f8c0f874276 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 10 Oct 2022 15:45:24 +0200 Subject: [PATCH 01/17] btrfs-util: Remove bogus assert() If size is zero, BTRFS_IOC_CLONE_RANGE will copy until the end of the source file. --- src/shared/btrfs-util.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 92a9bcde4f..4574a7899e 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) From ddf615a11c7cc38f0077bc67aa78c8b1b2f54ea2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 11 Oct 2022 00:10:39 +0200 Subject: [PATCH 02/17] mkfs-util: Make argument handling for mkfs binaries more flexible Preparation for the next commit. --- src/shared/mkfs-util.c | 182 ++++++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 83 deletions(-) diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index 8161dbf825..0f23a96de2 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -96,6 +96,7 @@ 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)] = {}; int r; @@ -169,95 +170,110 @@ int make_filesystem( if (isempty(vol_id)) assert_se(sd_id128_to_uuid_string(uuid, vol_id)); + /* 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); + + 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); + + 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(); + } + + } 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(); + r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR, 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); - - 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); From 59e2be46ccaef58b4cc652c6ef8cd3507726d215 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 7 Oct 2022 21:21:46 +0200 Subject: [PATCH 03/17] mkfs-util: Add root support for ext and btrfs For these filesysrems, it's useful to provide the filesystem upfront so that we don't have to mount it later which requires root privileges. --- src/partition/repart.c | 30 ++++++++++++++++++------------ src/shared/mkfs-util.c | 34 +++++++++++++++++++++++++++++----- src/shared/mkfs-util.h | 2 ++ 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index f4c3708766..d042043576 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3604,7 +3604,7 @@ static int context_mkfs(Context *context) { * 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. */ - if (fstype_is_ro(p->format)) { + if (mkfs_supports_root_option(p->format)) { r = partition_populate_directory(p, denylist, &root, &tmp_root); if (r < 0) return r; @@ -3625,7 +3625,7 @@ static int context_mkfs(Context *context) { 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)) { + if (!mkfs_supports_root_option(p->format)) { r = partition_populate_filesystem(p, fsdev, denylist); if (r < 0) { encrypted_dev_fd = safe_close(encrypted_dev_fd); @@ -5144,7 +5144,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,12 +5162,12 @@ static int context_minimize(Context *context) { r = sd_id128_randomize(&fs_uuid); if (r < 0) return r; - } else { + } + + if (mkfs_supports_root_option(p->format)) { 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, @@ -5180,9 +5182,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 @@ -5216,9 +5220,11 @@ static int context_minimize(Context *context) { 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/mkfs-util.c b/src/shared/mkfs-util.c index 0f23a96de2..f7f4a35212 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -33,6 +33,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"); +} + static int mangle_linux_fs_label(const char *s, size_t max_len, char **ret) { /* Not more than max_len bytes (12 or 16) */ @@ -129,9 +133,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); @@ -171,7 +175,7 @@ int make_filesystem( assert_se(sd_id128_to_uuid_string(uuid, vol_id)); /* When changing this conditional, also adjust the log statement below. */ - if (streq(fstype, "ext2")) + if (streq(fstype, "ext2")) { argv = strv_new(mkfs, "-q", "-L", label, @@ -180,8 +184,16 @@ int make_filesystem( "-m", "0", "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1", node); + if (!argv) + return log_oom(); - else if (STR_IN_SET(fstype, "ext3", "ext4")) + 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, @@ -192,7 +204,13 @@ int make_filesystem( "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1", node); - else if (streq(fstype, "btrfs")) { + 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, @@ -207,6 +225,12 @@ int make_filesystem( 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", diff --git a/src/shared/mkfs-util.h b/src/shared/mkfs-util.h index 3125ef176d..62061c6647 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); From 68665704dc9bb706d2b993808839d59dbabd7a9d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 9 Oct 2022 19:52:08 +0200 Subject: [PATCH 04/17] mkosi: Add mkfs tools to mkosi image Useful for testing systemd-repart --- mkosi.conf.d/10-systemd.conf | 2 ++ mkosi.conf.d/arch/10-mkosi.arch | 2 ++ mkosi.conf.d/debian/10-mkosi.debian | 2 ++ mkosi.conf.d/fedora/10-mkosi.fedora | 2 ++ mkosi.conf.d/opensuse/10-mkosi.opensuse | 2 ++ mkosi.conf.d/ubuntu/10-mkosi.ubuntu | 2 ++ 6 files changed, 12 insertions(+) diff --git a/mkosi.conf.d/10-systemd.conf b/mkosi.conf.d/10-systemd.conf index 5bc13f919a..0915e71357 100644 --- a/mkosi.conf.d/10-systemd.conf +++ b/mkosi.conf.d/10-systemd.conf @@ -21,6 +21,7 @@ Packages= coreutils diffutils dnsmasq + e2fsprogs findutils gcc # For sanitizer libraries gdb @@ -40,6 +41,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 883dc1fcd5..993e3dd344 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 b2da9b6232..c52d2e9e96 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 c76f479956..5f92aab95c 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 7a212237f2..417827f7c0 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 c7badf5742..346b129e52 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 From bf3598befff0137592834465ef728fdaabf1e778 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 11 Oct 2022 10:50:58 +0200 Subject: [PATCH 05/17] mkfs-util: Add support to populate vfat without mounting using mcopy mkfs.vfat doesn't support specifying a root directory to bootstrap the filesystem from (see https://github.com/dosfstools/dosfstools/issues/183). Instead, we can use the mcopy tool from the mtools package to copy files into the vfat filesystem after creating it without needing to mount the vfat filesystem. --- mkosi.conf.d/10-systemd.conf | 2 ++ src/shared/mkfs-util.c | 64 +++++++++++++++++++++++++++++++++++- test/TEST-58-REPART/test.sh | 1 + test/test-functions | 5 +++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/mkosi.conf.d/10-systemd.conf b/mkosi.conf.d/10-systemd.conf index 0915e71357..f9e4d08616 100644 --- a/mkosi.conf.d/10-systemd.conf +++ b/mkosi.conf.d/10-systemd.conf @@ -21,6 +21,7 @@ Packages= coreutils diffutils dnsmasq + dosfstools e2fsprogs findutils gcc # For sanitizer libraries @@ -30,6 +31,7 @@ Packages= kexec-tools kmod less + mtools nano nftables openssl diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index f7f4a35212..2adb2a25c7 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -2,11 +2,14 @@ #include +#include "dirent-util.h" +#include "fd-util.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" @@ -34,7 +37,7 @@ int mkfs_exists(const char *fstype) { } int mkfs_supports_root_option(const char *fstype) { - return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs"); + 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) { @@ -91,6 +94,59 @@ static int mangle_fat_label(const char *s, char **ret) { return 0; } +static int do_mcopy(const char *node, const char *root) { + _cleanup_strv_free_ char **argv = NULL; + _cleanup_closedir_ DIR *rootdir = NULL; + 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(); + + 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) { + /* 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, @@ -304,6 +360,12 @@ int make_filesystem( _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/test/TEST-58-REPART/test.sh b/test/TEST-58-REPART/test.sh index 1638cb2b16..fb4a05fc77 100755 --- a/test/TEST-58-REPART/test.sh +++ b/test/TEST-58-REPART/test.sh @@ -15,6 +15,7 @@ test_append_files() { if command -v openssl >/dev/null 2>&1; then inst_binary openssl fi + inst_binary mcopy instmods dm_verity =md generate_module_dependencies image_install -o /sbin/mksquashfs diff --git a/test/test-functions b/test/test-functions index 18c76a69ab..e59a20f091 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() { From 98e0456ec04c2b210291f41f6d8f8527263e0c67 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 13 Oct 2022 14:36:06 +0200 Subject: [PATCH 06/17] repart: Let libcryptsetup calculate the volume key We also bump the volume key size to 512 bits. --- src/partition/repart.c | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index d042043576..edc085dc15 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -90,6 +90,9 @@ /* LUKS2 takes off 16M of the partition size with its metadata by default */ #define LUKS2_METADATA_SIZE (16ULL*1024ULL*1024ULL) +/* 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 @@ -2991,9 +2994,7 @@ static int partition_encrypt( int *ret_fd) { #if HAVE_LIBCRYPTSETUP _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; sd_id128_t uuid; int r; @@ -3022,14 +3023,6 @@ static int partition_encrypt( 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); - if (r < 0) - return log_error_errno(r, "Failed to generate volume key: %m"); - r = sym_crypt_init(&cd, node); if (r < 0) return log_error_errno(r, "Failed to allocate libcryptsetup context: %m"); @@ -3041,8 +3034,8 @@ static int partition_encrypt( "aes", "xts-plain64", SD_ID128_TO_UUID_STRING(uuid), - volume_key, - volume_key_size, + NULL, + VOLUME_KEY_SIZE, &(struct crypt_params_luks2) { .label = strempty(p->new_label), .sector_size = context->sector_size, @@ -3054,8 +3047,8 @@ 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) @@ -3108,8 +3101,8 @@ 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) @@ -3141,8 +3134,8 @@ static int partition_encrypt( r = sym_crypt_activate_by_volume_key( cd, dm_name, - volume_key, - volume_key_size, + 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"); From 48a09a8fff480aab9a68e95e95cc37f6b1438751 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 9 Oct 2022 20:46:59 +0200 Subject: [PATCH 07/17] repart: Do offline encryption instead of online Offline encryption can be done without mounting the luks device. For now we still use loop devices to split out the partition we want to write to but in a later commit we'll replace this with a regular file. For offline encryption, we need to keep 2x the luks header size space free at the end of the partition, so this means our encrypted partitions will be 16M larger than before. --- meson.build | 5 +- src/partition/repart.c | 278 ++++++++++++++++------------------- src/shared/cryptsetup-util.c | 26 +++- src/shared/cryptsetup-util.h | 12 ++ 4 files changed, 171 insertions(+), 150 deletions(-) diff --git a/meson.build b/meson.build index 2d41ff8799..55ed968798 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/src/partition/repart.c b/src/partition/repart.c index edc085dc15..1ecfc6b8fd 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -90,6 +90,9 @@ /* 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) @@ -274,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) { @@ -566,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); @@ -2985,16 +2983,27 @@ static int context_wipe_and_discard(Context *context, bool from_scratch) { return 0; } -static int partition_encrypt( - Context *context, - Partition *p, - const char *node, - struct crypt_device **ret_cd, - char **ret_volume, - int *ret_fd) { -#if HAVE_LIBCRYPTSETUP +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_free_ char *dm_name = NULL, *vol = NULL; + _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; @@ -3002,33 +3011,46 @@ 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); - r = sym_crypt_init(&cd, node); + r = fopen_temporary(NULL, &h, &hp); if (r < 0) - return log_error_errno(r, "Failed to allocate libcryptsetup context: %m"); + 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 = posix_fallocate(fileno(h), 0, context->sector_size); + if (r < 0) + 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", @@ -3036,10 +3058,7 @@ static int partition_encrypt( SD_ID128_TO_UUID_STRING(uuid), NULL, VOLUME_KEY_SIZE, - &(struct crypt_params_luks2) { - .label = strempty(p->new_label), - .sector_size = context->sector_size, - }); + &luks_params); if (r < 0) return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); @@ -3053,11 +3072,13 @@ static int partition_encrypt( 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; @@ -3106,7 +3127,7 @@ static int partition_encrypt( 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, @@ -3125,63 +3146,67 @@ 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, NULL, - VOLUME_KEY_SIZE, - arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0); + 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"); -#endif -} - -static int deactivate_luks(struct crypt_device *cd, const char *node) { -#if HAVE_LIBCRYPTSETUP - int r; - - if (!cd) - return 0; - - assert(node); - - /* 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(cd, basename(node), CRYPT_DEACTIVATE_FORCE); - if (r < 0) - return log_error_errno(r, "Failed to deactivate LUKS device: %m"); - - return 1; -#else - return 0; + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcryptsetup is not supported or is missing required symbols, cannot encrypt: %m"); #endif } @@ -3193,10 +3218,7 @@ static int context_copy_blocks(Context *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; if (p->copy_blocks_fd < 0) @@ -3213,7 +3235,7 @@ 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); @@ -3223,14 +3245,7 @@ static int context_copy_blocks(Context *context) { 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; + target_fd = d->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"); @@ -3245,24 +3260,15 @@ 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 (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, d->node); 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"); } + if (fsync(target_fd) < 0) + return log_error_errno(errno, "Failed to synchronize copied data blocks: %m"); + log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); } @@ -3541,12 +3547,9 @@ 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_free_ char *root = NULL; if (p->dropped) continue; @@ -3566,29 +3569,22 @@ 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. */ + * also to cut out sections of block devices into new block devices. If we're doing + * encryption, we make sure we keep free space at the end which is required for cryptsetup's + * offline encryption. */ - r = loop_device_make(fd, O_RDWR, p->offset, p->new_size, 0, 0, LOCK_EX, &d); + r = loop_device_make(fd, O_RDWR, p->offset, + p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0), + 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); - 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; - log_info("Formatting future partition %" PRIu64 ".", p->partno); /* Ideally, we populate filesystems using our own code after creating the filesystem to @@ -3603,47 +3599,33 @@ static int context_mkfs(Context *context) { 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(d->node, p->format, strempty(p->new_label), root ?: tmp_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 (!mkfs_supports_root_option(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); + r = partition_populate_filesystem(p, d->node, denylist); + if (r < 0) return r; - } + } + + if (p->encrypt != ENCRYPT_OFF) { + r = loop_device_refresh_size(d, UINT64_MAX, p->new_size); + if (r < 0) + return log_error_errno(r, "Failed to refresh loopback device size: %m"); + + r = partition_encrypt(context, p, d->node); + 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"); diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 401e7a3f9c..7437cbed6b 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 b390dc9a5c..5ff439d9c2 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); From 2b392d860e7fe1fcf9845d918ecacc9cbf464158 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 9 Oct 2022 22:26:10 +0200 Subject: [PATCH 08/17] repart: Move verity hash formatting into data partition functions Refactoring to make implementing rootless repart easier. --- src/partition/repart.c | 235 +++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 129 deletions(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index 1ecfc6b8fd..6056655f47 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3210,6 +3210,102 @@ static int partition_encrypt(Context *context, Partition *p, const char *node) { #endif } +static int partition_format_verity_hash( + Context *context, + Partition *p, + const char *data_node) { + +#if HAVE_LIBCRYPTSETUP + Partition *dp; + _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; + _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; + _cleanup_free_ uint8_t *rh = NULL; + size_t rhs; + int whole_fd, r; + + assert(context); + assert(p); + assert(data_node); + + if (p->dropped) + return 0; + + if (PARTITION_EXISTS(p)) /* Never format existing partitions */ + return 0; + + if (p->verity != VERITY_HASH) + return 0; + + if (partition_skip(p)) + return 0; + + assert_se(dp = p->siblings[VERITY_DATA]); + assert(!dp->dropped); + + assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + + r = dlopen_cryptsetup(); + if (r < 0) + return log_error_errno(r, "libcryptsetup not found, cannot setup verity: %m"); + + 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 verity hash partition %" PRIu64 ": %m", + p->partno); + + r = sym_crypt_init(&cd, d->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_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 = 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 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity hashes: %m"); +#endif +} + static int context_copy_blocks(Context *context) { int whole_fd = -1, r; @@ -3270,6 +3366,12 @@ static int context_copy_blocks(Context *context) { return log_error_errno(errno, "Failed to synchronize copied data blocks: %m"); 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], d->node); + if (r < 0) + return r; + } } return 0; @@ -3629,133 +3731,12 @@ static int context_mkfs(Context *context) { 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); - 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], d->node); + 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; - } - - p->roothash = TAKE_PTR(rh); - p->roothash_size = rhs; } return 0; @@ -4510,10 +4491,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; From 4ecd39c5114fe7662a0abc374278138eaa1d5eae Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 9 Oct 2022 22:30:05 +0200 Subject: [PATCH 09/17] repart: Move verity sig formatting into data partition functions Refactoring to make implementing rootless repart easier. --- src/partition/repart.c | 257 +++++++++++++++++++++-------------------- 1 file changed, 129 insertions(+), 128 deletions(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index 6056655f47..ba7fcc5e7a 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3306,6 +3306,123 @@ static int partition_format_verity_hash( #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; @@ -3372,6 +3489,12 @@ static int context_copy_blocks(Context *context) { 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; @@ -3737,6 +3860,12 @@ static int context_mkfs(Context *context) { 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; @@ -3796,130 +3925,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; @@ -4491,10 +4496,6 @@ static int context_write_partition_table( 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; From cf2ed23cd23d1236086280db1947fe7099caba9a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 10 Oct 2022 18:12:15 +0200 Subject: [PATCH 10/17] repart: Fix copy failure error message --- src/partition/repart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index ba7fcc5e7a..f99d475e7f 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3556,7 +3556,8 @@ static int do_copy_files(Partition *p, const char *root, const Set *denylist) { 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; From e59678b2cf42e4206ddabc959d3cf9a5a865ecdc Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 10 Oct 2022 23:34:04 +0200 Subject: [PATCH 11/17] repart: Ensure files end up owned by root in generated filesystems By forking off a user namespace before running mkfs and ID mapping the user running repart to root in the user namespace, we can make sure that files in the generated filesystems are all owned by root instead of the user running repart. To make this work we have to make sure that all the files in the root directory that's passed to the mkfs binary are owned by the user running repart, so we have to drop the shortcut for only a single root directory in partition_populate_directory(). --- src/partition/repart.c | 55 ++++++++++++++---------------------------- src/shared/mkfs-util.c | 50 +++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index f99d475e7f..0bc5055283 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3501,6 +3501,7 @@ static int context_copy_blocks(Context *context) { } static int do_copy_files(Partition *p, const char *root, const Set *denylist) { + int r; assert(p); @@ -3545,14 +3546,14 @@ 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) @@ -3607,7 +3608,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); } @@ -3615,35 +3616,20 @@ 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); - - /* 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; - return 0; - } + assert(ret); r = mkdtemp_malloc("/var/tmp/repart-XXXXXX", &root); if (r < 0) return log_error_errno(r, "Failed to create 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; @@ -3652,8 +3638,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; } @@ -3774,8 +3759,7 @@ static int context_mkfs(Context *context) { LIST_FOREACH(partitions, p, context->partitions) { _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; - _cleanup_(rm_rf_physical_and_freep) char *tmp_root = NULL; - _cleanup_free_ char *root = NULL; + _cleanup_(rm_rf_physical_and_freep) char *root = NULL; if (p->dropped) continue; @@ -3820,12 +3804,12 @@ static int context_mkfs(Context *context) { * source tree when generating the read-only filesystem. */ if (mkfs_supports_root_option(p->format)) { - r = partition_populate_directory(p, denylist, &root, &tmp_root); + r = partition_populate_directory(p, denylist, &root); if (r < 0) return r; } - r = make_filesystem(d->node, p->format, strempty(p->new_label), root ?: tmp_root, p->fs_uuid, arg_discard); + r = make_filesystem(d->node, p->format, strempty(p->new_label), root, p->fs_uuid, arg_discard); if (r < 0) return r; @@ -5073,9 +5057,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; @@ -5119,13 +5102,12 @@ static int context_minimize(Context *context) { } if (mkfs_supports_root_option(p->format)) { - r = partition_populate_directory(p, denylist, &root, &tmp_root); + r = partition_populate_directory(p, denylist, &root); if (r < 0) return r; } - r = make_filesystem(temp, p->format, strempty(p->new_label), root ?: tmp_root, fs_uuid, - arg_discard); + r = make_filesystem(temp, p->format, strempty(p->new_label), root, fs_uuid, arg_discard); if (r < 0) return r; @@ -5169,8 +5151,7 @@ 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; diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index 2adb2a25c7..b1c8fe7bc4 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -4,6 +4,7 @@ #include "dirent-util.h" #include "fd-util.h" +#include "fileio.h" #include "id128-util.h" #include "mkfs-util.h" #include "mountpoint-util.h" @@ -94,9 +95,39 @@ 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); @@ -131,10 +162,17 @@ static int do_mcopy(const char *node, const char *root) { 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")); @@ -158,6 +196,7 @@ int make_filesystem( _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); @@ -347,12 +386,21 @@ int make_filesystem( if (!argv) return log_oom(); - r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR, NULL); + 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 */ + if (root) { + r = setup_userns(st.st_uid, st.st_gid); + if (r < 0) + _exit(EXIT_FAILURE); + } + execvp(mkfs, argv); log_error_errno(errno, "Failed to execute %s: %m", mkfs); From 6d6cefad37e4bf098ed0df487f8bbfb4ed5a9d0b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 11 Oct 2022 10:56:16 +0200 Subject: [PATCH 12/17] repart: Fail early if we're missing privileges to populate a filesystem --- src/partition/repart.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/partition/repart.c b/src/partition/repart.c index 0bc5055283..6f4da2c6b5 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -1594,6 +1594,11 @@ 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->verity != VERITY_OFF || p->encrypt != ENCRYPT_OFF) { r = dlopen_cryptsetup(); if (r < 0) From 2a99f964ff76e3a81c415e1d1ad06ed57fe44db9 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 12 Oct 2022 14:40:29 +0200 Subject: [PATCH 13/17] repart: Skip partition_populate_directory() if no sources are provided --- src/partition/repart.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/partition/repart.c b/src/partition/repart.c index 6f4da2c6b5..494a3100b7 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3627,6 +3627,11 @@ static int partition_populate_directory(Partition *p, const Set *denylist, char assert(ret); + if ((strv_isempty(p->copy_files) && strv_isempty(p->make_directories))) { + *ret = NULL; + return 0; + } + r = mkdtemp_malloc("/var/tmp/repart-XXXXXX", &root); if (r < 0) return log_error_errno(r, "Failed to create temporary directory: %m"); From 0eb23798d5e8ad52d4a17e2de5f27b70a4f5b43b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 12 Oct 2022 14:53:48 +0200 Subject: [PATCH 14/17] repart: Fail early if no sources are provided for a read-only filesystem --- src/partition/repart.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/partition/repart.c b/src/partition/repart.c index 494a3100b7..4e309ab214 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -1599,6 +1599,10 @@ static int partition_read_definition(Partition *p, const char *path, const char "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) From 0b34f351d17332d2e5b2c2fbb0124d33fd080a31 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 10 Nov 2022 14:32:15 +0100 Subject: [PATCH 15/17] repart: Change mode of temporary root to 755 Let's make sure regular users can access read/execute files when we're populating from a temporary root. --- src/partition/repart.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/partition/repart.c b/src/partition/repart.c index 4e309ab214..b786678557 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3640,6 +3640,9 @@ 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. */ From a64769d605b21d183972b585719d99aa2af44977 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 10 Oct 2022 00:14:17 +0200 Subject: [PATCH 16/17] repart: Don't use loop devices when we're not operating on a block device When repart is not operating on a block device, if we avoid using any loop devices at all, it becomes possible to run repart without needing root privileges. Note that this also depends on the filesystems in use to support population without needing root privileges (specifically, squashfs, ext4 or btrfs). --- src/partition/repart.c | 270 ++++++++++++++++++++++++++++++++--------- 1 file changed, 210 insertions(+), 60 deletions(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index b786678557..5d4cdbe689 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -2992,6 +2992,170 @@ static int context_wipe_and_discard(Context *context, bool from_scratch) { return 0; } +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, + 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 = { @@ -3226,11 +3390,11 @@ static int partition_format_verity_hash( #if HAVE_LIBCRYPTSETUP Partition *dp; - _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; + _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 whole_fd, r; + int r; assert(context); assert(p); @@ -3251,19 +3415,15 @@ static int partition_format_verity_hash( assert_se(dp = p->siblings[VERITY_DATA]); assert(!dp->dropped); - assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); - r = dlopen_cryptsetup(); if (r < 0) return log_error_errno(r, "libcryptsetup not found, cannot setup verity: %m"); - r = loop_device_make(whole_fd, O_RDWR, p->offset, p->new_size, 0, 0, LOCK_EX, &d); + r = partition_target_prepare(context, p, p->new_size, /*need_path=*/ true, &t); if (r < 0) - return log_error_errno(r, - "Failed to make loopback device of verity hash partition %" PRIu64 ": %m", - p->partno); + return r; - r = sym_crypt_init(&cd, d->node); + r = sym_crypt_init(&cd, partition_target_path(t)); if (r < 0) return log_error_errno(r, "Failed to allocate libcryptsetup context: %m"); @@ -3281,6 +3441,10 @@ static int partition_format_verity_hash( 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"); @@ -3433,15 +3597,14 @@ static int partition_format_verity_sig(Context *context, Partition *p) { } 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_(loop_device_unrefp) LoopDevice *d = NULL; - int target_fd; + _cleanup_(partition_target_freep) PartitionTarget *t = NULL; if (p->copy_blocks_fd < 0) continue; @@ -3459,42 +3622,34 @@ static int context_copy_blocks(Context *context) { assert(p->copy_blocks_size != UINT64_MAX); 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); - - target_fd = d->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 (p->encrypt != ENCRYPT_OFF) { - r = partition_encrypt(context, p, d->node); + r = partition_encrypt(context, p, partition_target_path(t)); if (r < 0) return r; } - if (fsync(target_fd) < 0) - return log_error_errno(errno, "Failed to synchronize copied data blocks: %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], d->node); + r = partition_format_verity_hash(context, p->siblings[VERITY_HASH], + partition_target_path(t)); if (r < 0) return r; } @@ -3764,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); @@ -3775,8 +3930,8 @@ static int context_mkfs(Context *context) { return r; LIST_FOREACH(partitions, p, context->partitions) { - _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; _cleanup_(rm_rf_physical_and_freep) char *root = NULL; + _cleanup_(partition_target_freep) PartitionTarget *t = NULL; if (p->dropped) continue; @@ -3798,27 +3953,20 @@ static int context_mkfs(Context *context) { 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. If we're doing - * encryption, we make sure we keep free space at the end which is required for cryptsetup's - * offline encryption. */ - - r = loop_device_make(fd, O_RDWR, p->offset, - p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0), - 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); + 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 (mkfs_supports_root_option(p->format)) { r = partition_populate_directory(p, denylist, &root); @@ -3826,25 +3974,26 @@ static int context_mkfs(Context *context) { return r; } - r = make_filesystem(d->node, p->format, strempty(p->new_label), root, p->fs_uuid, arg_discard); + 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); - /* Now, we can populate all the other filesystems that aren't read-only. */ + /* 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, d->node, denylist); + r = partition_populate_filesystem(p, partition_target_path(t), denylist); if (r < 0) return r; } if (p->encrypt != ENCRYPT_OFF) { - r = loop_device_refresh_size(d, UINT64_MAX, p->new_size); + r = partition_target_grow(t, p->new_size); if (r < 0) - return log_error_errno(r, "Failed to refresh loopback device size: %m"); + return r; - r = partition_encrypt(context, p, d->node); + r = partition_encrypt(context, p, partition_target_path(t)); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); } @@ -3853,12 +4002,13 @@ static int context_mkfs(Context *context) { * if we don't sync before detaching a block device the in-flight sectors possibly won't hit * the disk. */ - r = loop_device_sync(d); + r = partition_target_sync(context, p, t); if (r < 0) - return log_error_errno(r, "Failed to sync loopback device: %m"); + return r; if (p->siblings[VERITY_HASH]) { - r = partition_format_verity_hash(context, p->siblings[VERITY_HASH], d->node); + r = partition_format_verity_hash(context, p->siblings[VERITY_HASH], + partition_target_path(t)); if (r < 0) return r; } From e2d057456d69906fab64984d93c51a839f2d8702 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 10 Nov 2022 15:40:00 +0100 Subject: [PATCH 17/17] repart: Run most repart integration tests without root privileges To make sure rootless mode keeps working, let's run all repart integration tests that we can without root privileges. The only ones we need to keep running with root privileges are the tests that operate on a block/loop device and those that use --image=. --- test/TEST-58-REPART/test.sh | 9 +- test/units/testsuite-58.sh | 305 +++++++++++++++++++----------------- 2 files changed, 170 insertions(+), 144 deletions(-) diff --git a/test/TEST-58-REPART/test.sh b/test/TEST-58-REPART/test.sh index fb4a05fc77..0d513cf85b 100755 --- a/test/TEST-58-REPART/test.sh +++ b/test/TEST-58-REPART/test.sh @@ -12,14 +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 - inst_binary mcopy 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/units/testsuite-58.sh b/test/units/testsuite-58.sh index 121fabc0af..a225ac8bee 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" <