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