1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * An unmounted mount tree that a file descriptor keeps alive is put and 4 * vacated behind the root's back, so nothing may walk it under 5 * namespace_sem: it can't be copied recursively and its propagation can't 6 * be changed. The mount at its root alone can still be copied and that copy 7 * is an ordinary mount. 8 */ 9 #define _GNU_SOURCE 10 #include <errno.h> 11 #include <fcntl.h> 12 #include <sched.h> 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <sys/mount.h> 17 #include <sys/stat.h> 18 #include <sys/syscall.h> 19 #include <sys/wait.h> 20 #include <unistd.h> 21 #include <linux/mount.h> 22 23 #include "../wrappers.h" 24 #include "../../kselftest_harness.h" 25 26 #ifndef __NR_mount_setattr 27 #define __NR_mount_setattr 442 28 #endif 29 30 #ifndef __NR_pidfd_open 31 #define __NR_pidfd_open 434 32 #endif 33 34 #define PATH_LEN 64 35 36 static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flags, 37 struct mount_attr *attr, size_t size) 38 { 39 return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); 40 } 41 42 static inline int sys_pidfd_open(pid_t pid, unsigned int flags) 43 { 44 return syscall(__NR_pidfd_open, pid, flags); 45 } 46 47 static int write_file(const char *path, const char *s) 48 { 49 ssize_t n = -1; 50 int fd; 51 52 fd = open(path, O_WRONLY | O_CLOEXEC); 53 if (fd >= 0) { 54 n = write(fd, s, strlen(s)); 55 close(fd); 56 } 57 return n == (ssize_t)strlen(s) ? 0 : -1; 58 } 59 60 static int touch(const char *path) 61 { 62 int fd; 63 64 fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644); 65 if (fd < 0) 66 return -1; 67 close(fd); 68 return 0; 69 } 70 71 /* Become root in a new user namespace with a private mount namespace. */ 72 static int enter_userns(void) 73 { 74 uid_t uid = getuid(); 75 gid_t gid = getgid(); 76 char map[32]; 77 78 if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) 79 return -1; 80 if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) 81 return -1; 82 snprintf(map, sizeof(map), "0 %d 1", uid); 83 if (write_file("/proc/self/uid_map", map)) 84 return -1; 85 snprintf(map, sizeof(map), "0 %d 1", gid); 86 if (write_file("/proc/self/gid_map", map)) 87 return -1; 88 if (setgid(0) || setuid(0)) 89 return -1; 90 return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); 91 } 92 93 /* A private mount namespace of our own, all mounts private. */ 94 static int own_mntns(void) 95 { 96 if (unshare(CLONE_NEWNS)) 97 return -1; 98 return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL); 99 } 100 101 FIXTURE(unmounted_tree) { 102 char dir[PATH_LEN]; 103 }; 104 105 FIXTURE_SETUP(unmounted_tree) 106 { 107 snprintf(self->dir, sizeof(self->dir), "/tmp/unmounted_tree.XXXXXX"); 108 ASSERT_NE(mkdtemp(self->dir), NULL); 109 if (enter_userns()) { 110 rmdir(self->dir); 111 SKIP(return, "test requires user namespaces"); 112 } 113 ASSERT_EQ(mount("tmpfs", self->dir, "tmpfs", 0, NULL), 0); 114 } 115 116 FIXTURE_TEARDOWN(unmounted_tree) 117 { 118 umount2(self->dir, MNT_DETACH); 119 rmdir(self->dir); 120 } 121 122 /* A recursive copy of the mount @fd refers to must fail with EINVAL. */ 123 static void assert_not_walked(struct __test_metadata *_metadata, int fd, 124 const char *target) 125 { 126 char link[PATH_LEN]; 127 int tfd; 128 129 snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); 130 EXPECT_EQ(mount(link, target, NULL, MS_BIND | MS_REC, NULL), -1); 131 EXPECT_EQ(errno, EINVAL); 132 133 tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | AT_RECURSIVE | OPEN_TREE_CLONE | 134 OPEN_TREE_CLOEXEC); 135 EXPECT_LT(tfd, 0); 136 EXPECT_EQ(errno, EINVAL); 137 if (tfd >= 0) 138 close(tfd); 139 } 140 141 /* The mount @fd refers to can be copied on its own and mounted on @target. */ 142 static void assert_root_copied(struct __test_metadata *_metadata, int fd, 143 const char *target) 144 { 145 int tfd; 146 147 tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 148 ASSERT_GE(tfd, 0); 149 ASSERT_EQ(sys_move_mount(tfd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH), 0); 150 close(tfd); 151 ASSERT_EQ(umount2(target, MNT_DETACH), 0); 152 } 153 154 /* 155 * A lazily unmounted bind mount of an nsfs file, kept alive by an fd, can't 156 * be copied recursively anymore. Copying the mount itself still works and 157 * so does copying a mounted bind mount of the same file. 158 */ 159 TEST_F(unmounted_tree, detached_nsfs_bind_not_walked) 160 { 161 char x[PATH_LEN], y[PATH_LEN], z[PATH_LEN], link[PATH_LEN]; 162 int fd, tfd; 163 164 snprintf(x, sizeof(x), "%s/x", self->dir); 165 snprintf(y, sizeof(y), "%s/y", self->dir); 166 snprintf(z, sizeof(z), "%s/z", self->dir); 167 ASSERT_EQ(touch(x), 0); 168 ASSERT_EQ(touch(y), 0); 169 ASSERT_EQ(touch(z), 0); 170 171 ASSERT_EQ(mount("/proc/self/ns/net", x, NULL, MS_BIND, NULL), 0); 172 fd = open(x, O_PATH | O_CLOEXEC); 173 ASSERT_GE(fd, 0); 174 ASSERT_EQ(umount2(x, MNT_DETACH), 0); 175 176 assert_not_walked(_metadata, fd, y); 177 178 snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); 179 ASSERT_EQ(mount(link, y, NULL, MS_BIND, NULL), 0); 180 ASSERT_EQ(umount2(y, MNT_DETACH), 0); 181 assert_root_copied(_metadata, fd, y); 182 /* opening it is fine too */ 183 tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLOEXEC); 184 ASSERT_GE(tfd, 0); 185 close(tfd); 186 close(fd); 187 188 ASSERT_EQ(mount("/proc/self/ns/net", y, NULL, MS_BIND, NULL), 0); 189 ASSERT_EQ(mount(y, z, NULL, MS_BIND | MS_REC, NULL), 0); 190 tfd = sys_open_tree(AT_FDCWD, y, AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 191 ASSERT_GE(tfd, 0); 192 close(tfd); 193 } 194 195 /* Bind-mount the pidfd @pidfd on @target the way pidfd_bind_mount does. */ 196 static int bind_pidfd(int pidfd, const char *target) 197 { 198 int tfd, ret; 199 200 tfd = sys_open_tree(pidfd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 201 if (tfd < 0) 202 return -1; 203 ret = sys_move_mount(tfd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH); 204 close(tfd); 205 return ret; 206 } 207 208 /* The same for a bind mount of a pidfd. */ 209 TEST_F(unmounted_tree, detached_pidfs_bind_not_walked) 210 { 211 char x[PATH_LEN], y[PATH_LEN]; 212 int pidfd, fd, tfd; 213 214 snprintf(x, sizeof(x), "%s/x", self->dir); 215 snprintf(y, sizeof(y), "%s/y", self->dir); 216 ASSERT_EQ(touch(x), 0); 217 ASSERT_EQ(touch(y), 0); 218 219 pidfd = sys_pidfd_open(getpid(), 0); 220 ASSERT_GE(pidfd, 0); 221 ASSERT_EQ(bind_pidfd(pidfd, x), 0); 222 fd = open(x, O_PATH | O_CLOEXEC); 223 ASSERT_GE(fd, 0); 224 ASSERT_EQ(umount2(x, MNT_DETACH), 0); 225 226 assert_not_walked(_metadata, fd, y); 227 assert_root_copied(_metadata, fd, y); 228 close(fd); 229 230 ASSERT_EQ(bind_pidfd(pidfd, y), 0); 231 tfd = sys_open_tree(AT_FDCWD, y, AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 232 ASSERT_GE(tfd, 0); 233 close(tfd); 234 close(pidfd); 235 } 236 237 /* What a child in its own mount namespace reports back. */ 238 enum { 239 CHILD_OK, 240 CHILD_NS, /* could not set up the mount namespace */ 241 CHILD_MOUNT, /* could not set up the mounts */ 242 CHILD_PIPE, /* the parent went away */ 243 CHILD_WALKED, /* the detached tree was copied recursively */ 244 CHILD_ROOT_REFUSED, /* the mount at its root wasn't copied */ 245 CHILD_STILL_MOUNTED, /* the copy survived the unlink of its mountpoint */ 246 CHILD_ERRNO, /* refused, but not with EINVAL */ 247 }; 248 249 static const char *child_reason(int code) 250 { 251 static const char *const reasons[] = { 252 [CHILD_OK] = "ok", 253 [CHILD_NS] = "could not set up the mount namespace", 254 [CHILD_MOUNT] = "could not set up the mounts", 255 [CHILD_PIPE] = "the parent went away", 256 [CHILD_WALKED] = "the detached tree was copied recursively", 257 [CHILD_ROOT_REFUSED] = "the mount at its root wasn't copied", 258 [CHILD_STILL_MOUNTED] = "the copy survived the unlink of its mountpoint", 259 [CHILD_ERRNO] = "refused, but not with EINVAL", 260 }; 261 262 if (code < 0 || code >= (int)(sizeof(reasons) / sizeof(reasons[0]))) 263 return "child died"; 264 return reasons[code]; 265 } 266 267 struct child_args { 268 const char *x; /* the nsfs bind mount goes here */ 269 const char *s; /* a file to bind on top of it */ 270 const char *y; /* a target to copy to */ 271 }; 272 273 /* 274 * Run @fn in a child in a mount namespace of its own. Once the child 275 * reports that its mounts are in place, unlink @victim here, where it is a 276 * plain file, and let the child carry on. Returns what the child reported. 277 */ 278 static int run_child(struct __test_metadata *_metadata, 279 int (*fn)(const struct child_args *, int, int), 280 const struct child_args *a, const char *victim) 281 { 282 int to_parent[2], to_child[2], status; 283 pid_t pid; 284 char c; 285 286 if (pipe(to_parent) || pipe(to_child)) 287 return -1; 288 pid = fork(); 289 if (pid < 0) 290 return -1; 291 if (pid == 0) { 292 close(to_parent[0]); 293 close(to_child[1]); 294 _exit(fn(a, to_parent[1], to_child[0])); 295 } 296 close(to_parent[1]); 297 close(to_child[0]); 298 if (read(to_parent[0], &c, 1) == 1) { 299 /* not a mountpoint in this namespace, so the file can go */ 300 EXPECT_EQ(unlink(victim), 0); 301 EXPECT_EQ(write(to_child[1], "", 1), 1); 302 } 303 close(to_parent[0]); 304 close(to_child[1]); 305 if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) 306 return -1; 307 return WEXITSTATUS(status); 308 } 309 310 /* 311 * An nsfs bind mount on @x, an fd on it and a bind mount of @s on top. Once 312 * the parent has unlinked @x underneath, the first mount is detached and the 313 * second one, which stayed attached to it, has been vacated. The tree may 314 * not be walked, the mount at its root may still be copied. 315 */ 316 static int vacant_child(const struct child_args *a, int to_parent, int from_parent) 317 { 318 char link[PATH_LEN]; 319 int fd, tfd; 320 char c; 321 322 if (own_mntns()) 323 return CHILD_NS; 324 if (mount("/proc/self/ns/net", a->x, NULL, MS_BIND, NULL)) 325 return CHILD_MOUNT; 326 fd = open(a->x, O_PATH | O_CLOEXEC); 327 if (fd < 0 || mount(a->s, a->x, NULL, MS_BIND, NULL)) 328 return CHILD_MOUNT; 329 330 if (write(to_parent, "", 1) != 1 || read(from_parent, &c, 1) != 1) 331 return CHILD_PIPE; 332 333 tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | AT_RECURSIVE | OPEN_TREE_CLONE | 334 OPEN_TREE_CLOEXEC); 335 if (tfd >= 0) { 336 close(tfd); 337 return CHILD_WALKED; 338 } 339 if (errno != EINVAL) 340 return CHILD_ERRNO; 341 snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); 342 if (!mount(link, a->y, NULL, MS_BIND | MS_REC, NULL)) 343 return CHILD_WALKED; 344 if (errno != EINVAL) 345 return CHILD_ERRNO; 346 347 tfd = sys_open_tree(fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 348 if (tfd < 0) 349 return CHILD_ROOT_REFUSED; 350 close(tfd); 351 /* the last reference on the detached mount collects the vacant one */ 352 close(fd); 353 return CHILD_OK; 354 } 355 356 /* 357 * unlink() of the child's mountpoint from here, where it is a plain file, 358 * detaches the child's nsfs bind mount with the mount on top left attached 359 * to it. The mount on top loses its last reference right there and is 360 * vacated. The child must not be able to walk that tree. 361 */ 362 TEST_F(unmounted_tree, vacant_child_not_copied) 363 { 364 char x[PATH_LEN], s[PATH_LEN], y[PATH_LEN]; 365 struct child_args a = { .x = x, .s = s, .y = y }; 366 int ret; 367 368 snprintf(x, sizeof(x), "%s/x", self->dir); 369 snprintf(s, sizeof(s), "%s/s", self->dir); 370 snprintf(y, sizeof(y), "%s/y", self->dir); 371 ASSERT_EQ(touch(x), 0); 372 ASSERT_EQ(touch(s), 0); 373 ASSERT_EQ(touch(y), 0); 374 375 ret = run_child(_metadata, vacant_child, &a, x); 376 ASSERT_EQ(ret, CHILD_OK) 377 TH_LOG("child: %s", child_reason(ret)); 378 } 379 380 /* 381 * An nsfs bind mount on @x, lazily unmounted but kept alive by an fd, and a 382 * copy of it on @y. That copy is a mount like any other: once the parent has 383 * unlinked @y underneath, it is unmounted and its attributes can't be changed 384 * anymore. A copy that took the flag along is only unhooked, stays in the 385 * namespace and still takes the change. 386 */ 387 static int ordinary_copy(const struct child_args *a, int to_parent, int from_parent) 388 { 389 struct mount_attr attr = { 390 .attr_set = MOUNT_ATTR_NOSUID, 391 }; 392 char link[PATH_LEN]; 393 int fd, fdy; 394 char c; 395 396 if (own_mntns()) 397 return CHILD_NS; 398 if (mount("/proc/self/ns/net", a->x, NULL, MS_BIND, NULL)) 399 return CHILD_MOUNT; 400 fd = open(a->x, O_PATH | O_CLOEXEC); 401 if (fd < 0 || umount2(a->x, MNT_DETACH)) 402 return CHILD_MOUNT; 403 snprintf(link, sizeof(link), "/proc/self/fd/%d", fd); 404 if (mount(link, a->y, NULL, MS_BIND, NULL)) 405 return CHILD_MOUNT; 406 fdy = open(a->y, O_PATH | O_CLOEXEC); 407 if (fdy < 0) 408 return CHILD_MOUNT; 409 410 if (write(to_parent, "", 1) != 1 || read(from_parent, &c, 1) != 1) 411 return CHILD_PIPE; 412 413 if (!sys_mount_setattr(fdy, "", AT_EMPTY_PATH, &attr, sizeof(attr))) 414 return CHILD_STILL_MOUNTED; 415 if (errno != EINVAL) 416 return CHILD_ERRNO; 417 close(fdy); 418 close(fd); 419 return CHILD_OK; 420 } 421 422 /* 423 * A copy of a detached nsfs bind mount must not take after its source and 424 * read as unmounted. unlink() of its mountpoint from here unmounts it like 425 * any other mount, so the child can't change its attributes anymore. 426 */ 427 TEST_F(unmounted_tree, copy_of_detached_bind_is_ordinary) 428 { 429 char x[PATH_LEN], y[PATH_LEN]; 430 struct child_args a = { .x = x, .y = y }; 431 int ret; 432 433 snprintf(x, sizeof(x), "%s/x", self->dir); 434 snprintf(y, sizeof(y), "%s/y", self->dir); 435 ASSERT_EQ(touch(x), 0); 436 ASSERT_EQ(touch(y), 0); 437 438 ret = run_child(_metadata, ordinary_copy, &a, y); 439 ASSERT_EQ(ret, CHILD_OK) 440 TH_LOG("child: %s", child_reason(ret)); 441 } 442 443 /* 444 * mount_setattr() may change the propagation of a detached tree while that 445 * tree is still the root of an anonymous mount namespace, but not once the 446 * tree has been dissolved and only a second fd keeps its root alive. 447 */ 448 TEST_F(unmounted_tree, detached_tree_setattr_refused) 449 { 450 struct mount_attr attr = { 451 .propagation = MS_SHARED, 452 }; 453 char a[PATH_LEN], b[PATH_LEN]; 454 int tfd, fd; 455 456 snprintf(a, sizeof(a), "%s/a", self->dir); 457 snprintf(b, sizeof(b), "%s/a/b", self->dir); 458 ASSERT_EQ(mkdir(a, 0755), 0); 459 ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0); 460 ASSERT_EQ(mkdir(b, 0755), 0); 461 ASSERT_EQ(mount("tmpfs", b, "tmpfs", 0, NULL), 0); 462 463 tfd = sys_open_tree(AT_FDCWD, self->dir, AT_RECURSIVE | OPEN_TREE_CLONE | 464 OPEN_TREE_CLOEXEC); 465 ASSERT_GE(tfd, 0); 466 ASSERT_EQ(sys_mount_setattr(tfd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), 0); 467 attr.propagation = MS_PRIVATE; 468 ASSERT_EQ(sys_mount_setattr(tfd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), 0); 469 470 /* a second reference on the root, then dissolve the tree */ 471 fd = sys_open_tree(tfd, "", AT_EMPTY_PATH | OPEN_TREE_CLOEXEC); 472 ASSERT_GE(fd, 0); 473 close(tfd); 474 475 attr.propagation = MS_SHARED; 476 ASSERT_EQ(sys_mount_setattr(fd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)), -1); 477 ASSERT_EQ(errno, EINVAL); 478 attr.propagation = MS_PRIVATE; 479 ASSERT_EQ(sys_mount_setattr(fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), -1); 480 ASSERT_EQ(errno, EINVAL); 481 close(fd); 482 } 483 484 TEST_HARNESS_MAIN 485