xref: /linux/tools/testing/selftests/filesystems/mount_cycle/unmounted_tree_test.c (revision 8e8cd9a4c6dc006e9bd51b8ad0079fb4576b48d3)
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 
sys_mount_setattr(int dfd,const char * path,unsigned int flags,struct mount_attr * attr,size_t size)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 
sys_pidfd_open(pid_t pid,unsigned int flags)42 static inline int sys_pidfd_open(pid_t pid, unsigned int flags)
43 {
44 	return syscall(__NR_pidfd_open, pid, flags);
45 }
46 
write_file(const char * path,const char * s)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 
touch(const char * path)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. */
enter_userns(void)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. */
own_mntns(void)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 
FIXTURE(unmounted_tree)101 FIXTURE(unmounted_tree) {
102 	char dir[PATH_LEN];
103 };
104 
FIXTURE_SETUP(unmounted_tree)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 
FIXTURE_TEARDOWN(unmounted_tree)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. */
assert_not_walked(struct __test_metadata * _metadata,int fd,const char * target)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. */
assert_root_copied(struct __test_metadata * _metadata,int fd,const char * 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  */
TEST_F(unmounted_tree,detached_nsfs_bind_not_walked)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. */
bind_pidfd(int pidfd,const char * target)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. */
TEST_F(unmounted_tree,detached_pidfs_bind_not_walked)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 
child_reason(int code)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  */
run_child(struct __test_metadata * _metadata,int (* fn)(const struct child_args *,int,int),const struct child_args * a,const char * victim)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  */
vacant_child(const struct child_args * a,int to_parent,int from_parent)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  */
TEST_F(unmounted_tree,vacant_child_not_copied)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  */
ordinary_copy(const struct child_args * a,int to_parent,int from_parent)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  */
TEST_F(unmounted_tree,copy_of_detached_bind_is_ordinary)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  */
TEST_F(unmounted_tree,detached_tree_setattr_refused)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