xref: /linux/tools/testing/selftests/filesystems/move_mount/move_mount_test.c (revision 0fc8f6200d2313278fbf4539bbab74677c685531)
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