1 // SPDX-License-Identifier: GPL-2.0 2 #define _GNU_SOURCE 3 #include <sched.h> 4 #include <stdio.h> 5 #include <errno.h> 6 #include <string.h> 7 #include <sys/stat.h> 8 #include <sys/types.h> 9 #include <sys/mount.h> 10 #include <sys/wait.h> 11 #include <stdlib.h> 12 #include <unistd.h> 13 #include <fcntl.h> 14 #include <stdbool.h> 15 #include <stdarg.h> 16 #include <sys/syscall.h> 17 18 #include "kselftest_harness.h" 19 20 #ifndef CLONE_NEWNS 21 #define CLONE_NEWNS 0x00020000 22 #endif 23 24 #ifndef CLONE_NEWUSER 25 #define CLONE_NEWUSER 0x10000000 26 #endif 27 28 #ifndef MS_SHARED 29 #define MS_SHARED (1 << 20) 30 #endif 31 32 #ifndef MS_PRIVATE 33 #define MS_PRIVATE (1<<18) 34 #endif 35 36 #ifndef MOVE_MOUNT_SET_GROUP 37 #define MOVE_MOUNT_SET_GROUP 0x00000100 38 #endif 39 40 #ifndef MOVE_MOUNT_F_EMPTY_PATH 41 #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 42 #endif 43 44 #ifndef MOVE_MOUNT_T_EMPTY_PATH 45 #define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 46 #endif 47 48 static ssize_t write_nointr(int fd, const void *buf, size_t count) 49 { 50 ssize_t ret; 51 52 do { 53 ret = write(fd, buf, count); 54 } while (ret < 0 && errno == EINTR); 55 56 return ret; 57 } 58 59 static int write_file(const char *path, const void *buf, size_t count) 60 { 61 int fd; 62 ssize_t ret; 63 64 fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW); 65 if (fd < 0) 66 return -1; 67 68 ret = write_nointr(fd, buf, count); 69 close(fd); 70 if (ret < 0 || (size_t)ret != count) 71 return -1; 72 73 return 0; 74 } 75 76 static int create_and_enter_userns(void) 77 { 78 uid_t uid; 79 gid_t gid; 80 char map[100]; 81 82 uid = getuid(); 83 gid = getgid(); 84 85 if (unshare(CLONE_NEWUSER)) 86 return -1; 87 88 if (write_file("/proc/self/setgroups", "deny", sizeof("deny") - 1) && 89 errno != ENOENT) 90 return -1; 91 92 snprintf(map, sizeof(map), "0 %d 1", uid); 93 if (write_file("/proc/self/uid_map", map, strlen(map))) 94 return -1; 95 96 97 snprintf(map, sizeof(map), "0 %d 1", gid); 98 if (write_file("/proc/self/gid_map", map, strlen(map))) 99 return -1; 100 101 if (setgid(0)) 102 return -1; 103 104 if (setuid(0)) 105 return -1; 106 107 return 0; 108 } 109 110 static int prepare_unpriv_mountns(void) 111 { 112 if (create_and_enter_userns()) 113 return -1; 114 115 if (unshare(CLONE_NEWNS)) 116 return -1; 117 118 if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0)) 119 return -1; 120 121 return 0; 122 } 123 124 static char *get_field(char *src, int nfields) 125 { 126 int i; 127 char *p = src; 128 129 for (i = 0; i < nfields; i++) { 130 while (*p && *p != ' ' && *p != '\t') 131 p++; 132 133 if (!*p) 134 break; 135 136 p++; 137 } 138 139 return p; 140 } 141 142 static void null_endofword(char *word) 143 { 144 while (*word && *word != ' ' && *word != '\t') 145 word++; 146 *word = '\0'; 147 } 148 149 /* Does the mount on @path carry the optional field @field in mountinfo? */ 150 static bool mount_has_field(const char *path, const char *field) 151 { 152 size_t len = 0; 153 char *line = NULL; 154 FILE *f = NULL; 155 bool found = false; 156 157 f = fopen("/proc/self/mountinfo", "re"); 158 if (!f) 159 return false; 160 161 while (!found && getline(&line, &len, f) != -1) { 162 char *opts, *target; 163 164 target = get_field(line, 4); 165 if (!target) 166 continue; 167 168 opts = get_field(target, 2); 169 if (!opts) 170 continue; 171 172 null_endofword(target); 173 174 if (strcmp(target, path) != 0) 175 continue; 176 177 /* the optional fields end at the "-" separator */ 178 while (opts && *opts != '-') { 179 char *next = strchr(opts, ' '); 180 181 if (next) 182 *next++ = '\0'; 183 if (!strncmp(opts, field, strlen(field))) { 184 found = true; 185 break; 186 } 187 opts = next; 188 } 189 } 190 191 free(line); 192 fclose(f); 193 194 return found; 195 } 196 197 static bool is_shared_mount(const char *path) 198 { 199 return mount_has_field(path, "shared:"); 200 } 201 202 /* Attempt to de-conflict with the selftests tree. */ 203 #ifndef SKIP 204 #define SKIP(s, ...) XFAIL(s, ##__VA_ARGS__) 205 #endif 206 207 #define SET_GROUP_FROM "/tmp/move_mount_set_group_supported_from" 208 #define SET_GROUP_TO "/tmp/move_mount_set_group_supported_to" 209 210 static bool move_mount_set_group_supported(void) 211 { 212 int ret; 213 214 if (mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV, 215 "size=100000,mode=700")) 216 return -1; 217 218 if (mount(NULL, "/tmp", NULL, MS_PRIVATE, 0)) 219 return -1; 220 221 if (mkdir(SET_GROUP_FROM, 0777)) 222 return -1; 223 224 if (mkdir(SET_GROUP_TO, 0777)) 225 return -1; 226 227 if (mount("testing", SET_GROUP_FROM, "tmpfs", MS_NOATIME | MS_NODEV, 228 "size=100000,mode=700")) 229 return -1; 230 231 if (mount(SET_GROUP_FROM, SET_GROUP_TO, NULL, MS_BIND, NULL)) 232 return -1; 233 234 if (mount(NULL, SET_GROUP_FROM, NULL, MS_SHARED, 0)) 235 return -1; 236 237 ret = syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_FROM, 238 AT_FDCWD, SET_GROUP_TO, MOVE_MOUNT_SET_GROUP); 239 umount2("/tmp", MNT_DETACH); 240 241 return ret >= 0; 242 } 243 244 FIXTURE(move_mount_set_group) { 245 }; 246 247 #define SET_GROUP_A "/tmp/A" 248 249 FIXTURE_SETUP(move_mount_set_group) 250 { 251 bool ret; 252 253 ASSERT_EQ(prepare_unpriv_mountns(), 0); 254 255 ret = move_mount_set_group_supported(); 256 ASSERT_GE(ret, 0); 257 if (!ret) 258 SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported"); 259 260 umount2("/tmp", MNT_DETACH); 261 262 ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV, 263 "size=100000,mode=700"), 0); 264 265 ASSERT_EQ(mkdir(SET_GROUP_A, 0777), 0); 266 267 ASSERT_EQ(mount("testing", SET_GROUP_A, "tmpfs", MS_NOATIME | MS_NODEV, 268 "size=100000,mode=700"), 0); 269 } 270 271 FIXTURE_TEARDOWN(move_mount_set_group) 272 { 273 bool ret; 274 275 ret = move_mount_set_group_supported(); 276 ASSERT_GE(ret, 0); 277 if (!ret) 278 SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported"); 279 280 umount2("/tmp", MNT_DETACH); 281 } 282 283 #define __STACK_SIZE (8 * 1024 * 1024) 284 static pid_t do_clone(int (*fn)(void *), void *arg, int flags) 285 { 286 void *stack; 287 288 stack = malloc(__STACK_SIZE); 289 if (!stack) 290 return -ENOMEM; 291 292 #ifdef __ia64__ 293 return __clone2(fn, stack, __STACK_SIZE, flags | SIGCHLD, arg, NULL); 294 #else 295 return clone(fn, stack + __STACK_SIZE, flags | SIGCHLD, arg, NULL); 296 #endif 297 } 298 299 static int wait_for_pid(pid_t pid) 300 { 301 int status, ret; 302 303 again: 304 ret = waitpid(pid, &status, 0); 305 if (ret == -1) { 306 if (errno == EINTR) 307 goto again; 308 309 return -1; 310 } 311 312 if (!WIFEXITED(status)) 313 return -1; 314 315 return WEXITSTATUS(status); 316 } 317 318 struct child_args { 319 int unsfd; 320 int mntnsfd; 321 bool shared; 322 int mntfd; 323 }; 324 325 static int get_nestedns_mount_cb(void *data) 326 { 327 struct child_args *ca = (struct child_args *)data; 328 int ret; 329 330 ret = prepare_unpriv_mountns(); 331 if (ret) 332 return 1; 333 334 if (ca->shared) { 335 ret = mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0); 336 if (ret) 337 return 1; 338 } 339 340 ret = open("/proc/self/ns/user", O_RDONLY); 341 if (ret < 0) 342 return 1; 343 ca->unsfd = ret; 344 345 ret = open("/proc/self/ns/mnt", O_RDONLY); 346 if (ret < 0) 347 return 1; 348 ca->mntnsfd = ret; 349 350 ret = open(SET_GROUP_A, O_RDONLY); 351 if (ret < 0) 352 return 1; 353 ca->mntfd = ret; 354 355 return 0; 356 } 357 358 TEST_F(move_mount_set_group, complex_sharing_copying) 359 { 360 struct child_args ca_from = { 361 .shared = true, 362 }; 363 struct child_args ca_to = { 364 .shared = false, 365 }; 366 pid_t pid; 367 bool ret; 368 369 ret = move_mount_set_group_supported(); 370 ASSERT_GE(ret, 0); 371 if (!ret) 372 SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported"); 373 374 pid = do_clone(get_nestedns_mount_cb, (void *)&ca_from, CLONE_VFORK | 375 CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0); 376 ASSERT_EQ(wait_for_pid(pid), 0); 377 378 pid = do_clone(get_nestedns_mount_cb, (void *)&ca_to, CLONE_VFORK | 379 CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0); 380 ASSERT_EQ(wait_for_pid(pid), 0); 381 382 ASSERT_EQ(syscall(__NR_move_mount, ca_from.mntfd, "", 383 ca_to.mntfd, "", MOVE_MOUNT_SET_GROUP 384 | MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 385 0); 386 387 ASSERT_EQ(setns(ca_to.mntnsfd, CLONE_NEWNS), 0); 388 ASSERT_EQ(is_shared_mount(SET_GROUP_A), 1); 389 } 390 391 #define SET_GROUP_B "/tmp/B" 392 #define SET_GROUP_C "/tmp/C" 393 394 /* 395 * An unbindable mount is neither shared nor a slave, so it must not be 396 * accepted as the target: with a slave source it would end up unbindable 397 * and a slave at the same time. 398 */ 399 TEST_F(move_mount_set_group, unbindable_target) 400 { 401 bool ret; 402 403 ret = move_mount_set_group_supported(); 404 ASSERT_GE(ret, 0); 405 if (!ret) 406 SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported"); 407 408 ASSERT_EQ(mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0), 0); 409 410 /* B: a slave of A's peer group */ 411 ASSERT_EQ(mkdir(SET_GROUP_B, 0777), 0); 412 ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_B, NULL, MS_BIND, NULL), 0); 413 ASSERT_EQ(mount(NULL, SET_GROUP_B, NULL, MS_SLAVE, 0), 0); 414 ASSERT_TRUE(mount_has_field(SET_GROUP_B, "master:")); 415 416 /* C: unbindable */ 417 ASSERT_EQ(mkdir(SET_GROUP_C, 0777), 0); 418 ASSERT_EQ(mount(SET_GROUP_A, SET_GROUP_C, NULL, MS_BIND, NULL), 0); 419 ASSERT_EQ(mount(NULL, SET_GROUP_C, NULL, MS_UNBINDABLE, 0), 0); 420 ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable")); 421 422 /* from a slave */ 423 ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_B, 424 AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1); 425 ASSERT_EQ(errno, EINVAL); 426 ASSERT_FALSE(mount_has_field(SET_GROUP_C, "master:")); 427 ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable")); 428 429 /* from a shared mount */ 430 ASSERT_EQ(syscall(__NR_move_mount, AT_FDCWD, SET_GROUP_A, 431 AT_FDCWD, SET_GROUP_C, MOVE_MOUNT_SET_GROUP), -1); 432 ASSERT_EQ(errno, EINVAL); 433 ASSERT_FALSE(mount_has_field(SET_GROUP_C, "shared:")); 434 ASSERT_TRUE(mount_has_field(SET_GROUP_C, "unbindable")); 435 } 436 437 TEST_HARNESS_MAIN 438