1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * An unbindable mount stays unbindable in a cloned mount namespace.
4 */
5 #define _GNU_SOURCE
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <sched.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/mount.h>
13 #include <sys/stat.h>
14 #include <sys/syscall.h>
15 #include <sys/wait.h>
16 #include <unistd.h>
17
18 #include "../../kselftest_harness.h"
19
20 #ifndef OPEN_TREE_CLONE
21 #define OPEN_TREE_CLONE 1
22 #endif
23 #ifndef OPEN_TREE_CLOEXEC
24 #define OPEN_TREE_CLOEXEC O_CLOEXEC
25 #endif
26
sys_open_tree(int dfd,const char * filename,unsigned int flags)27 static int sys_open_tree(int dfd, const char *filename, unsigned int flags)
28 {
29 return syscall(__NR_open_tree, dfd, filename, flags);
30 }
31
32 /* Child exit codes. */
33 enum {
34 CHILD_OK, /* the operation failed with EINVAL as it must */
35 CHILD_ALLOWED, /* the operation succeeded: the flag was lost */
36 CHILD_UNSHARE, /* unshare(CLONE_NEWNS) failed */
37 CHILD_ERRNO, /* the operation failed with some other errno */
38 CHILD_MOUNTINFO, /* the mount was not found in mountinfo */
39 };
40
FIXTURE(mntns_unbindable)41 FIXTURE(mntns_unbindable)
42 {
43 char base[64];
44 char src[80];
45 char dst[80];
46 bool mounted;
47 };
48
FIXTURE_SETUP(mntns_unbindable)49 FIXTURE_SETUP(mntns_unbindable)
50 {
51 self->mounted = false;
52
53 if (geteuid() != 0)
54 SKIP(return, "test requires CAP_SYS_ADMIN");
55
56 ASSERT_EQ(unshare(CLONE_NEWNS), 0);
57 ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
58
59 snprintf(self->base, sizeof(self->base), "/tmp/mntns_unbindable.XXXXXX");
60 ASSERT_NE(mkdtemp(self->base), NULL);
61 ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0);
62 self->mounted = true;
63
64 snprintf(self->src, sizeof(self->src), "%s/src", self->base);
65 snprintf(self->dst, sizeof(self->dst), "%s/dst", self->base);
66 ASSERT_EQ(mkdir(self->src, 0755), 0);
67 ASSERT_EQ(mkdir(self->dst, 0755), 0);
68
69 ASSERT_EQ(mount("tmpfs", self->src, "tmpfs", 0, NULL), 0);
70 ASSERT_EQ(mount(NULL, self->src, NULL, MS_UNBINDABLE, NULL), 0);
71 }
72
FIXTURE_TEARDOWN(mntns_unbindable)73 FIXTURE_TEARDOWN(mntns_unbindable)
74 {
75 if (self->mounted)
76 umount2(self->base, MNT_DETACH);
77 rmdir(self->base);
78 }
79
classify(int ret,int err)80 static int classify(int ret, int err)
81 {
82 if (ret >= 0)
83 return CHILD_ALLOWED;
84 return err == EINVAL ? CHILD_OK : CHILD_ERRNO;
85 }
86
87 /* Is the mount on @mountpoint marked unbindable in /proc/self/mountinfo? */
mountinfo_unbindable(const char * mountpoint)88 static int mountinfo_unbindable(const char *mountpoint)
89 {
90 char line[4096];
91 FILE *f;
92 int ret = CHILD_MOUNTINFO;
93
94 f = fopen("/proc/self/mountinfo", "re");
95 if (!f)
96 return CHILD_ERRNO;
97
98 while (fgets(line, sizeof(line), f)) {
99 char *fields[6], *p = line, *opt;
100 int i;
101
102 for (i = 0; i < 6; i++) {
103 fields[i] = strsep(&p, " ");
104 if (!fields[i])
105 break;
106 }
107 if (i < 6 || strcmp(fields[4], mountpoint))
108 continue;
109
110 /* the optional fields, up to the "-" separator */
111 ret = CHILD_ALLOWED;
112 while ((opt = strsep(&p, " ")) && strcmp(opt, "-")) {
113 if (!strcmp(opt, "unbindable"))
114 ret = CHILD_OK;
115 }
116 break;
117 }
118 fclose(f);
119 return ret;
120 }
121
run_in_child(int (* fn)(const char * src,const char * dst),const char * src,const char * dst)122 static int run_in_child(int (*fn)(const char *src, const char *dst),
123 const char *src, const char *dst)
124 {
125 int status;
126 pid_t pid;
127
128 pid = fork();
129 if (pid < 0)
130 return -1;
131 if (pid == 0)
132 _exit(fn(src, dst));
133 if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
134 return -1;
135 return WEXITSTATUS(status);
136 }
137
bind_after_clone(const char * src,const char * dst)138 static int bind_after_clone(const char *src, const char *dst)
139 {
140 int ret;
141
142 if (unshare(CLONE_NEWNS))
143 return CHILD_UNSHARE;
144 ret = mount(src, dst, NULL, MS_BIND, NULL);
145 return classify(ret, errno);
146 }
147
rbind_after_clone(const char * src,const char * dst)148 static int rbind_after_clone(const char *src, const char *dst)
149 {
150 int ret;
151
152 if (unshare(CLONE_NEWNS))
153 return CHILD_UNSHARE;
154 ret = mount(src, dst, NULL, MS_BIND | MS_REC, NULL);
155 return classify(ret, errno);
156 }
157
open_tree_after_clone(const char * src,const char * dst)158 static int open_tree_after_clone(const char *src, const char *dst)
159 {
160 int ret;
161
162 if (unshare(CLONE_NEWNS))
163 return CHILD_UNSHARE;
164 ret = sys_open_tree(AT_FDCWD, src, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
165 return classify(ret, errno);
166 }
167
mountinfo_after_clone(const char * src,const char * dst)168 static int mountinfo_after_clone(const char *src, const char *dst)
169 {
170 if (unshare(CLONE_NEWNS))
171 return CHILD_UNSHARE;
172 return mountinfo_unbindable(src);
173 }
174
bind_after_two_clones(const char * src,const char * dst)175 static int bind_after_two_clones(const char *src, const char *dst)
176 {
177 int ret;
178
179 if (unshare(CLONE_NEWNS))
180 return CHILD_UNSHARE;
181 if (unshare(CLONE_NEWNS))
182 return CHILD_UNSHARE;
183 ret = mount(src, dst, NULL, MS_BIND, NULL);
184 return classify(ret, errno);
185 }
186
187 /* The namespace the mount was made unbindable in. */
TEST_F(mntns_unbindable,refuses_bind)188 TEST_F(mntns_unbindable, refuses_bind)
189 {
190 int ret = mount(self->src, self->dst, NULL, MS_BIND, NULL);
191
192 ASSERT_EQ(classify(ret, errno), CHILD_OK);
193 ASSERT_EQ(mountinfo_unbindable(self->src), CHILD_OK);
194 }
195
196 /* A copy of the namespace must not turn the mount bindable. */
TEST_F(mntns_unbindable,refuses_bind_after_clone)197 TEST_F(mntns_unbindable, refuses_bind_after_clone)
198 {
199 ASSERT_EQ(run_in_child(bind_after_clone, self->src, self->dst), CHILD_OK)
200 TH_LOG("bind of an unbindable mount allowed in a copied mount namespace");
201 }
202
TEST_F(mntns_unbindable,refuses_rbind_after_clone)203 TEST_F(mntns_unbindable, refuses_rbind_after_clone)
204 {
205 ASSERT_EQ(run_in_child(rbind_after_clone, self->src, self->dst), CHILD_OK)
206 TH_LOG("rbind of an unbindable mount allowed in a copied mount namespace");
207 }
208
TEST_F(mntns_unbindable,refuses_open_tree_after_clone)209 TEST_F(mntns_unbindable, refuses_open_tree_after_clone)
210 {
211 ASSERT_EQ(run_in_child(open_tree_after_clone, self->src, self->dst), CHILD_OK)
212 TH_LOG("OPEN_TREE_CLONE of an unbindable mount allowed in a copied mount namespace");
213 }
214
TEST_F(mntns_unbindable,mountinfo_after_clone)215 TEST_F(mntns_unbindable, mountinfo_after_clone)
216 {
217 ASSERT_EQ(run_in_child(mountinfo_after_clone, self->src, self->dst), CHILD_OK)
218 TH_LOG("mountinfo does not show the mount as unbindable in a copied mount namespace");
219 }
220
TEST_F(mntns_unbindable,refuses_bind_after_two_clones)221 TEST_F(mntns_unbindable, refuses_bind_after_two_clones)
222 {
223 ASSERT_EQ(run_in_child(bind_after_two_clones, self->src, self->dst), CHILD_OK)
224 TH_LOG("bind of an unbindable mount allowed two mount namespace copies down");
225 }
226
227 TEST_HARNESS_MAIN
228