1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2024-12-22 13:33:56 +03:00

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().
This commit is contained in:
Daan De Meyer 2022-10-10 23:34:04 +02:00
parent cf2ed23cd2
commit e59678b2cf
2 changed files with 67 additions and 38 deletions

View File

@ -3501,6 +3501,7 @@ static int context_copy_blocks(Context *context) {
} }
static int do_copy_files(Partition *p, const char *root, const Set *denylist) { static int do_copy_files(Partition *p, const char *root, const Set *denylist) {
int r; int r;
assert(p); assert(p);
@ -3545,14 +3546,14 @@ static int do_copy_files(Partition *p, const char *root, const Set *denylist) {
r = copy_tree_at( r = copy_tree_at(
sfd, ".", sfd, ".",
pfd, fn, pfd, fn,
UID_INVALID, GID_INVALID, getuid(), getgid(),
COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS, COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS,
denylist); denylist);
} else } else
r = copy_tree_at( r = copy_tree_at(
sfd, ".", sfd, ".",
tfd, ".", tfd, ".",
UID_INVALID, GID_INVALID, getuid(), getgid(),
COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS, COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS,
denylist); denylist);
if (r < 0) if (r < 0)
@ -3607,7 +3608,7 @@ static int do_make_directories(Partition *p, const char *root) {
STRV_FOREACH(d, p->make_directories) { 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) if (r < 0)
return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d); 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; 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; _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
int r; int r;
assert(ret_root); assert(ret);
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;
}
r = mkdtemp_malloc("/var/tmp/repart-XXXXXX", &root); r = mkdtemp_malloc("/var/tmp/repart-XXXXXX", &root);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to create temporary directory: %m"); 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); r = do_copy_files(p, root, denylist);
if (r < 0) if (r < 0)
return r; return r;
@ -3652,8 +3638,7 @@ static int partition_populate_directory(Partition *p, const Set *denylist, char
if (r < 0) if (r < 0)
return r; return r;
*ret_root = NULL; *ret = TAKE_PTR(root);
*ret_tmp_root = TAKE_PTR(root);
return 0; return 0;
} }
@ -3774,8 +3759,7 @@ static int context_mkfs(Context *context) {
LIST_FOREACH(partitions, p, context->partitions) { LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL; _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_(rm_rf_physical_and_freep) char *tmp_root = NULL; _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
_cleanup_free_ char *root = NULL;
if (p->dropped) if (p->dropped)
continue; continue;
@ -3820,12 +3804,12 @@ static int context_mkfs(Context *context) {
* source tree when generating the read-only filesystem. */ * source tree when generating the read-only filesystem. */
if (mkfs_supports_root_option(p->format)) { 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) if (r < 0)
return r; 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) if (r < 0)
return r; return r;
@ -5073,9 +5057,8 @@ static int context_minimize(Context *context) {
return log_error_errno(r, "Could not determine temporary directory: %m"); return log_error_errno(r, "Could not determine temporary directory: %m");
LIST_FOREACH(partitions, p, context->partitions) { 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_(unlink_and_freep) char *temp = NULL;
_cleanup_free_ char *root = NULL;
_cleanup_close_ int fd = -1; _cleanup_close_ int fd = -1;
sd_id128_t fs_uuid; sd_id128_t fs_uuid;
uint64_t fsz; uint64_t fsz;
@ -5119,13 +5102,12 @@ static int context_minimize(Context *context) {
} }
if (mkfs_supports_root_option(p->format)) { 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) if (r < 0)
return r; return r;
} }
r = make_filesystem(temp, p->format, strempty(p->new_label), root ?: tmp_root, fs_uuid, r = make_filesystem(temp, p->format, strempty(p->new_label), root, fs_uuid, arg_discard);
arg_discard);
if (r < 0) if (r < 0)
return r; return r;
@ -5169,8 +5151,7 @@ static int context_minimize(Context *context) {
if (ftruncate(fd, fsz)) if (ftruncate(fd, fsz))
return log_error_errno(errno, "Failed to truncate temporary file to %s: %m", FORMAT_BYTES(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, r = make_filesystem(temp, p->format, strempty(p->new_label), root, p->fs_uuid, arg_discard);
arg_discard);
if (r < 0) if (r < 0)
return r; return r;

View File

@ -4,6 +4,7 @@
#include "dirent-util.h" #include "dirent-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "fileio.h"
#include "id128-util.h" #include "id128-util.h"
#include "mkfs-util.h" #include "mkfs-util.h"
#include "mountpoint-util.h" #include "mountpoint-util.h"
@ -94,9 +95,39 @@ static int mangle_fat_label(const char *s, char **ret) {
return 0; 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) { static int do_mcopy(const char *node, const char *root) {
_cleanup_strv_free_ char **argv = NULL; _cleanup_strv_free_ char **argv = NULL;
_cleanup_closedir_ DIR *rootdir = NULL; _cleanup_closedir_ DIR *rootdir = NULL;
struct stat st;
int r; int r;
assert(node); assert(node);
@ -131,10 +162,17 @@ static int do_mcopy(const char *node, const char *root) {
if (r < 0) if (r < 0)
return log_oom(); 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); 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) if (r < 0)
return r; return r;
if (r == 0) { 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 /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling
* the stricter mcopy checks using MTOOLS_SKIP_CHECK. */ * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */
execvpe("mcopy", argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1")); 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_free_ char *mkfs = NULL, *mangled_label = NULL;
_cleanup_strv_free_ char **argv = NULL; _cleanup_strv_free_ char **argv = NULL;
char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {}; char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
struct stat st;
int r; int r;
assert(node); assert(node);
@ -347,12 +386,21 @@ int make_filesystem(
if (!argv) if (!argv)
return log_oom(); 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) if (r < 0)
return r; return r;
if (r == 0) { if (r == 0) {
/* Child */ /* Child */
if (root) {
r = setup_userns(st.st_uid, st.st_gid);
if (r < 0)
_exit(EXIT_FAILURE);
}
execvp(mkfs, argv); execvp(mkfs, argv);
log_error_errno(errno, "Failed to execute %s: %m", mkfs); log_error_errno(errno, "Failed to execute %s: %m", mkfs);