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