xref: /linux/tools/testing/selftests/filesystems/mount_cycle/overmount_reparent_test.c (revision 8e8cd9a4c6dc006e9bd51b8ad0079fb4576b48d3)
1*7fbcc793SChristian Brauner // SPDX-License-Identifier: GPL-2.0
2*7fbcc793SChristian Brauner /*
3*7fbcc793SChristian Brauner  * propagate_umount() slides a surviving overmount off a mount that is being
4*7fbcc793SChristian Brauner  * unmounted and unmounts that mount right after. If a file descriptor keeps
5*7fbcc793SChristian Brauner  * the unmounted mount alive its ->overmount is left pointing at the moved
6*7fbcc793SChristian Brauner  * mount, which is freed on its own schedule. move_mount(MOVE_MOUNT_BENEATH)
7*7fbcc793SChristian Brauner  * with such a file descriptor as the target walks ->overmount in
8*7fbcc793SChristian Brauner  * topmost_overmount() before it checks that the target is still mounted and
9*7fbcc793SChristian Brauner  * must not step into the freed mount.
10*7fbcc793SChristian Brauner  */
11*7fbcc793SChristian Brauner #define _GNU_SOURCE
12*7fbcc793SChristian Brauner #include <errno.h>
13*7fbcc793SChristian Brauner #include <fcntl.h>
14*7fbcc793SChristian Brauner #include <sched.h>
15*7fbcc793SChristian Brauner #include <stdio.h>
16*7fbcc793SChristian Brauner #include <stdlib.h>
17*7fbcc793SChristian Brauner #include <string.h>
18*7fbcc793SChristian Brauner #include <unistd.h>
19*7fbcc793SChristian Brauner #include <sys/mount.h>
20*7fbcc793SChristian Brauner #include <sys/stat.h>
21*7fbcc793SChristian Brauner #include <sys/syscall.h>
22*7fbcc793SChristian Brauner 
23*7fbcc793SChristian Brauner #include "../wrappers.h"
24*7fbcc793SChristian Brauner #include "../../kselftest_harness.h"
25*7fbcc793SChristian Brauner 
26*7fbcc793SChristian Brauner #ifndef MOVE_MOUNT_BENEATH
27*7fbcc793SChristian Brauner #define MOVE_MOUNT_BENEATH 0x00000200
28*7fbcc793SChristian Brauner #endif
29*7fbcc793SChristian Brauner 
30*7fbcc793SChristian Brauner #ifndef FSCONFIG_CMD_CREATE
31*7fbcc793SChristian Brauner #define FSCONFIG_CMD_CREATE 6
32*7fbcc793SChristian Brauner #endif
33*7fbcc793SChristian Brauner 
34*7fbcc793SChristian Brauner #define DIR_LEN 64
35*7fbcc793SChristian Brauner #define PATH_LEN 128
36*7fbcc793SChristian Brauner 
write_file(const char * path,const char * s)37*7fbcc793SChristian Brauner static int write_file(const char *path, const char *s)
38*7fbcc793SChristian Brauner {
39*7fbcc793SChristian Brauner 	ssize_t n = -1;
40*7fbcc793SChristian Brauner 	int fd;
41*7fbcc793SChristian Brauner 
42*7fbcc793SChristian Brauner 	fd = open(path, O_WRONLY | O_CLOEXEC);
43*7fbcc793SChristian Brauner 	if (fd >= 0) {
44*7fbcc793SChristian Brauner 		n = write(fd, s, strlen(s));
45*7fbcc793SChristian Brauner 		close(fd);
46*7fbcc793SChristian Brauner 	}
47*7fbcc793SChristian Brauner 	return n == (ssize_t)strlen(s) ? 0 : -1;
48*7fbcc793SChristian Brauner }
49*7fbcc793SChristian Brauner 
50*7fbcc793SChristian Brauner /* Become root in a new user namespace with a private mount namespace. */
enter_userns(void)51*7fbcc793SChristian Brauner static int enter_userns(void)
52*7fbcc793SChristian Brauner {
53*7fbcc793SChristian Brauner 	uid_t uid = getuid();
54*7fbcc793SChristian Brauner 	gid_t gid = getgid();
55*7fbcc793SChristian Brauner 	char map[32];
56*7fbcc793SChristian Brauner 
57*7fbcc793SChristian Brauner 	if (unshare(CLONE_NEWUSER | CLONE_NEWNS))
58*7fbcc793SChristian Brauner 		return -1;
59*7fbcc793SChristian Brauner 	if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT)
60*7fbcc793SChristian Brauner 		return -1;
61*7fbcc793SChristian Brauner 	snprintf(map, sizeof(map), "0 %d 1", uid);
62*7fbcc793SChristian Brauner 	if (write_file("/proc/self/uid_map", map))
63*7fbcc793SChristian Brauner 		return -1;
64*7fbcc793SChristian Brauner 	snprintf(map, sizeof(map), "0 %d 1", gid);
65*7fbcc793SChristian Brauner 	if (write_file("/proc/self/gid_map", map))
66*7fbcc793SChristian Brauner 		return -1;
67*7fbcc793SChristian Brauner 	if (setgid(0) || setuid(0))
68*7fbcc793SChristian Brauner 		return -1;
69*7fbcc793SChristian Brauner 	return mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL);
70*7fbcc793SChristian Brauner }
71*7fbcc793SChristian Brauner 
72*7fbcc793SChristian Brauner /* A detached tmpfs mount to serve as the source of a move. */
detached_tmpfs(void)73*7fbcc793SChristian Brauner static int detached_tmpfs(void)
74*7fbcc793SChristian Brauner {
75*7fbcc793SChristian Brauner 	int sfd, mfd;
76*7fbcc793SChristian Brauner 
77*7fbcc793SChristian Brauner 	sfd = sys_fsopen("tmpfs", 0);
78*7fbcc793SChristian Brauner 	if (sfd < 0)
79*7fbcc793SChristian Brauner 		return -1;
80*7fbcc793SChristian Brauner 	if (sys_fsconfig(sfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) {
81*7fbcc793SChristian Brauner 		close(sfd);
82*7fbcc793SChristian Brauner 		return -1;
83*7fbcc793SChristian Brauner 	}
84*7fbcc793SChristian Brauner 	mfd = sys_fsmount(sfd, 0, 0);
85*7fbcc793SChristian Brauner 	close(sfd);
86*7fbcc793SChristian Brauner 	return mfd;
87*7fbcc793SChristian Brauner }
88*7fbcc793SChristian Brauner 
FIXTURE(overmount_reparent)89*7fbcc793SChristian Brauner FIXTURE(overmount_reparent) {
90*7fbcc793SChristian Brauner 	char dir[DIR_LEN];
91*7fbcc793SChristian Brauner };
92*7fbcc793SChristian Brauner 
FIXTURE_SETUP(overmount_reparent)93*7fbcc793SChristian Brauner FIXTURE_SETUP(overmount_reparent)
94*7fbcc793SChristian Brauner {
95*7fbcc793SChristian Brauner 	snprintf(self->dir, sizeof(self->dir), "/tmp/overmount_reparent.XXXXXX");
96*7fbcc793SChristian Brauner 	ASSERT_NE(mkdtemp(self->dir), NULL);
97*7fbcc793SChristian Brauner 	if (enter_userns()) {
98*7fbcc793SChristian Brauner 		rmdir(self->dir);
99*7fbcc793SChristian Brauner 		SKIP(return, "test requires user namespaces");
100*7fbcc793SChristian Brauner 	}
101*7fbcc793SChristian Brauner }
102*7fbcc793SChristian Brauner 
FIXTURE_TEARDOWN(overmount_reparent)103*7fbcc793SChristian Brauner FIXTURE_TEARDOWN(overmount_reparent)
104*7fbcc793SChristian Brauner {
105*7fbcc793SChristian Brauner 	char p[PATH_LEN];
106*7fbcc793SChristian Brauner 
107*7fbcc793SChristian Brauner 	snprintf(p, sizeof(p), "%s/b", self->dir);
108*7fbcc793SChristian Brauner 	umount2(p, MNT_DETACH);
109*7fbcc793SChristian Brauner 	snprintf(p, sizeof(p), "%s/a", self->dir);
110*7fbcc793SChristian Brauner 	umount2(p, MNT_DETACH);
111*7fbcc793SChristian Brauner }
112*7fbcc793SChristian Brauner 
113*7fbcc793SChristian Brauner /*
114*7fbcc793SChristian Brauner  * A shared mount base_a is bind-mounted to base_b as its peer. A mount X on
115*7fbcc793SChristian Brauner  * base_a/mp propagates a copy X_b onto base_b/mp. A file descriptor pins X_b,
116*7fbcc793SChristian Brauner  * then X_b is made private and an overmount is stacked on its root.
117*7fbcc793SChristian Brauner  *
118*7fbcc793SChristian Brauner  * A lazy unmount of X propagates to X_b: X_b is committed to the unmount while
119*7fbcc793SChristian Brauner  * its overmount survives, so propagate_umount() reparents the overmount onto
120*7fbcc793SChristian Brauner  * base_b and unmounts X_b, which the file descriptor keeps alive. Unmounting
121*7fbcc793SChristian Brauner  * the reparented overmount frees it. X_b->overmount now dangles unless
122*7fbcc793SChristian Brauner  * mnt_change_mountpoint() reset it.
123*7fbcc793SChristian Brauner  *
124*7fbcc793SChristian Brauner  * move_mount(MOVE_MOUNT_BENEATH) through the file descriptor makes
125*7fbcc793SChristian Brauner  * do_lock_mount() follow X_b->overmount in topmost_overmount() before it
126*7fbcc793SChristian Brauner  * notices that X_b is no longer mounted. With the pointer reset the move fails
127*7fbcc793SChristian Brauner  * cleanly with ENOENT; otherwise it reads the freed overmount.
128*7fbcc793SChristian Brauner  */
TEST_F(overmount_reparent,dead_overmount_holder_not_followed)129*7fbcc793SChristian Brauner TEST_F(overmount_reparent, dead_overmount_holder_not_followed)
130*7fbcc793SChristian Brauner {
131*7fbcc793SChristian Brauner 	char a[PATH_LEN], b[PATH_LEN], amp[PATH_LEN], bmp[PATH_LEN];
132*7fbcc793SChristian Brauner 	int fd, mfd, ret;
133*7fbcc793SChristian Brauner 
134*7fbcc793SChristian Brauner 	snprintf(a, sizeof(a), "%s/a", self->dir);
135*7fbcc793SChristian Brauner 	snprintf(b, sizeof(b), "%s/b", self->dir);
136*7fbcc793SChristian Brauner 	ASSERT_EQ(mkdir(a, 0755), 0);
137*7fbcc793SChristian Brauner 	ASSERT_EQ(mkdir(b, 0755), 0);
138*7fbcc793SChristian Brauner 
139*7fbcc793SChristian Brauner 	ASSERT_EQ(mount("tmpfs", a, "tmpfs", 0, NULL), 0);
140*7fbcc793SChristian Brauner 	ASSERT_EQ(mount(NULL, a, NULL, MS_SHARED, NULL), 0);
141*7fbcc793SChristian Brauner 	ASSERT_EQ(mount(a, b, NULL, MS_BIND, NULL), 0);
142*7fbcc793SChristian Brauner 
143*7fbcc793SChristian Brauner 	snprintf(amp, sizeof(amp), "%s/a/mp", self->dir);
144*7fbcc793SChristian Brauner 	snprintf(bmp, sizeof(bmp), "%s/b/mp", self->dir);
145*7fbcc793SChristian Brauner 	/* base_a and base_b share one superblock, so this dir is in both. */
146*7fbcc793SChristian Brauner 	ASSERT_EQ(mkdir(amp, 0755), 0);
147*7fbcc793SChristian Brauner 
148*7fbcc793SChristian Brauner 	/* X on base_a/mp propagates a copy X_b onto base_b/mp. */
149*7fbcc793SChristian Brauner 	ASSERT_EQ(mount("tmpfs", amp, "tmpfs", 0, NULL), 0);
150*7fbcc793SChristian Brauner 
151*7fbcc793SChristian Brauner 	/* Pin X_b before anything is stacked on top of it. */
152*7fbcc793SChristian Brauner 	fd = open(bmp, O_PATH | O_CLOEXEC);
153*7fbcc793SChristian Brauner 	ASSERT_GE(fd, 0);
154*7fbcc793SChristian Brauner 
155*7fbcc793SChristian Brauner 	/* Keep the overmount local to X_b. */
156*7fbcc793SChristian Brauner 	ASSERT_EQ(mount(NULL, bmp, NULL, MS_PRIVATE, NULL), 0);
157*7fbcc793SChristian Brauner 
158*7fbcc793SChristian Brauner 	/* The overmount on X_b's root: X_b->overmount points at it. */
159*7fbcc793SChristian Brauner 	ASSERT_EQ(mount("tmpfs", bmp, "tmpfs", 0, NULL), 0);
160*7fbcc793SChristian Brauner 
161*7fbcc793SChristian Brauner 	/* Reparents the overmount onto base_b and unmounts X_b. */
162*7fbcc793SChristian Brauner 	ASSERT_EQ(umount2(amp, MNT_DETACH), 0);
163*7fbcc793SChristian Brauner 
164*7fbcc793SChristian Brauner 	/* Free the reparented overmount. */
165*7fbcc793SChristian Brauner 	ASSERT_EQ(umount2(bmp, MNT_DETACH), 0);
166*7fbcc793SChristian Brauner 	usleep(100000);
167*7fbcc793SChristian Brauner 
168*7fbcc793SChristian Brauner 	mfd = detached_tmpfs();
169*7fbcc793SChristian Brauner 	ASSERT_GE(mfd, 0);
170*7fbcc793SChristian Brauner 
171*7fbcc793SChristian Brauner 	ret = sys_move_mount(mfd, "", fd, "",
172*7fbcc793SChristian Brauner 			     MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH |
173*7fbcc793SChristian Brauner 			     MOVE_MOUNT_BENEATH);
174*7fbcc793SChristian Brauner 	EXPECT_EQ(ret, -1);
175*7fbcc793SChristian Brauner 	EXPECT_EQ(errno, ENOENT);
176*7fbcc793SChristian Brauner 
177*7fbcc793SChristian Brauner 	close(mfd);
178*7fbcc793SChristian Brauner 	close(fd);
179*7fbcc793SChristian Brauner }
180*7fbcc793SChristian Brauner 
181*7fbcc793SChristian Brauner TEST_HARNESS_MAIN
182