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