dissect-image: Generalize foreign tree logic from import

This commit is contained in:
Daan De Meyer
2026-01-21 12:03:07 +01:00
parent ebbc333880
commit ec1ab07605
6 changed files with 115 additions and 161 deletions
+5 -116
View File
@@ -7,7 +7,6 @@
#include "sd-event.h"
#include "capability-util.h"
#include "copy.h"
#include "dirent-util.h"
#include "dissect-image.h"
#include "fd-util.h"
@@ -383,129 +382,19 @@ int import_make_foreign_userns(int *userns_fd) {
return 1;
}
int import_copy_foreign(
int source_fd,
int target_fd,
int *userns_fd) {
int r;
assert(source_fd >= 0);
assert(target_fd >= 0);
assert(userns_fd);
/* Copies dir referenced by source_fd into dir referenced by source_fd, moves to the specified userns
* for that (allocated if needed), which should be foreign UID range */
r = import_make_foreign_userns(userns_fd);
if (r < 0)
return r;
r = pidref_safe_fork_full(
"copy-tree",
/* stdio_fds= */ NULL,
(int[]) { *userns_fd, source_fd, target_fd }, 3,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT,
/* ret= */ NULL);
if (r < 0)
return r;
if (r == 0) {
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 = copy_tree_at(
source_fd, /* from= */ NULL,
target_fd, /* to= */ NULL,
/* override_uid= */ UID_INVALID,
/* override_gid= */ GID_INVALID,
COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS,
/* denylist= */ NULL,
/* subvolumes= */ NULL);
if (r < 0) {
log_error_errno(r, "Failed to copy tree: %m");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
return 0;
}
int import_remove_tree_foreign(const char *path, int *userns_fd) {
int r;
assert(path);
assert(userns_fd);
r = import_make_foreign_userns(userns_fd);
if (r < 0)
return r;
_cleanup_close_ int tree_fd = -EBADF;
r = mountfsd_mount_directory(
path,
*userns_fd,
DISSECT_IMAGE_FOREIGN_UID,
&tree_fd);
if (r < 0)
return r;
r = pidref_safe_fork_full(
"rm-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= */ 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);
}
_cleanup_close_ int dfd = fd_reopen(tree_fd, O_DIRECTORY|O_CLOEXEC);
if (dfd < 0) {
log_error_errno(r, "Failed to reopen tree fd: %m");
_exit(EXIT_FAILURE);
}
r = rm_rf_children(dfd, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, /* root_dev= */ NULL);
if (r < 0)
log_warning_errno(r, "Failed to empty '%s' directory in foreign UID mode, ignoring: %m", path);
_exit(EXIT_SUCCESS);
}
return 0;
}
int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) {
int r;
assert(path);
assert(userns_fd);
r = import_make_foreign_userns(userns_fd);
if (r < 0)
return r;
/* Try the userns dance first, to remove foreign UID range owned trees */
if (FLAGS_SET(flags, IMPORT_FOREIGN_UID))
(void) import_remove_tree_foreign(path, userns_fd);
(void) remove_tree_foreign(path, *userns_fd);
r = rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
if (r < 0)
-3
View File
@@ -47,9 +47,6 @@ int import_allocate_event_with_signals(sd_event **ret);
int import_make_foreign_userns(int *userns_fd);
int import_copy_foreign(int source_fd, int target_fd, int *userns_fd);
int import_remove_tree_foreign(const char *path, int *userns_fd);
int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags);
#define IMPORT_BUFFER_SIZE (128U*1024U)
+1 -1
View File
@@ -309,7 +309,7 @@ static int tar_pull_make_local_copy(TarPull *p) {
if (r < 0)
return r;
r = import_copy_foreign(p->tree_fd, copy_fd, &p->userns_fd);
r = copy_tree_at_foreign(p->tree_fd, copy_fd, p->userns_fd);
if (r < 0)
return r;
} else {
+3 -41
View File
@@ -1469,7 +1469,7 @@ static int get_pool_directory(
return 0;
}
static int unpriviled_clone(Image *i, const char *new_path) {
static int unprivileged_clone(Image *i, const char *new_path) {
int r;
assert(i);
@@ -1510,45 +1510,7 @@ static int unpriviled_clone(Image *i, const char *new_path) {
return r;
/* Fork off child that moves into userns and does the copying */
r = pidref_safe_fork_full(
"clone-tree",
/* stdio_fds= */ NULL,
(int[]) { userns_fd, tree_fd, target_fd }, 3,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_REOPEN_LOG,
/* ret= */ NULL);
if (r < 0)
return log_debug_errno(r, "Process that was supposed to clone tree failed: %m");
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_debug_errno(r, "Failed to join user namespace: %m");
_exit(EXIT_FAILURE);
}
r = copy_tree_at(
tree_fd, /* from= */ NULL,
target_fd, /* to= */ NULL,
/* override_uid= */ UID_INVALID,
/* override_gid= */ GID_INVALID,
COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS,
/* denylist= */ NULL,
/* subvolumes= */ NULL);
if (r < 0) {
log_debug_errno(r, "Failed to copy clone tree: %m");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
return 0;
return copy_tree_at_foreign(tree_fd, target_fd, userns_fd);
}
int image_clone(Image *i, const char *new_name, bool read_only, RuntimeScope scope) {
@@ -1596,7 +1558,7 @@ int image_clone(Image *i, const char *new_name, bool read_only, RuntimeScope sco
return r;
if (i->foreign_uid_owned)
r = unpriviled_clone(i, new_path);
r = unprivileged_clone(i, new_path);
else {
r = btrfs_subvol_snapshot_at(
AT_FDCWD, i->path,
+103
View File
@@ -63,6 +63,7 @@
#include "proc-cmdline.h"
#include "process-util.h"
#include "resize-fs.h"
#include "rm-rf.h"
#include "runtime-scope.h"
#include "siphash24.h"
#include "stat-util.h"
@@ -5491,3 +5492,105 @@ int mountfsd_make_directory(
return mountfsd_make_directory_fd(fd, dirname, mode, flags, ret_directory_fd);
}
int copy_tree_at_foreign(int source_fd, int target_fd, int userns_fd) {
int r;
assert(source_fd >= 0);
assert(target_fd >= 0);
assert(userns_fd >= 0);
/* Copies dir referenced by source_fd into dir referenced by source_fd, moves to the specified userns
* for that, which should be foreign UID range */
r = pidref_safe_fork_full(
"copy-tree",
/* stdio_fds= */ NULL,
(int[]) { userns_fd, source_fd, target_fd }, 3,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_REOPEN_LOG,
/* ret= */ NULL);
if (r < 0)
return r;
if (r == 0) {
r = namespace_enter(
/* pidns_fd= */ -EBADF,
/* mntns_fd= */ -EBADF,
/* netns_fd= */ -EBADF,
userns_fd,
/* root_fd= */ -EBADF);
if (r < 0) {
log_debug_errno(r, "Failed to join user namespace: %m");
_exit(EXIT_FAILURE);
}
r = copy_tree_at(
source_fd, /* from= */ NULL,
target_fd, /* to= */ NULL,
/* override_uid= */ UID_INVALID,
/* override_gid= */ GID_INVALID,
COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS,
/* denylist= */ NULL,
/* subvolumes= */ NULL);
if (r < 0) {
log_debug_errno(r, "Failed to copy tree: %m");
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
return 0;
}
int remove_tree_foreign(const char *path, int userns_fd) {
int r;
assert(path);
assert(userns_fd >= 0);
_cleanup_close_ int tree_fd = -EBADF;
r = mountfsd_mount_directory(
path,
userns_fd,
DISSECT_IMAGE_FOREIGN_UID,
&tree_fd);
if (r < 0)
return r;
r = pidref_safe_fork_full(
"rm-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= */ 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);
}
_cleanup_close_ int dfd = fd_reopen(tree_fd, O_DIRECTORY|O_CLOEXEC);
if (dfd < 0) {
log_error_errno(r, "Failed to reopen tree fd: %m");
_exit(EXIT_FAILURE);
}
r = rm_rf_children(dfd, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, /* root_dev= */ NULL);
if (r < 0)
log_warning_errno(r, "Failed to empty '%s' directory in foreign UID mode, ignoring: %m", path);
_exit(EXIT_SUCCESS);
}
return 0;
}
+3
View File
@@ -275,3 +275,6 @@ int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags
int mountfsd_make_directory_fd(int parent_fd, const char *name, mode_t mode, DissectImageFlags flags, int *ret_directory_fd);
int mountfsd_make_directory(const char *path, mode_t mode, DissectImageFlags flags, int *ret_directory_fd);
int copy_tree_at_foreign(int source_fd, int target_fd, int userns_fd);
int remove_tree_foreign(const char *path, int userns_fd);