1*bb5c17bcSChristian Brauner // SPDX-License-Identifier: GPL-2.0-or-later 2*bb5c17bcSChristian Brauner // Copyright (c) 2026 Christian Brauner <brauner@kernel.org> 3*bb5c17bcSChristian Brauner 4*bb5c17bcSChristian Brauner #define _GNU_SOURCE 5*bb5c17bcSChristian Brauner 6*bb5c17bcSChristian Brauner #include <errno.h> 7*bb5c17bcSChristian Brauner #include <fcntl.h> 8*bb5c17bcSChristian Brauner #include <sched.h> 9*bb5c17bcSChristian Brauner #include <stdio.h> 10*bb5c17bcSChristian Brauner #include <string.h> 11*bb5c17bcSChristian Brauner #include <sys/stat.h> 12*bb5c17bcSChristian Brauner #include <sys/mount.h> 13*bb5c17bcSChristian Brauner #include <unistd.h> 14*bb5c17bcSChristian Brauner #include <sys/syscall.h> 15*bb5c17bcSChristian Brauner 16*bb5c17bcSChristian Brauner #include "../wrappers.h" 17*bb5c17bcSChristian Brauner #include "../utils.h" 18*bb5c17bcSChristian Brauner #include "../statmount/statmount.h" 19*bb5c17bcSChristian Brauner #include "../../kselftest_harness.h" 20*bb5c17bcSChristian Brauner 21*bb5c17bcSChristian Brauner #include <linux/stat.h> 22*bb5c17bcSChristian Brauner 23*bb5c17bcSChristian Brauner #ifndef MOVE_MOUNT_BENEATH 24*bb5c17bcSChristian Brauner #define MOVE_MOUNT_BENEATH 0x00000200 25*bb5c17bcSChristian Brauner #endif 26*bb5c17bcSChristian Brauner 27*bb5c17bcSChristian Brauner static uint64_t get_unique_mnt_id_fd(int fd) 28*bb5c17bcSChristian Brauner { 29*bb5c17bcSChristian Brauner struct statx sx; 30*bb5c17bcSChristian Brauner int ret; 31*bb5c17bcSChristian Brauner 32*bb5c17bcSChristian Brauner ret = statx(fd, "", AT_EMPTY_PATH, STATX_MNT_ID_UNIQUE, &sx); 33*bb5c17bcSChristian Brauner if (ret) 34*bb5c17bcSChristian Brauner return 0; 35*bb5c17bcSChristian Brauner 36*bb5c17bcSChristian Brauner if (!(sx.stx_mask & STATX_MNT_ID_UNIQUE)) 37*bb5c17bcSChristian Brauner return 0; 38*bb5c17bcSChristian Brauner 39*bb5c17bcSChristian Brauner return sx.stx_mnt_id; 40*bb5c17bcSChristian Brauner } 41*bb5c17bcSChristian Brauner 42*bb5c17bcSChristian Brauner /* 43*bb5c17bcSChristian Brauner * Create a locked overmount stack at /mnt_dir for testing MNT_LOCKED 44*bb5c17bcSChristian Brauner * transfer on non-rootfs mounts. 45*bb5c17bcSChristian Brauner * 46*bb5c17bcSChristian Brauner * Mounts tmpfs A at /mnt_dir, overmounts with tmpfs B, then enters a 47*bb5c17bcSChristian Brauner * new user+mount namespace where both become locked. Returns the exit 48*bb5c17bcSChristian Brauner * code to use on failure, or 0 on success. 49*bb5c17bcSChristian Brauner */ 50*bb5c17bcSChristian Brauner static int setup_locked_overmount(void) 51*bb5c17bcSChristian Brauner { 52*bb5c17bcSChristian Brauner /* Isolate so mounts don't leak. */ 53*bb5c17bcSChristian Brauner if (unshare(CLONE_NEWNS)) 54*bb5c17bcSChristian Brauner return 1; 55*bb5c17bcSChristian Brauner if (mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) 56*bb5c17bcSChristian Brauner return 2; 57*bb5c17bcSChristian Brauner 58*bb5c17bcSChristian Brauner /* 59*bb5c17bcSChristian Brauner * Create mounts while still in the initial user namespace so 60*bb5c17bcSChristian Brauner * they become locked after the subsequent user namespace 61*bb5c17bcSChristian Brauner * unshare. 62*bb5c17bcSChristian Brauner */ 63*bb5c17bcSChristian Brauner rmdir("/mnt_dir"); 64*bb5c17bcSChristian Brauner if (mkdir("/mnt_dir", 0755)) 65*bb5c17bcSChristian Brauner return 3; 66*bb5c17bcSChristian Brauner 67*bb5c17bcSChristian Brauner /* Mount tmpfs A */ 68*bb5c17bcSChristian Brauner if (mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL)) 69*bb5c17bcSChristian Brauner return 4; 70*bb5c17bcSChristian Brauner 71*bb5c17bcSChristian Brauner /* Overmount with tmpfs B */ 72*bb5c17bcSChristian Brauner if (mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL)) 73*bb5c17bcSChristian Brauner return 5; 74*bb5c17bcSChristian Brauner 75*bb5c17bcSChristian Brauner /* 76*bb5c17bcSChristian Brauner * Create user+mount namespace. Mounts A and B become locked 77*bb5c17bcSChristian Brauner * because they might be covering something that is not supposed 78*bb5c17bcSChristian Brauner * to be revealed. 79*bb5c17bcSChristian Brauner */ 80*bb5c17bcSChristian Brauner if (setup_userns()) 81*bb5c17bcSChristian Brauner return 6; 82*bb5c17bcSChristian Brauner 83*bb5c17bcSChristian Brauner /* Sanity check: B must be locked */ 84*bb5c17bcSChristian Brauner if (!umount2("/mnt_dir", MNT_DETACH) || errno != EINVAL) 85*bb5c17bcSChristian Brauner return 7; 86*bb5c17bcSChristian Brauner 87*bb5c17bcSChristian Brauner return 0; 88*bb5c17bcSChristian Brauner } 89*bb5c17bcSChristian Brauner 90*bb5c17bcSChristian Brauner /* 91*bb5c17bcSChristian Brauner * Create a detached tmpfs mount and return its fd, or -1 on failure. 92*bb5c17bcSChristian Brauner */ 93*bb5c17bcSChristian Brauner static int create_detached_tmpfs(void) 94*bb5c17bcSChristian Brauner { 95*bb5c17bcSChristian Brauner int fs_fd, mnt_fd; 96*bb5c17bcSChristian Brauner 97*bb5c17bcSChristian Brauner fs_fd = sys_fsopen("tmpfs", FSOPEN_CLOEXEC); 98*bb5c17bcSChristian Brauner if (fs_fd < 0) 99*bb5c17bcSChristian Brauner return -1; 100*bb5c17bcSChristian Brauner 101*bb5c17bcSChristian Brauner if (sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) { 102*bb5c17bcSChristian Brauner close(fs_fd); 103*bb5c17bcSChristian Brauner return -1; 104*bb5c17bcSChristian Brauner } 105*bb5c17bcSChristian Brauner 106*bb5c17bcSChristian Brauner mnt_fd = sys_fsmount(fs_fd, FSMOUNT_CLOEXEC, 0); 107*bb5c17bcSChristian Brauner close(fs_fd); 108*bb5c17bcSChristian Brauner return mnt_fd; 109*bb5c17bcSChristian Brauner } 110*bb5c17bcSChristian Brauner 111*bb5c17bcSChristian Brauner FIXTURE(move_mount) { 112*bb5c17bcSChristian Brauner uint64_t orig_root_id; 113*bb5c17bcSChristian Brauner }; 114*bb5c17bcSChristian Brauner 115*bb5c17bcSChristian Brauner FIXTURE_SETUP(move_mount) 116*bb5c17bcSChristian Brauner { 117*bb5c17bcSChristian Brauner ASSERT_EQ(unshare(CLONE_NEWNS), 0); 118*bb5c17bcSChristian Brauner 119*bb5c17bcSChristian Brauner ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); 120*bb5c17bcSChristian Brauner 121*bb5c17bcSChristian Brauner self->orig_root_id = get_unique_mnt_id("/"); 122*bb5c17bcSChristian Brauner ASSERT_NE(self->orig_root_id, 0); 123*bb5c17bcSChristian Brauner } 124*bb5c17bcSChristian Brauner 125*bb5c17bcSChristian Brauner FIXTURE_TEARDOWN(move_mount) 126*bb5c17bcSChristian Brauner { 127*bb5c17bcSChristian Brauner } 128*bb5c17bcSChristian Brauner 129*bb5c17bcSChristian Brauner /* 130*bb5c17bcSChristian Brauner * Test successful MOVE_MOUNT_BENEATH on the rootfs. 131*bb5c17bcSChristian Brauner * Mount a clone beneath /, fchdir to the clone, chroot to switch root, 132*bb5c17bcSChristian Brauner * then detach the old root. 133*bb5c17bcSChristian Brauner */ 134*bb5c17bcSChristian Brauner TEST_F(move_mount, beneath_rootfs_success) 135*bb5c17bcSChristian Brauner { 136*bb5c17bcSChristian Brauner int fd_tree, ret; 137*bb5c17bcSChristian Brauner uint64_t clone_id, root_id; 138*bb5c17bcSChristian Brauner 139*bb5c17bcSChristian Brauner fd_tree = sys_open_tree(AT_FDCWD, "/", 140*bb5c17bcSChristian Brauner OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 141*bb5c17bcSChristian Brauner ASSERT_GE(fd_tree, 0); 142*bb5c17bcSChristian Brauner 143*bb5c17bcSChristian Brauner clone_id = get_unique_mnt_id_fd(fd_tree); 144*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, 0); 145*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, self->orig_root_id); 146*bb5c17bcSChristian Brauner 147*bb5c17bcSChristian Brauner ASSERT_EQ(fchdir(fd_tree), 0); 148*bb5c17bcSChristian Brauner 149*bb5c17bcSChristian Brauner ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/", 150*bb5c17bcSChristian Brauner MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH); 151*bb5c17bcSChristian Brauner ASSERT_EQ(ret, 0); 152*bb5c17bcSChristian Brauner 153*bb5c17bcSChristian Brauner close(fd_tree); 154*bb5c17bcSChristian Brauner 155*bb5c17bcSChristian Brauner /* Switch root to the clone */ 156*bb5c17bcSChristian Brauner ASSERT_EQ(chroot("."), 0); 157*bb5c17bcSChristian Brauner 158*bb5c17bcSChristian Brauner /* Verify "/" is now the clone */ 159*bb5c17bcSChristian Brauner root_id = get_unique_mnt_id("/"); 160*bb5c17bcSChristian Brauner ASSERT_NE(root_id, 0); 161*bb5c17bcSChristian Brauner ASSERT_EQ(root_id, clone_id); 162*bb5c17bcSChristian Brauner 163*bb5c17bcSChristian Brauner /* Detach old root */ 164*bb5c17bcSChristian Brauner ASSERT_EQ(umount2(".", MNT_DETACH), 0); 165*bb5c17bcSChristian Brauner } 166*bb5c17bcSChristian Brauner 167*bb5c17bcSChristian Brauner /* 168*bb5c17bcSChristian Brauner * Test that after MOVE_MOUNT_BENEATH on the rootfs the old root is 169*bb5c17bcSChristian Brauner * stacked on top of the clone. Verify via statmount that the old 170*bb5c17bcSChristian Brauner * root's parent is the clone. 171*bb5c17bcSChristian Brauner */ 172*bb5c17bcSChristian Brauner TEST_F(move_mount, beneath_rootfs_old_root_stacked) 173*bb5c17bcSChristian Brauner { 174*bb5c17bcSChristian Brauner int fd_tree, ret; 175*bb5c17bcSChristian Brauner uint64_t clone_id; 176*bb5c17bcSChristian Brauner struct statmount sm; 177*bb5c17bcSChristian Brauner 178*bb5c17bcSChristian Brauner fd_tree = sys_open_tree(AT_FDCWD, "/", 179*bb5c17bcSChristian Brauner OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 180*bb5c17bcSChristian Brauner ASSERT_GE(fd_tree, 0); 181*bb5c17bcSChristian Brauner 182*bb5c17bcSChristian Brauner clone_id = get_unique_mnt_id_fd(fd_tree); 183*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, 0); 184*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, self->orig_root_id); 185*bb5c17bcSChristian Brauner 186*bb5c17bcSChristian Brauner ASSERT_EQ(fchdir(fd_tree), 0); 187*bb5c17bcSChristian Brauner 188*bb5c17bcSChristian Brauner ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/", 189*bb5c17bcSChristian Brauner MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH); 190*bb5c17bcSChristian Brauner ASSERT_EQ(ret, 0); 191*bb5c17bcSChristian Brauner 192*bb5c17bcSChristian Brauner close(fd_tree); 193*bb5c17bcSChristian Brauner 194*bb5c17bcSChristian Brauner ASSERT_EQ(chroot("."), 0); 195*bb5c17bcSChristian Brauner 196*bb5c17bcSChristian Brauner /* Old root's parent should now be the clone */ 197*bb5c17bcSChristian Brauner ASSERT_EQ(statmount(self->orig_root_id, 0, 0, 198*bb5c17bcSChristian Brauner STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0), 0); 199*bb5c17bcSChristian Brauner ASSERT_EQ(sm.mnt_parent_id, clone_id); 200*bb5c17bcSChristian Brauner 201*bb5c17bcSChristian Brauner ASSERT_EQ(umount2(".", MNT_DETACH), 0); 202*bb5c17bcSChristian Brauner } 203*bb5c17bcSChristian Brauner 204*bb5c17bcSChristian Brauner /* 205*bb5c17bcSChristian Brauner * Test that MOVE_MOUNT_BENEATH on rootfs fails when chroot'd into a 206*bb5c17bcSChristian Brauner * subdirectory of the same mount. The caller's fs->root.dentry doesn't 207*bb5c17bcSChristian Brauner * match mnt->mnt_root so the kernel rejects it. 208*bb5c17bcSChristian Brauner */ 209*bb5c17bcSChristian Brauner TEST_F(move_mount, beneath_rootfs_in_chroot_fail) 210*bb5c17bcSChristian Brauner { 211*bb5c17bcSChristian Brauner int fd_tree, ret; 212*bb5c17bcSChristian Brauner uint64_t chroot_id, clone_id; 213*bb5c17bcSChristian Brauner 214*bb5c17bcSChristian Brauner rmdir("/chroot_dir"); 215*bb5c17bcSChristian Brauner ASSERT_EQ(mkdir("/chroot_dir", 0755), 0); 216*bb5c17bcSChristian Brauner 217*bb5c17bcSChristian Brauner chroot_id = get_unique_mnt_id("/chroot_dir"); 218*bb5c17bcSChristian Brauner ASSERT_NE(chroot_id, 0); 219*bb5c17bcSChristian Brauner ASSERT_EQ(self->orig_root_id, chroot_id); 220*bb5c17bcSChristian Brauner 221*bb5c17bcSChristian Brauner ASSERT_EQ(chdir("/chroot_dir"), 0); 222*bb5c17bcSChristian Brauner ASSERT_EQ(chroot("."), 0); 223*bb5c17bcSChristian Brauner 224*bb5c17bcSChristian Brauner fd_tree = sys_open_tree(AT_FDCWD, "/", 225*bb5c17bcSChristian Brauner OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 226*bb5c17bcSChristian Brauner ASSERT_GE(fd_tree, 0); 227*bb5c17bcSChristian Brauner 228*bb5c17bcSChristian Brauner clone_id = get_unique_mnt_id_fd(fd_tree); 229*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, 0); 230*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, chroot_id); 231*bb5c17bcSChristian Brauner 232*bb5c17bcSChristian Brauner ASSERT_EQ(fchdir(fd_tree), 0); 233*bb5c17bcSChristian Brauner 234*bb5c17bcSChristian Brauner /* 235*bb5c17bcSChristian Brauner * Should fail: fs->root.dentry (/chroot_dir) doesn't match 236*bb5c17bcSChristian Brauner * the mount's mnt_root (/). 237*bb5c17bcSChristian Brauner */ 238*bb5c17bcSChristian Brauner ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/", 239*bb5c17bcSChristian Brauner MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH); 240*bb5c17bcSChristian Brauner ASSERT_EQ(ret, -1); 241*bb5c17bcSChristian Brauner ASSERT_EQ(errno, EINVAL); 242*bb5c17bcSChristian Brauner 243*bb5c17bcSChristian Brauner close(fd_tree); 244*bb5c17bcSChristian Brauner } 245*bb5c17bcSChristian Brauner 246*bb5c17bcSChristian Brauner /* 247*bb5c17bcSChristian Brauner * Test that MOVE_MOUNT_BENEATH on rootfs succeeds when chroot'd into a 248*bb5c17bcSChristian Brauner * separate tmpfs mount. The caller's root dentry matches the mount's 249*bb5c17bcSChristian Brauner * mnt_root since it's a dedicated mount. 250*bb5c17bcSChristian Brauner */ 251*bb5c17bcSChristian Brauner TEST_F(move_mount, beneath_rootfs_in_chroot_success) 252*bb5c17bcSChristian Brauner { 253*bb5c17bcSChristian Brauner int fd_tree, ret; 254*bb5c17bcSChristian Brauner uint64_t chroot_id, clone_id, root_id; 255*bb5c17bcSChristian Brauner struct statmount sm; 256*bb5c17bcSChristian Brauner 257*bb5c17bcSChristian Brauner rmdir("/chroot_dir"); 258*bb5c17bcSChristian Brauner ASSERT_EQ(mkdir("/chroot_dir", 0755), 0); 259*bb5c17bcSChristian Brauner ASSERT_EQ(mount("tmpfs", "/chroot_dir", "tmpfs", 0, NULL), 0); 260*bb5c17bcSChristian Brauner 261*bb5c17bcSChristian Brauner chroot_id = get_unique_mnt_id("/chroot_dir"); 262*bb5c17bcSChristian Brauner ASSERT_NE(chroot_id, 0); 263*bb5c17bcSChristian Brauner 264*bb5c17bcSChristian Brauner ASSERT_EQ(chdir("/chroot_dir"), 0); 265*bb5c17bcSChristian Brauner ASSERT_EQ(chroot("."), 0); 266*bb5c17bcSChristian Brauner 267*bb5c17bcSChristian Brauner ASSERT_EQ(get_unique_mnt_id("/"), chroot_id); 268*bb5c17bcSChristian Brauner 269*bb5c17bcSChristian Brauner fd_tree = sys_open_tree(AT_FDCWD, "/", 270*bb5c17bcSChristian Brauner OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); 271*bb5c17bcSChristian Brauner ASSERT_GE(fd_tree, 0); 272*bb5c17bcSChristian Brauner 273*bb5c17bcSChristian Brauner clone_id = get_unique_mnt_id_fd(fd_tree); 274*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, 0); 275*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, chroot_id); 276*bb5c17bcSChristian Brauner 277*bb5c17bcSChristian Brauner ASSERT_EQ(fchdir(fd_tree), 0); 278*bb5c17bcSChristian Brauner 279*bb5c17bcSChristian Brauner ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/", 280*bb5c17bcSChristian Brauner MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH); 281*bb5c17bcSChristian Brauner ASSERT_EQ(ret, 0); 282*bb5c17bcSChristian Brauner 283*bb5c17bcSChristian Brauner close(fd_tree); 284*bb5c17bcSChristian Brauner 285*bb5c17bcSChristian Brauner ASSERT_EQ(chroot("."), 0); 286*bb5c17bcSChristian Brauner 287*bb5c17bcSChristian Brauner root_id = get_unique_mnt_id("/"); 288*bb5c17bcSChristian Brauner ASSERT_NE(root_id, 0); 289*bb5c17bcSChristian Brauner ASSERT_EQ(root_id, clone_id); 290*bb5c17bcSChristian Brauner 291*bb5c17bcSChristian Brauner ASSERT_EQ(statmount(chroot_id, 0, 0, 292*bb5c17bcSChristian Brauner STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0), 0); 293*bb5c17bcSChristian Brauner ASSERT_EQ(sm.mnt_parent_id, clone_id); 294*bb5c17bcSChristian Brauner 295*bb5c17bcSChristian Brauner ASSERT_EQ(umount2(".", MNT_DETACH), 0); 296*bb5c17bcSChristian Brauner } 297*bb5c17bcSChristian Brauner 298*bb5c17bcSChristian Brauner /* 299*bb5c17bcSChristian Brauner * Test MNT_LOCKED transfer when mounting beneath rootfs in a user+mount 300*bb5c17bcSChristian Brauner * namespace. After mount-beneath the new root gets MNT_LOCKED and the 301*bb5c17bcSChristian Brauner * old root has MNT_LOCKED cleared so it can be unmounted. 302*bb5c17bcSChristian Brauner */ 303*bb5c17bcSChristian Brauner TEST_F(move_mount, beneath_rootfs_locked_transfer) 304*bb5c17bcSChristian Brauner { 305*bb5c17bcSChristian Brauner int fd_tree, ret; 306*bb5c17bcSChristian Brauner uint64_t clone_id, root_id; 307*bb5c17bcSChristian Brauner 308*bb5c17bcSChristian Brauner ASSERT_EQ(setup_userns(), 0); 309*bb5c17bcSChristian Brauner 310*bb5c17bcSChristian Brauner ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); 311*bb5c17bcSChristian Brauner 312*bb5c17bcSChristian Brauner fd_tree = sys_open_tree(AT_FDCWD, "/", 313*bb5c17bcSChristian Brauner OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | 314*bb5c17bcSChristian Brauner AT_RECURSIVE); 315*bb5c17bcSChristian Brauner ASSERT_GE(fd_tree, 0); 316*bb5c17bcSChristian Brauner 317*bb5c17bcSChristian Brauner clone_id = get_unique_mnt_id_fd(fd_tree); 318*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, 0); 319*bb5c17bcSChristian Brauner 320*bb5c17bcSChristian Brauner ASSERT_EQ(fchdir(fd_tree), 0); 321*bb5c17bcSChristian Brauner 322*bb5c17bcSChristian Brauner ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/", 323*bb5c17bcSChristian Brauner MOVE_MOUNT_F_EMPTY_PATH | 324*bb5c17bcSChristian Brauner MOVE_MOUNT_BENEATH); 325*bb5c17bcSChristian Brauner ASSERT_EQ(ret, 0); 326*bb5c17bcSChristian Brauner 327*bb5c17bcSChristian Brauner close(fd_tree); 328*bb5c17bcSChristian Brauner 329*bb5c17bcSChristian Brauner ASSERT_EQ(chroot("."), 0); 330*bb5c17bcSChristian Brauner 331*bb5c17bcSChristian Brauner root_id = get_unique_mnt_id("/"); 332*bb5c17bcSChristian Brauner ASSERT_EQ(root_id, clone_id); 333*bb5c17bcSChristian Brauner 334*bb5c17bcSChristian Brauner /* 335*bb5c17bcSChristian Brauner * The old root should be unmountable (MNT_LOCKED was 336*bb5c17bcSChristian Brauner * transferred to the clone). If MNT_LOCKED wasn't 337*bb5c17bcSChristian Brauner * cleared, this would fail with EINVAL. 338*bb5c17bcSChristian Brauner */ 339*bb5c17bcSChristian Brauner ASSERT_EQ(umount2(".", MNT_DETACH), 0); 340*bb5c17bcSChristian Brauner 341*bb5c17bcSChristian Brauner /* Verify "/" is still the clone after detaching old root */ 342*bb5c17bcSChristian Brauner root_id = get_unique_mnt_id("/"); 343*bb5c17bcSChristian Brauner ASSERT_EQ(root_id, clone_id); 344*bb5c17bcSChristian Brauner } 345*bb5c17bcSChristian Brauner 346*bb5c17bcSChristian Brauner /* 347*bb5c17bcSChristian Brauner * Test containment invariant: after mount-beneath rootfs in a user+mount 348*bb5c17bcSChristian Brauner * namespace, the new root must be MNT_LOCKED. The lock transfer from the 349*bb5c17bcSChristian Brauner * old root preserves containment -- the process cannot unmount the new root 350*bb5c17bcSChristian Brauner * to escape the namespace. 351*bb5c17bcSChristian Brauner */ 352*bb5c17bcSChristian Brauner TEST_F(move_mount, beneath_rootfs_locked_containment) 353*bb5c17bcSChristian Brauner { 354*bb5c17bcSChristian Brauner int fd_tree, ret; 355*bb5c17bcSChristian Brauner uint64_t clone_id, root_id; 356*bb5c17bcSChristian Brauner 357*bb5c17bcSChristian Brauner ASSERT_EQ(setup_userns(), 0); 358*bb5c17bcSChristian Brauner 359*bb5c17bcSChristian Brauner ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); 360*bb5c17bcSChristian Brauner 361*bb5c17bcSChristian Brauner /* Sanity: rootfs must be locked in the new userns */ 362*bb5c17bcSChristian Brauner ASSERT_EQ(umount2("/", MNT_DETACH), -1); 363*bb5c17bcSChristian Brauner ASSERT_EQ(errno, EINVAL); 364*bb5c17bcSChristian Brauner 365*bb5c17bcSChristian Brauner fd_tree = sys_open_tree(AT_FDCWD, "/", 366*bb5c17bcSChristian Brauner OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | 367*bb5c17bcSChristian Brauner AT_RECURSIVE); 368*bb5c17bcSChristian Brauner ASSERT_GE(fd_tree, 0); 369*bb5c17bcSChristian Brauner 370*bb5c17bcSChristian Brauner clone_id = get_unique_mnt_id_fd(fd_tree); 371*bb5c17bcSChristian Brauner ASSERT_NE(clone_id, 0); 372*bb5c17bcSChristian Brauner 373*bb5c17bcSChristian Brauner ASSERT_EQ(fchdir(fd_tree), 0); 374*bb5c17bcSChristian Brauner 375*bb5c17bcSChristian Brauner ret = sys_move_mount(fd_tree, "", AT_FDCWD, "/", 376*bb5c17bcSChristian Brauner MOVE_MOUNT_F_EMPTY_PATH | 377*bb5c17bcSChristian Brauner MOVE_MOUNT_BENEATH); 378*bb5c17bcSChristian Brauner ASSERT_EQ(ret, 0); 379*bb5c17bcSChristian Brauner 380*bb5c17bcSChristian Brauner close(fd_tree); 381*bb5c17bcSChristian Brauner 382*bb5c17bcSChristian Brauner ASSERT_EQ(chroot("."), 0); 383*bb5c17bcSChristian Brauner 384*bb5c17bcSChristian Brauner root_id = get_unique_mnt_id("/"); 385*bb5c17bcSChristian Brauner ASSERT_EQ(root_id, clone_id); 386*bb5c17bcSChristian Brauner 387*bb5c17bcSChristian Brauner /* Detach old root (MNT_LOCKED was cleared from it) */ 388*bb5c17bcSChristian Brauner ASSERT_EQ(umount2(".", MNT_DETACH), 0); 389*bb5c17bcSChristian Brauner 390*bb5c17bcSChristian Brauner /* Verify "/" is still the clone after detaching old root */ 391*bb5c17bcSChristian Brauner root_id = get_unique_mnt_id("/"); 392*bb5c17bcSChristian Brauner ASSERT_EQ(root_id, clone_id); 393*bb5c17bcSChristian Brauner 394*bb5c17bcSChristian Brauner /* 395*bb5c17bcSChristian Brauner * The new root must be locked (MNT_LOCKED was transferred 396*bb5c17bcSChristian Brauner * from the old root). Attempting to unmount it must fail 397*bb5c17bcSChristian Brauner * with EINVAL, preserving the containment invariant. 398*bb5c17bcSChristian Brauner */ 399*bb5c17bcSChristian Brauner ASSERT_EQ(umount2("/", MNT_DETACH), -1); 400*bb5c17bcSChristian Brauner ASSERT_EQ(errno, EINVAL); 401*bb5c17bcSChristian Brauner } 402*bb5c17bcSChristian Brauner 403*bb5c17bcSChristian Brauner /* 404*bb5c17bcSChristian Brauner * Test MNT_LOCKED transfer when mounting beneath a non-rootfs locked mount. 405*bb5c17bcSChristian Brauner * Mounts created before unshare(CLONE_NEWUSER | CLONE_NEWNS) become locked 406*bb5c17bcSChristian Brauner * in the new namespace. Mount-beneath transfers the lock from the displaced 407*bb5c17bcSChristian Brauner * mount to the new mount, so the displaced mount can be unmounted. 408*bb5c17bcSChristian Brauner */ 409*bb5c17bcSChristian Brauner TEST_F(move_mount, beneath_non_rootfs_locked_transfer) 410*bb5c17bcSChristian Brauner { 411*bb5c17bcSChristian Brauner int mnt_fd, ret; 412*bb5c17bcSChristian Brauner uint64_t mnt_new_id, mnt_visible_id; 413*bb5c17bcSChristian Brauner 414*bb5c17bcSChristian Brauner ASSERT_EQ(setup_locked_overmount(), 0); 415*bb5c17bcSChristian Brauner 416*bb5c17bcSChristian Brauner mnt_fd = create_detached_tmpfs(); 417*bb5c17bcSChristian Brauner ASSERT_GE(mnt_fd, 0); 418*bb5c17bcSChristian Brauner 419*bb5c17bcSChristian Brauner mnt_new_id = get_unique_mnt_id_fd(mnt_fd); 420*bb5c17bcSChristian Brauner ASSERT_NE(mnt_new_id, 0); 421*bb5c17bcSChristian Brauner 422*bb5c17bcSChristian Brauner /* Move mount beneath B (which is locked) */ 423*bb5c17bcSChristian Brauner ret = sys_move_mount(mnt_fd, "", AT_FDCWD, "/mnt_dir", 424*bb5c17bcSChristian Brauner MOVE_MOUNT_F_EMPTY_PATH | 425*bb5c17bcSChristian Brauner MOVE_MOUNT_BENEATH); 426*bb5c17bcSChristian Brauner ASSERT_EQ(ret, 0); 427*bb5c17bcSChristian Brauner 428*bb5c17bcSChristian Brauner close(mnt_fd); 429*bb5c17bcSChristian Brauner 430*bb5c17bcSChristian Brauner /* 431*bb5c17bcSChristian Brauner * B should now be unmountable (MNT_LOCKED was transferred 432*bb5c17bcSChristian Brauner * to the new mount beneath it). If MNT_LOCKED wasn't 433*bb5c17bcSChristian Brauner * cleared from B, this would fail with EINVAL. 434*bb5c17bcSChristian Brauner */ 435*bb5c17bcSChristian Brauner ASSERT_EQ(umount2("/mnt_dir", MNT_DETACH), 0); 436*bb5c17bcSChristian Brauner 437*bb5c17bcSChristian Brauner /* Verify the new mount is now visible */ 438*bb5c17bcSChristian Brauner mnt_visible_id = get_unique_mnt_id("/mnt_dir"); 439*bb5c17bcSChristian Brauner ASSERT_EQ(mnt_visible_id, mnt_new_id); 440*bb5c17bcSChristian Brauner } 441*bb5c17bcSChristian Brauner 442*bb5c17bcSChristian Brauner /* 443*bb5c17bcSChristian Brauner * Test MNT_LOCKED containment when mounting beneath a non-rootfs mount 444*bb5c17bcSChristian Brauner * that was locked during unshare(CLONE_NEWUSER | CLONE_NEWNS). 445*bb5c17bcSChristian Brauner * Mounts created before unshare become locked in the new namespace. 446*bb5c17bcSChristian Brauner * Mount-beneath transfers the lock, preserving containment: the new 447*bb5c17bcSChristian Brauner * mount cannot be unmounted, but the displaced mount can. 448*bb5c17bcSChristian Brauner */ 449*bb5c17bcSChristian Brauner TEST_F(move_mount, beneath_non_rootfs_locked_containment) 450*bb5c17bcSChristian Brauner { 451*bb5c17bcSChristian Brauner int mnt_fd, ret; 452*bb5c17bcSChristian Brauner uint64_t mnt_new_id, mnt_visible_id; 453*bb5c17bcSChristian Brauner 454*bb5c17bcSChristian Brauner ASSERT_EQ(setup_locked_overmount(), 0); 455*bb5c17bcSChristian Brauner 456*bb5c17bcSChristian Brauner mnt_fd = create_detached_tmpfs(); 457*bb5c17bcSChristian Brauner ASSERT_GE(mnt_fd, 0); 458*bb5c17bcSChristian Brauner 459*bb5c17bcSChristian Brauner mnt_new_id = get_unique_mnt_id_fd(mnt_fd); 460*bb5c17bcSChristian Brauner ASSERT_NE(mnt_new_id, 0); 461*bb5c17bcSChristian Brauner 462*bb5c17bcSChristian Brauner /* 463*bb5c17bcSChristian Brauner * Move new tmpfs beneath B at /mnt_dir. 464*bb5c17bcSChristian Brauner * Stack becomes: A -> new -> B 465*bb5c17bcSChristian Brauner * Lock transfers from B to new. 466*bb5c17bcSChristian Brauner */ 467*bb5c17bcSChristian Brauner ret = sys_move_mount(mnt_fd, "", AT_FDCWD, "/mnt_dir", 468*bb5c17bcSChristian Brauner MOVE_MOUNT_F_EMPTY_PATH | 469*bb5c17bcSChristian Brauner MOVE_MOUNT_BENEATH); 470*bb5c17bcSChristian Brauner ASSERT_EQ(ret, 0); 471*bb5c17bcSChristian Brauner 472*bb5c17bcSChristian Brauner close(mnt_fd); 473*bb5c17bcSChristian Brauner 474*bb5c17bcSChristian Brauner /* 475*bb5c17bcSChristian Brauner * B lost MNT_LOCKED -- unmounting it must succeed. 476*bb5c17bcSChristian Brauner * This reveals the new mount at /mnt_dir. 477*bb5c17bcSChristian Brauner */ 478*bb5c17bcSChristian Brauner ASSERT_EQ(umount2("/mnt_dir", MNT_DETACH), 0); 479*bb5c17bcSChristian Brauner 480*bb5c17bcSChristian Brauner /* Verify the new mount is now visible */ 481*bb5c17bcSChristian Brauner mnt_visible_id = get_unique_mnt_id("/mnt_dir"); 482*bb5c17bcSChristian Brauner ASSERT_EQ(mnt_visible_id, mnt_new_id); 483*bb5c17bcSChristian Brauner 484*bb5c17bcSChristian Brauner /* 485*bb5c17bcSChristian Brauner * The new mount gained MNT_LOCKED -- unmounting it must 486*bb5c17bcSChristian Brauner * fail with EINVAL, preserving the containment invariant. 487*bb5c17bcSChristian Brauner */ 488*bb5c17bcSChristian Brauner ASSERT_EQ(umount2("/mnt_dir", MNT_DETACH), -1); 489*bb5c17bcSChristian Brauner ASSERT_EQ(errno, EINVAL); 490*bb5c17bcSChristian Brauner } 491*bb5c17bcSChristian Brauner 492*bb5c17bcSChristian Brauner TEST_HARNESS_MAIN 493