|
9 | 9 | #include <string.h> |
10 | 10 | #include <linux/nsfs.h> |
11 | 11 | #include <sys/mount.h> |
| 12 | +#include <sys/socket.h> |
12 | 13 | #include <sys/stat.h> |
13 | 14 | #include <sys/types.h> |
14 | 15 | #include <sys/wait.h> |
@@ -2350,4 +2351,322 @@ TEST(thread_ns_fd_keeps_active) |
2350 | 2351 | ASSERT_TRUE(errno == ENOENT || errno == ESTALE); |
2351 | 2352 | } |
2352 | 2353 |
|
| 2354 | +/* Structure for thread data in subprocess */ |
| 2355 | +struct thread_sleep_data { |
| 2356 | + int syncfd_read; |
| 2357 | +}; |
| 2358 | + |
| 2359 | +static void *thread_sleep_and_wait(void *arg) |
| 2360 | +{ |
| 2361 | + struct thread_sleep_data *data = (struct thread_sleep_data *)arg; |
| 2362 | + char sync_byte; |
| 2363 | + |
| 2364 | + /* Wait for signal to exit - read will unblock when pipe is closed */ |
| 2365 | + (void)read(data->syncfd_read, &sync_byte, 1); |
| 2366 | + return NULL; |
| 2367 | +} |
| 2368 | + |
| 2369 | +/* |
| 2370 | + * Test that namespaces become inactive after subprocess with multiple threads exits. |
| 2371 | + * Create a subprocess that unshares user and network namespaces, then creates two |
| 2372 | + * threads that share those namespaces. Verify that after all threads and subprocess |
| 2373 | + * exit, the namespaces are no longer listed by listns() and cannot be opened by |
| 2374 | + * open_by_handle_at(). |
| 2375 | + */ |
| 2376 | +TEST(thread_subprocess_ns_inactive_after_all_exit) |
| 2377 | +{ |
| 2378 | + int pipefd[2]; |
| 2379 | + int sv[2]; |
| 2380 | + pid_t pid; |
| 2381 | + int status; |
| 2382 | + __u64 user_id, net_id; |
| 2383 | + struct file_handle *user_handle, *net_handle; |
| 2384 | + char user_buf[sizeof(*user_handle) + MAX_HANDLE_SZ]; |
| 2385 | + char net_buf[sizeof(*net_handle) + MAX_HANDLE_SZ]; |
| 2386 | + char sync_byte; |
| 2387 | + int ret; |
| 2388 | + |
| 2389 | + ASSERT_EQ(pipe(pipefd), 0); |
| 2390 | + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0); |
| 2391 | + |
| 2392 | + pid = fork(); |
| 2393 | + ASSERT_GE(pid, 0); |
| 2394 | + |
| 2395 | + if (pid == 0) { |
| 2396 | + /* Child process */ |
| 2397 | + close(pipefd[0]); |
| 2398 | + close(sv[0]); |
| 2399 | + |
| 2400 | + /* Create user namespace with mappings */ |
| 2401 | + if (setup_userns() < 0) { |
| 2402 | + fprintf(stderr, "Child: setup_userns() failed: %s\n", strerror(errno)); |
| 2403 | + close(pipefd[1]); |
| 2404 | + close(sv[1]); |
| 2405 | + exit(1); |
| 2406 | + } |
| 2407 | + fprintf(stderr, "Child: setup_userns() succeeded\n"); |
| 2408 | + |
| 2409 | + /* Get user namespace ID */ |
| 2410 | + int user_fd = open("/proc/self/ns/user", O_RDONLY); |
| 2411 | + if (user_fd < 0) { |
| 2412 | + fprintf(stderr, "Child: open(/proc/self/ns/user) failed: %s\n", strerror(errno)); |
| 2413 | + close(pipefd[1]); |
| 2414 | + close(sv[1]); |
| 2415 | + exit(1); |
| 2416 | + } |
| 2417 | + |
| 2418 | + if (ioctl(user_fd, NS_GET_ID, &user_id) < 0) { |
| 2419 | + fprintf(stderr, "Child: ioctl(NS_GET_ID) for user ns failed: %s\n", strerror(errno)); |
| 2420 | + close(user_fd); |
| 2421 | + close(pipefd[1]); |
| 2422 | + close(sv[1]); |
| 2423 | + exit(1); |
| 2424 | + } |
| 2425 | + close(user_fd); |
| 2426 | + fprintf(stderr, "Child: user ns ID = %llu\n", (unsigned long long)user_id); |
| 2427 | + |
| 2428 | + /* Unshare network namespace */ |
| 2429 | + if (unshare(CLONE_NEWNET) < 0) { |
| 2430 | + fprintf(stderr, "Child: unshare(CLONE_NEWNET) failed: %s\n", strerror(errno)); |
| 2431 | + close(pipefd[1]); |
| 2432 | + close(sv[1]); |
| 2433 | + exit(1); |
| 2434 | + } |
| 2435 | + fprintf(stderr, "Child: unshare(CLONE_NEWNET) succeeded\n"); |
| 2436 | + |
| 2437 | + /* Get network namespace ID */ |
| 2438 | + int net_fd = open("/proc/self/ns/net", O_RDONLY); |
| 2439 | + if (net_fd < 0) { |
| 2440 | + fprintf(stderr, "Child: open(/proc/self/ns/net) failed: %s\n", strerror(errno)); |
| 2441 | + close(pipefd[1]); |
| 2442 | + close(sv[1]); |
| 2443 | + exit(1); |
| 2444 | + } |
| 2445 | + |
| 2446 | + if (ioctl(net_fd, NS_GET_ID, &net_id) < 0) { |
| 2447 | + fprintf(stderr, "Child: ioctl(NS_GET_ID) for net ns failed: %s\n", strerror(errno)); |
| 2448 | + close(net_fd); |
| 2449 | + close(pipefd[1]); |
| 2450 | + close(sv[1]); |
| 2451 | + exit(1); |
| 2452 | + } |
| 2453 | + close(net_fd); |
| 2454 | + fprintf(stderr, "Child: net ns ID = %llu\n", (unsigned long long)net_id); |
| 2455 | + |
| 2456 | + /* Send namespace IDs to parent */ |
| 2457 | + if (write(pipefd[1], &user_id, sizeof(user_id)) != sizeof(user_id)) { |
| 2458 | + fprintf(stderr, "Child: write(user_id) failed: %s\n", strerror(errno)); |
| 2459 | + exit(1); |
| 2460 | + } |
| 2461 | + if (write(pipefd[1], &net_id, sizeof(net_id)) != sizeof(net_id)) { |
| 2462 | + fprintf(stderr, "Child: write(net_id) failed: %s\n", strerror(errno)); |
| 2463 | + exit(1); |
| 2464 | + } |
| 2465 | + close(pipefd[1]); |
| 2466 | + fprintf(stderr, "Child: sent namespace IDs to parent\n"); |
| 2467 | + |
| 2468 | + /* Create two threads that share the namespaces */ |
| 2469 | + pthread_t thread1, thread2; |
| 2470 | + struct thread_sleep_data data; |
| 2471 | + data.syncfd_read = sv[1]; |
| 2472 | + |
| 2473 | + int ret_thread = pthread_create(&thread1, NULL, thread_sleep_and_wait, &data); |
| 2474 | + if (ret_thread != 0) { |
| 2475 | + fprintf(stderr, "Child: pthread_create(thread1) failed: %s\n", strerror(ret_thread)); |
| 2476 | + close(sv[1]); |
| 2477 | + exit(1); |
| 2478 | + } |
| 2479 | + fprintf(stderr, "Child: created thread1\n"); |
| 2480 | + |
| 2481 | + ret_thread = pthread_create(&thread2, NULL, thread_sleep_and_wait, &data); |
| 2482 | + if (ret_thread != 0) { |
| 2483 | + fprintf(stderr, "Child: pthread_create(thread2) failed: %s\n", strerror(ret_thread)); |
| 2484 | + close(sv[1]); |
| 2485 | + pthread_cancel(thread1); |
| 2486 | + exit(1); |
| 2487 | + } |
| 2488 | + fprintf(stderr, "Child: created thread2\n"); |
| 2489 | + |
| 2490 | + /* Wait for threads to complete - they will unblock when parent writes */ |
| 2491 | + fprintf(stderr, "Child: waiting for threads to exit\n"); |
| 2492 | + pthread_join(thread1, NULL); |
| 2493 | + fprintf(stderr, "Child: thread1 exited\n"); |
| 2494 | + pthread_join(thread2, NULL); |
| 2495 | + fprintf(stderr, "Child: thread2 exited\n"); |
| 2496 | + |
| 2497 | + close(sv[1]); |
| 2498 | + |
| 2499 | + /* Exit - namespaces should become inactive */ |
| 2500 | + fprintf(stderr, "Child: all threads joined, exiting with success\n"); |
| 2501 | + exit(0); |
| 2502 | + } |
| 2503 | + |
| 2504 | + /* Parent process */ |
| 2505 | + close(pipefd[1]); |
| 2506 | + close(sv[1]); |
| 2507 | + |
| 2508 | + TH_LOG("Parent: waiting to read namespace IDs from child"); |
| 2509 | + |
| 2510 | + /* Read namespace IDs from child */ |
| 2511 | + ret = read(pipefd[0], &user_id, sizeof(user_id)); |
| 2512 | + if (ret != sizeof(user_id)) { |
| 2513 | + TH_LOG("Parent: failed to read user_id, ret=%d, errno=%s", ret, strerror(errno)); |
| 2514 | + close(pipefd[0]); |
| 2515 | + sync_byte = 'X'; |
| 2516 | + (void)write(sv[0], &sync_byte, 1); |
| 2517 | + close(sv[0]); |
| 2518 | + waitpid(pid, NULL, 0); |
| 2519 | + SKIP(return, "Failed to read user namespace ID from child"); |
| 2520 | + } |
| 2521 | + |
| 2522 | + ret = read(pipefd[0], &net_id, sizeof(net_id)); |
| 2523 | + close(pipefd[0]); |
| 2524 | + if (ret != sizeof(net_id)) { |
| 2525 | + TH_LOG("Parent: failed to read net_id, ret=%d, errno=%s", ret, strerror(errno)); |
| 2526 | + sync_byte = 'X'; |
| 2527 | + (void)write(sv[0], &sync_byte, 1); |
| 2528 | + close(sv[0]); |
| 2529 | + waitpid(pid, NULL, 0); |
| 2530 | + SKIP(return, "Failed to read network namespace ID from child"); |
| 2531 | + } |
| 2532 | + |
| 2533 | + TH_LOG("Child created user ns %llu and net ns %llu with 2 threads", |
| 2534 | + (unsigned long long)user_id, (unsigned long long)net_id); |
| 2535 | + |
| 2536 | + /* Construct file handles */ |
| 2537 | + user_handle = (struct file_handle *)user_buf; |
| 2538 | + user_handle->handle_bytes = sizeof(struct nsfs_file_handle); |
| 2539 | + user_handle->handle_type = FILEID_NSFS; |
| 2540 | + struct nsfs_file_handle *user_fh = (struct nsfs_file_handle *)user_handle->f_handle; |
| 2541 | + user_fh->ns_id = user_id; |
| 2542 | + user_fh->ns_type = 0; |
| 2543 | + user_fh->ns_inum = 0; |
| 2544 | + |
| 2545 | + net_handle = (struct file_handle *)net_buf; |
| 2546 | + net_handle->handle_bytes = sizeof(struct nsfs_file_handle); |
| 2547 | + net_handle->handle_type = FILEID_NSFS; |
| 2548 | + struct nsfs_file_handle *net_fh = (struct nsfs_file_handle *)net_handle->f_handle; |
| 2549 | + net_fh->ns_id = net_id; |
| 2550 | + net_fh->ns_type = 0; |
| 2551 | + net_fh->ns_inum = 0; |
| 2552 | + |
| 2553 | + /* Verify namespaces are active while subprocess and threads are alive */ |
| 2554 | + TH_LOG("Verifying namespaces are active while subprocess with threads is running"); |
| 2555 | + int user_fd = open_by_handle_at(FD_NSFS_ROOT, user_handle, O_RDONLY); |
| 2556 | + ASSERT_GE(user_fd, 0); |
| 2557 | + |
| 2558 | + int net_fd = open_by_handle_at(FD_NSFS_ROOT, net_handle, O_RDONLY); |
| 2559 | + ASSERT_GE(net_fd, 0); |
| 2560 | + |
| 2561 | + close(user_fd); |
| 2562 | + close(net_fd); |
| 2563 | + |
| 2564 | + /* Also verify they appear in listns() */ |
| 2565 | + TH_LOG("Verifying namespaces appear in listns() while active"); |
| 2566 | + struct ns_id_req req = { |
| 2567 | + .size = sizeof(struct ns_id_req), |
| 2568 | + .spare = 0, |
| 2569 | + .ns_id = 0, |
| 2570 | + .ns_type = CLONE_NEWUSER, |
| 2571 | + .spare2 = 0, |
| 2572 | + .user_ns_id = 0, |
| 2573 | + }; |
| 2574 | + __u64 ns_ids[256]; |
| 2575 | + int nr_ids = sys_listns(&req, ns_ids, 256, 0); |
| 2576 | + if (nr_ids < 0) { |
| 2577 | + TH_LOG("listns() not available, skipping listns verification"); |
| 2578 | + } else { |
| 2579 | + /* Check if user_id is in the list */ |
| 2580 | + int found_user = 0; |
| 2581 | + for (int i = 0; i < nr_ids; i++) { |
| 2582 | + if (ns_ids[i] == user_id) { |
| 2583 | + found_user = 1; |
| 2584 | + break; |
| 2585 | + } |
| 2586 | + } |
| 2587 | + ASSERT_TRUE(found_user); |
| 2588 | + TH_LOG("User namespace found in listns() as expected"); |
| 2589 | + |
| 2590 | + /* Check network namespace */ |
| 2591 | + req.ns_type = CLONE_NEWNET; |
| 2592 | + nr_ids = sys_listns(&req, ns_ids, 256, 0); |
| 2593 | + if (nr_ids >= 0) { |
| 2594 | + int found_net = 0; |
| 2595 | + for (int i = 0; i < nr_ids; i++) { |
| 2596 | + if (ns_ids[i] == net_id) { |
| 2597 | + found_net = 1; |
| 2598 | + break; |
| 2599 | + } |
| 2600 | + } |
| 2601 | + ASSERT_TRUE(found_net); |
| 2602 | + TH_LOG("Network namespace found in listns() as expected"); |
| 2603 | + } |
| 2604 | + } |
| 2605 | + |
| 2606 | + /* Signal threads to exit */ |
| 2607 | + TH_LOG("Signaling threads to exit"); |
| 2608 | + sync_byte = 'X'; |
| 2609 | + /* Write two bytes - one for each thread */ |
| 2610 | + ASSERT_EQ(write(sv[0], &sync_byte, 1), 1); |
| 2611 | + ASSERT_EQ(write(sv[0], &sync_byte, 1), 1); |
| 2612 | + close(sv[0]); |
| 2613 | + |
| 2614 | + /* Wait for child process to exit */ |
| 2615 | + waitpid(pid, &status, 0); |
| 2616 | + ASSERT_TRUE(WIFEXITED(status)); |
| 2617 | + if (WEXITSTATUS(status) != 0) { |
| 2618 | + TH_LOG("Child process failed with exit code %d", WEXITSTATUS(status)); |
| 2619 | + SKIP(return, "Child process failed"); |
| 2620 | + } |
| 2621 | + |
| 2622 | + TH_LOG("Subprocess and all threads have exited successfully"); |
| 2623 | + |
| 2624 | + /* Verify namespaces are now inactive - open_by_handle_at should fail */ |
| 2625 | + TH_LOG("Verifying namespaces are inactive after subprocess and threads exit"); |
| 2626 | + user_fd = open_by_handle_at(FD_NSFS_ROOT, user_handle, O_RDONLY); |
| 2627 | + ASSERT_LT(user_fd, 0); |
| 2628 | + TH_LOG("User namespace inactive as expected: %s (errno=%d)", |
| 2629 | + strerror(errno), errno); |
| 2630 | + ASSERT_TRUE(errno == ENOENT || errno == ESTALE); |
| 2631 | + |
| 2632 | + net_fd = open_by_handle_at(FD_NSFS_ROOT, net_handle, O_RDONLY); |
| 2633 | + ASSERT_LT(net_fd, 0); |
| 2634 | + TH_LOG("Network namespace inactive as expected: %s (errno=%d)", |
| 2635 | + strerror(errno), errno); |
| 2636 | + ASSERT_TRUE(errno == ENOENT || errno == ESTALE); |
| 2637 | + |
| 2638 | + /* Verify namespaces do NOT appear in listns() */ |
| 2639 | + TH_LOG("Verifying namespaces do NOT appear in listns() when inactive"); |
| 2640 | + memset(&req, 0, sizeof(req)); |
| 2641 | + req.size = sizeof(struct ns_id_req); |
| 2642 | + req.ns_type = CLONE_NEWUSER; |
| 2643 | + nr_ids = sys_listns(&req, ns_ids, 256, 0); |
| 2644 | + if (nr_ids >= 0) { |
| 2645 | + int found_user = 0; |
| 2646 | + for (int i = 0; i < nr_ids; i++) { |
| 2647 | + if (ns_ids[i] == user_id) { |
| 2648 | + found_user = 1; |
| 2649 | + break; |
| 2650 | + } |
| 2651 | + } |
| 2652 | + ASSERT_FALSE(found_user); |
| 2653 | + TH_LOG("User namespace correctly not listed in listns()"); |
| 2654 | + |
| 2655 | + /* Check network namespace */ |
| 2656 | + req.ns_type = CLONE_NEWNET; |
| 2657 | + nr_ids = sys_listns(&req, ns_ids, 256, 0); |
| 2658 | + if (nr_ids >= 0) { |
| 2659 | + int found_net = 0; |
| 2660 | + for (int i = 0; i < nr_ids; i++) { |
| 2661 | + if (ns_ids[i] == net_id) { |
| 2662 | + found_net = 1; |
| 2663 | + break; |
| 2664 | + } |
| 2665 | + } |
| 2666 | + ASSERT_FALSE(found_net); |
| 2667 | + TH_LOG("Network namespace correctly not listed in listns()"); |
| 2668 | + } |
| 2669 | + } |
| 2670 | +} |
| 2671 | + |
2353 | 2672 | TEST_HARNESS_MAIN |
0 commit comments