1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * may_decode_fh() refuses a file handle below a mount with locked children 4 * to root in a user namespace. That has to hold while the mount is lazily 5 * unmounted and its children, the locked ones too, are taken off it. 6 */ 7 #define _GNU_SOURCE 8 #include <errno.h> 9 #include <fcntl.h> 10 #include <pthread.h> 11 #include <sched.h> 12 #include <stdatomic.h> 13 #include <stdbool.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 #include <unistd.h> 18 #include <sys/mount.h> 19 #include <sys/prctl.h> 20 #include <sys/stat.h> 21 #include <sys/syscall.h> 22 #include <sys/wait.h> 23 24 #include "../../kselftest_harness.h" 25 26 #ifndef OPEN_TREE_CLONE 27 #define OPEN_TREE_CLONE 1 28 #endif 29 #ifndef OPEN_TREE_CLOEXEC 30 #define OPEN_TREE_CLOEXEC O_CLOEXEC 31 #endif 32 #ifndef AT_RECURSIVE 33 #define AT_RECURSIVE 0x8000 34 #endif 35 36 #define DIR_LEN 64 37 #define PATH_LEN 128 38 39 #define ROUNDS 100 /* lazy umounts raced per test */ 40 #define RACERS 3 /* threads in open_by_handle_at() */ 41 #define EXTRA_MOUNTS 64 /* make umount_tree() hold mount_lock longer */ 42 #define MAX_DELAY_US 3000 /* before the umount */ 43 #define TAIL_US 2000 /* after it */ 44 45 struct handle { 46 struct file_handle fh; 47 unsigned char buf[MAX_HANDLE_SZ]; 48 }; 49 50 /* exit codes of the child */ 51 enum { 52 CHILD_OK, 53 CHILD_SETUP, 54 CHILD_DECODED, /* decoded past a locked child */ 55 CHILD_MOUNTED, /* not refused while mounted */ 56 CHILD_UNMOUNTED, /* not refused once unmounted */ 57 CHILD_ALLOWED, /* refused where nothing is locked */ 58 CHILD_NOUSERNS, /* no user namespace to be had */ 59 }; 60 61 static int write_file(const char *path, const char *s) 62 { 63 ssize_t n = -1; 64 int fd; 65 66 fd = open(path, O_WRONLY | O_CLOEXEC); 67 if (fd >= 0) { 68 n = write(fd, s, strlen(s)); 69 close(fd); 70 } 71 return n == (ssize_t)strlen(s) ? 0 : -1; 72 } 73 74 /* Become root in a new user namespace with a private mount namespace. */ 75 static int enter_userns(void) 76 { 77 uid_t uid = getuid(); 78 gid_t gid = getgid(); 79 char map[32]; 80 81 prctl(PR_SET_DUMPABLE, 1); 82 /* EINVAL: no USER_NS, ENOSPC: user.max_user_namespaces is 0, EPERM: an LSM */ 83 if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) 84 return errno == EINVAL || errno == ENOSPC || errno == EPERM ? 85 CHILD_NOUSERNS : CHILD_SETUP; 86 if (write_file("/proc/self/setgroups", "deny") && errno != ENOENT) 87 return CHILD_SETUP; 88 snprintf(map, sizeof(map), "0 %d 1", uid); 89 if (write_file("/proc/self/uid_map", map)) 90 return CHILD_SETUP; 91 snprintf(map, sizeof(map), "0 %d 1", gid); 92 if (write_file("/proc/self/gid_map", map)) 93 return CHILD_SETUP; 94 if (setgid(0) || setuid(0)) 95 return CHILD_SETUP; 96 if (mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL)) 97 return CHILD_SETUP; 98 return CHILD_OK; 99 } 100 101 static int get_handle(const char *path, struct handle *h) 102 { 103 int mntid; 104 105 h->fh.handle_bytes = MAX_HANDLE_SZ; 106 return name_to_handle_at(AT_FDCWD, path, &h->fh, &mntid, 0); 107 } 108 109 static int decode(int dfd, struct handle *h) 110 { 111 return open_by_handle_at(dfd, &h->fh, O_RDONLY | O_DIRECTORY | O_CLOEXEC); 112 } 113 114 struct race { 115 int dfd; 116 struct handle *h; 117 atomic_int go; 118 atomic_int stop; 119 atomic_int won; /* the first decoded descriptor */ 120 }; 121 122 static void *racer(void *arg) 123 { 124 struct race *r = arg; 125 126 while (!atomic_load(&r->go)) 127 ; 128 while (!atomic_load(&r->stop)) { 129 int none = -1; 130 int fd; 131 132 fd = decode(r->dfd, r->h); 133 if (fd < 0) 134 continue; 135 if (!atomic_compare_exchange_strong(&r->won, &none, fd)) 136 close(fd); 137 } 138 return NULL; 139 } 140 141 /* stop the @n racers started so far and wait for them, they spin on @r */ 142 static void stop_racers(struct race *r, pthread_t *th, int n) 143 { 144 int i; 145 146 atomic_store(&r->stop, 1); 147 atomic_store(&r->go, 1); /* one still waiting for the start sees stop next */ 148 for (i = 0; i < n; i++) 149 pthread_join(th[i], NULL); 150 } 151 152 /* 153 * Bind @h recursively at @w, or take a detached copy of it, and race 154 * open_by_handle_at() of @inner against the lazy umount. The decoded 155 * descriptor, if there was one, is left in @won. 156 */ 157 static int race_round(const char *h, const char *w, struct handle *inner, 158 bool dissolve, int *won) 159 { 160 struct race r = { .h = inner, .won = -1 }; 161 pthread_t th[RACERS]; 162 int treefd = -1, fd, i; 163 164 if (dissolve) { 165 treefd = syscall(__NR_open_tree, AT_FDCWD, h, 166 OPEN_TREE_CLONE | AT_RECURSIVE | OPEN_TREE_CLOEXEC); 167 if (treefd < 0) 168 return CHILD_SETUP; 169 /* an ordinary descriptor on the copy, treefd's close dissolves it */ 170 r.dfd = openat(treefd, ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 171 } else { 172 if (mount(h, w, NULL, MS_BIND | MS_REC, NULL)) 173 return CHILD_SETUP; 174 r.dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC); 175 } 176 if (r.dfd < 0) 177 return CHILD_SETUP; 178 179 fd = decode(r.dfd, inner); 180 if (fd >= 0 || errno != EPERM) { 181 if (fd >= 0) 182 close(fd); 183 return CHILD_MOUNTED; 184 } 185 186 for (i = 0; i < RACERS; i++) { 187 if (pthread_create(&th[i], NULL, racer, &r)) { 188 stop_racers(&r, th, i); 189 return CHILD_SETUP; 190 } 191 } 192 atomic_store(&r.go, 1); 193 usleep(rand() % MAX_DELAY_US); 194 if (dissolve) 195 close(treefd); 196 else if (umount2(w, MNT_DETACH)) { 197 stop_racers(&r, th, RACERS); 198 return CHILD_SETUP; 199 } 200 usleep(TAIL_US); 201 stop_racers(&r, th, RACERS); 202 203 fd = decode(r.dfd, inner); 204 if (fd >= 0) { 205 close(fd); 206 return CHILD_UNMOUNTED; 207 } 208 close(r.dfd); 209 *won = atomic_load(&r.won); 210 return CHILD_OK; 211 } 212 213 static int race_child(const char *h, const char *w, struct handle *inner, 214 bool dissolve) 215 { 216 int ret, won, i; 217 218 srand(getpid()); 219 ret = enter_userns(); 220 if (ret) 221 return ret; 222 for (i = 0; i < ROUNDS; i++) { 223 ret = race_round(h, w, inner, dissolve, &won); 224 if (ret) 225 return ret; 226 if (won >= 0) 227 return CHILD_DECODED; 228 } 229 return CHILD_OK; 230 } 231 232 /* a bind without locked children decodes while mounted and not after */ 233 static int allowed_child(const char *plain, const char *w, struct handle *h) 234 { 235 int dfd, fd, ret; 236 237 ret = enter_userns(); 238 if (ret) 239 return ret; 240 if (mount(plain, w, NULL, MS_BIND | MS_REC, NULL)) 241 return CHILD_SETUP; 242 dfd = open(w, O_RDONLY | O_DIRECTORY | O_CLOEXEC); 243 if (dfd < 0) 244 return CHILD_SETUP; 245 fd = decode(dfd, h); 246 if (fd < 0) 247 return CHILD_ALLOWED; 248 close(fd); 249 if (umount2(w, MNT_DETACH)) 250 return CHILD_SETUP; 251 fd = decode(dfd, h); 252 if (fd >= 0) { 253 close(fd); 254 return CHILD_UNMOUNTED; 255 } 256 return errno == EPERM ? CHILD_OK : CHILD_UNMOUNTED; 257 } 258 259 FIXTURE(locked_handle) { 260 char base[DIR_LEN]; 261 char h[PATH_LEN]; /* the tree with the covered directory */ 262 char plain[PATH_LEN]; /* a tree with no mount in it */ 263 char w[PATH_LEN]; /* where the child binds either */ 264 struct handle inner; /* h/top/secret/inner, covered */ 265 struct handle uncovered; /* plain/inner */ 266 }; 267 268 FIXTURE_SETUP(locked_handle) 269 { 270 char p[PATH_LEN], e[PATH_LEN]; 271 int i; 272 273 if (geteuid()) 274 SKIP(return, "test requires root"); 275 276 snprintf(self->base, sizeof(self->base), "/tmp/locked_handle.XXXXXX"); 277 ASSERT_NE(mkdtemp(self->base), NULL); 278 ASSERT_EQ(unshare(CLONE_NEWNS), 0); 279 ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); 280 ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0); 281 282 snprintf(self->h, sizeof(self->h), "%s/h", self->base); 283 snprintf(self->plain, sizeof(self->plain), "%s/plain", self->base); 284 snprintf(self->w, sizeof(self->w), "%s/w", self->base); 285 ASSERT_EQ(mkdir(self->h, 0755), 0); 286 ASSERT_EQ(mkdir(self->plain, 0755), 0); 287 ASSERT_EQ(mkdir(self->w, 0755), 0); 288 snprintf(p, sizeof(p), "%s/plain/inner", self->base); 289 ASSERT_EQ(mkdir(p, 0755), 0); 290 ASSERT_EQ(get_handle(p, &self->uncovered), 0); 291 292 /* the handle is for this filesystem */ 293 ASSERT_EQ(mount("tmpfs", self->h, "tmpfs", 0, NULL), 0); 294 snprintf(p, sizeof(p), "%s/h/top", self->base); 295 ASSERT_EQ(mkdir(p, 0755), 0); 296 snprintf(p, sizeof(p), "%s/h/top/secret", self->base); 297 ASSERT_EQ(mkdir(p, 0755), 0); 298 snprintf(p, sizeof(p), "%s/h/top/secret/inner", self->base); 299 ASSERT_EQ(mkdir(p, 0755), 0); 300 ASSERT_EQ(get_handle(p, &self->inner), 0); 301 snprintf(e, sizeof(e), "%s/h/empty", self->base); 302 ASSERT_EQ(mkdir(e, 0755), 0); 303 304 /* cover it, and some more so the umount takes longer */ 305 snprintf(p, sizeof(p), "%s/h/top/secret", self->base); 306 ASSERT_EQ(mount("tmpfs", p, "tmpfs", 0, NULL), 0); 307 for (i = 0; i < EXTRA_MOUNTS; i++) { 308 snprintf(p, sizeof(p), "%s/h/c%d", self->base, i); 309 ASSERT_EQ(mkdir(p, 0755), 0); 310 ASSERT_EQ(mount(e, p, NULL, MS_BIND, NULL), 0); 311 } 312 } 313 314 FIXTURE_TEARDOWN(locked_handle) 315 { 316 umount2(self->base, MNT_DETACH); 317 rmdir(self->base); 318 } 319 320 static int wait_child(pid_t pid) 321 { 322 int status; 323 324 if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) 325 return -1; 326 return WEXITSTATUS(status); 327 } 328 329 TEST_F(locked_handle, lazy_umount) 330 { 331 pid_t pid; 332 int ret; 333 334 pid = fork(); 335 ASSERT_GE(pid, 0); 336 if (pid == 0) 337 _exit(race_child(self->h, self->w, &self->inner, false)); 338 ret = wait_child(pid); 339 TH_LOG("child exit code %d", ret); 340 if (ret == CHILD_NOUSERNS) 341 SKIP(return, "no user namespaces"); 342 EXPECT_EQ(ret, CHILD_OK); 343 } 344 345 TEST_F(locked_handle, dissolve) 346 { 347 pid_t pid; 348 int ret; 349 350 pid = fork(); 351 ASSERT_GE(pid, 0); 352 if (pid == 0) 353 _exit(race_child(self->h, self->w, &self->inner, true)); 354 ret = wait_child(pid); 355 TH_LOG("child exit code %d", ret); 356 if (ret == CHILD_NOUSERNS) 357 SKIP(return, "no user namespaces"); 358 EXPECT_EQ(ret, CHILD_OK); 359 } 360 361 TEST_F(locked_handle, allowed_use) 362 { 363 pid_t pid; 364 int ret; 365 366 pid = fork(); 367 ASSERT_GE(pid, 0); 368 if (pid == 0) 369 _exit(allowed_child(self->plain, self->w, &self->uncovered)); 370 ret = wait_child(pid); 371 TH_LOG("child exit code %d", ret); 372 if (ret == CHILD_NOUSERNS) 373 SKIP(return, "no user namespaces"); 374 EXPECT_EQ(ret, CHILD_OK); 375 } 376 377 TEST_HARNESS_MAIN 378