Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: add a test for the foreign mnt ns extensions

This tests both statmount and listmount to make sure they work with the
extensions that allow us to specify a mount ns to enter in order to find
the mount entries.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Link: https://lore.kernel.org/r/2d1a35bc9ab94b4656c056c420f25e429e7eb0b1.1719243756.git.josef@toxicpanda.com
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

Josef Bacik and committed by
Christian Brauner
d896f71c e8e43a1f

+424 -41
+1 -1
tools/testing/selftests/filesystems/statmount/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0-or-later 2 2 3 3 CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) 4 - TEST_GEN_PROGS := statmount_test 4 + TEST_GEN_PROGS := statmount_test statmount_test_ns 5 5 6 6 include ../../lib.mk
+46
tools/testing/selftests/filesystems/statmount/statmount.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + 3 + #ifndef __STATMOUNT_H 4 + #define __STATMOUNT_H 5 + 6 + #include <stdint.h> 7 + #include <linux/mount.h> 8 + #include <asm/unistd.h> 9 + 10 + static inline int statmount(uint64_t mnt_id, uint64_t mnt_ns_id, uint64_t mask, 11 + struct statmount *buf, size_t bufsize, 12 + unsigned int flags) 13 + { 14 + struct mnt_id_req req = { 15 + .size = MNT_ID_REQ_SIZE_VER0, 16 + .mnt_id = mnt_id, 17 + .param = mask, 18 + }; 19 + 20 + if (mnt_ns_id) { 21 + req.size = MNT_ID_REQ_SIZE_VER1; 22 + req.mnt_ns_id = mnt_ns_id; 23 + } 24 + 25 + return syscall(__NR_statmount, &req, buf, bufsize, flags); 26 + } 27 + 28 + static ssize_t listmount(uint64_t mnt_id, uint64_t mnt_ns_id, 29 + uint64_t last_mnt_id, uint64_t list[], size_t num, 30 + unsigned int flags) 31 + { 32 + struct mnt_id_req req = { 33 + .size = MNT_ID_REQ_SIZE_VER0, 34 + .mnt_id = mnt_id, 35 + .param = last_mnt_id, 36 + }; 37 + 38 + if (mnt_ns_id) { 39 + req.size = MNT_ID_REQ_SIZE_VER1; 40 + req.mnt_ns_id = mnt_ns_id; 41 + } 42 + 43 + return syscall(__NR_listmount, &req, list, num, flags); 44 + } 45 + 46 + #endif /* __STATMOUNT_H */
+13 -40
tools/testing/selftests/filesystems/statmount/statmount_test.c
··· 4 4 5 5 #include <assert.h> 6 6 #include <stddef.h> 7 - #include <stdint.h> 8 7 #include <sched.h> 9 8 #include <fcntl.h> 10 9 #include <sys/param.h> 11 10 #include <sys/mount.h> 12 11 #include <sys/stat.h> 13 12 #include <sys/statfs.h> 14 - #include <linux/mount.h> 15 13 #include <linux/stat.h> 16 - #include <asm/unistd.h> 17 14 15 + #include "statmount.h" 18 16 #include "../../kselftest.h" 19 17 20 18 static const char *const known_fs[] = { ··· 34 36 "ufs", "v7", "vboxsf", "vfat", "virtiofs", "vxfs", "xenfs", "xfs", 35 37 "zonefs", NULL }; 36 38 37 - static int statmount(uint64_t mnt_id, uint64_t mask, struct statmount *buf, 38 - size_t bufsize, unsigned int flags) 39 - { 40 - struct mnt_id_req req = { 41 - .size = MNT_ID_REQ_SIZE_VER0, 42 - .mnt_id = mnt_id, 43 - .param = mask, 44 - }; 45 - 46 - return syscall(__NR_statmount, &req, buf, bufsize, flags); 47 - } 48 - 49 39 static struct statmount *statmount_alloc(uint64_t mnt_id, uint64_t mask, unsigned int flags) 50 40 { 51 41 size_t bufsize = 1 << 15; ··· 42 56 int ret; 43 57 44 58 for (;;) { 45 - ret = statmount(mnt_id, mask, tmp, bufsize, flags); 59 + ret = statmount(mnt_id, 0, mask, tmp, bufsize, flags); 46 60 if (ret != -1) 47 61 break; 48 62 if (tofree) ··· 108 122 static uint64_t root_id, parent_id; 109 123 static uint32_t old_root_id, old_parent_id; 110 124 111 - 112 125 static void cleanup_namespace(void) 113 126 { 114 127 fchdir(orig_root); ··· 123 138 uid_t uid = getuid(); 124 139 gid_t gid = getgid(); 125 140 126 - ret = unshare(CLONE_NEWNS|CLONE_NEWUSER); 141 + ret = unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWPID); 127 142 if (ret == -1) 128 143 ksft_exit_fail_msg("unsharing mountns and userns: %s\n", 129 144 strerror(errno)); ··· 193 208 return 0; 194 209 } 195 210 196 - static ssize_t listmount(uint64_t mnt_id, uint64_t last_mnt_id, 197 - uint64_t list[], size_t num, unsigned int flags) 198 - { 199 - struct mnt_id_req req = { 200 - .size = MNT_ID_REQ_SIZE_VER0, 201 - .mnt_id = mnt_id, 202 - .param = last_mnt_id, 203 - }; 204 - 205 - return syscall(__NR_listmount, &req, list, num, flags); 206 - } 207 - 208 211 static void test_listmount_empty_root(void) 209 212 { 210 213 ssize_t res; 211 214 const unsigned int size = 32; 212 215 uint64_t list[size]; 213 216 214 - res = listmount(LSMT_ROOT, 0, list, size, 0); 217 + res = listmount(LSMT_ROOT, 0, 0, list, size, 0); 215 218 if (res == -1) { 216 219 ksft_test_result_fail("listmount: %s\n", strerror(errno)); 217 220 return; ··· 224 251 struct statmount sm; 225 252 int ret; 226 253 227 - ret = statmount(root_id, 0, &sm, sizeof(sm), 0); 254 + ret = statmount(root_id, 0, 0, &sm, sizeof(sm), 0); 228 255 if (ret == -1) { 229 256 ksft_test_result_fail("statmount zero mask: %s\n", 230 257 strerror(errno)); ··· 250 277 int ret; 251 278 uint64_t mask = STATMOUNT_MNT_BASIC; 252 279 253 - ret = statmount(root_id, mask, &sm, sizeof(sm), 0); 280 + ret = statmount(root_id, 0, mask, &sm, sizeof(sm), 0); 254 281 if (ret == -1) { 255 282 ksft_test_result_fail("statmount mnt basic: %s\n", 256 283 strerror(errno)); ··· 310 337 struct statx sx; 311 338 struct statfs sf; 312 339 313 - ret = statmount(root_id, mask, &sm, sizeof(sm), 0); 340 + ret = statmount(root_id, 0, mask, &sm, sizeof(sm), 0); 314 341 if (ret == -1) { 315 342 ksft_test_result_fail("statmount sb basic: %s\n", 316 343 strerror(errno)); ··· 471 498 exactsize = sm->size; 472 499 shortsize = sizeof(*sm) + i; 473 500 474 - ret = statmount(root_id, mask, sm, exactsize, 0); 501 + ret = statmount(root_id, 0, mask, sm, exactsize, 0); 475 502 if (ret == -1) { 476 503 ksft_test_result_fail("statmount exact size: %s\n", 477 504 strerror(errno)); 478 505 goto out; 479 506 } 480 507 errno = 0; 481 - ret = statmount(root_id, mask, sm, shortsize, 0); 508 + ret = statmount(root_id, 0, mask, sm, shortsize, 0); 482 509 if (ret != -1 || errno != EOVERFLOW) { 483 510 ksft_test_result_fail("should have failed with EOVERFLOW: %s\n", 484 511 strerror(errno)); ··· 506 533 if (res == -1) 507 534 return; 508 535 509 - num = res = listmount(LSMT_ROOT, 0, list, size, 0); 536 + num = res = listmount(LSMT_ROOT, 0, 0, list, size, 0); 510 537 if (res == -1) { 511 538 ksft_test_result_fail("listmount: %s\n", strerror(errno)); 512 539 return; ··· 518 545 } 519 546 520 547 for (i = 0; i < size - step;) { 521 - res = listmount(LSMT_ROOT, i ? list2[i - 1] : 0, list2 + i, step, 0); 548 + res = listmount(LSMT_ROOT, 0, i ? list2[i - 1] : 0, list2 + i, step, 0); 522 549 if (res == -1) 523 550 ksft_test_result_fail("short listmount: %s\n", 524 551 strerror(errno)); ··· 550 577 int ret; 551 578 uint64_t all_mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC | 552 579 STATMOUNT_PROPAGATE_FROM | STATMOUNT_MNT_ROOT | 553 - STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE; 580 + STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE | STATMOUNT_MNT_NS_ID; 554 581 555 582 ksft_print_header(); 556 583 557 - ret = statmount(0, 0, NULL, 0, 0); 584 + ret = statmount(0, 0, 0, NULL, 0, 0); 558 585 assert(ret == -1); 559 586 if (errno == ENOSYS) 560 587 ksft_exit_skip("statmount() syscall not supported\n");
+364
tools/testing/selftests/filesystems/statmount/statmount_test_ns.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + 3 + #define _GNU_SOURCE 4 + 5 + #include <assert.h> 6 + #include <fcntl.h> 7 + #include <limits.h> 8 + #include <sched.h> 9 + #include <stdlib.h> 10 + #include <sys/mount.h> 11 + #include <sys/stat.h> 12 + #include <sys/wait.h> 13 + #include <linux/nsfs.h> 14 + #include <linux/stat.h> 15 + 16 + #include "statmount.h" 17 + #include "../../kselftest.h" 18 + 19 + #define NSID_PASS 0 20 + #define NSID_FAIL 1 21 + #define NSID_SKIP 2 22 + #define NSID_ERROR 3 23 + 24 + static void handle_result(int ret, const char *testname) 25 + { 26 + if (ret == NSID_PASS) 27 + ksft_test_result_pass("%s\n", testname); 28 + else if (ret == NSID_FAIL) 29 + ksft_test_result_fail("%s\n", testname); 30 + else if (ret == NSID_ERROR) 31 + ksft_exit_fail_msg("%s\n", testname); 32 + else 33 + ksft_test_result_skip("%s\n", testname); 34 + } 35 + 36 + static inline int wait_for_pid(pid_t pid) 37 + { 38 + int status, ret; 39 + 40 + again: 41 + ret = waitpid(pid, &status, 0); 42 + if (ret == -1) { 43 + if (errno == EINTR) 44 + goto again; 45 + 46 + ksft_print_msg("waitpid returned -1, errno=%d\n", errno); 47 + return -1; 48 + } 49 + 50 + if (!WIFEXITED(status)) { 51 + ksft_print_msg( 52 + "waitpid !WIFEXITED, WIFSIGNALED=%d, WTERMSIG=%d\n", 53 + WIFSIGNALED(status), WTERMSIG(status)); 54 + return -1; 55 + } 56 + 57 + ret = WEXITSTATUS(status); 58 + return ret; 59 + } 60 + 61 + static int get_mnt_ns_id(const char *mnt_ns, uint64_t *mnt_ns_id) 62 + { 63 + int fd = open(mnt_ns, O_RDONLY); 64 + 65 + if (fd < 0) { 66 + ksft_print_msg("failed to open for ns %s: %s\n", 67 + mnt_ns, strerror(errno)); 68 + sleep(60); 69 + return NSID_ERROR; 70 + } 71 + 72 + if (ioctl(fd, NS_GET_MNTNS_ID, mnt_ns_id) < 0) { 73 + ksft_print_msg("failed to get the nsid for ns %s: %s\n", 74 + mnt_ns, strerror(errno)); 75 + return NSID_ERROR; 76 + } 77 + close(fd); 78 + return NSID_PASS; 79 + } 80 + 81 + static int get_mnt_id(const char *path, uint64_t *mnt_id) 82 + { 83 + struct statx sx; 84 + int ret; 85 + 86 + ret = statx(AT_FDCWD, path, 0, STATX_MNT_ID_UNIQUE, &sx); 87 + if (ret == -1) { 88 + ksft_print_msg("retrieving unique mount ID for %s: %s\n", path, 89 + strerror(errno)); 90 + return NSID_ERROR; 91 + } 92 + 93 + if (!(sx.stx_mask & STATX_MNT_ID_UNIQUE)) { 94 + ksft_print_msg("no unique mount ID available for %s\n", path); 95 + return NSID_ERROR; 96 + } 97 + 98 + *mnt_id = sx.stx_mnt_id; 99 + return NSID_PASS; 100 + } 101 + 102 + static int write_file(const char *path, const char *val) 103 + { 104 + int fd = open(path, O_WRONLY); 105 + size_t len = strlen(val); 106 + int ret; 107 + 108 + if (fd == -1) { 109 + ksft_print_msg("opening %s for write: %s\n", path, strerror(errno)); 110 + return NSID_ERROR; 111 + } 112 + 113 + ret = write(fd, val, len); 114 + if (ret == -1) { 115 + ksft_print_msg("writing to %s: %s\n", path, strerror(errno)); 116 + return NSID_ERROR; 117 + } 118 + if (ret != len) { 119 + ksft_print_msg("short write to %s\n", path); 120 + return NSID_ERROR; 121 + } 122 + 123 + ret = close(fd); 124 + if (ret == -1) { 125 + ksft_print_msg("closing %s\n", path); 126 + return NSID_ERROR; 127 + } 128 + 129 + return NSID_PASS; 130 + } 131 + 132 + static int setup_namespace(void) 133 + { 134 + int ret; 135 + char buf[32]; 136 + uid_t uid = getuid(); 137 + gid_t gid = getgid(); 138 + 139 + ret = unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWPID); 140 + if (ret == -1) 141 + ksft_exit_fail_msg("unsharing mountns and userns: %s\n", 142 + strerror(errno)); 143 + 144 + sprintf(buf, "0 %d 1", uid); 145 + ret = write_file("/proc/self/uid_map", buf); 146 + if (ret != NSID_PASS) 147 + return ret; 148 + ret = write_file("/proc/self/setgroups", "deny"); 149 + if (ret != NSID_PASS) 150 + return ret; 151 + sprintf(buf, "0 %d 1", gid); 152 + ret = write_file("/proc/self/gid_map", buf); 153 + if (ret != NSID_PASS) 154 + return ret; 155 + 156 + ret = mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL); 157 + if (ret == -1) { 158 + ksft_print_msg("making mount tree private: %s\n", 159 + strerror(errno)); 160 + return NSID_ERROR; 161 + } 162 + 163 + return NSID_PASS; 164 + } 165 + 166 + static int _test_statmount_mnt_ns_id(void) 167 + { 168 + struct statmount sm; 169 + uint64_t mnt_ns_id; 170 + uint64_t root_id; 171 + int ret; 172 + 173 + ret = get_mnt_ns_id("/proc/self/ns/mnt", &mnt_ns_id); 174 + if (ret != NSID_PASS) 175 + return ret; 176 + 177 + ret = get_mnt_id("/", &root_id); 178 + if (ret != NSID_PASS) 179 + return ret; 180 + 181 + ret = statmount(root_id, 0, STATMOUNT_MNT_NS_ID, &sm, sizeof(sm), 0); 182 + if (ret == -1) { 183 + ksft_print_msg("statmount mnt ns id: %s\n", strerror(errno)); 184 + return NSID_ERROR; 185 + } 186 + 187 + if (sm.size != sizeof(sm)) { 188 + ksft_print_msg("unexpected size: %u != %u\n", sm.size, 189 + (uint32_t)sizeof(sm)); 190 + return NSID_FAIL; 191 + } 192 + if (sm.mask != STATMOUNT_MNT_NS_ID) { 193 + ksft_print_msg("statmount mnt ns id unavailable\n"); 194 + return NSID_SKIP; 195 + } 196 + 197 + if (sm.mnt_ns_id != mnt_ns_id) { 198 + ksft_print_msg("unexpected mnt ns ID: 0x%llx != 0x%llx\n", 199 + (unsigned long long)sm.mnt_ns_id, 200 + (unsigned long long)mnt_ns_id); 201 + return NSID_FAIL; 202 + } 203 + 204 + return NSID_PASS; 205 + } 206 + 207 + static void test_statmount_mnt_ns_id(void) 208 + { 209 + pid_t pid; 210 + int ret; 211 + 212 + pid = fork(); 213 + if (pid < 0) 214 + ksft_exit_fail_msg("failed to fork: %s\n", strerror(errno)); 215 + 216 + /* We're the original pid, wait for the result. */ 217 + if (pid != 0) { 218 + ret = wait_for_pid(pid); 219 + handle_result(ret, "test statmount ns id"); 220 + return; 221 + } 222 + 223 + ret = setup_namespace(); 224 + if (ret != NSID_PASS) 225 + exit(ret); 226 + ret = _test_statmount_mnt_ns_id(); 227 + exit(ret); 228 + } 229 + 230 + static int validate_external_listmount(pid_t pid, uint64_t child_nr_mounts) 231 + { 232 + uint64_t list[256]; 233 + uint64_t mnt_ns_id; 234 + uint64_t nr_mounts; 235 + char buf[256]; 236 + int ret; 237 + 238 + /* Get the mount ns id for our child. */ 239 + snprintf(buf, sizeof(buf), "/proc/%lu/ns/mnt", (unsigned long)pid); 240 + ret = get_mnt_ns_id(buf, &mnt_ns_id); 241 + 242 + nr_mounts = listmount(LSMT_ROOT, mnt_ns_id, 0, list, 256, 0); 243 + if (nr_mounts == (uint64_t)-1) { 244 + ksft_print_msg("listmount: %s\n", strerror(errno)); 245 + return NSID_ERROR; 246 + } 247 + 248 + if (nr_mounts != child_nr_mounts) { 249 + ksft_print_msg("listmount results is %zi != %zi\n", nr_mounts, 250 + child_nr_mounts); 251 + return NSID_FAIL; 252 + } 253 + 254 + /* Validate that all of our entries match our mnt_ns_id. */ 255 + for (int i = 0; i < nr_mounts; i++) { 256 + struct statmount sm; 257 + 258 + ret = statmount(list[i], mnt_ns_id, STATMOUNT_MNT_NS_ID, &sm, 259 + sizeof(sm), 0); 260 + if (ret < 0) { 261 + ksft_print_msg("statmount mnt ns id: %s\n", strerror(errno)); 262 + return NSID_ERROR; 263 + } 264 + 265 + if (sm.mask != STATMOUNT_MNT_NS_ID) { 266 + ksft_print_msg("statmount mnt ns id unavailable\n"); 267 + return NSID_SKIP; 268 + } 269 + 270 + if (sm.mnt_ns_id != mnt_ns_id) { 271 + ksft_print_msg("listmount gave us the wrong ns id: 0x%llx != 0x%llx\n", 272 + (unsigned long long)sm.mnt_ns_id, 273 + (unsigned long long)mnt_ns_id); 274 + return NSID_FAIL; 275 + } 276 + } 277 + 278 + return NSID_PASS; 279 + } 280 + 281 + static void test_listmount_ns(void) 282 + { 283 + uint64_t nr_mounts; 284 + char pval; 285 + int child_ready_pipe[2]; 286 + int parent_ready_pipe[2]; 287 + pid_t pid; 288 + int ret, child_ret; 289 + 290 + if (pipe(child_ready_pipe) < 0) 291 + ksft_exit_fail_msg("failed to create the child pipe: %s\n", 292 + strerror(errno)); 293 + if (pipe(parent_ready_pipe) < 0) 294 + ksft_exit_fail_msg("failed to create the parent pipe: %s\n", 295 + strerror(errno)); 296 + 297 + pid = fork(); 298 + if (pid < 0) 299 + ksft_exit_fail_msg("failed to fork: %s\n", strerror(errno)); 300 + 301 + if (pid == 0) { 302 + char cval; 303 + uint64_t list[256]; 304 + 305 + close(child_ready_pipe[0]); 306 + close(parent_ready_pipe[1]); 307 + 308 + ret = setup_namespace(); 309 + if (ret != NSID_PASS) 310 + exit(ret); 311 + 312 + nr_mounts = listmount(LSMT_ROOT, 0, 0, list, 256, 0); 313 + if (nr_mounts == (uint64_t)-1) { 314 + ksft_print_msg("listmount: %s\n", strerror(errno)); 315 + exit(NSID_FAIL); 316 + } 317 + 318 + /* 319 + * Tell our parent how many mounts we have, and then wait for it 320 + * to tell us we're done. 321 + */ 322 + write(child_ready_pipe[1], &nr_mounts, sizeof(nr_mounts)); 323 + read(parent_ready_pipe[0], &cval, sizeof(cval)); 324 + exit(NSID_PASS); 325 + } 326 + 327 + close(child_ready_pipe[1]); 328 + close(parent_ready_pipe[0]); 329 + 330 + /* Wait until the child has created everything. */ 331 + if (read(child_ready_pipe[0], &nr_mounts, sizeof(nr_mounts)) != 332 + sizeof(nr_mounts)) 333 + ret = NSID_ERROR; 334 + 335 + ret = validate_external_listmount(pid, nr_mounts); 336 + 337 + if (write(parent_ready_pipe[1], &pval, sizeof(pval)) != sizeof(pval)) 338 + ret = NSID_ERROR; 339 + 340 + child_ret = wait_for_pid(pid); 341 + if (child_ret != NSID_PASS) 342 + ret = child_ret; 343 + handle_result(ret, "test listmount ns id"); 344 + } 345 + 346 + int main(void) 347 + { 348 + int ret; 349 + 350 + ksft_print_header(); 351 + ret = statmount(0, 0, 0, NULL, 0, 0); 352 + assert(ret == -1); 353 + if (errno == ENOSYS) 354 + ksft_exit_skip("statmount() syscall not supported\n"); 355 + 356 + ksft_set_plan(2); 357 + test_statmount_mnt_ns_id(); 358 + test_listmount_ns(); 359 + 360 + if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0) 361 + ksft_exit_fail(); 362 + else 363 + ksft_exit_pass(); 364 + }