importd: support OS tree "mangling" unpriv too (#39406)

Split out of #38728 

(background: os tree "mangling" is what we do if a tarball with an OS
image inside it if is nested inside an extra top-level dir inside the
tarball, which we need to "mangle" and move everything inside one level
up)
This commit is contained in:
Lennart Poettering
2025-10-31 09:46:36 +01:00
committed by GitHub
7 changed files with 111 additions and 27 deletions
+20
View File
@@ -114,6 +114,26 @@ int path_is_extension_tree(ImageClass image_class, const char *path, const char
return 1;
}
int fd_is_os_tree(int fd) {
int r;
assert(fd >= 0);
r = open_extension_release_at(
fd,
IMAGE_MACHINE,
/* extension= */ NULL,
/* relax_extension_release_check= */ false,
/* ret_path= */ NULL,
/* ret_fd= */ NULL);
if (r == -ENOENT)
return false;
if (r < 0)
return r;
return true;
}
static int extension_release_strict_xattr_value(int extension_release_fd, const char *extension_release_dir_path, const char *filename) {
int r;
+1
View File
@@ -28,6 +28,7 @@ int path_is_extension_tree(ImageClass image_class, const char *path, const char
static inline int path_is_os_tree(const char *path) {
return path_is_extension_tree(_IMAGE_CLASS_INVALID, path, NULL, false);
}
int fd_is_os_tree(int fd);
int open_extension_release(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
int open_extension_release_at(int rfd, ImageClass image_class, const char *extension, bool relax_extension_release_check, char **ret_path, int *ret_fd);
+83 -22
View File
@@ -148,19 +148,27 @@ int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid) {
return TAKE_FD(pipefd[0]);
}
int import_mangle_os_tree(const char *path) {
int import_mangle_os_tree_fd(int tree_fd, int userns_fd, ImportFlags flags) {
_cleanup_free_ char *child = NULL, *t = NULL, *joined = NULL;
_cleanup_closedir_ DIR *d = NULL, *cd = NULL;
struct dirent *dent;
struct stat st;
int r;
assert(path);
assert(tree_fd >= 0);
if (FLAGS_SET(flags, IMPORT_FOREIGN_UID) && userns_fd >= 0)
return import_mangle_os_tree_fd_foreign(tree_fd, userns_fd);
/* Some tarballs contain a single top-level directory that contains the actual OS directory tree. Try to
* recognize this, and move the tree one level up. */
r = path_is_os_tree(path);
_cleanup_free_ char *path = NULL;
r = fd_get_path(tree_fd, &path);
if (r < 0)
return log_error_errno(r, "Failed to determine path of fd: %m");
r = fd_is_os_tree(tree_fd);
if (r < 0)
return log_error_errno(r, "Failed to determine whether '%s' is an OS tree: %m", path);
if (r > 0) {
@@ -170,9 +178,9 @@ int import_mangle_os_tree(const char *path) {
log_debug("Directory tree '%s' is not recognizable as OS tree, checking whether to rearrange it.", path);
d = opendir(path);
d = xopendirat(tree_fd, /* path= */ NULL, /* flags= */ 0);
if (!d)
return log_error_errno(r, "Failed to open directory '%s': %m", path);
return log_error_errno(errno, "Failed to open directory '%s': %m", path);
errno = 0;
dent = readdir_no_dot(d);
@@ -191,29 +199,29 @@ int import_mangle_os_tree(const char *path) {
errno = 0;
dent = readdir_no_dot(d);
if (dent) {
if (errno != 0)
return log_error_errno(errno, "Failed to iterate through directory '%s': %m", path);
log_debug("Directory '%s' does not look like an OS tree, and has multiple children, leaving as it is.", path);
return 0;
} else if (errno != 0)
return log_error_errno(errno, "Failed to iterate through directory '%s': %m", path);
_cleanup_close_ int child_fd = openat(dirfd(d), child, O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW|O_NONBLOCK);
if (child_fd < 0) {
if (IN_SET(errno, ENOTDIR, ELOOP)) {
log_debug_errno(errno, "Child '%s' of directory '%s' is not a directory, leaving things as they are.", child, path);
return 0;
}
return log_debug_errno(errno, "Failed to open file '%s/%s': %m", path, child);
}
if (fstatat(dirfd(d), child, &st, AT_SYMLINK_NOFOLLOW) < 0)
if (fstat(child_fd, &st) < 0)
return log_debug_errno(errno, "Failed to stat file '%s/%s': %m", path, child);
r = stat_verify_directory(&st);
if (r < 0) {
log_debug_errno(r, "Child '%s' of directory '%s' is not a directory, leaving things as they are.", child, path);
return 0;
}
joined = path_join(path, child);
if (!joined)
return log_oom();
r = path_is_os_tree(joined);
if (r == -ENOTDIR) {
log_debug("Directory '%s' does not look like an OS tree, and contains a single regular file only, leaving as it is.", path);
return 0;
}
r = fd_is_os_tree(child_fd);
if (r < 0)
return log_error_errno(r, "Failed to determine whether '%s' is an OS tree: %m", joined);
if (r == 0) {
@@ -230,7 +238,7 @@ int import_mangle_os_tree(const char *path) {
*
* Let's now rearrange things, moving everything in the inner directory one level up */
cd = xopendirat(dirfd(d), child, O_NOFOLLOW);
cd = take_fdopendir(&child_fd);
if (!cd)
return log_error_errno(errno, "Can't open directory '%s': %m", joined);
@@ -238,7 +246,7 @@ int import_mangle_os_tree(const char *path) {
/* Let's rename the child to an unguessable name so that we can be sure all files contained in it can be
* safely moved up and won't collide with the name. */
r = tempfn_random(child, NULL, &t);
r = tempfn_random(child, /* extra= */ NULL, &t);
if (r < 0)
return log_oom();
r = rename_noreplace(dirfd(d), child, dirfd(d), t);
@@ -259,15 +267,68 @@ int import_mangle_os_tree(const char *path) {
r = futimens(dirfd(d), (struct timespec[2]) { st.st_atim, st.st_mtim });
if (r < 0)
log_debug_errno(r, "Failed to adjust top-level timestamps '%s', ignoring: %m", path);
log_debug_errno(errno, "Failed to adjust top-level timestamps '%s', ignoring: %m", path);
r = fchmod_and_chown(dirfd(d), st.st_mode, st.st_uid, st.st_gid);
if (r < 0)
return log_error_errno(r, "Failed to adjust top-level directory mode/ownership '%s': %m", path);
log_info("Successfully rearranged OS tree.");
return 0;
}
int import_mangle_os_tree(const char *path, int userns_fd, ImportFlags flags) {
assert(path);
_cleanup_close_ int fd = open(path, O_DIRECTORY|O_CLOEXEC|O_PATH);
if (fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", path);
return import_mangle_os_tree_fd(fd, userns_fd, flags);
}
int import_mangle_os_tree_fd_foreign(
int tree_fd,
int userns_fd) {
int r;
assert(tree_fd >= 0);
assert(userns_fd >= 0);
r = safe_fork_full(
"mangle-tree",
/* stdio_fds= */ NULL,
(int[]) { userns_fd, tree_fd }, 2,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT,
/* ret_pid= */ NULL);
if (r < 0)
return r;
if (r == 0) {
/* child */
r = namespace_enter(
/* pidns_fd= */ -EBADF,
/* mntns_fd= */ -EBADF,
/* netns_fd= */ -EBADF,
userns_fd,
/* root_fd= */ -EBADF);
if (r < 0) {
log_error_errno(r, "Failed to join user namespace: %m");
_exit(EXIT_FAILURE);
}
r = import_mangle_os_tree_fd(tree_fd, /* userns_fd= */ -EBADF, /* flags= */ 0);
if (r < 0) {
log_error_errno(r, "Failed to mangle OS tree in foreign UID mode: %m");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
return 0;
}
bool import_validate_local(const char *name, ImportFlags flags) {
+3 -1
View File
@@ -37,7 +37,9 @@ typedef enum ImportFlags {
int import_fork_tar_c(int tree_fd, int userns_fd, PidRef *ret_pid);
int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid);
int import_mangle_os_tree(const char *path);
int import_mangle_os_tree_fd(int tree_fd, int userns_fd, ImportFlags flags);
int import_mangle_os_tree(const char *path, int userns_fd, ImportFlags flags);
int import_mangle_os_tree_fd_foreign(int tree_fd, int userns_fd);
bool import_validate_local(const char *name, ImportFlags flags);
+1 -1
View File
@@ -238,7 +238,7 @@ static int import_fs(int argc, char *argv[], void *userdata) {
return log_error_errno(r, "Failed to copy directory: %m");
}
r = import_mangle_os_tree(dest);
r = import_mangle_os_tree(dest, /* userns_fd= */ -EBADF, /* flags= */ 0);
if (r < 0)
return r;
+1 -1
View File
@@ -199,7 +199,7 @@ static int tar_import_finish(TarImport *i) {
assert_se(d = i->temp_path ?: i->local);
r = import_mangle_os_tree(d);
r = import_mangle_os_tree_fd(i->tree_fd, i->userns_fd, i->flags);
if (r < 0)
return r;
+2 -2
View File
@@ -497,7 +497,7 @@ static void tar_pull_job_on_finished(PullJob *j) {
tar_pull_report_progress(i, TAR_FINALIZING);
r = import_mangle_os_tree(i->local);
r = import_mangle_os_tree_fd(i->tree_fd, i->userns_fd, i->flags);
if (r < 0)
goto finish;
@@ -523,7 +523,7 @@ static void tar_pull_job_on_finished(PullJob *j) {
tar_pull_report_progress(i, TAR_FINALIZING);
r = import_mangle_os_tree(i->temp_path);
r = import_mangle_os_tree_fd(i->tree_fd, i->userns_fd, i->flags);
if (r < 0)
goto finish;