xref: /linux/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c (revision 2a31b5378c6db9c4a8d399e5d10d32cd4b732bef)
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 
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 
41 FIXTURE(mntns_unbindable)
42 {
43 	char base[64];
44 	char src[80];
45 	char dst[80];
46 	bool mounted;
47 };
48 
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 
73 FIXTURE_TEARDOWN(mntns_unbindable)
74 {
75 	if (self->mounted)
76 		umount2(self->base, MNT_DETACH);
77 	rmdir(self->base);
78 }
79 
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? */
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 
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 
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 
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 
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 
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 
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. */
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. */
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 
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 
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 
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 
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