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