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