xref: /linux/tools/testing/selftests/filesystems/mntns_unbindable/mntns_unbindable_test.c (revision 8e8cd9a4c6dc006e9bd51b8ad0079fb4576b48d3)
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