Various fixes related to mountfsd (#40564)

This commit is contained in:
Daan De Meyer
2026-02-05 16:39:28 +01:00
committed by GitHub
4 changed files with 219 additions and 125 deletions
+1 -1
View File
@@ -618,7 +618,7 @@ int userns_enter_and_pin(int userns_fd, PidRef *ret) {
"(sd-pinuserns)",
/* stdio_fds= */ NULL,
(int[]) { pfd[1], userns_fd }, 2,
FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL,
FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_DEATHSIG_SIGKILL,
&pidref);
if (r < 0)
return r;
+97 -5
View File
@@ -36,6 +36,8 @@
#include "os-util.h"
#include "path-util.h"
#include "pidref.h"
#include "process-util.h"
#include "socket-util.h"
#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
@@ -878,6 +880,99 @@ static DirectoryOwnership validate_directory_fd(
return DIRECTORY_IS_OTHERWISE_OWNED;
}
static int open_tree_try_drop_idmap_harder(sd_varlink *link, int directory_fd, const char *directory_path) {
int r;
_cleanup_close_ int mount_fd = open_tree_try_drop_idmap(
directory_fd,
"",
OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH);
if (mount_fd >= 0)
return TAKE_FD(mount_fd);
if (mount_fd != -EINVAL)
return log_debug_errno(mount_fd, "Failed to issue open_tree() of provided directory '%s': %m", strna(directory_path));
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
r = varlink_get_peer_pidref(link, &pidref);
if (r < 0)
return r;
_cleanup_close_ int mntns_fd = pidref_namespace_open_by_type(&pidref, NAMESPACE_MOUNT);
if (mntns_fd < 0)
return log_debug_errno(mntns_fd, "Failed to open mount namespace of peer: %m");
r = is_our_namespace(mntns_fd, NAMESPACE_MOUNT);
if (r < 0)
return log_debug_errno(r, "Failed to check if peer is in same mount namespace: %m");
if (r > 0)
return log_debug_errno(mount_fd, "Failed to issue open_tree() of provided directory '%s': %m", strna(directory_path));
/* The peer is in a different mount namespace. open_tree() will fail with EINVAL on directory fds
* from a different mount namespace, so we need to fork off a child process that joins the peer's
* mount namespace and calls open_tree() there. */
_cleanup_close_pair_ int errno_pipe_fd[2] = EBADF_PAIR, mount_fd_socket[2] = EBADF_PAIR;
if (pipe2(errno_pipe_fd, O_CLOEXEC) < 0)
return log_debug_errno(errno, "Failed to create pipe: %m");
if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, mount_fd_socket) < 0)
return log_debug_errno(errno, "Failed to create socket pair: %m");
_cleanup_(pidref_done) PidRef child = PIDREF_NULL;
r = namespace_fork(
"(sd-opentreens)",
"(sd-opentree)",
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL,
/* pidns_fd= */ -EBADF,
mntns_fd,
/* netns_fd= */ -EBADF,
/* userns_fd= */ -EBADF,
/* root_fd= */ -EBADF,
&child);
if (r < 0)
return log_debug_errno(r, "Failed to fork into peer's mount namespace: %m");
if (r == 0) {
/* Child */
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
mount_fd_socket[0] = safe_close(mount_fd_socket[0]);
mount_fd = open_tree_try_drop_idmap(
directory_fd,
"",
OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH);
if (mount_fd < 0) {
log_debug_errno(mount_fd, "Failed to issue open_tree() of provided directory '%s': %m", strna(directory_path));
report_errno_and_exit(errno_pipe_fd[1], mount_fd);
}
r = send_one_fd(mount_fd_socket[1], mount_fd, /* flags= */ 0);
if (r < 0) {
log_debug_errno(r, "Failed to send mount fd: %m");
report_errno_and_exit(errno_pipe_fd[1], r);
}
_exit(EXIT_SUCCESS);
}
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
mount_fd_socket[1] = safe_close(mount_fd_socket[1]);
r = pidref_wait_for_terminate_and_check("(sd-opentreens)", &child, /* flags= */ 0);
if (r < 0)
return log_debug_errno(r, "Failed to wait for child: %m");
r = read_errno(errno_pipe_fd[0]);
if (r < 0)
return r;
mount_fd = receive_one_fd(mount_fd_socket[0], MSG_DONTWAIT);
if (mount_fd < 0)
return log_debug_errno(mount_fd, "Failed to receive mount fd from child: %m");
return TAKE_FD(mount_fd);
}
static int vl_method_mount_directory(
sd_varlink *link,
sd_json_variant *parameters,
@@ -992,12 +1087,9 @@ static int vl_method_mount_directory(
if (r < 0)
return r;
_cleanup_close_ int mount_fd = open_tree_try_drop_idmap(
directory_fd,
"",
OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW|AT_EMPTY_PATH);
_cleanup_close_ int mount_fd = open_tree_try_drop_idmap_harder(link, directory_fd, directory_path);
if (mount_fd < 0)
return log_debug_errno(errno, "Failed to issue open_tree() of provided directory '%s': %m", strna(directory_path));
return mount_fd;
/* MOUNT_ATTR_IDMAP has possibly been cleared. Let's verify that the underlying data matches our expectations. */
struct stat unmapped_st;
@@ -31,7 +31,7 @@ void* bpf_rdonly_cast(const void *, __u32) __ksym;
* by their inode number in nsfs) that restricts creation of inodes (which would inherit the callers UID/GID)
* or changing of ownership (similar).
*
* This hooks into the various path-based LSM entrypoints that control inode creation as well as chmod(), and
* This hooks into the various path-based LSM entrypoints that control inode creation as well as chown(), and
* then looks up the calling process' user namespace in a global map of namespaces, which points us to
* another map that is simply a list of allowed mnt_ids. */
@@ -140,6 +140,8 @@ int BPF_PROG(userns_restrict_path_mkdir, struct path *dir, struct dentry *dentry
return validate_path(dir, ret);
}
/* The mknod hook covers all file creations, including regular files, in case the reader is looking for a
* missing hook for open(). */
SEC("lsm/path_mknod")
int BPF_PROG(userns_restrict_path_mknod, const struct path *dir, struct dentry *dentry, umode_t mode, unsigned dev, int ret) {
return validate_path(dir, ret);
+118 -118
View File
@@ -15,104 +15,104 @@ TEST(uid_range) {
_cleanup_(uid_range_freep) UIDRange *p = NULL;
uid_t search;
assert_se(uid_range_covers(p, 0, 0));
assert_se(!uid_range_covers(p, 0, 1));
assert_se(!uid_range_covers(p, 100, UINT32_MAX));
assert_se(!uid_range_covers(p, UINT32_MAX, 1));
assert_se(!uid_range_covers(p, UINT32_MAX - 10, 11));
ASSERT_TRUE(uid_range_covers(p, 0, 0));
ASSERT_FALSE(uid_range_covers(p, 0, 1));
ASSERT_FALSE(uid_range_covers(p, 100, UINT32_MAX));
ASSERT_FALSE(uid_range_covers(p, UINT32_MAX, 1));
ASSERT_FALSE(uid_range_covers(p, UINT32_MAX - 10, 11));
assert_se(uid_range_entries(p) == 0);
assert_se(uid_range_size(p) == 0);
assert_se(uid_range_is_empty(p));
ASSERT_EQ(uid_range_entries(p), 0U);
ASSERT_EQ(uid_range_size(p), 0U);
ASSERT_TRUE(uid_range_is_empty(p));
assert_se(uid_range_add_str(&p, "500-999") >= 0);
assert_se(p);
assert_se(uid_range_entries(p) == 1);
assert_se(uid_range_size(p) == 500);
assert_se(!uid_range_is_empty(p));
assert_se(p->entries[0].start == 500);
assert_se(p->entries[0].nr == 500);
ASSERT_OK(uid_range_add_str(&p, "500-999"));
ASSERT_NOT_NULL(p);
ASSERT_EQ(uid_range_entries(p), 1U);
ASSERT_EQ(uid_range_size(p), 500U);
ASSERT_FALSE(uid_range_is_empty(p));
ASSERT_EQ(p->entries[0].start, 500U);
ASSERT_EQ(p->entries[0].nr, 500U);
assert_se(!uid_range_contains(p, 499));
assert_se(uid_range_contains(p, 500));
assert_se(uid_range_contains(p, 999));
assert_se(!uid_range_contains(p, 1000));
ASSERT_FALSE(uid_range_contains(p, 499));
ASSERT_TRUE(uid_range_contains(p, 500));
ASSERT_TRUE(uid_range_contains(p, 999));
ASSERT_FALSE(uid_range_contains(p, 1000));
assert_se(!uid_range_covers(p, 100, 150));
assert_se(!uid_range_covers(p, 400, 200));
assert_se(!uid_range_covers(p, 499, 1));
assert_se(uid_range_covers(p, 500, 1));
assert_se(uid_range_covers(p, 501, 10));
assert_se(uid_range_covers(p, 999, 1));
assert_se(!uid_range_covers(p, 999, 2));
assert_se(!uid_range_covers(p, 1000, 1));
assert_se(!uid_range_covers(p, 1000, 100));
assert_se(!uid_range_covers(p, 1001, 100));
ASSERT_FALSE(uid_range_covers(p, 100, 150));
ASSERT_FALSE(uid_range_covers(p, 400, 200));
ASSERT_FALSE(uid_range_covers(p, 499, 1));
ASSERT_TRUE(uid_range_covers(p, 500, 1));
ASSERT_TRUE(uid_range_covers(p, 501, 10));
ASSERT_TRUE(uid_range_covers(p, 999, 1));
ASSERT_FALSE(uid_range_covers(p, 999, 2));
ASSERT_FALSE(uid_range_covers(p, 1000, 1));
ASSERT_FALSE(uid_range_covers(p, 1000, 100));
ASSERT_FALSE(uid_range_covers(p, 1001, 100));
search = UID_INVALID;
assert_se(uid_range_next_lower(p, &search));
assert_se(search == 999);
assert_se(uid_range_next_lower(p, &search));
assert_se(search == 998);
ASSERT_OK_POSITIVE(uid_range_next_lower(p, &search));
ASSERT_EQ(search, 999U);
ASSERT_OK_POSITIVE(uid_range_next_lower(p, &search));
ASSERT_EQ(search, 998U);
search = 501;
assert_se(uid_range_next_lower(p, &search));
assert_se(search == 500);
assert_se(uid_range_next_lower(p, &search) == -EBUSY);
ASSERT_OK_POSITIVE(uid_range_next_lower(p, &search));
ASSERT_EQ(search, 500U);
ASSERT_ERROR(uid_range_next_lower(p, &search), EBUSY);
assert_se(uid_range_add_str(&p, "1000") >= 0);
assert_se(uid_range_entries(p) == 1);
assert_se(p->entries[0].start == 500);
assert_se(p->entries[0].nr == 501);
ASSERT_OK(uid_range_add_str(&p, "1000"));
ASSERT_EQ(uid_range_entries(p), 1U);
ASSERT_EQ(p->entries[0].start, 500U);
ASSERT_EQ(p->entries[0].nr, 501U);
assert_se(uid_range_add_str(&p, "30-40") >= 0);
assert_se(uid_range_entries(p) == 2);
assert_se(uid_range_size(p) == 500 + 1 + 11);
assert_se(!uid_range_is_empty(p));
assert_se(p->entries[0].start == 30);
assert_se(p->entries[0].nr == 11);
assert_se(p->entries[1].start == 500);
assert_se(p->entries[1].nr == 501);
ASSERT_OK(uid_range_add_str(&p, "30-40"));
ASSERT_EQ(uid_range_entries(p), 2U);
ASSERT_EQ(uid_range_size(p), 500U + 1U + 11U);
ASSERT_FALSE(uid_range_is_empty(p));
ASSERT_EQ(p->entries[0].start, 30U);
ASSERT_EQ(p->entries[0].nr, 11U);
ASSERT_EQ(p->entries[1].start , 500U);
ASSERT_EQ(p->entries[1].nr , 501U);
assert_se(uid_range_add_str(&p, "60-70") >= 0);
assert_se(uid_range_entries(p) == 3);
assert_se(uid_range_size(p) == 500 + 1 + 11 + 11);
assert_se(!uid_range_is_empty(p));
assert_se(p->entries[0].start == 30);
assert_se(p->entries[0].nr == 11);
assert_se(p->entries[1].start == 60);
assert_se(p->entries[1].nr == 11);
assert_se(p->entries[2].start == 500);
assert_se(p->entries[2].nr == 501);
ASSERT_OK(uid_range_add_str(&p, "60-70"));
ASSERT_EQ(uid_range_entries(p), 3U);
ASSERT_EQ(uid_range_size(p), 500U + 1U + 11U + 11U);
ASSERT_FALSE(uid_range_is_empty(p));
ASSERT_EQ(p->entries[0].start, 30U);
ASSERT_EQ(p->entries[0].nr, 11U);
ASSERT_EQ(p->entries[1].start, 60U);
ASSERT_EQ(p->entries[1].nr, 11U);
ASSERT_EQ(p->entries[2].start, 500U);
ASSERT_EQ(p->entries[2].nr, 501U);
assert_se(uid_range_add_str(&p, "20-2000") >= 0);
assert_se(uid_range_entries(p) == 1);
assert_se(uid_range_size(p) == 1981);
assert_se(p->entries[0].start == 20);
assert_se(p->entries[0].nr == 1981);
ASSERT_OK(uid_range_add_str(&p, "20-2000"));
ASSERT_EQ(uid_range_entries(p), 1U);
ASSERT_EQ(uid_range_size(p), 1981U);
ASSERT_EQ(p->entries[0].start, 20U);
ASSERT_EQ(p->entries[0].nr, 1981U);
assert_se(uid_range_add_str(&p, "2002") >= 0);
assert_se(uid_range_entries(p) == 2);
assert_se(uid_range_size(p) == 1982);
assert_se(p->entries[0].start == 20);
assert_se(p->entries[0].nr == 1981);
assert_se(p->entries[1].start == 2002);
assert_se(p->entries[1].nr == 1);
ASSERT_OK(uid_range_add_str(&p, "2002"));
ASSERT_EQ(uid_range_entries(p), 2U);
ASSERT_EQ(uid_range_size(p), 1982U);
ASSERT_EQ(p->entries[0].start, 20U);
ASSERT_EQ(p->entries[0].nr, 1981U);
ASSERT_EQ(p->entries[1].start, 2002U);
ASSERT_EQ(p->entries[1].nr, 1U);
_cleanup_(uid_range_freep) UIDRange *q = NULL;
assert_se(!uid_range_equal(p, q));
assert_se(uid_range_add_str(&q, "20-2000") >= 0);
assert_se(!uid_range_equal(p, q));
assert_se(uid_range_add_str(&q, "2002") >= 0);
assert_se(uid_range_equal(p, q));
ASSERT_FALSE(uid_range_equal(p, q));
ASSERT_OK(uid_range_add_str(&q, "20-2000"));
ASSERT_FALSE(uid_range_equal(p, q));
ASSERT_OK(uid_range_add_str(&q, "2002"));
ASSERT_TRUE(uid_range_equal(p, q));
assert_se(uid_range_add_str(&p, "2001") >= 0);
assert_se(uid_range_entries(p) == 1);
assert_se(uid_range_size(p) == 1983);
assert_se(p->entries[0].start == 20);
assert_se(p->entries[0].nr == 1983);
ASSERT_OK(uid_range_add_str(&p, "2001"));
ASSERT_EQ(uid_range_entries(p), 1U);
ASSERT_EQ(uid_range_size(p), 1983U);
ASSERT_EQ(p->entries[0].start, 20U);
ASSERT_EQ(p->entries[0].nr, 1983U);
assert_se(uid_range_add_str(&q, "2001") >= 0);
assert_se(uid_range_equal(p, q));
ASSERT_OK(uid_range_add_str(&q, "2001"));
ASSERT_TRUE(uid_range_equal(p, q));
}
TEST(load_userns) {
@@ -125,74 +125,74 @@ TEST(load_userns) {
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
return;
assert_se(r >= 0);
assert_se(uid_range_contains(p, getuid()));
ASSERT_OK(r);
ASSERT_TRUE(uid_range_contains(p, getuid()));
r = running_in_userns();
if (r == 0) {
assert_se(p->n_entries == 1);
assert_se(p->entries[0].start == 0);
assert_se(p->entries[0].nr == UINT32_MAX);
ASSERT_EQ(p->n_entries, 1U);
ASSERT_EQ(p->entries[0].start, 0U);
ASSERT_EQ(p->entries[0].nr, UINT32_MAX);
assert_se(uid_range_covers(p, 0, UINT32_MAX));
ASSERT_TRUE(uid_range_covers(p, 0, UINT32_MAX));
}
assert_se(fopen_temporary_child(NULL, &f, &fn) >= 0);
ASSERT_OK(fopen_temporary_child(NULL, &f, &fn));
fputs("0 0 20\n"
"100 0 20\n", f);
assert_se(fflush_and_check(f) >= 0);
ASSERT_OK(fflush_and_check(f));
p = uid_range_free(p);
assert_se(uid_range_load_userns(fn, UID_RANGE_USERNS_INSIDE, &p) >= 0);
ASSERT_OK(uid_range_load_userns(fn, UID_RANGE_USERNS_INSIDE, &p));
assert_se(uid_range_contains(p, 0));
assert_se(uid_range_contains(p, 19));
assert_se(!uid_range_contains(p, 20));
ASSERT_TRUE(uid_range_contains(p, 0));
ASSERT_TRUE(uid_range_contains(p, 19));
ASSERT_FALSE(uid_range_contains(p, 20));
assert_se(!uid_range_contains(p, 99));
assert_se(uid_range_contains(p, 100));
assert_se(uid_range_contains(p, 119));
assert_se(!uid_range_contains(p, 120));
ASSERT_FALSE(uid_range_contains(p, 99));
ASSERT_TRUE(uid_range_contains(p, 100));
ASSERT_TRUE(uid_range_contains(p, 119));
ASSERT_FALSE(uid_range_contains(p, 120));
}
TEST(uid_range_coalesce) {
_cleanup_(uid_range_freep) UIDRange *p = NULL;
for (size_t i = 0; i < 10; i++) {
assert_se(uid_range_add_internal(&p, i * 10, 10, /* coalesce= */ false) >= 0);
assert_se(uid_range_add_internal(&p, i * 10 + 5, 10, /* coalesce= */ false) >= 0);
ASSERT_OK(uid_range_add_internal(&p, i * 10, 10, /* coalesce= */ false));
ASSERT_OK(uid_range_add_internal(&p, i * 10 + 5, 10, /* coalesce= */ false));
}
assert_se(uid_range_add_internal(&p, 100, 1, /* coalesce= */ true) >= 0);
assert_se(p->n_entries == 1);
assert_se(p->entries[0].start == 0);
assert_se(p->entries[0].nr == 105);
ASSERT_OK(uid_range_add_internal(&p, 100, 1, /* coalesce= */ true));
ASSERT_EQ(p->n_entries, 1U);
ASSERT_EQ(p->entries[0].start, 0U);
ASSERT_EQ(p->entries[0].nr, 105U);
p = uid_range_free(p);
for (size_t i = 0; i < 10; i++) {
assert_se(uid_range_add_internal(&p, (10 - i) * 10, 10, /* coalesce= */ false) >= 0);
assert_se(uid_range_add_internal(&p, (10 - i) * 10 + 5, 10, /* coalesce= */ false) >= 0);
ASSERT_OK(uid_range_add_internal(&p, (10 - i) * 10, 10, /* coalesce= */ false));
ASSERT_OK(uid_range_add_internal(&p, (10 - i) * 10 + 5, 10, /* coalesce= */ false));
}
assert_se(uid_range_add_internal(&p, 100, 1, /* coalesce= */ true) >= 0);
assert_se(p->n_entries == 1);
assert_se(p->entries[0].start == 10);
assert_se(p->entries[0].nr == 105);
ASSERT_OK(uid_range_add_internal(&p, 100, 1, /* coalesce= */ true));
ASSERT_EQ(p->n_entries, 1U);
ASSERT_EQ(p->entries[0].start, 10U);
ASSERT_EQ(p->entries[0].nr, 105U);
p = uid_range_free(p);
for (size_t i = 0; i < 10; i++) {
assert_se(uid_range_add_internal(&p, i * 10, 10, /* coalesce= */ false) >= 0);
assert_se(uid_range_add_internal(&p, i * 10 + 5, 10, /* coalesce= */ false) >= 0);
assert_se(uid_range_add_internal(&p, (10 - i) * 10, 10, /* coalesce= */ false) >= 0);
assert_se(uid_range_add_internal(&p, (10 - i) * 10 + 5, 10, /* coalesce= */ false) >= 0);
ASSERT_OK(uid_range_add_internal(&p, i * 10, 10, /* coalesce= */ false));
ASSERT_OK(uid_range_add_internal(&p, i * 10 + 5, 10, /* coalesce= */ false));
ASSERT_OK(uid_range_add_internal(&p, (10 - i) * 10, 10, /* coalesce= */ false));
ASSERT_OK(uid_range_add_internal(&p, (10 - i) * 10 + 5, 10, /* coalesce= */ false));
}
assert_se(uid_range_add_internal(&p, 100, 1, /* coalesce= */ true) >= 0);
assert_se(p->n_entries == 1);
assert_se(p->entries[0].start == 0);
assert_se(p->entries[0].nr == 115);
ASSERT_OK(uid_range_add_internal(&p, 100, 1, /* coalesce= */ true));
ASSERT_EQ(p->n_entries, 1U);
ASSERT_EQ(p->entries[0].start, 0U);
ASSERT_EQ(p->entries[0].nr, 115U);
}
DEFINE_TEST_MAIN(LOG_DEBUG);