Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge patch series "fsmount: add FSMOUNT_NAMESPACE"

Christian Brauner <brauner@kernel.org> says:

Add FSMOUNT_NAMESPACE flag to fsmount() that creates a new mount
namespace with the newly created filesystem attached to a copy of the
real rootfs. This returns a namespace file descriptor instead of an
O_PATH mount fd, similar to how OPEN_TREE_NAMESPACE works for
open_tree().

This allows creating a new filesystem and immediately placing it in a
new mount namespace in a single operation, which is useful for container
runtimes and other namespace-based isolation mechanisms.

This accompanies OPEN_TREE_NAMESPACE and avoids a needless detour via
OPEN_TREE_NAMESPACE to get the same effect. Will be especially useful
when you mount an actual filesystem to be used as the container rootfs.

* patches from https://patch.msgid.link/20260122-work-fsmount-namespace-v1-0-5ef0a886e646@kernel.org:
selftests/open_tree_ns: fix compilation
selftests: add FSMOUNT_NAMESPACE tests
selftests/statmount: add statmount_alloc() helper
tools: update mount.h header
mount: add FSMOUNT_NAMESPACE
mount: simplify __do_loopback()
mount: start iterating from start of rbtree

Link: https://patch.msgid.link/20260122-work-fsmount-namespace-v1-0-5ef0a886e646@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>

+1271 -112
+44 -34
fs/namespace.c
··· 2958 2958 } 2959 2959 2960 2960 static struct mount *__do_loopback(const struct path *old_path, 2961 - unsigned int flags, unsigned int copy_flags) 2961 + bool recurse, unsigned int copy_flags) 2962 2962 { 2963 2963 struct mount *old = real_mount(old_path->mnt); 2964 - bool recurse = flags & AT_RECURSIVE; 2965 2964 2966 2965 if (IS_MNT_UNBINDABLE(old)) 2967 2966 return ERR_PTR(-EINVAL); ··· 2970 2971 2971 2972 if (!recurse && __has_locked_children(old, old_path->dentry)) 2972 2973 return ERR_PTR(-EINVAL); 2973 - 2974 - /* 2975 - * When creating a new mount namespace we don't want to copy over 2976 - * mounts of mount namespaces to avoid the risk of cycles and also to 2977 - * minimize the default complex interdependencies between mount 2978 - * namespaces. 2979 - * 2980 - * We could ofc just check whether all mount namespace files aren't 2981 - * creating cycles but really let's keep this simple. 2982 - */ 2983 - if (!(flags & OPEN_TREE_NAMESPACE)) 2984 - copy_flags |= CL_COPY_MNT_NS_FILE; 2985 2974 2986 2975 if (recurse) 2987 2976 return copy_tree(old, old_path->dentry, copy_flags); ··· 2985 2998 { 2986 2999 struct path old_path __free(path_put) = {}; 2987 3000 struct mount *mnt = NULL; 2988 - unsigned int flags = recurse ? AT_RECURSIVE : 0; 2989 3001 int err; 2990 3002 2991 3003 if (!old_name || !*old_name) ··· 3003 3017 if (!check_mnt(mp.parent)) 3004 3018 return -EINVAL; 3005 3019 3006 - mnt = __do_loopback(&old_path, flags, 0); 3020 + mnt = __do_loopback(&old_path, recurse, CL_COPY_MNT_NS_FILE); 3007 3021 if (IS_ERR(mnt)) 3008 3022 return PTR_ERR(mnt); 3009 3023 ··· 3041 3055 ns->seq_origin = src_mnt_ns->ns.ns_id; 3042 3056 } 3043 3057 3044 - mnt = __do_loopback(path, flags, 0); 3058 + mnt = __do_loopback(path, (flags & AT_RECURSIVE), CL_COPY_MNT_NS_FILE); 3045 3059 if (IS_ERR(mnt)) { 3046 3060 emptied_ns = ns; 3047 3061 return ERR_CAST(mnt); ··· 3073 3087 return file; 3074 3088 } 3075 3089 3076 - static struct mnt_namespace *create_new_namespace(struct path *path, unsigned int flags) 3090 + static struct mnt_namespace *create_new_namespace(struct path *path, 3091 + bool recurse) 3077 3092 { 3078 3093 struct mnt_namespace *ns = current->nsproxy->mnt_ns; 3079 3094 struct user_namespace *user_ns = current_user_ns(); ··· 3118 3131 } 3119 3132 3120 3133 /* 3121 - * We don't emulate unshare()ing a mount namespace. We stick 3122 - * to the restrictions of creating detached bind-mounts. It 3123 - * has a lot saner and simpler semantics. 3134 + * We don't emulate unshare()ing a mount namespace. We stick to 3135 + * the restrictions of creating detached bind-mounts. It has a 3136 + * lot saner and simpler semantics. 3124 3137 */ 3125 - mnt = __do_loopback(path, flags, copy_flags); 3138 + mnt = real_mount(path->mnt); 3139 + if (!mnt->mnt_ns) { 3140 + /* 3141 + * If we're moving into a new mount namespace via 3142 + * fsmount() swap the mount ids so the nullfs mount id 3143 + * is the lowest in the mount namespace avoiding another 3144 + * useless copy. This is fine we're not attached to any 3145 + * mount namespace so the mount ids are pure decoration 3146 + * at that point. 3147 + */ 3148 + swap(mnt->mnt_id_unique, new_ns_root->mnt_id_unique); 3149 + swap(mnt->mnt_id, new_ns_root->mnt_id); 3150 + mntget(&mnt->mnt); 3151 + } else { 3152 + mnt = __do_loopback(path, recurse, copy_flags); 3153 + } 3126 3154 scoped_guard(mount_writer) { 3127 3155 if (IS_ERR(mnt)) { 3128 3156 emptied_ns = new_ns; ··· 3166 3164 return new_ns; 3167 3165 } 3168 3166 3169 - static struct file *open_new_namespace(struct path *path, unsigned int flags) 3167 + static struct file *open_new_namespace(struct path *path, bool recurse) 3170 3168 { 3171 3169 struct mnt_namespace *new_ns; 3172 3170 3173 - new_ns = create_new_namespace(path, flags); 3171 + new_ns = create_new_namespace(path, recurse); 3174 3172 if (IS_ERR(new_ns)) 3175 3173 return ERR_CAST(new_ns); 3176 3174 return open_namespace_file(to_ns_common(new_ns)); ··· 3219 3217 return ERR_PTR(ret); 3220 3218 3221 3219 if (flags & OPEN_TREE_NAMESPACE) 3222 - return open_new_namespace(&path, flags); 3220 + return open_new_namespace(&path, (flags & AT_RECURSIVE)); 3223 3221 3224 3222 if (flags & OPEN_TREE_CLONE) 3225 3223 return open_detached_copy(&path, flags); ··· 4416 4414 unsigned int mnt_flags = 0; 4417 4415 long ret; 4418 4416 4419 - if (!may_mount()) 4417 + if ((flags & ~(FSMOUNT_CLOEXEC | FSMOUNT_NAMESPACE)) != 0) 4418 + return -EINVAL; 4419 + 4420 + if ((flags & FSMOUNT_NAMESPACE) && 4421 + !ns_capable(current_user_ns(), CAP_SYS_ADMIN)) 4420 4422 return -EPERM; 4421 4423 4422 - if ((flags & ~(FSMOUNT_CLOEXEC)) != 0) 4423 - return -EINVAL; 4424 + if (!(flags & FSMOUNT_NAMESPACE) && !may_mount()) 4425 + return -EPERM; 4424 4426 4425 4427 if (attr_flags & ~FSMOUNT_VALID_FLAGS) 4426 4428 return -EINVAL; ··· 4490 4484 * don't want to have to handle any errors incurred. 4491 4485 */ 4492 4486 vfs_clean_context(fc); 4487 + 4488 + if (flags & FSMOUNT_NAMESPACE) 4489 + return FD_ADD((flags & FSMOUNT_CLOEXEC) ? O_CLOEXEC : 0, 4490 + open_new_namespace(&new_path, 0)); 4493 4491 4494 4492 ns = alloc_mnt_ns(current->nsproxy->mnt_ns->user_ns, true); 4495 4493 if (IS_ERR(ns)) ··· 5659 5649 if (mnt_ns_empty(ns)) 5660 5650 return -ENOENT; 5661 5651 5662 - first = child = ns->root; 5663 - for (;;) { 5664 - child = listmnt_next(child, false); 5665 - if (!child) 5666 - return -ENOENT; 5667 - if (child->mnt_parent == first) 5652 + first = ns->root; 5653 + for (child = node_to_mount(ns->mnt_first_node); child; 5654 + child = listmnt_next(child, false)) { 5655 + if (child != first && child->mnt_parent == first) 5668 5656 break; 5669 5657 } 5658 + if (!child) 5659 + return -ENOENT; 5670 5660 5671 5661 root->mnt = mntget(&child->mnt); 5672 5662 root->dentry = dget(root->mnt->mnt_root);
+1
include/uapi/linux/mount.h
··· 110 110 * fsmount() flags. 111 111 */ 112 112 #define FSMOUNT_CLOEXEC 0x00000001 113 + #define FSMOUNT_NAMESPACE 0x00000002 /* Create the mount in a new mount namespace */ 113 114 114 115 /* 115 116 * Mount attributes.
+12 -2
tools/include/uapi/linux/mount.h
··· 61 61 /* 62 62 * open_tree() flags. 63 63 */ 64 - #define OPEN_TREE_CLONE 1 /* Clone the target tree and attach the clone */ 64 + #define OPEN_TREE_CLONE (1 << 0) /* Clone the target tree and attach the clone */ 65 + #define OPEN_TREE_NAMESPACE (1 << 1) /* Clone the target tree into a new mount namespace */ 65 66 #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */ 66 67 67 68 /* ··· 110 109 * fsmount() flags. 111 110 */ 112 111 #define FSMOUNT_CLOEXEC 0x00000001 112 + #define FSMOUNT_NAMESPACE 0x00000002 /* Create the mount in a new mount namespace */ 113 113 114 114 /* 115 115 * Mount attributes. ··· 199 197 */ 200 198 struct mnt_id_req { 201 199 __u32 size; 202 - __u32 spare; 200 + union { 201 + __u32 mnt_ns_fd; 202 + __u32 mnt_fd; 203 + }; 203 204 __u64 mnt_id; 204 205 __u64 param; 205 206 __u64 mnt_ns_id; ··· 236 231 */ 237 232 #define LSMT_ROOT 0xffffffffffffffff /* root mount */ 238 233 #define LISTMOUNT_REVERSE (1 << 0) /* List later mounts first */ 234 + 235 + /* 236 + * @flag bits for statmount(2) 237 + */ 238 + #define STATMOUNT_BY_FD 0x00000001U /* want mountinfo for given fd */ 239 239 240 240 #endif /* _UAPI_LINUX_MOUNT_H */
+1
tools/testing/selftests/filesystems/fsmount_ns/.gitignore
··· 1 + fsmount_ns_test
+10
tools/testing/selftests/filesystems/fsmount_ns/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + TEST_GEN_PROGS := fsmount_ns_test 3 + 4 + CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) 5 + LDLIBS := -lcap 6 + 7 + include ../../lib.mk 8 + 9 + $(OUTPUT)/fsmount_ns_test: fsmount_ns_test.c ../utils.c 10 + $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)
+1138
tools/testing/selftests/filesystems/fsmount_ns/fsmount_ns_test.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (C) 2026 Christian Brauner <brauner@kernel.org> 4 + * 5 + * Test for FSMOUNT_NAMESPACE flag. 6 + * 7 + * Test that fsmount() with FSMOUNT_NAMESPACE creates a new mount 8 + * namespace containing the specified mount. 9 + */ 10 + #define _GNU_SOURCE 11 + 12 + #include <errno.h> 13 + #include <fcntl.h> 14 + #include <limits.h> 15 + #include <linux/nsfs.h> 16 + #include <sched.h> 17 + #include <stdio.h> 18 + #include <stdlib.h> 19 + #include <string.h> 20 + #include <sys/ioctl.h> 21 + #include <sys/mount.h> 22 + #include <sys/stat.h> 23 + #include <sys/wait.h> 24 + #include <unistd.h> 25 + 26 + #include "../wrappers.h" 27 + #include "../statmount/statmount.h" 28 + #include "../utils.h" 29 + #include "../../kselftest_harness.h" 30 + 31 + #ifndef FSMOUNT_NAMESPACE 32 + #define FSMOUNT_NAMESPACE 0x00000002 33 + #endif 34 + 35 + #ifndef FSMOUNT_CLOEXEC 36 + #define FSMOUNT_CLOEXEC 0x00000001 37 + #endif 38 + 39 + #ifndef FSCONFIG_CMD_CREATE 40 + #define FSCONFIG_CMD_CREATE 6 41 + #endif 42 + 43 + static int get_mnt_ns_id(int fd, uint64_t *mnt_ns_id) 44 + { 45 + if (ioctl(fd, NS_GET_MNTNS_ID, mnt_ns_id) < 0) 46 + return -errno; 47 + return 0; 48 + } 49 + 50 + static int get_mnt_ns_id_from_path(const char *path, uint64_t *mnt_ns_id) 51 + { 52 + int fd, ret; 53 + 54 + fd = open(path, O_RDONLY); 55 + if (fd < 0) 56 + return -errno; 57 + 58 + ret = get_mnt_ns_id(fd, mnt_ns_id); 59 + close(fd); 60 + return ret; 61 + } 62 + 63 + static void log_mount(struct __test_metadata *_metadata, struct statmount *sm) 64 + { 65 + const char *fs_type = ""; 66 + const char *mnt_root = ""; 67 + const char *mnt_point = ""; 68 + 69 + if (sm->mask & STATMOUNT_FS_TYPE) 70 + fs_type = sm->str + sm->fs_type; 71 + if (sm->mask & STATMOUNT_MNT_ROOT) 72 + mnt_root = sm->str + sm->mnt_root; 73 + if (sm->mask & STATMOUNT_MNT_POINT) 74 + mnt_point = sm->str + sm->mnt_point; 75 + 76 + TH_LOG(" mnt_id: %llu, parent_id: %llu, fs_type: %s, root: %s, point: %s", 77 + (unsigned long long)sm->mnt_id, 78 + (unsigned long long)sm->mnt_parent_id, 79 + fs_type, mnt_root, mnt_point); 80 + } 81 + 82 + static void dump_mounts(struct __test_metadata *_metadata, uint64_t mnt_ns_id) 83 + { 84 + uint64_t list[256]; 85 + ssize_t nr_mounts; 86 + 87 + nr_mounts = listmount(LSMT_ROOT, mnt_ns_id, 0, list, 256, 0); 88 + if (nr_mounts < 0) { 89 + TH_LOG("listmount failed: %s", strerror(errno)); 90 + return; 91 + } 92 + 93 + TH_LOG("Mount namespace %llu contains %zd mount(s):", 94 + (unsigned long long)mnt_ns_id, nr_mounts); 95 + 96 + for (ssize_t i = 0; i < nr_mounts; i++) { 97 + struct statmount *sm; 98 + 99 + sm = statmount_alloc(list[i], mnt_ns_id, 100 + STATMOUNT_MNT_BASIC | 101 + STATMOUNT_FS_TYPE | 102 + STATMOUNT_MNT_ROOT | 103 + STATMOUNT_MNT_POINT, 0); 104 + if (!sm) { 105 + TH_LOG(" [%zd] mnt_id %llu: statmount failed: %s", 106 + i, (unsigned long long)list[i], strerror(errno)); 107 + continue; 108 + } 109 + 110 + log_mount(_metadata, sm); 111 + free(sm); 112 + } 113 + } 114 + 115 + static int create_tmpfs_fd(void) 116 + { 117 + int fs_fd, ret; 118 + 119 + fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); 120 + if (fs_fd < 0) 121 + return -errno; 122 + 123 + ret = sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); 124 + if (ret < 0) { 125 + close(fs_fd); 126 + return -errno; 127 + } 128 + 129 + return fs_fd; 130 + } 131 + 132 + FIXTURE(fsmount_ns) 133 + { 134 + int fd; 135 + int fs_fd; 136 + uint64_t current_ns_id; 137 + }; 138 + 139 + FIXTURE_VARIANT(fsmount_ns) 140 + { 141 + const char *fstype; 142 + unsigned int flags; 143 + bool expect_success; 144 + bool expect_different_ns; 145 + int min_mounts; 146 + }; 147 + 148 + FIXTURE_VARIANT_ADD(fsmount_ns, basic_tmpfs) 149 + { 150 + .fstype = "tmpfs", 151 + .flags = FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 152 + .expect_success = true, 153 + .expect_different_ns = true, 154 + .min_mounts = 1, 155 + }; 156 + 157 + FIXTURE_VARIANT_ADD(fsmount_ns, cloexec_only) 158 + { 159 + .fstype = "tmpfs", 160 + .flags = FSMOUNT_CLOEXEC, 161 + .expect_success = true, 162 + .expect_different_ns = false, 163 + .min_mounts = 1, 164 + }; 165 + 166 + FIXTURE_VARIANT_ADD(fsmount_ns, namespace_only) 167 + { 168 + .fstype = "tmpfs", 169 + .flags = FSMOUNT_NAMESPACE, 170 + .expect_success = true, 171 + .expect_different_ns = true, 172 + .min_mounts = 1, 173 + }; 174 + 175 + FIXTURE_SETUP(fsmount_ns) 176 + { 177 + int ret; 178 + 179 + self->fd = -1; 180 + self->fs_fd = -1; 181 + 182 + /* Check if fsopen syscall is supported */ 183 + ret = sys_fsopen("tmpfs", 0); 184 + if (ret == -1 && errno == ENOSYS) 185 + SKIP(return, "fsopen() syscall not supported"); 186 + if (ret >= 0) 187 + close(ret); 188 + 189 + /* Check if statmount/listmount are supported */ 190 + ret = statmount(0, 0, 0, 0, NULL, 0, 0); 191 + if (ret == -1 && errno == ENOSYS) 192 + SKIP(return, "statmount() syscall not supported"); 193 + 194 + /* Get current mount namespace ID for comparison */ 195 + ret = get_mnt_ns_id_from_path("/proc/self/ns/mnt", &self->current_ns_id); 196 + if (ret < 0) 197 + SKIP(return, "Failed to get current mount namespace ID"); 198 + } 199 + 200 + FIXTURE_TEARDOWN(fsmount_ns) 201 + { 202 + if (self->fd >= 0) 203 + close(self->fd); 204 + if (self->fs_fd >= 0) 205 + close(self->fs_fd); 206 + } 207 + 208 + TEST_F(fsmount_ns, create_namespace) 209 + { 210 + uint64_t new_ns_id; 211 + uint64_t list[256]; 212 + ssize_t nr_mounts; 213 + int ret; 214 + 215 + self->fs_fd = create_tmpfs_fd(); 216 + ASSERT_GE(self->fs_fd, 0); 217 + 218 + self->fd = sys_fsmount(self->fs_fd, variant->flags, 0); 219 + 220 + if (!variant->expect_success) { 221 + ASSERT_LT(self->fd, 0); 222 + return; 223 + } 224 + 225 + if (self->fd < 0 && errno == EINVAL) 226 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 227 + 228 + ASSERT_GE(self->fd, 0); 229 + 230 + if (variant->expect_different_ns) { 231 + /* Verify we can get the namespace ID from the fd */ 232 + ret = get_mnt_ns_id(self->fd, &new_ns_id); 233 + ASSERT_EQ(ret, 0); 234 + 235 + /* Verify it's a different namespace */ 236 + ASSERT_NE(new_ns_id, self->current_ns_id); 237 + 238 + /* List mounts in the new namespace */ 239 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); 240 + ASSERT_GE(nr_mounts, 0) { 241 + TH_LOG("%m - listmount failed"); 242 + } 243 + 244 + /* Verify minimum expected mounts */ 245 + ASSERT_GE(nr_mounts, variant->min_mounts); 246 + TH_LOG("Namespace contains %zd mounts", nr_mounts); 247 + } 248 + } 249 + 250 + TEST_F(fsmount_ns, setns_into_namespace) 251 + { 252 + uint64_t new_ns_id; 253 + pid_t pid; 254 + int status; 255 + int ret; 256 + 257 + /* Only test with FSMOUNT_NAMESPACE flag */ 258 + if (!(variant->flags & FSMOUNT_NAMESPACE)) 259 + SKIP(return, "setns test only for FSMOUNT_NAMESPACE case"); 260 + 261 + self->fs_fd = create_tmpfs_fd(); 262 + ASSERT_GE(self->fs_fd, 0); 263 + 264 + self->fd = sys_fsmount(self->fs_fd, variant->flags, 0); 265 + if (self->fd < 0 && errno == EINVAL) 266 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 267 + 268 + ASSERT_GE(self->fd, 0); 269 + 270 + /* Get namespace ID and dump all mounts */ 271 + ret = get_mnt_ns_id(self->fd, &new_ns_id); 272 + ASSERT_EQ(ret, 0); 273 + 274 + dump_mounts(_metadata, new_ns_id); 275 + 276 + pid = fork(); 277 + ASSERT_GE(pid, 0); 278 + 279 + if (pid == 0) { 280 + /* Child: try to enter the namespace */ 281 + if (setns(self->fd, CLONE_NEWNS) < 0) 282 + _exit(1); 283 + _exit(0); 284 + } 285 + 286 + ASSERT_EQ(waitpid(pid, &status, 0), pid); 287 + ASSERT_TRUE(WIFEXITED(status)); 288 + ASSERT_EQ(WEXITSTATUS(status), 0); 289 + } 290 + 291 + TEST_F(fsmount_ns, verify_mount_properties) 292 + { 293 + struct statmount sm; 294 + uint64_t new_ns_id; 295 + uint64_t list[256]; 296 + ssize_t nr_mounts; 297 + int ret; 298 + 299 + /* Only test with basic FSMOUNT_NAMESPACE flags */ 300 + if (variant->flags != (FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC)) 301 + SKIP(return, "mount properties test only for basic case"); 302 + 303 + self->fs_fd = create_tmpfs_fd(); 304 + ASSERT_GE(self->fs_fd, 0); 305 + 306 + self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); 307 + if (self->fd < 0 && errno == EINVAL) 308 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 309 + 310 + ASSERT_GE(self->fd, 0); 311 + 312 + ret = get_mnt_ns_id(self->fd, &new_ns_id); 313 + ASSERT_EQ(ret, 0); 314 + 315 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); 316 + ASSERT_GE(nr_mounts, 1); 317 + 318 + /* Get info about the root mount */ 319 + ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); 320 + ASSERT_EQ(ret, 0); 321 + 322 + TH_LOG("Root mount id: %llu, parent: %llu", 323 + (unsigned long long)sm.mnt_id, 324 + (unsigned long long)sm.mnt_parent_id); 325 + } 326 + 327 + TEST_F(fsmount_ns, verify_tmpfs_type) 328 + { 329 + struct statmount *sm; 330 + uint64_t new_ns_id; 331 + uint64_t list[256]; 332 + ssize_t nr_mounts; 333 + const char *fs_type; 334 + int ret; 335 + 336 + /* Only test with basic FSMOUNT_NAMESPACE flags */ 337 + if (variant->flags != (FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC)) 338 + SKIP(return, "fs type test only for basic case"); 339 + 340 + self->fs_fd = create_tmpfs_fd(); 341 + ASSERT_GE(self->fs_fd, 0); 342 + 343 + self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); 344 + if (self->fd < 0 && errno == EINVAL) 345 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 346 + 347 + ASSERT_GE(self->fd, 0); 348 + 349 + ret = get_mnt_ns_id(self->fd, &new_ns_id); 350 + ASSERT_EQ(ret, 0); 351 + 352 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); 353 + ASSERT_GE(nr_mounts, 1); 354 + 355 + sm = statmount_alloc(list[0], new_ns_id, STATMOUNT_FS_TYPE, 0); 356 + ASSERT_NE(sm, NULL); 357 + 358 + fs_type = sm->str + sm->fs_type; 359 + ASSERT_STREQ(fs_type, "tmpfs"); 360 + 361 + free(sm); 362 + } 363 + 364 + FIXTURE(fsmount_ns_caps) 365 + { 366 + bool has_caps; 367 + }; 368 + 369 + FIXTURE_SETUP(fsmount_ns_caps) 370 + { 371 + int ret; 372 + 373 + /* Check if fsopen syscall is supported */ 374 + ret = sys_fsopen("tmpfs", 0); 375 + if (ret == -1 && errno == ENOSYS) 376 + SKIP(return, "fsopen() syscall not supported"); 377 + if (ret >= 0) 378 + close(ret); 379 + 380 + self->has_caps = (geteuid() == 0); 381 + } 382 + 383 + FIXTURE_TEARDOWN(fsmount_ns_caps) 384 + { 385 + } 386 + 387 + TEST_F(fsmount_ns_caps, requires_cap_sys_admin) 388 + { 389 + pid_t pid; 390 + int status; 391 + 392 + pid = fork(); 393 + ASSERT_GE(pid, 0); 394 + 395 + if (pid == 0) { 396 + int fs_fd, fd; 397 + 398 + /* Child: drop privileges using utils.h helper */ 399 + if (enter_userns() != 0) 400 + _exit(2); 401 + 402 + /* Drop all caps using utils.h helper */ 403 + if (caps_down() == 0) 404 + _exit(3); 405 + 406 + fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); 407 + if (fs_fd < 0) 408 + _exit(4); 409 + 410 + if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { 411 + close(fs_fd); 412 + _exit(5); 413 + } 414 + 415 + fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); 416 + close(fs_fd); 417 + 418 + if (fd >= 0) { 419 + close(fd); 420 + /* Should have failed without caps */ 421 + _exit(1); 422 + } 423 + 424 + if (errno == EPERM) 425 + _exit(0); 426 + 427 + /* EINVAL means FSMOUNT_NAMESPACE not supported */ 428 + if (errno == EINVAL) 429 + _exit(6); 430 + 431 + /* Unexpected error */ 432 + _exit(7); 433 + } 434 + 435 + ASSERT_EQ(waitpid(pid, &status, 0), pid); 436 + ASSERT_TRUE(WIFEXITED(status)); 437 + 438 + switch (WEXITSTATUS(status)) { 439 + case 0: 440 + /* Expected: EPERM without caps */ 441 + break; 442 + case 1: 443 + ASSERT_FALSE(true) TH_LOG("FSMOUNT_NAMESPACE succeeded without caps"); 444 + break; 445 + case 2: 446 + SKIP(return, "enter_userns failed"); 447 + break; 448 + case 3: 449 + SKIP(return, "caps_down failed"); 450 + break; 451 + case 4: 452 + SKIP(return, "fsopen failed in userns"); 453 + break; 454 + case 5: 455 + SKIP(return, "fsconfig CMD_CREATE failed in userns"); 456 + break; 457 + case 6: 458 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 459 + break; 460 + default: 461 + ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", 462 + WEXITSTATUS(status)); 463 + break; 464 + } 465 + } 466 + 467 + FIXTURE(fsmount_ns_userns) 468 + { 469 + int fd; 470 + int fs_fd; 471 + }; 472 + 473 + FIXTURE_SETUP(fsmount_ns_userns) 474 + { 475 + int ret; 476 + 477 + self->fd = -1; 478 + self->fs_fd = -1; 479 + 480 + /* Check if fsopen syscall is supported */ 481 + ret = sys_fsopen("tmpfs", 0); 482 + if (ret == -1 && errno == ENOSYS) 483 + SKIP(return, "fsopen() syscall not supported"); 484 + if (ret >= 0) 485 + close(ret); 486 + 487 + /* Check if statmount/listmount are supported */ 488 + ret = statmount(0, 0, 0, 0, NULL, 0, 0); 489 + if (ret == -1 && errno == ENOSYS) 490 + SKIP(return, "statmount() syscall not supported"); 491 + } 492 + 493 + FIXTURE_TEARDOWN(fsmount_ns_userns) 494 + { 495 + if (self->fd >= 0) 496 + close(self->fd); 497 + if (self->fs_fd >= 0) 498 + close(self->fs_fd); 499 + } 500 + 501 + TEST_F(fsmount_ns_userns, create_in_userns) 502 + { 503 + pid_t pid; 504 + int status; 505 + 506 + pid = fork(); 507 + ASSERT_GE(pid, 0); 508 + 509 + if (pid == 0) { 510 + uint64_t new_ns_id; 511 + uint64_t list[256]; 512 + ssize_t nr_mounts; 513 + int fs_fd, fd; 514 + 515 + /* Create new user namespace (also creates mount namespace) */ 516 + if (setup_userns() != 0) 517 + _exit(2); 518 + 519 + /* Now we have CAP_SYS_ADMIN in the user namespace */ 520 + fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); 521 + if (fs_fd < 0) 522 + _exit(3); 523 + 524 + if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { 525 + close(fs_fd); 526 + _exit(4); 527 + } 528 + 529 + fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); 530 + close(fs_fd); 531 + 532 + if (fd < 0) { 533 + if (errno == EINVAL) 534 + _exit(6); /* FSMOUNT_NAMESPACE not supported */ 535 + _exit(1); 536 + } 537 + 538 + /* Verify we can get the namespace ID */ 539 + if (get_mnt_ns_id(fd, &new_ns_id) != 0) 540 + _exit(7); 541 + 542 + /* Verify we can list mounts in the new namespace */ 543 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); 544 + if (nr_mounts < 0) 545 + _exit(8); 546 + 547 + /* Should have at least 1 mount (the tmpfs) */ 548 + if (nr_mounts < 1) 549 + _exit(9); 550 + 551 + close(fd); 552 + _exit(0); 553 + } 554 + 555 + ASSERT_EQ(waitpid(pid, &status, 0), pid); 556 + ASSERT_TRUE(WIFEXITED(status)); 557 + 558 + switch (WEXITSTATUS(status)) { 559 + case 0: 560 + /* Success */ 561 + break; 562 + case 1: 563 + ASSERT_FALSE(true) TH_LOG("fsmount(FSMOUNT_NAMESPACE) failed in userns"); 564 + break; 565 + case 2: 566 + SKIP(return, "setup_userns failed"); 567 + break; 568 + case 3: 569 + SKIP(return, "fsopen failed in userns"); 570 + break; 571 + case 4: 572 + SKIP(return, "fsconfig CMD_CREATE failed in userns"); 573 + break; 574 + case 6: 575 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 576 + break; 577 + case 7: 578 + ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID"); 579 + break; 580 + case 8: 581 + ASSERT_FALSE(true) TH_LOG("listmount failed in new namespace"); 582 + break; 583 + case 9: 584 + ASSERT_FALSE(true) TH_LOG("New namespace has no mounts"); 585 + break; 586 + default: 587 + ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", 588 + WEXITSTATUS(status)); 589 + break; 590 + } 591 + } 592 + 593 + TEST_F(fsmount_ns_userns, setns_in_userns) 594 + { 595 + pid_t pid; 596 + int status; 597 + 598 + pid = fork(); 599 + ASSERT_GE(pid, 0); 600 + 601 + if (pid == 0) { 602 + uint64_t new_ns_id; 603 + int fs_fd, fd; 604 + pid_t inner_pid; 605 + int inner_status; 606 + 607 + /* Create new user namespace */ 608 + if (setup_userns() != 0) 609 + _exit(2); 610 + 611 + fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); 612 + if (fs_fd < 0) 613 + _exit(3); 614 + 615 + if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { 616 + close(fs_fd); 617 + _exit(4); 618 + } 619 + 620 + fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); 621 + close(fs_fd); 622 + 623 + if (fd < 0) { 624 + if (errno == EINVAL) 625 + _exit(6); 626 + _exit(1); 627 + } 628 + 629 + if (get_mnt_ns_id(fd, &new_ns_id) != 0) 630 + _exit(7); 631 + 632 + /* Fork again to test setns into the new namespace */ 633 + inner_pid = fork(); 634 + if (inner_pid < 0) 635 + _exit(10); 636 + 637 + if (inner_pid == 0) { 638 + /* Inner child: enter the new namespace */ 639 + if (setns(fd, CLONE_NEWNS) < 0) 640 + _exit(1); 641 + _exit(0); 642 + } 643 + 644 + if (waitpid(inner_pid, &inner_status, 0) != inner_pid) 645 + _exit(11); 646 + 647 + if (!WIFEXITED(inner_status) || WEXITSTATUS(inner_status) != 0) 648 + _exit(12); 649 + 650 + close(fd); 651 + _exit(0); 652 + } 653 + 654 + ASSERT_EQ(waitpid(pid, &status, 0), pid); 655 + ASSERT_TRUE(WIFEXITED(status)); 656 + 657 + switch (WEXITSTATUS(status)) { 658 + case 0: 659 + /* Success */ 660 + break; 661 + case 1: 662 + ASSERT_FALSE(true) TH_LOG("fsmount or setns failed in userns"); 663 + break; 664 + case 2: 665 + SKIP(return, "setup_userns failed"); 666 + break; 667 + case 3: 668 + SKIP(return, "fsopen failed in userns"); 669 + break; 670 + case 4: 671 + SKIP(return, "fsconfig CMD_CREATE failed in userns"); 672 + break; 673 + case 6: 674 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 675 + break; 676 + case 7: 677 + ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID"); 678 + break; 679 + case 10: 680 + ASSERT_FALSE(true) TH_LOG("Inner fork failed"); 681 + break; 682 + case 11: 683 + ASSERT_FALSE(true) TH_LOG("Inner waitpid failed"); 684 + break; 685 + case 12: 686 + ASSERT_FALSE(true) TH_LOG("setns into new namespace failed"); 687 + break; 688 + default: 689 + ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", 690 + WEXITSTATUS(status)); 691 + break; 692 + } 693 + } 694 + 695 + TEST_F(fsmount_ns_userns, umount_fails_einval) 696 + { 697 + pid_t pid; 698 + int status; 699 + 700 + pid = fork(); 701 + ASSERT_GE(pid, 0); 702 + 703 + if (pid == 0) { 704 + uint64_t new_ns_id; 705 + uint64_t list[256]; 706 + ssize_t nr_mounts; 707 + int fs_fd, fd; 708 + ssize_t i; 709 + 710 + /* Create new user namespace */ 711 + if (setup_userns() != 0) 712 + _exit(2); 713 + 714 + fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); 715 + if (fs_fd < 0) 716 + _exit(3); 717 + 718 + if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { 719 + close(fs_fd); 720 + _exit(4); 721 + } 722 + 723 + fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); 724 + close(fs_fd); 725 + 726 + if (fd < 0) { 727 + if (errno == EINVAL) 728 + _exit(6); 729 + _exit(1); 730 + } 731 + 732 + if (get_mnt_ns_id(fd, &new_ns_id) != 0) 733 + _exit(7); 734 + 735 + /* Get all mounts in the new namespace */ 736 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, LISTMOUNT_REVERSE); 737 + if (nr_mounts < 0) 738 + _exit(13); 739 + 740 + if (nr_mounts < 1) 741 + _exit(14); 742 + 743 + /* Enter the new namespace */ 744 + if (setns(fd, CLONE_NEWNS) < 0) 745 + _exit(8); 746 + 747 + for (i = 0; i < nr_mounts; i++) { 748 + struct statmount *sm; 749 + const char *mnt_point; 750 + 751 + sm = statmount_alloc(list[i], new_ns_id, 752 + STATMOUNT_MNT_POINT, 0); 753 + if (!sm) 754 + _exit(15); 755 + 756 + mnt_point = sm->str + sm->mnt_point; 757 + 758 + if (umount2(mnt_point, MNT_DETACH) == 0) { 759 + free(sm); 760 + _exit(9); 761 + } 762 + 763 + if (errno != EINVAL) { 764 + /* Wrong error */ 765 + free(sm); 766 + _exit(10); 767 + } 768 + 769 + free(sm); 770 + } 771 + 772 + close(fd); 773 + _exit(0); 774 + } 775 + 776 + ASSERT_EQ(waitpid(pid, &status, 0), pid); 777 + ASSERT_TRUE(WIFEXITED(status)); 778 + 779 + switch (WEXITSTATUS(status)) { 780 + case 0: 781 + break; 782 + case 1: 783 + ASSERT_FALSE(true) TH_LOG("fsmount(FSMOUNT_NAMESPACE) failed"); 784 + break; 785 + case 2: 786 + SKIP(return, "setup_userns failed"); 787 + break; 788 + case 3: 789 + SKIP(return, "fsopen failed in userns"); 790 + break; 791 + case 4: 792 + SKIP(return, "fsconfig CMD_CREATE failed in userns"); 793 + break; 794 + case 6: 795 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 796 + break; 797 + case 7: 798 + ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID"); 799 + break; 800 + case 8: 801 + ASSERT_FALSE(true) TH_LOG("setns into new namespace failed"); 802 + break; 803 + case 9: 804 + ASSERT_FALSE(true) TH_LOG("umount succeeded but should have failed with EINVAL"); 805 + break; 806 + case 10: 807 + ASSERT_FALSE(true) TH_LOG("umount failed with wrong error (expected EINVAL)"); 808 + break; 809 + case 13: 810 + ASSERT_FALSE(true) TH_LOG("listmount failed"); 811 + break; 812 + case 14: 813 + ASSERT_FALSE(true) TH_LOG("No mounts in new namespace"); 814 + break; 815 + case 15: 816 + ASSERT_FALSE(true) TH_LOG("statmount_alloc failed"); 817 + break; 818 + default: 819 + ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", 820 + WEXITSTATUS(status)); 821 + break; 822 + } 823 + } 824 + 825 + TEST_F(fsmount_ns_userns, umount_succeeds) 826 + { 827 + pid_t pid; 828 + int status; 829 + 830 + pid = fork(); 831 + ASSERT_GE(pid, 0); 832 + 833 + if (pid == 0) { 834 + uint64_t new_ns_id; 835 + uint64_t list[256]; 836 + ssize_t nr_mounts; 837 + int fs_fd, fd; 838 + ssize_t i; 839 + 840 + if (unshare(CLONE_NEWNS)) 841 + _exit(1); 842 + 843 + if (sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) != 0) 844 + _exit(1); 845 + 846 + fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); 847 + if (fs_fd < 0) 848 + _exit(3); 849 + 850 + if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) { 851 + close(fs_fd); 852 + _exit(4); 853 + } 854 + 855 + fd = sys_fsmount(fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 0); 856 + close(fs_fd); 857 + 858 + if (fd < 0) { 859 + if (errno == EINVAL) 860 + _exit(6); 861 + _exit(1); 862 + } 863 + 864 + if (get_mnt_ns_id(fd, &new_ns_id) != 0) 865 + _exit(7); 866 + 867 + /* Get all mounts in the new namespace */ 868 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, LISTMOUNT_REVERSE); 869 + if (nr_mounts < 0) 870 + _exit(13); 871 + 872 + if (nr_mounts < 1) 873 + _exit(14); 874 + 875 + /* Enter the new namespace */ 876 + if (setns(fd, CLONE_NEWNS) < 0) 877 + _exit(8); 878 + 879 + for (i = 0; i < nr_mounts; i++) { 880 + struct statmount *sm; 881 + const char *mnt_point; 882 + 883 + sm = statmount_alloc(list[i], new_ns_id, 884 + STATMOUNT_MNT_POINT, 0); 885 + if (!sm) 886 + _exit(15); 887 + 888 + mnt_point = sm->str + sm->mnt_point; 889 + 890 + if (umount2(mnt_point, MNT_DETACH) != 0) { 891 + free(sm); 892 + _exit(9); 893 + } 894 + 895 + free(sm); 896 + } 897 + 898 + close(fd); 899 + _exit(0); 900 + } 901 + 902 + ASSERT_EQ(waitpid(pid, &status, 0), pid); 903 + ASSERT_TRUE(WIFEXITED(status)); 904 + 905 + switch (WEXITSTATUS(status)) { 906 + case 0: 907 + break; 908 + case 1: 909 + ASSERT_FALSE(true) TH_LOG("fsmount(FSMOUNT_NAMESPACE) failed or unshare failed"); 910 + break; 911 + case 3: 912 + SKIP(return, "fsopen failed"); 913 + break; 914 + case 4: 915 + SKIP(return, "fsconfig CMD_CREATE failed"); 916 + break; 917 + case 6: 918 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 919 + break; 920 + case 7: 921 + ASSERT_FALSE(true) TH_LOG("Failed to get mount namespace ID"); 922 + break; 923 + case 8: 924 + ASSERT_FALSE(true) TH_LOG("setns into new namespace failed"); 925 + break; 926 + case 9: 927 + ASSERT_FALSE(true) TH_LOG("umount failed but should have succeeded"); 928 + break; 929 + case 13: 930 + ASSERT_FALSE(true) TH_LOG("listmount failed"); 931 + break; 932 + case 14: 933 + ASSERT_FALSE(true) TH_LOG("No mounts in new namespace"); 934 + break; 935 + case 15: 936 + ASSERT_FALSE(true) TH_LOG("statmount_alloc failed"); 937 + break; 938 + default: 939 + ASSERT_FALSE(true) TH_LOG("Unexpected error in child (exit %d)", 940 + WEXITSTATUS(status)); 941 + break; 942 + } 943 + } 944 + 945 + FIXTURE(fsmount_ns_mount_attrs) 946 + { 947 + int fd; 948 + int fs_fd; 949 + }; 950 + 951 + FIXTURE_SETUP(fsmount_ns_mount_attrs) 952 + { 953 + int ret; 954 + 955 + self->fd = -1; 956 + self->fs_fd = -1; 957 + 958 + /* Check if fsopen syscall is supported */ 959 + ret = sys_fsopen("tmpfs", 0); 960 + if (ret == -1 && errno == ENOSYS) 961 + SKIP(return, "fsopen() syscall not supported"); 962 + if (ret >= 0) 963 + close(ret); 964 + 965 + /* Check if statmount/listmount are supported */ 966 + ret = statmount(0, 0, 0, 0, NULL, 0, 0); 967 + if (ret == -1 && errno == ENOSYS) 968 + SKIP(return, "statmount() syscall not supported"); 969 + } 970 + 971 + FIXTURE_TEARDOWN(fsmount_ns_mount_attrs) 972 + { 973 + if (self->fd >= 0) 974 + close(self->fd); 975 + if (self->fs_fd >= 0) 976 + close(self->fs_fd); 977 + } 978 + 979 + TEST_F(fsmount_ns_mount_attrs, readonly) 980 + { 981 + struct statmount sm; 982 + uint64_t new_ns_id; 983 + uint64_t list[256]; 984 + ssize_t nr_mounts; 985 + int ret; 986 + 987 + self->fs_fd = create_tmpfs_fd(); 988 + ASSERT_GE(self->fs_fd, 0); 989 + 990 + self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 991 + MOUNT_ATTR_RDONLY); 992 + if (self->fd < 0 && errno == EINVAL) 993 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 994 + 995 + ASSERT_GE(self->fd, 0); 996 + 997 + ret = get_mnt_ns_id(self->fd, &new_ns_id); 998 + ASSERT_EQ(ret, 0); 999 + 1000 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); 1001 + ASSERT_GE(nr_mounts, 1); 1002 + 1003 + ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); 1004 + ASSERT_EQ(ret, 0); 1005 + 1006 + /* Verify the mount is read-only */ 1007 + ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_RDONLY); 1008 + } 1009 + 1010 + TEST_F(fsmount_ns_mount_attrs, noexec) 1011 + { 1012 + struct statmount sm; 1013 + uint64_t new_ns_id; 1014 + uint64_t list[256]; 1015 + ssize_t nr_mounts; 1016 + int ret; 1017 + 1018 + self->fs_fd = create_tmpfs_fd(); 1019 + ASSERT_GE(self->fs_fd, 0); 1020 + 1021 + self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 1022 + MOUNT_ATTR_NOEXEC); 1023 + if (self->fd < 0 && errno == EINVAL) 1024 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 1025 + 1026 + ASSERT_GE(self->fd, 0); 1027 + 1028 + ret = get_mnt_ns_id(self->fd, &new_ns_id); 1029 + ASSERT_EQ(ret, 0); 1030 + 1031 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); 1032 + ASSERT_GE(nr_mounts, 1); 1033 + 1034 + ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); 1035 + ASSERT_EQ(ret, 0); 1036 + 1037 + /* Verify the mount is noexec */ 1038 + ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOEXEC); 1039 + } 1040 + 1041 + TEST_F(fsmount_ns_mount_attrs, nosuid) 1042 + { 1043 + struct statmount sm; 1044 + uint64_t new_ns_id; 1045 + uint64_t list[256]; 1046 + ssize_t nr_mounts; 1047 + int ret; 1048 + 1049 + self->fs_fd = create_tmpfs_fd(); 1050 + ASSERT_GE(self->fs_fd, 0); 1051 + 1052 + self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 1053 + MOUNT_ATTR_NOSUID); 1054 + if (self->fd < 0 && errno == EINVAL) 1055 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 1056 + 1057 + ASSERT_GE(self->fd, 0); 1058 + 1059 + ret = get_mnt_ns_id(self->fd, &new_ns_id); 1060 + ASSERT_EQ(ret, 0); 1061 + 1062 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); 1063 + ASSERT_GE(nr_mounts, 1); 1064 + 1065 + ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); 1066 + ASSERT_EQ(ret, 0); 1067 + 1068 + /* Verify the mount is nosuid */ 1069 + ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOSUID); 1070 + } 1071 + 1072 + TEST_F(fsmount_ns_mount_attrs, noatime) 1073 + { 1074 + struct statmount sm; 1075 + uint64_t new_ns_id; 1076 + uint64_t list[256]; 1077 + ssize_t nr_mounts; 1078 + int ret; 1079 + 1080 + self->fs_fd = create_tmpfs_fd(); 1081 + ASSERT_GE(self->fs_fd, 0); 1082 + 1083 + self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 1084 + MOUNT_ATTR_NOATIME); 1085 + if (self->fd < 0 && errno == EINVAL) 1086 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 1087 + 1088 + ASSERT_GE(self->fd, 0); 1089 + 1090 + ret = get_mnt_ns_id(self->fd, &new_ns_id); 1091 + ASSERT_EQ(ret, 0); 1092 + 1093 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); 1094 + ASSERT_GE(nr_mounts, 1); 1095 + 1096 + ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); 1097 + ASSERT_EQ(ret, 0); 1098 + 1099 + /* Verify the mount is noatime */ 1100 + ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOATIME); 1101 + } 1102 + 1103 + TEST_F(fsmount_ns_mount_attrs, combined) 1104 + { 1105 + struct statmount sm; 1106 + uint64_t new_ns_id; 1107 + uint64_t list[256]; 1108 + ssize_t nr_mounts; 1109 + int ret; 1110 + 1111 + self->fs_fd = create_tmpfs_fd(); 1112 + ASSERT_GE(self->fs_fd, 0); 1113 + 1114 + self->fd = sys_fsmount(self->fs_fd, FSMOUNT_NAMESPACE | FSMOUNT_CLOEXEC, 1115 + MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | 1116 + MOUNT_ATTR_NOSUID | MOUNT_ATTR_NOATIME); 1117 + if (self->fd < 0 && errno == EINVAL) 1118 + SKIP(return, "FSMOUNT_NAMESPACE not supported"); 1119 + 1120 + ASSERT_GE(self->fd, 0); 1121 + 1122 + ret = get_mnt_ns_id(self->fd, &new_ns_id); 1123 + ASSERT_EQ(ret, 0); 1124 + 1125 + nr_mounts = listmount(LSMT_ROOT, new_ns_id, 0, list, 256, 0); 1126 + ASSERT_GE(nr_mounts, 1); 1127 + 1128 + ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); 1129 + ASSERT_EQ(ret, 0); 1130 + 1131 + /* Verify all attributes are set */ 1132 + ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_RDONLY); 1133 + ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOEXEC); 1134 + ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOSUID); 1135 + ASSERT_TRUE(sm.mnt_attr & MOUNT_ATTR_NOATIME); 1136 + } 1137 + 1138 + TEST_HARNESS_MAIN
+1 -1
tools/testing/selftests/filesystems/open_tree_ns/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 TEST_GEN_PROGS := open_tree_ns_test 3 3 4 - CFLAGS := -Wall -Werror -g $(KHDR_INCLUDES) 4 + CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) 5 5 LDLIBS := -lcap 6 6 7 7 include ../../lib.mk
+10 -33
tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c
··· 1 1 // SPDX-License-Identifier: GPL-2.0 2 2 /* 3 + * Copyright (c) 2026 Christian Brauner <brauner@kernel.org> 4 + * 3 5 * Test for OPEN_TREE_NAMESPACE flag. 4 6 * 5 7 * Test that open_tree() with OPEN_TREE_NAMESPACE creates a new mount ··· 52 50 return ret; 53 51 } 54 52 55 - #define STATMOUNT_BUFSIZE (1 << 15) 56 - 57 - static struct statmount *statmount_alloc(uint64_t mnt_id, uint64_t mnt_ns_id, uint64_t mask) 58 - { 59 - struct statmount *buf; 60 - size_t bufsize = STATMOUNT_BUFSIZE; 61 - int ret; 62 - 63 - for (;;) { 64 - buf = malloc(bufsize); 65 - if (!buf) 66 - return NULL; 67 - 68 - ret = statmount(mnt_id, mnt_ns_id, mask, buf, bufsize, 0); 69 - if (ret == 0) 70 - return buf; 71 - 72 - free(buf); 73 - if (errno != EOVERFLOW) 74 - return NULL; 75 - 76 - bufsize <<= 1; 77 - } 78 - } 79 - 80 53 static void log_mount(struct __test_metadata *_metadata, struct statmount *sm) 81 54 { 82 55 const char *fs_type = ""; ··· 92 115 STATMOUNT_MNT_BASIC | 93 116 STATMOUNT_FS_TYPE | 94 117 STATMOUNT_MNT_ROOT | 95 - STATMOUNT_MNT_POINT); 118 + STATMOUNT_MNT_POINT, 0); 96 119 if (!sm) { 97 120 TH_LOG(" [%zd] mnt_id %llu: statmount failed: %s", 98 121 i, (unsigned long long)list[i], strerror(errno)); ··· 198 221 SKIP(return, "open_tree() syscall not supported"); 199 222 200 223 /* Check if statmount/listmount are supported */ 201 - ret = statmount(0, 0, 0, NULL, 0, 0); 224 + ret = statmount(0, 0, 0, 0, NULL, 0, 0); 202 225 if (ret == -1 && errno == ENOSYS) 203 226 SKIP(return, "statmount() syscall not supported"); 204 227 ··· 317 340 ASSERT_GE(nr_mounts, 1); 318 341 319 342 /* Get info about the root mount (the bind mount, rootfs is hidden) */ 320 - ret = statmount(list[0], new_ns_id, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); 343 + ret = statmount(list[0], new_ns_id, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0); 321 344 ASSERT_EQ(ret, 0); 322 345 323 346 ASSERT_NE(sm.mnt_id, sm.mnt_parent_id); ··· 429 452 SKIP(return, "open_tree() syscall not supported"); 430 453 431 454 /* Check if statmount/listmount are supported */ 432 - ret = statmount(0, 0, 0, NULL, 0, 0); 455 + ret = statmount(0, 0, 0, 0, NULL, 0, 0); 433 456 if (ret == -1 && errno == ENOSYS) 434 457 SKIP(return, "statmount() syscall not supported"); 435 458 } ··· 723 746 const char *mnt_point; 724 747 725 748 sm = statmount_alloc(list[i], new_ns_id, 726 - STATMOUNT_MNT_POINT); 749 + STATMOUNT_MNT_POINT, 0); 727 750 if (!sm) 728 751 _exit(11); 729 752 ··· 840 863 const char *mnt_point; 841 864 842 865 sm = statmount_alloc(list[i], new_ns_id, 843 - STATMOUNT_MNT_POINT); 866 + STATMOUNT_MNT_POINT, 0); 844 867 if (!sm) 845 868 _exit(11); 846 869 ··· 881 904 ASSERT_FALSE(true) TH_LOG("setns into new namespace failed"); 882 905 break; 883 906 case 7: 884 - ASSERT_FALSE(true) TH_LOG("umount succeeded but should have failed with EINVAL"); 907 + ASSERT_FALSE(true) TH_LOG("umount failed but should have succeeded"); 885 908 break; 886 909 case 9: 887 910 ASSERT_FALSE(true) TH_LOG("listmount failed"); ··· 980 1003 struct statmount *sm; 981 1004 const char *mnt_point; 982 1005 983 - sm = statmount_alloc(list[i], new_ns_id, STATMOUNT_MNT_POINT); 1006 + sm = statmount_alloc(list[i], new_ns_id, STATMOUNT_MNT_POINT, 0); 984 1007 ASSERT_NE(sm, NULL) { 985 1008 TH_LOG("statmount_alloc failed for mnt_id %llu", 986 1009 (unsigned long long)list[i]);
+51
tools/testing/selftests/filesystems/statmount/statmount.h
··· 3 3 #ifndef __STATMOUNT_H 4 4 #define __STATMOUNT_H 5 5 6 + #include <errno.h> 6 7 #include <stdint.h> 8 + #include <stdlib.h> 7 9 #include <linux/mount.h> 8 10 #include <asm/unistd.h> 11 + 12 + #define STATMOUNT_BUFSIZE (1 << 15) 9 13 10 14 #ifndef __NR_statmount 11 15 #if defined __alpha__ ··· 86 82 } 87 83 88 84 return syscall(__NR_listmount, &req, list, num, flags); 85 + } 86 + 87 + static inline struct statmount *statmount_alloc(uint64_t mnt_id, uint64_t mnt_ns_id, 88 + uint64_t mask, unsigned int flags) 89 + { 90 + struct statmount *buf; 91 + size_t bufsize = STATMOUNT_BUFSIZE; 92 + int ret; 93 + 94 + for (;;) { 95 + buf = malloc(bufsize); 96 + if (!buf) 97 + return NULL; 98 + 99 + ret = statmount(mnt_id, mnt_ns_id, 0, mask, buf, bufsize, flags); 100 + if (ret == 0) 101 + return buf; 102 + 103 + free(buf); 104 + if (errno != EOVERFLOW) 105 + return NULL; 106 + 107 + bufsize <<= 1; 108 + } 109 + } 110 + 111 + static inline struct statmount *statmount_alloc_by_fd(int fd, uint64_t mask) 112 + { 113 + struct statmount *buf; 114 + size_t bufsize = STATMOUNT_BUFSIZE; 115 + int ret; 116 + 117 + for (;;) { 118 + buf = malloc(bufsize); 119 + if (!buf) 120 + return NULL; 121 + 122 + ret = statmount(0, 0, fd, mask, buf, bufsize, STATMOUNT_BY_FD); 123 + if (ret == 0) 124 + return buf; 125 + 126 + free(buf); 127 + if (errno != EOVERFLOW) 128 + return NULL; 129 + 130 + bufsize <<= 1; 131 + } 89 132 } 90 133 91 134 #endif /* __STATMOUNT_H */
+3 -42
tools/testing/selftests/filesystems/statmount/statmount_test.c
··· 33 33 "sysv", "tmpfs", "tracefs", "ubifs", "udf", "ufs", "v7", "vboxsf", 34 34 "vfat", "virtiofs", "vxfs", "xenfs", "xfs", "zonefs", NULL }; 35 35 36 - static struct statmount *statmount_alloc(uint64_t mnt_id, int fd, uint64_t mask, unsigned int flags) 37 - { 38 - size_t bufsize = 1 << 15; 39 - struct statmount *buf = NULL, *tmp = NULL; 40 - int tofree = 0; 41 - int ret; 42 - 43 - if (flags & STATMOUNT_BY_FD && fd < 0) 44 - return NULL; 45 - 46 - tmp = alloca(bufsize); 47 - 48 - for (;;) { 49 - if (flags & STATMOUNT_BY_FD) 50 - ret = statmount(0, 0, (uint32_t) fd, mask, tmp, bufsize, flags); 51 - else 52 - ret = statmount(mnt_id, 0, 0, mask, tmp, bufsize, flags); 53 - 54 - if (ret != -1) 55 - break; 56 - if (tofree) 57 - free(tmp); 58 - if (errno != EOVERFLOW) 59 - return NULL; 60 - bufsize <<= 1; 61 - tofree = 1; 62 - tmp = malloc(bufsize); 63 - if (!tmp) 64 - return NULL; 65 - } 66 - buf = malloc(tmp->size); 67 - if (buf) 68 - memcpy(buf, tmp, tmp->size); 69 - if (tofree) 70 - free(tmp); 71 - 72 - return buf; 73 - } 74 - 75 36 static void write_file(const char *path, const char *val) 76 37 { 77 38 int fd = open(path, O_WRONLY); ··· 676 715 goto err_fd; 677 716 } 678 717 679 - sm = statmount_alloc(0, fd, STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT, STATMOUNT_BY_FD); 718 + sm = statmount_alloc_by_fd(fd, STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT); 680 719 if (!sm) { 681 720 ksft_test_result_fail("statmount by fd failed: %s\n", strerror(errno)); 682 721 goto err_chroot; ··· 711 750 } 712 751 713 752 free(sm); 714 - sm = statmount_alloc(0, fd, STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT, STATMOUNT_BY_FD); 753 + sm = statmount_alloc_by_fd(fd, STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT); 715 754 if (!sm) { 716 755 ksft_test_result_fail("statmount by fd failed: %s\n", strerror(errno)); 717 756 goto err_fd; ··· 805 844 goto err_fd; 806 845 } 807 846 808 - sm = statmount_alloc(0, fd, STATMOUNT_MNT_POINT | STATMOUNT_MNT_ROOT, STATMOUNT_BY_FD); 847 + sm = statmount_alloc_by_fd(fd, STATMOUNT_MNT_POINT | STATMOUNT_MNT_ROOT); 809 848 if (!sm) { 810 849 ksft_test_result_fail("statmount by fd unmounted: %s\n", 811 850 strerror(errno));