| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2026 Christian Brauner <brauner@kernel.org> |
| * |
| * Test for FSMOUNT_NAMESPACE flag. |
| * |
| * Test that fsmount() with FSMOUNT_NAMESPACE creates a new mount |
| * namespace containing the specified mount. |
| */ |
| #define _GNU_SOURCE |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <linux/nsfs.h> |
| #include <sched.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include "../wrappers.h" |
| #include "../statmount/statmount.h" |
| #include "../utils.h" |
| #include "../../kselftest_harness.h" |
| |
| #ifndef FSMOUNT_NAMESPACE |
| #define FSMOUNT_NAMESPACE 0x00000002 |
| #endif |
| |
| #ifndef FSMOUNT_CLOEXEC |
| #define FSMOUNT_CLOEXEC 0x00000001 |
| #endif |
| |
| #ifndef FSCONFIG_CMD_CREATE |
| #define FSCONFIG_CMD_CREATE 6 |
| #endif |
| |
| static int get_mnt_ns_id(int fd, uint64_t *mnt_ns_id) |
| { |
| if (ioctl(fd, NS_GET_MNTNS_ID, mnt_ns_id) < 0) |
| return -errno; |
| return 0; |
| } |
| |
| static int get_mnt_ns_id_from_path(const char *path, uint64_t *mnt_ns_id) |
| { |
| int fd, ret; |
| |
| fd = open(path, O_RDONLY); |
| if (fd < 0) |
| return -errno; |
| |
| ret = get_mnt_ns_id(fd, mnt_ns_id); |
| close(fd); |
| return ret; |
| } |
| |
| static void log_mount(struct __test_metadata *_metadata, struct statmount *sm) |
| { |
| const char *fs_type = ""; |
| const char *mnt_root = ""; |
| const char *mnt_point = ""; |
| |
| if (sm->mask & STATMOUNT_FS_TYPE) |
| fs_type = sm->str + sm->fs_type; |
| if (sm->mask & STATMOUNT_MNT_ROOT) |
| mnt_root = sm->str + sm->mnt_root; |
| if (sm->mask & STATMOUNT_MNT_POINT) |
| mnt_point = sm->str + sm->mnt_point; |
| |
| TH_LOG(" mnt_id: %llu, parent_id: %llu, fs_type: %s, root: %s, point: %s", |
| (unsigned long long)sm->mnt_id, |
| (unsigned long long)sm->mnt_parent_id, |
| fs_type, mnt_root, mnt_point); |
| } |
| |
| static void dump_mounts(struct __test_metadata *_metadata, uint64_t mnt_ns_id) |
| { |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| |
| nr_mounts = listmount(LSMT_ROOT, mnt_ns_id, 0, list, 256, 0); |
| if (nr_mounts < 0) { |
| TH_LOG("listmount failed: %s", strerror(errno)); |
| return; |
| } |
| |
| TH_LOG("Mount namespace %llu contains %zd mount(s):", |
| (unsigned long long)mnt_ns_id, nr_mounts); |
| |
| for (ssize_t i = 0; i < nr_mounts; i++) { |
| struct statmount *sm; |
| |
| sm = statmount_alloc(list[i], mnt_ns_id, |
| STATMOUNT_MNT_BASIC | |
| STATMOUNT_FS_TYPE | |
| STATMOUNT_MNT_ROOT | |
| STATMOUNT_MNT_POINT, 0); |
| if (!sm) { |
| TH_LOG(" [%zd] mnt_id %llu: statmount failed: %s", |
| i, (unsigned long long)list[i], strerror(errno)); |
| continue; |
| } |
| |
| log_mount(_metadata, sm); |
| free(sm); |
| } |
| } |
| |
| static int create_tmpfs_fd(void) |
| { |
| int fs_fd, ret; |
| |
| fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); |
| if (fs_fd < 0) |
| return -errno; |
| |
| ret = sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); |
| if (ret < 0) { |
| close(fs_fd); |
| return -errno; |
| } |
| |
| return fs_fd; |
| } |
| |
| FIXTURE(fsmount_ns) |
| { |
| int fd; |
| int fs_fd; |
| uint64_t current_ns_id; |
| }; |
| |
| FIXTURE_VARIANT(fsmount_ns) |
| { |
| const char *fstype; |
| unsigned int flags; |
| bool expect_success; |
| bool expect_different_ns; |
| int min_mounts; |
| }; |
| |
| FIXTURE_VARIANT_ADD(fsmount_ns, basic_tmpfs) |
| { |
| .fstype = "tmpfs", |
| .flags = FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, |
| .expect_success = true, |
| .expect_different_ns = true, |
| .min_mounts = 1, |
| }; |
| |
| FIXTURE_VARIANT_ADD(fsmount_ns, cloexec_only) |
| { |
| .fstype = "tmpfs", |
| .flags = FSMOUNT_CLOEXEC, |
| .expect_success = true, |
| .expect_different_ns = false, |
| .min_mounts = 1, |
| }; |
| |
| FIXTURE_VARIANT_ADD(fsmount_ns, namespace_only) |
| { |
| .fstype = "tmpfs", |
| .flags = FSMOUNT_NAMESPACE, |
| .expect_success = true, |
| .expect_different_ns = true, |
| .min_mounts = 1, |
| }; |
| |
| FIXTURE_SETUP(fsmount_ns) |
| { |
| int ret; |
| |
| self->fd = -1; |
| self->fs_fd = -1; |
| |
| /* Check if fsopen syscall is supported */ |
| ret = sys_fsopen("tmpfs", 0); |
| if (ret == -1 && errno == ENOSYS) |
| SKIP(return, "fsopen() syscall not supported"); |
| if (ret >= 0) |
| close(ret); |
| |
| /* Check if statmount/listmount are supported */ |
| ret = statmount(0, 0, 0, 0, NULL, 0, 0); |
| if (ret == -1 && errno == ENOSYS) |
| SKIP(return, "statmount() syscall not supported"); |
| |
| /* Get current mount namespace ID for comparison */ |
| ret = get_mnt_ns_id_from_path("/proc/self/ns/mnt", &self->current_ns_id); |
| if (ret < 0) |
| SKIP(return, "Failed to get current mount namespace ID"); |
| } |
| |
| FIXTURE_TEARDOWN(fsmount_ns) |
| { |
| if (self->fd >= 0) |
| close(self->fd); |
| if (self->fs_fd >= 0) |
| close(self->fs_fd); |
| } |
| |
| TEST_F(fsmount_ns, create_namespace) |
| { |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int ret; |
| |
| self->fs_fd = create_tmpfs_fd(); |
| ASSERT_GE(self->fs_fd, 0); |
| |
| self->fd = sys_fsmount(self->fs_fd, variant->flags, 0); |
| |
| if (!variant->expect_success) { |
| ASSERT_LT(self->fd, 0); |
| return; |
| } |
| |
| if (self->fd < 0 && errno == EINVAL) |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| |
| ASSERT_GE(self->fd, 0); |
| |
| if (variant->expect_different_ns) { |
| /* Verify we can get the namespace ID from the fd */ |
| ret = get_mnt_ns_id(self->fd, &new_ns_id); |
| ASSERT_EQ(ret, 0); |
| |
| /* Verify it's a different namespace */ |
| ASSERT_NE(new_ns_id, self->current_ns_id); |
| |
| /* List mounts in the new namespace */ |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); |
| ASSERT_GE(nr_mounts, 0) { |
| TH_LOG("%m - listmount failed"); |
| } |
| |
| /* Verify minimum expected mounts */ |
| ASSERT_GE(nr_mounts, variant->min_mounts); |
| TH_LOG("Namespace contains %zd mounts", nr_mounts); |
| } |
| } |
| |
| TEST_F(fsmount_ns, setns_into_namespace) |
| { |
| uint64_t new_ns_id; |
| pid_t pid; |
| int status; |
| int ret; |
| |
| /* Only test with FSMOUNT_NAMESPACE flag */ |
| if (!(variant->flags & FSMOUNT_NAMESPACE)) |
| SKIP(return, "setns test only for FSMOUNT_NAMESPACE case"); |
| |
| self->fs_fd = create_tmpfs_fd(); |
| ASSERT_GE(self->fs_fd, 0); |
| |
| self->fd = sys_fsmount(self->fs_fd, variant->flags, 0); |
| if (self->fd < 0 && errno == EINVAL) |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| |
| ASSERT_GE(self->fd, 0); |
| |
| /* Get namespace ID and dump all mounts */ |
| ret = get_mnt_ns_id(self->fd, &new_ns_id); |
| ASSERT_EQ(ret, 0); |
| |
| dump_mounts(_metadata, new_ns_id); |
| |
| pid = fork(); |
| ASSERT_GE(pid, 0); |
| |
| if (pid == 0) { |
| /* Child: try to enter the namespace */ |
| if (setns(self->fd, CLONE_NEWNS) < 0) |
| _exit(1); |
| _exit(0); |
| } |
| |
| ASSERT_EQ(waitpid(pid, &status, 0), pid); |
| ASSERT_TRUE(WIFEXITED(status)); |
| ASSERT_EQ(WEXITSTATUS(status), 0); |
| } |
| |
| TEST_F(fsmount_ns, verify_mount_properties) |
| { |
| struct statmount sm; |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int ret; |
| |
| /* Only test with basic FSMOUNT_NAMESPACE flags */ |
| if (variant->flags != (FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC)) |
| SKIP(return, "mount properties test only for basic case"); |
| |
| self->fs_fd = create_tmpfs_fd(); |
| ASSERT_GE(self->fs_fd, 0); |
| |
| self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); |
| if (self->fd < 0 && errno == EINVAL) |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| |
| ASSERT_GE(self->fd, 0); |
| |
| ret = get_mnt_ns_id(self->fd, &new_ns_id); |
| ASSERT_EQ(ret, 0); |
| |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); |
| ASSERT_GE(nr_mounts, 1); |
| |
| /* Get info about the root mount */ |
| ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); |
| ASSERT_EQ(ret, 0); |
| |
| TH_LOG("Root mount id: %llu, parent: %llu", |
| (unsigned long long)sm.mnt_id, |
| (unsigned long long)sm.mnt_parent_id); |
| } |
| |
| TEST_F(fsmount_ns, verify_tmpfs_type) |
| { |
| struct statmount *sm; |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| const char *fs_type; |
| int ret; |
| |
| /* Only test with basic FSMOUNT_NAMESPACE flags */ |
| if (variant->flags != (FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC)) |
| SKIP(return, "fs type test only for basic case"); |
| |
| self->fs_fd = create_tmpfs_fd(); |
| ASSERT_GE(self->fs_fd, 0); |
| |
| self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); |
| if (self->fd < 0 && errno == EINVAL) |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| |
| ASSERT_GE(self->fd, 0); |
| |
| ret = get_mnt_ns_id(self->fd, &new_ns_id); |
| ASSERT_EQ(ret, 0); |
| |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); |
| ASSERT_GE(nr_mounts, 1); |
| |
| sm = statmount_alloc(list[0], new_ns_id, STATMOUNT_FS_TYPE, 0); |
| ASSERT_NE(sm, NULL); |
| |
| fs_type = sm->str + sm->fs_type; |
| ASSERT_STREQ(fs_type, "tmpfs"); |
| |
| free(sm); |
| } |
| |
| FIXTURE(fsmount_ns_caps) |
| { |
| bool has_caps; |
| }; |
| |
| FIXTURE_SETUP(fsmount_ns_caps) |
| { |
| int ret; |
| |
| /* Check if fsopen syscall is supported */ |
| ret = sys_fsopen("tmpfs", 0); |
| if (ret == -1 && errno == ENOSYS) |
| SKIP(return, "fsopen() syscall not supported"); |
| if (ret >= 0) |
| close(ret); |
| |
| self->has_caps = (geteuid() == 0); |
| } |
| |
| FIXTURE_TEARDOWN(fsmount_ns_caps) |
| { |
| } |
| |
| TEST_F(fsmount_ns_caps, requires_cap_sys_admin) |
| { |
| pid_t pid; |
| int status; |
| int fs_fd; |
| |
| /* |
| * Prepare the configured filesystem fd as root before forking. |
| * fsopen() requires CAP_SYS_ADMIN in the mount namespace's |
| * user_ns, which won't be available after enter_userns(). |
| */ |
| fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); |
| ASSERT_GE(fs_fd, 0); |
| |
| ASSERT_EQ(sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0), 0); |
| |
| pid = fork(); |
| ASSERT_GE(pid, 0); |
| |
| if (pid == 0) { |
| int fd; |
| |
| /* Child: drop privileges using utils.h helper */ |
| if (enter_userns() != 0) |
| _exit(2); |
| |
| /* Drop all caps using utils.h helper */ |
| if (caps_down() == 0) |
| _exit(3); |
| |
| fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); |
| close(fs_fd); |
| |
| if (fd >= 0) { |
| close(fd); |
| /* Should have failed without caps */ |
| _exit(1); |
| } |
| |
| if (errno == EPERM) |
| _exit(0); |
| |
| /* EINVAL means FSMOUNT_NAMESPACE not supported */ |
| if (errno == EINVAL) |
| _exit(6); |
| |
| /* Unexpected error */ |
| _exit(7); |
| } |
| |
| close(fs_fd); |
| ASSERT_EQ(waitpid(pid, &status, 0), pid); |
| ASSERT_TRUE(WIFEXITED(status)); |
| |
| switch (WEXITSTATUS(status)) { |
| case 0: |
| /* Expected: EPERM without caps */ |
| break; |
| case 1: |
| ASSERT_FALSE(true) TH_LOG("FSMOUNT_NAMESPACE succeeded without caps"); |
| break; |
| case 2: |
| SKIP(return, "enter_userns failed"); |
| break; |
| case 3: |
| SKIP(return, "caps_down failed"); |
| break; |
| case 6: |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| break; |
| default: |
| ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", |
| WEXITSTATUS(status)); |
| break; |
| } |
| } |
| |
| FIXTURE(fsmount_ns_userns) |
| { |
| int fd; |
| int fs_fd; |
| }; |
| |
| FIXTURE_SETUP(fsmount_ns_userns) |
| { |
| int ret; |
| |
| self->fd = -1; |
| self->fs_fd = -1; |
| |
| /* Check if fsopen syscall is supported */ |
| ret = sys_fsopen("tmpfs", 0); |
| if (ret == -1 && errno == ENOSYS) |
| SKIP(return, "fsopen() syscall not supported"); |
| if (ret >= 0) |
| close(ret); |
| |
| /* Check if statmount/listmount are supported */ |
| ret = statmount(0, 0, 0, 0, NULL, 0, 0); |
| if (ret == -1 && errno == ENOSYS) |
| SKIP(return, "statmount() syscall not supported"); |
| } |
| |
| FIXTURE_TEARDOWN(fsmount_ns_userns) |
| { |
| if (self->fd >= 0) |
| close(self->fd); |
| if (self->fs_fd >= 0) |
| close(self->fs_fd); |
| } |
| |
| TEST_F(fsmount_ns_userns, create_in_userns) |
| { |
| pid_t pid; |
| int status; |
| |
| pid = fork(); |
| ASSERT_GE(pid, 0); |
| |
| if (pid == 0) { |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int fs_fd, fd; |
| |
| /* Create new user namespace (also creates mount namespace) */ |
| if (setup_userns() != 0) |
| _exit(2); |
| |
| /* Now we have CAP_SYS_ADMIN in the user namespace */ |
| fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); |
| if (fs_fd < 0) |
| _exit(3); |
| |
| if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { |
| close(fs_fd); |
| _exit(4); |
| } |
| |
| fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); |
| close(fs_fd); |
| |
| if (fd < 0) { |
| if (errno == EINVAL) |
| _exit(6); /* FSMOUNT_NAMESPACE not supported */ |
| _exit(1); |
| } |
| |
| /* Verify we can get the namespace ID */ |
| if (get_mnt_ns_id(fd, &new_ns_id) != 0) |
| _exit(7); |
| |
| /* Verify we can list mounts in the new namespace */ |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); |
| if (nr_mounts < 0) |
| _exit(8); |
| |
| /* Should have at least 1 mount (the tmpfs) */ |
| if (nr_mounts < 1) |
| _exit(9); |
| |
| close(fd); |
| _exit(0); |
| } |
| |
| ASSERT_EQ(waitpid(pid, &status, 0), pid); |
| ASSERT_TRUE(WIFEXITED(status)); |
| |
| switch (WEXITSTATUS(status)) { |
| case 0: |
| /* Success */ |
| break; |
| case 1: |
| ASSERT_FALSE(true) TH_LOG("fsmount(FSMOUNT_NAMESPACE) failed in userns"); |
| break; |
| case 2: |
| SKIP(return, "setup_userns failed"); |
| break; |
| case 3: |
| SKIP(return, "fsopen failed in userns"); |
| break; |
| case 4: |
| SKIP(return, "fsconfig CMD_CREATE failed in userns"); |
| break; |
| case 6: |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| break; |
| case 7: |
| ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID"); |
| break; |
| case 8: |
| ASSERT_FALSE(true) TH_LOG("listmount failed in new namespace"); |
| break; |
| case 9: |
| ASSERT_FALSE(true) TH_LOG("New namespace has no mounts"); |
| break; |
| default: |
| ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", |
| WEXITSTATUS(status)); |
| break; |
| } |
| } |
| |
| TEST_F(fsmount_ns_userns, setns_in_userns) |
| { |
| pid_t pid; |
| int status; |
| |
| pid = fork(); |
| ASSERT_GE(pid, 0); |
| |
| if (pid == 0) { |
| uint64_t new_ns_id; |
| int fs_fd, fd; |
| pid_t inner_pid; |
| int inner_status; |
| |
| /* Create new user namespace */ |
| if (setup_userns() != 0) |
| _exit(2); |
| |
| fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); |
| if (fs_fd < 0) |
| _exit(3); |
| |
| if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { |
| close(fs_fd); |
| _exit(4); |
| } |
| |
| fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); |
| close(fs_fd); |
| |
| if (fd < 0) { |
| if (errno == EINVAL) |
| _exit(6); |
| _exit(1); |
| } |
| |
| if (get_mnt_ns_id(fd, &new_ns_id) != 0) |
| _exit(7); |
| |
| /* Fork again to test setns into the new namespace */ |
| inner_pid = fork(); |
| if (inner_pid < 0) |
| _exit(10); |
| |
| if (inner_pid == 0) { |
| /* Inner child: enter the new namespace */ |
| if (setns(fd, CLONE_NEWNS) < 0) |
| _exit(1); |
| _exit(0); |
| } |
| |
| if (waitpid(inner_pid, &inner_status, 0) != inner_pid) |
| _exit(11); |
| |
| if (!WIFEXITED(inner_status) || WEXITSTATUS(inner_status) != 0) |
| _exit(12); |
| |
| close(fd); |
| _exit(0); |
| } |
| |
| ASSERT_EQ(waitpid(pid, &status, 0), pid); |
| ASSERT_TRUE(WIFEXITED(status)); |
| |
| switch (WEXITSTATUS(status)) { |
| case 0: |
| /* Success */ |
| break; |
| case 1: |
| ASSERT_FALSE(true) TH_LOG("fsmount or setns failed in userns"); |
| break; |
| case 2: |
| SKIP(return, "setup_userns failed"); |
| break; |
| case 3: |
| SKIP(return, "fsopen failed in userns"); |
| break; |
| case 4: |
| SKIP(return, "fsconfig CMD_CREATE failed in userns"); |
| break; |
| case 6: |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| break; |
| case 7: |
| ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID"); |
| break; |
| case 10: |
| ASSERT_FALSE(true) TH_LOG("Inner fork failed"); |
| break; |
| case 11: |
| ASSERT_FALSE(true) TH_LOG("Inner waitpid failed"); |
| break; |
| case 12: |
| ASSERT_FALSE(true) TH_LOG("setns into new namespace failed"); |
| break; |
| default: |
| ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", |
| WEXITSTATUS(status)); |
| break; |
| } |
| } |
| |
| TEST_F(fsmount_ns_userns, umount_fails_einval) |
| { |
| pid_t pid; |
| int status; |
| |
| pid = fork(); |
| ASSERT_GE(pid, 0); |
| |
| if (pid == 0) { |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int fs_fd, fd; |
| ssize_t i; |
| |
| /* Create new user namespace */ |
| if (setup_userns() != 0) |
| _exit(2); |
| |
| fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); |
| if (fs_fd < 0) |
| _exit(3); |
| |
| if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { |
| close(fs_fd); |
| _exit(4); |
| } |
| |
| fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); |
| close(fs_fd); |
| |
| if (fd < 0) { |
| if (errno == EINVAL) |
| _exit(6); |
| _exit(1); |
| } |
| |
| if (get_mnt_ns_id(fd, &new_ns_id) != 0) |
| _exit(7); |
| |
| /* Get all mounts in the new namespace */ |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, LISTMOUNT_REVERSE); |
| if (nr_mounts < 0) |
| _exit(13); |
| |
| if (nr_mounts < 1) |
| _exit(14); |
| |
| /* Enter the new namespace */ |
| if (setns(fd, CLONE_NEWNS) < 0) |
| _exit(8); |
| |
| for (i = 0; i < nr_mounts; i++) { |
| struct statmount *sm; |
| const char *mnt_point; |
| |
| sm = statmount_alloc(list[i], new_ns_id, |
| STATMOUNT_MNT_POINT, 0); |
| if (!sm) |
| _exit(15); |
| |
| mnt_point = sm->str + sm->mnt_point; |
| |
| if (umount2(mnt_point, MNT_DETACH) == 0) { |
| free(sm); |
| _exit(9); |
| } |
| |
| if (errno != EINVAL) { |
| /* Wrong error */ |
| free(sm); |
| _exit(10); |
| } |
| |
| free(sm); |
| } |
| |
| close(fd); |
| _exit(0); |
| } |
| |
| ASSERT_EQ(waitpid(pid, &status, 0), pid); |
| ASSERT_TRUE(WIFEXITED(status)); |
| |
| switch (WEXITSTATUS(status)) { |
| case 0: |
| break; |
| case 1: |
| ASSERT_FALSE(true) TH_LOG("fsmount(FSMOUNT_NAMESPACE) failed"); |
| break; |
| case 2: |
| SKIP(return, "setup_userns failed"); |
| break; |
| case 3: |
| SKIP(return, "fsopen failed in userns"); |
| break; |
| case 4: |
| SKIP(return, "fsconfig CMD_CREATE failed in userns"); |
| break; |
| case 6: |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| break; |
| case 7: |
| ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID"); |
| break; |
| case 8: |
| ASSERT_FALSE(true) TH_LOG("setns into new namespace failed"); |
| break; |
| case 9: |
| ASSERT_FALSE(true) TH_LOG("umount succeeded but should have failed with EINVAL"); |
| break; |
| case 10: |
| ASSERT_FALSE(true) TH_LOG("umount failed with wrong error (expected EINVAL)"); |
| break; |
| case 13: |
| ASSERT_FALSE(true) TH_LOG("listmount failed"); |
| break; |
| case 14: |
| ASSERT_FALSE(true) TH_LOG("No mounts in new namespace"); |
| break; |
| case 15: |
| ASSERT_FALSE(true) TH_LOG("statmount_alloc failed"); |
| break; |
| default: |
| ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", |
| WEXITSTATUS(status)); |
| break; |
| } |
| } |
| |
| TEST_F(fsmount_ns_userns, umount_succeeds) |
| { |
| pid_t pid; |
| int status; |
| |
| pid = fork(); |
| ASSERT_GE(pid, 0); |
| |
| if (pid == 0) { |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int fs_fd, fd; |
| ssize_t i; |
| |
| if (unshare(CLONE_NEWNS)) |
| _exit(1); |
| |
| if (sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) != 0) |
| _exit(1); |
| |
| fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); |
| if (fs_fd < 0) |
| _exit(3); |
| |
| if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { |
| close(fs_fd); |
| _exit(4); |
| } |
| |
| fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); |
| close(fs_fd); |
| |
| if (fd < 0) { |
| if (errno == EINVAL) |
| _exit(6); |
| _exit(1); |
| } |
| |
| if (get_mnt_ns_id(fd, &new_ns_id) != 0) |
| _exit(7); |
| |
| /* Get all mounts in the new namespace */ |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, LISTMOUNT_REVERSE); |
| if (nr_mounts < 0) |
| _exit(13); |
| |
| if (nr_mounts < 1) |
| _exit(14); |
| |
| /* Enter the new namespace */ |
| if (setns(fd, CLONE_NEWNS) < 0) |
| _exit(8); |
| |
| for (i = 0; i < nr_mounts; i++) { |
| struct statmount *sm; |
| const char *mnt_point; |
| |
| sm = statmount_alloc(list[i], new_ns_id, |
| STATMOUNT_MNT_POINT, 0); |
| if (!sm) |
| _exit(15); |
| |
| mnt_point = sm->str + sm->mnt_point; |
| |
| if (umount2(mnt_point, MNT_DETACH) != 0) { |
| free(sm); |
| _exit(9); |
| } |
| |
| free(sm); |
| } |
| |
| close(fd); |
| _exit(0); |
| } |
| |
| ASSERT_EQ(waitpid(pid, &status, 0), pid); |
| ASSERT_TRUE(WIFEXITED(status)); |
| |
| switch (WEXITSTATUS(status)) { |
| case 0: |
| break; |
| case 1: |
| ASSERT_FALSE(true) TH_LOG("fsmount(FSMOUNT_NAMESPACE) failed or unshare failed"); |
| break; |
| case 3: |
| SKIP(return, "fsopen failed"); |
| break; |
| case 4: |
| SKIP(return, "fsconfig CMD_CREATE failed"); |
| break; |
| case 6: |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| break; |
| case 7: |
| ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID"); |
| break; |
| case 8: |
| ASSERT_FALSE(true) TH_LOG("setns into new namespace failed"); |
| break; |
| case 9: |
| ASSERT_FALSE(true) TH_LOG("umount failed but should have succeeded"); |
| break; |
| case 13: |
| ASSERT_FALSE(true) TH_LOG("listmount failed"); |
| break; |
| case 14: |
| ASSERT_FALSE(true) TH_LOG("No mounts in new namespace"); |
| break; |
| case 15: |
| ASSERT_FALSE(true) TH_LOG("statmount_alloc failed"); |
| break; |
| default: |
| ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", |
| WEXITSTATUS(status)); |
| break; |
| } |
| } |
| |
| FIXTURE(fsmount_ns_mount_attrs) |
| { |
| int fd; |
| int fs_fd; |
| }; |
| |
| FIXTURE_SETUP(fsmount_ns_mount_attrs) |
| { |
| int ret; |
| |
| self->fd = -1; |
| self->fs_fd = -1; |
| |
| /* Check if fsopen syscall is supported */ |
| ret = sys_fsopen("tmpfs", 0); |
| if (ret == -1 && errno == ENOSYS) |
| SKIP(return, "fsopen() syscall not supported"); |
| if (ret >= 0) |
| close(ret); |
| |
| /* Check if statmount/listmount are supported */ |
| ret = statmount(0, 0, 0, 0, NULL, 0, 0); |
| if (ret == -1 && errno == ENOSYS) |
| SKIP(return, "statmount() syscall not supported"); |
| } |
| |
| FIXTURE_TEARDOWN(fsmount_ns_mount_attrs) |
| { |
| if (self->fd >= 0) |
| close(self->fd); |
| if (self->fs_fd >= 0) |
| close(self->fs_fd); |
| } |
| |
| TEST_F(fsmount_ns_mount_attrs, readonly) |
| { |
| struct statmount sm; |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int ret; |
| |
| self->fs_fd = create_tmpfs_fd(); |
| ASSERT_GE(self->fs_fd, 0); |
| |
| self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, |
| MOUNT_ATTR_RDONLY); |
| if (self->fd < 0 && errno == EINVAL) |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| |
| ASSERT_GE(self->fd, 0); |
| |
| ret = get_mnt_ns_id(self->fd, &new_ns_id); |
| ASSERT_EQ(ret, 0); |
| |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); |
| ASSERT_GE(nr_mounts, 1); |
| |
| ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); |
| ASSERT_EQ(ret, 0); |
| |
| /* Verify the mount is read-only */ |
| ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_RDONLY); |
| } |
| |
| TEST_F(fsmount_ns_mount_attrs, noexec) |
| { |
| struct statmount sm; |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int ret; |
| |
| self->fs_fd = create_tmpfs_fd(); |
| ASSERT_GE(self->fs_fd, 0); |
| |
| self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, |
| MOUNT_ATTR_NOEXEC); |
| if (self->fd < 0 && errno == EINVAL) |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| |
| ASSERT_GE(self->fd, 0); |
| |
| ret = get_mnt_ns_id(self->fd, &new_ns_id); |
| ASSERT_EQ(ret, 0); |
| |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); |
| ASSERT_GE(nr_mounts, 1); |
| |
| ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); |
| ASSERT_EQ(ret, 0); |
| |
| /* Verify the mount is noexec */ |
| ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOEXEC); |
| } |
| |
| TEST_F(fsmount_ns_mount_attrs, nosuid) |
| { |
| struct statmount sm; |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int ret; |
| |
| self->fs_fd = create_tmpfs_fd(); |
| ASSERT_GE(self->fs_fd, 0); |
| |
| self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, |
| MOUNT_ATTR_NOSUID); |
| if (self->fd < 0 && errno == EINVAL) |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| |
| ASSERT_GE(self->fd, 0); |
| |
| ret = get_mnt_ns_id(self->fd, &new_ns_id); |
| ASSERT_EQ(ret, 0); |
| |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); |
| ASSERT_GE(nr_mounts, 1); |
| |
| ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); |
| ASSERT_EQ(ret, 0); |
| |
| /* Verify the mount is nosuid */ |
| ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOSUID); |
| } |
| |
| TEST_F(fsmount_ns_mount_attrs, noatime) |
| { |
| struct statmount sm; |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int ret; |
| |
| self->fs_fd = create_tmpfs_fd(); |
| ASSERT_GE(self->fs_fd, 0); |
| |
| self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, |
| MOUNT_ATTR_NOATIME); |
| if (self->fd < 0 && errno == EINVAL) |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| |
| ASSERT_GE(self->fd, 0); |
| |
| ret = get_mnt_ns_id(self->fd, &new_ns_id); |
| ASSERT_EQ(ret, 0); |
| |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); |
| ASSERT_GE(nr_mounts, 1); |
| |
| ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); |
| ASSERT_EQ(ret, 0); |
| |
| /* Verify the mount is noatime */ |
| ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOATIME); |
| } |
| |
| TEST_F(fsmount_ns_mount_attrs, combined) |
| { |
| struct statmount sm; |
| uint64_t new_ns_id; |
| uint64_t list[256]; |
| ssize_t nr_mounts; |
| int ret; |
| |
| self->fs_fd = create_tmpfs_fd(); |
| ASSERT_GE(self->fs_fd, 0); |
| |
| self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, |
| MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | |
| MOUNT_ATTR_NOSUID | MOUNT_ATTR_NOATIME); |
| if (self->fd < 0 && errno == EINVAL) |
| SKIP(return, "FSMOUNT_NAMESPACE not supported"); |
| |
| ASSERT_GE(self->fd, 0); |
| |
| ret = get_mnt_ns_id(self->fd, &new_ns_id); |
| ASSERT_EQ(ret, 0); |
| |
| nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); |
| ASSERT_GE(nr_mounts, 1); |
| |
| ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); |
| ASSERT_EQ(ret, 0); |
| |
| /* Verify all attributes are set */ |
| ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_RDONLY); |
| ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOEXEC); |
| ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOSUID); |
| ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOATIME); |
| } |
| |
| TEST_HARNESS_MAIN |