diff --git a/src/partition/repart.c b/src/partition/repart.c index f99d475e7ff..0bc50552838 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 2adb2a25c76..b1c8fe7bc49 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);