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