xref: /linux/tools/testing/selftests/filesystems/umount_propagation/umount_propagation_test.c (revision 8e8cd9a4c6dc006e9bd51b8ad0079fb4576b48d3)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * A synchronous umount fails with EBUSY when a mount it would pull out by
4  * propagation is still in use.
5  */
6 #define _GNU_SOURCE
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <sched.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/mount.h>
14 #include <sys/stat.h>
15 #include <sys/syscall.h>
16 #include <sys/wait.h>
17 #include <unistd.h>
18 #include <linux/mount.h>
19 #include <linux/stat.h>
20 
21 #include "../../kselftest_harness.h"
22 
23 #ifndef OPEN_TREE_CLONE
24 #define OPEN_TREE_CLONE		1
25 #endif
26 #ifndef OPEN_TREE_CLOEXEC
27 #define OPEN_TREE_CLOEXEC	O_CLOEXEC
28 #endif
29 #ifndef AT_RECURSIVE
30 #define AT_RECURSIVE		0x8000
31 #endif
32 #ifndef MOVE_MOUNT_F_EMPTY_PATH
33 #define MOVE_MOUNT_F_EMPTY_PATH	0x00000004
34 #endif
35 #ifndef MOVE_MOUNT_BENEATH
36 #define MOVE_MOUNT_BENEATH	0x00000200
37 #endif
38 #ifndef STATX_MNT_ID
39 #define STATX_MNT_ID		0x00001000U
40 #endif
41 
sys_open_tree(int dfd,const char * filename,unsigned int flags)42 static int sys_open_tree(int dfd, const char *filename, unsigned int flags)
43 {
44 	return syscall(__NR_open_tree, dfd, filename, flags);
45 }
46 
sys_move_mount(int from_dfd,const char * from_pathname,int to_dfd,const char * to_pathname,unsigned int flags)47 static int sys_move_mount(int from_dfd, const char *from_pathname,
48 			  int to_dfd, const char *to_pathname,
49 			  unsigned int flags)
50 {
51 	return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd,
52 		       to_pathname, flags);
53 }
54 
55 /* Child exit codes. */
56 enum {
57 	CHILD_OK,
58 	CHILD_UNSHARE,		/* could not set up the slave namespace */
59 	CHILD_OPEN_TREE,	/* open_tree() failed */
60 	CHILD_MOVE_MOUNT,	/* move_mount() failed */
61 	CHILD_STATX,		/* statx() failed */
62 	CHILD_PIPE,		/* the parent went away */
63 };
64 
65 /* Messages between parent and child. */
66 enum {
67 	MSG_READY = 'r',	/* child: the copy is mounted and referenced */
68 	MSG_CHECK = 'c',	/* parent: check that the copy is still attached */
69 	MSG_ATTACHED = 'a',	/* child: it is */
70 	MSG_DETACHED = 'd',	/* child: it is not */
71 	MSG_CLOSE = 'x',	/* parent: drop the reference */
72 	MSG_CLOSED = 'y',	/* child: dropped */
73 	MSG_EXIT = 'e',		/* parent: done */
74 };
75 
FIXTURE(umount_propagation)76 FIXTURE(umount_propagation)
77 {
78 	char base[64];
79 	char victim[80];
80 	bool mounted;
81 };
82 
FIXTURE_SETUP(umount_propagation)83 FIXTURE_SETUP(umount_propagation)
84 {
85 	self->mounted = false;
86 
87 	if (geteuid() != 0)
88 		SKIP(return, "test requires CAP_SYS_ADMIN");
89 
90 	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
91 	ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
92 
93 	snprintf(self->base, sizeof(self->base), "/tmp/umount_propagation.XXXXXX");
94 	ASSERT_NE(mkdtemp(self->base), NULL);
95 	ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0);
96 	self->mounted = true;
97 	ASSERT_EQ(mount(NULL, self->base, NULL, MS_SHARED, NULL), 0);
98 
99 	snprintf(self->victim, sizeof(self->victim), "%s/victim", self->base);
100 	ASSERT_EQ(mkdir(self->victim, 0755), 0);
101 	ASSERT_EQ(mount("tmpfs", self->victim, "tmpfs", 0, NULL), 0);
102 }
103 
FIXTURE_TEARDOWN(umount_propagation)104 FIXTURE_TEARDOWN(umount_propagation)
105 {
106 	if (self->mounted)
107 		umount2(self->base, MNT_DETACH);
108 	rmdir(self->base);
109 }
110 
send_msg(int fd,char msg)111 static int send_msg(int fd, char msg)
112 {
113 	return write(fd, &msg, 1) == 1 ? 0 : -1;
114 }
115 
recv_msg(int fd)116 static char recv_msg(int fd)
117 {
118 	char msg;
119 
120 	if (read(fd, &msg, 1) != 1)
121 		return 0;
122 	return msg;
123 }
124 
125 /* Is the mount with id @mnt_id attached in this mount namespace? */
mount_attached(__u64 mnt_id)126 static bool mount_attached(__u64 mnt_id)
127 {
128 	char line[4096];
129 	bool found = false;
130 	FILE *f;
131 
132 	f = fopen("/proc/self/mountinfo", "re");
133 	if (!f)
134 		return false;
135 
136 	while (fgets(line, sizeof(line), f)) {
137 		if (strtoull(line, NULL, 10) == mnt_id) {
138 			found = true;
139 			break;
140 		}
141 	}
142 	fclose(f);
143 	return found;
144 }
145 
146 /*
147  * The slave namespace: take a detached copy of the shared tree and move it
148  * beneath the propagated copy of the victim, keeping the open_tree()
149  * descriptor as a reference on it.
150  */
slave_child(const char * base,const char * victim,int to_parent,int from_parent)151 static int slave_child(const char *base, const char *victim, int to_parent,
152 		       int from_parent)
153 {
154 	struct statx stx;
155 	int fd;
156 
157 	if (unshare(CLONE_NEWNS))
158 		return CHILD_UNSHARE;
159 	if (mount("", "/", NULL, MS_REC | MS_SLAVE, NULL))
160 		return CHILD_UNSHARE;
161 
162 	fd = sys_open_tree(AT_FDCWD, base,
163 			   OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_RECURSIVE);
164 	if (fd < 0)
165 		return CHILD_OPEN_TREE;
166 	if (sys_move_mount(fd, "", AT_FDCWD, victim,
167 			   MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_BENEATH))
168 		return CHILD_MOVE_MOUNT;
169 	if (statx(fd, "", AT_EMPTY_PATH, STATX_MNT_ID, &stx))
170 		return CHILD_STATX;
171 
172 	if (send_msg(to_parent, MSG_READY) || recv_msg(from_parent) != MSG_CHECK)
173 		return CHILD_PIPE;
174 	if (send_msg(to_parent, mount_attached(stx.stx_mnt_id) ?
175 				MSG_ATTACHED : MSG_DETACHED))
176 		return CHILD_PIPE;
177 
178 	if (recv_msg(from_parent) != MSG_CLOSE)
179 		return CHILD_PIPE;
180 	close(fd);
181 	if (send_msg(to_parent, MSG_CLOSED) || recv_msg(from_parent) != MSG_EXIT)
182 		return CHILD_PIPE;
183 	return CHILD_OK;
184 }
185 
TEST_F(umount_propagation,busy_copy_pulled_out)186 TEST_F(umount_propagation, busy_copy_pulled_out)
187 {
188 	int to_child[2], to_parent[2];
189 	int status;
190 	pid_t pid;
191 
192 	ASSERT_EQ(pipe(to_child), 0);
193 	ASSERT_EQ(pipe(to_parent), 0);
194 
195 	pid = fork();
196 	ASSERT_GE(pid, 0);
197 	if (pid == 0) {
198 		close(to_child[1]);
199 		close(to_parent[0]);
200 		_exit(slave_child(self->base, self->victim, to_parent[1],
201 				  to_child[0]));
202 	}
203 	close(to_child[0]);
204 	close(to_parent[1]);
205 
206 	ASSERT_EQ(recv_msg(to_parent[0]), MSG_READY);
207 
208 	/* the copy in the slave namespace is in use */
209 	ASSERT_EQ(umount2(self->victim, 0), -1);
210 	ASSERT_EQ(errno, EBUSY);
211 
212 	ASSERT_EQ(send_msg(to_child[1], MSG_CHECK), 0);
213 	ASSERT_EQ(recv_msg(to_parent[0]), MSG_ATTACHED);
214 
215 	/* and once it is not, the umount goes through */
216 	ASSERT_EQ(send_msg(to_child[1], MSG_CLOSE), 0);
217 	ASSERT_EQ(recv_msg(to_parent[0]), MSG_CLOSED);
218 	ASSERT_EQ(umount2(self->victim, 0), 0);
219 
220 	ASSERT_EQ(send_msg(to_child[1], MSG_EXIT), 0);
221 	ASSERT_EQ(waitpid(pid, &status, 0), pid);
222 	ASSERT_TRUE(WIFEXITED(status));
223 	ASSERT_EQ(WEXITSTATUS(status), CHILD_OK);
224 }
225 
226 TEST_HARNESS_MAIN
227