File Index
Files and directories
Sign in
FILE

ns_active_ref_test.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/namespaces/ns_active_ref_test.c C
   1// SPDX-License-Identifier: GPL-2.0
   2#define _GNU_SOURCE
   3#include <errno.h>
   4#include <fcntl.h>
   5#include <limits.h>
   6#include <sched.h>
   7#include <stdio.h>
   8#include <stdlib.h>
   9#include <string.h>
  10#include <linux/nsfs.h>
  11#include <sys/mount.h>
  12#include <sys/socket.h>
  13#include <sys/stat.h>
  14#include <sys/types.h>
  15#include <sys/wait.h>
  16#include <sys/syscall.h>
  17#include <unistd.h>
  18#include <pthread.h>
  19#include "../kselftest_harness.h"
  20#include "../filesystems/utils.h"
  21#include "wrappers.h"
  22
  23#ifndef FD_NSFS_ROOT
  24#define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */
  25#endif
  26
  27#ifndef FILEID_NSFS
  28#define FILEID_NSFS 0xf1
  29#endif
  30
  31/*
  32 * Test that initial namespaces can be reopened via file handle.
  33 * Initial namespaces should have active ref count of 1 from boot.
  34 */
  35TEST(init_ns_always_active)
  36{
  37	struct file_handle *handle;
  38	int mount_id;
  39	int ret;
  40	int fd1, fd2;
  41	struct stat st1, st2;
  42
  43	handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
  44	ASSERT_NE(handle, NULL);
  45
  46	/* Open initial network namespace */
  47	fd1 = open("/proc/1/ns/net", O_RDONLY);
  48	ASSERT_GE(fd1, 0);
  49
  50	/* Get file handle for initial namespace */
  51	handle->handle_bytes = MAX_HANDLE_SZ;
  52	ret = name_to_handle_at(fd1, "", handle, &mount_id, AT_EMPTY_PATH);
  53	if (ret < 0 && errno == EOPNOTSUPP) {
  54		SKIP(free(handle); close(fd1);
  55		     return, "nsfs doesn't support file handles");
  56	}
  57	ASSERT_EQ(ret, 0);
  58
  59	/* Close the namespace fd */
  60	close(fd1);
  61
  62	/* Try to reopen via file handle - should succeed since init ns is always active */
  63	fd2 = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
  64	if (fd2 < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) {
  65		SKIP(free(handle);
  66		     return, "open_by_handle_at with FD_NSFS_ROOT not supported");
  67	}
  68	ASSERT_GE(fd2, 0);
  69
  70	/* Verify we opened the same namespace */
  71	fd1 = open("/proc/1/ns/net", O_RDONLY);
  72	ASSERT_GE(fd1, 0);
  73	ASSERT_EQ(fstat(fd1, &st1), 0);
  74	ASSERT_EQ(fstat(fd2, &st2), 0);
  75	ASSERT_EQ(st1.st_ino, st2.st_ino);
  76
  77	close(fd1);
  78	close(fd2);
  79	free(handle);
  80}
  81
  82/*
  83 * Test namespace lifecycle: create a namespace in a child process,
  84 * get a file handle while it's active, then try to reopen after
  85 * the process exits (namespace becomes inactive).
  86 */
  87TEST(ns_inactive_after_exit)
  88{
  89	struct file_handle *handle;
  90	int mount_id;
  91	int ret;
  92	int fd;
  93	int pipefd[2];
  94	pid_t pid;
  95	int status;
  96	char buf[sizeof(*handle) + MAX_HANDLE_SZ];
  97
  98	/* Create pipe for passing file handle from child */
  99	ASSERT_EQ(pipe(pipefd), 0);
 100
 101	pid = fork();
 102	ASSERT_GE(pid, 0);
 103
 104	if (pid == 0) {
 105		/* Child process */
 106		close(pipefd[0]);
 107
 108		/* Create new network namespace */
 109		ret = unshare(CLONE_NEWNET);
 110		if (ret < 0) {
 111			close(pipefd[1]);
 112			exit(1);
 113		}
 114
 115		/* Open our new namespace */
 116		fd = open("/proc/self/ns/net", O_RDONLY);
 117		if (fd < 0) {
 118			close(pipefd[1]);
 119			exit(1);
 120		}
 121
 122		/* Get file handle for the namespace */
 123		handle = (struct file_handle *)buf;
 124		handle->handle_bytes = MAX_HANDLE_SZ;
 125		ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
 126		close(fd);
 127
 128		if (ret < 0) {
 129			close(pipefd[1]);
 130			exit(1);
 131		}
 132
 133		/* Send handle to parent */
 134		write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
 135		close(pipefd[1]);
 136
 137		/* Exit - namespace should become inactive */
 138		exit(0);
 139	}
 140
 141	/* Parent process */
 142	close(pipefd[1]);
 143
 144	/* Read file handle from child */
 145	ret = read(pipefd[0], buf, sizeof(buf));
 146	close(pipefd[0]);
 147
 148	/* Wait for child to exit */
 149	waitpid(pid, &status, 0);
 150	ASSERT_TRUE(WIFEXITED(status));
 151	ASSERT_EQ(WEXITSTATUS(status), 0);
 152
 153	ASSERT_GT(ret, 0);
 154	handle = (struct file_handle *)buf;
 155
 156	/* Try to reopen namespace - should fail with ENOENT since it's inactive */
 157	fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
 158	ASSERT_LT(fd, 0);
 159	/* Should fail with ENOENT (namespace inactive) or ESTALE */
 160	ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
 161}
 162
 163/*
 164 * Test that a namespace remains active while a process is using it,
 165 * even after the creating process exits.
 166 */
 167TEST(ns_active_with_multiple_processes)
 168{
 169	struct file_handle *handle;
 170	int mount_id;
 171	int ret;
 172	int fd;
 173	int pipefd[2];
 174	int syncpipe[2];
 175	pid_t pid1, pid2;
 176	int status;
 177	char buf[sizeof(*handle) + MAX_HANDLE_SZ];
 178	char sync_byte;
 179
 180	/* Create pipes for communication */
 181	ASSERT_EQ(pipe(pipefd), 0);
 182	ASSERT_EQ(pipe(syncpipe), 0);
 183
 184	pid1 = fork();
 185	ASSERT_GE(pid1, 0);
 186
 187	if (pid1 == 0) {
 188		/* First child - creates namespace */
 189		close(pipefd[0]);
 190		close(syncpipe[1]);
 191
 192		/* Create new network namespace */
 193		ret = unshare(CLONE_NEWNET);
 194		if (ret < 0) {
 195			close(pipefd[1]);
 196			close(syncpipe[0]);
 197			exit(1);
 198		}
 199
 200		/* Open and get handle */
 201		fd = open("/proc/self/ns/net", O_RDONLY);
 202		if (fd < 0) {
 203			close(pipefd[1]);
 204			close(syncpipe[0]);
 205			exit(1);
 206		}
 207
 208		handle = (struct file_handle *)buf;
 209		handle->handle_bytes = MAX_HANDLE_SZ;
 210		ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
 211		close(fd);
 212
 213		if (ret < 0) {
 214			close(pipefd[1]);
 215			close(syncpipe[0]);
 216			exit(1);
 217		}
 218
 219		/* Send handle to parent */
 220		write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
 221		close(pipefd[1]);
 222
 223		/* Wait for signal before exiting */
 224		read(syncpipe[0], &sync_byte, 1);
 225		close(syncpipe[0]);
 226		exit(0);
 227	}
 228
 229	/* Parent reads handle */
 230	close(pipefd[1]);
 231	ret = read(pipefd[0], buf, sizeof(buf));
 232	close(pipefd[0]);
 233	ASSERT_GT(ret, 0);
 234
 235	handle = (struct file_handle *)buf;
 236
 237	/* Create second child that will keep namespace active */
 238	pid2 = fork();
 239	ASSERT_GE(pid2, 0);
 240
 241	if (pid2 == 0) {
 242		/* Second child - reopens the namespace */
 243		close(syncpipe[0]);
 244		close(syncpipe[1]);
 245
 246		/* Open the namespace via handle */
 247		fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
 248		if (fd < 0) {
 249			exit(1);
 250		}
 251
 252		/* Join the namespace */
 253		ret = setns(fd, CLONE_NEWNET);
 254		close(fd);
 255		if (ret < 0) {
 256			exit(1);
 257		}
 258
 259		/* Sleep to keep namespace active */
 260		sleep(1);
 261		exit(0);
 262	}
 263
 264	/* Let second child enter the namespace */
 265	usleep(100000); /* 100ms */
 266
 267	/* Signal first child to exit */
 268	close(syncpipe[0]);
 269	sync_byte = 'X';
 270	write(syncpipe[1], &sync_byte, 1);
 271	close(syncpipe[1]);
 272
 273	/* Wait for first child */
 274	waitpid(pid1, &status, 0);
 275	ASSERT_TRUE(WIFEXITED(status));
 276
 277	/* Namespace should still be active because second child is using it */
 278	fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
 279	ASSERT_GE(fd, 0);
 280	close(fd);
 281
 282	/* Wait for second child */
 283	waitpid(pid2, &status, 0);
 284	ASSERT_TRUE(WIFEXITED(status));
 285}
 286
 287/*
 288 * Test user namespace active ref tracking via credential lifecycle
 289 */
 290TEST(userns_active_ref_lifecycle)
 291{
 292	struct file_handle *handle;
 293	int mount_id;
 294	int ret;
 295	int fd;
 296	int pipefd[2];
 297	pid_t pid;
 298	int status;
 299	char buf[sizeof(*handle) + MAX_HANDLE_SZ];
 300
 301	ASSERT_EQ(pipe(pipefd), 0);
 302
 303	pid = fork();
 304	ASSERT_GE(pid, 0);
 305
 306	if (pid == 0) {
 307		/* Child process */
 308		close(pipefd[0]);
 309
 310		/* Create new user namespace */
 311		ret = unshare(CLONE_NEWUSER);
 312		if (ret < 0) {
 313			close(pipefd[1]);
 314			exit(1);
 315		}
 316
 317		/* Set up uid/gid mappings */
 318		int uid_map_fd = open("/proc/self/uid_map", O_WRONLY);
 319		int gid_map_fd = open("/proc/self/gid_map", O_WRONLY);
 320		int setgroups_fd = open("/proc/self/setgroups", O_WRONLY);
 321
 322		if (uid_map_fd >= 0 && gid_map_fd >= 0 && setgroups_fd >= 0) {
 323			write(setgroups_fd, "deny", 4);
 324			close(setgroups_fd);
 325
 326			char mapping[64];
 327			snprintf(mapping, sizeof(mapping), "0 %d 1", getuid());
 328			write(uid_map_fd, mapping, strlen(mapping));
 329			close(uid_map_fd);
 330
 331			snprintf(mapping, sizeof(mapping), "0 %d 1", getgid());
 332			write(gid_map_fd, mapping, strlen(mapping));
 333			close(gid_map_fd);
 334		}
 335
 336		/* Get file handle */
 337		fd = open("/proc/self/ns/user", O_RDONLY);
 338		if (fd < 0) {
 339			close(pipefd[1]);
 340			exit(1);
 341		}
 342
 343		handle = (struct file_handle *)buf;
 344		handle->handle_bytes = MAX_HANDLE_SZ;
 345		ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
 346		close(fd);
 347
 348		if (ret < 0) {
 349			close(pipefd[1]);
 350			exit(1);
 351		}
 352
 353		/* Send handle to parent */
 354		write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
 355		close(pipefd[1]);
 356		exit(0);
 357	}
 358
 359	/* Parent */
 360	close(pipefd[1]);
 361	ret = read(pipefd[0], buf, sizeof(buf));
 362	close(pipefd[0]);
 363
 364	waitpid(pid, &status, 0);
 365	ASSERT_TRUE(WIFEXITED(status));
 366	ASSERT_EQ(WEXITSTATUS(status), 0);
 367
 368	ASSERT_GT(ret, 0);
 369	handle = (struct file_handle *)buf;
 370
 371	/* Namespace should be inactive after all tasks exit */
 372	fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
 373	ASSERT_LT(fd, 0);
 374	ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
 375}
 376
 377/*
 378 * Test PID namespace active ref tracking
 379 */
 380TEST(pidns_active_ref_lifecycle)
 381{
 382	struct file_handle *handle;
 383	int mount_id;
 384	int ret;
 385	int fd;
 386	int pipefd[2];
 387	pid_t pid;
 388	int status;
 389	char buf[sizeof(*handle) + MAX_HANDLE_SZ];
 390
 391	ASSERT_EQ(pipe(pipefd), 0);
 392
 393	pid = fork();
 394	ASSERT_GE(pid, 0);
 395
 396	if (pid == 0) {
 397		/* Child process */
 398		close(pipefd[0]);
 399
 400		/* Create new PID namespace */
 401		ret = unshare(CLONE_NEWPID);
 402		if (ret < 0) {
 403			close(pipefd[1]);
 404			exit(1);
 405		}
 406
 407		/* Fork to actually enter the PID namespace */
 408		pid_t child = fork();
 409		if (child < 0) {
 410			close(pipefd[1]);
 411			exit(1);
 412		}
 413
 414		if (child == 0) {
 415			/* Grandchild - in new PID namespace */
 416			fd = open("/proc/self/ns/pid", O_RDONLY);
 417			if (fd < 0) {
 418				exit(1);
 419			}
 420
 421			handle = (struct file_handle *)buf;
 422			handle->handle_bytes = MAX_HANDLE_SZ;
 423			ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
 424			close(fd);
 425
 426			if (ret < 0) {
 427				exit(1);
 428			}
 429
 430			/* Send handle to grandparent */
 431			write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
 432			close(pipefd[1]);
 433			exit(0);
 434		}
 435
 436		/* Wait for grandchild */
 437		waitpid(child, NULL, 0);
 438		exit(0);
 439	}
 440
 441	/* Parent */
 442	close(pipefd[1]);
 443	ret = read(pipefd[0], buf, sizeof(buf));
 444	close(pipefd[0]);
 445
 446	waitpid(pid, &status, 0);
 447	ASSERT_TRUE(WIFEXITED(status));
 448	ASSERT_EQ(WEXITSTATUS(status), 0);
 449
 450	ASSERT_GT(ret, 0);
 451	handle = (struct file_handle *)buf;
 452
 453	/* Namespace should be inactive after all processes exit */
 454	fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
 455	ASSERT_LT(fd, 0);
 456	ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
 457}
 458
 459/*
 460 * Test that an open file descriptor keeps a namespace active.
 461 * Even after the creating process exits, the namespace should remain
 462 * active as long as an fd is held open.
 463 */
 464TEST(ns_fd_keeps_active)
 465{
 466	struct file_handle *handle;
 467	int mount_id;
 468	int ret;
 469	int nsfd;
 470	int pipe_child_ready[2];
 471	int pipe_parent_ready[2];
 472	pid_t pid;
 473	int status;
 474	char buf[sizeof(*handle) + MAX_HANDLE_SZ];
 475	char sync_byte;
 476	char proc_path[64];
 477
 478	ASSERT_EQ(pipe(pipe_child_ready), 0);
 479	ASSERT_EQ(pipe(pipe_parent_ready), 0);
 480
 481	pid = fork();
 482	ASSERT_GE(pid, 0);
 483
 484	if (pid == 0) {
 485		/* Child process */
 486		close(pipe_child_ready[0]);
 487		close(pipe_parent_ready[1]);
 488
 489		TH_LOG("Child: creating new network namespace");
 490
 491		/* Create new network namespace */
 492		ret = unshare(CLONE_NEWNET);
 493		if (ret < 0) {
 494			TH_LOG("Child: unshare(CLONE_NEWNET) failed: %s", strerror(errno));
 495			close(pipe_child_ready[1]);
 496			close(pipe_parent_ready[0]);
 497			exit(1);
 498		}
 499
 500		TH_LOG("Child: network namespace created successfully");
 501
 502		/* Get file handle for the namespace */
 503		nsfd = open("/proc/self/ns/net", O_RDONLY);
 504		if (nsfd < 0) {
 505			TH_LOG("Child: failed to open /proc/self/ns/net: %s", strerror(errno));
 506			close(pipe_child_ready[1]);
 507			close(pipe_parent_ready[0]);
 508			exit(1);
 509		}
 510
 511		TH_LOG("Child: opened namespace fd %d", nsfd);
 512
 513		handle = (struct file_handle *)buf;
 514		handle->handle_bytes = MAX_HANDLE_SZ;
 515		ret = name_to_handle_at(nsfd, "", handle, &mount_id, AT_EMPTY_PATH);
 516		close(nsfd);
 517
 518		if (ret < 0) {
 519			TH_LOG("Child: name_to_handle_at failed: %s", strerror(errno));
 520			close(pipe_child_ready[1]);
 521			close(pipe_parent_ready[0]);
 522			exit(1);
 523		}
 524
 525		TH_LOG("Child: got file handle (bytes=%u)", handle->handle_bytes);
 526
 527		/* Send file handle to parent */
 528		ret = write(pipe_child_ready[1], buf, sizeof(*handle) + handle->handle_bytes);
 529		TH_LOG("Child: sent %d bytes of file handle to parent", ret);
 530		close(pipe_child_ready[1]);
 531
 532		/* Wait for parent to open the fd */
 533		TH_LOG("Child: waiting for parent to open fd");
 534		ret = read(pipe_parent_ready[0], &sync_byte, 1);
 535		close(pipe_parent_ready[0]);
 536
 537		TH_LOG("Child: parent signaled (read %d bytes), exiting now", ret);
 538		/* Exit - namespace should stay active because parent holds fd */
 539		exit(0);
 540	}
 541
 542	/* Parent process */
 543	close(pipe_child_ready[1]);
 544	close(pipe_parent_ready[0]);
 545
 546	TH_LOG("Parent: reading file handle from child");
 547
 548	/* Read file handle from child */
 549	ret = read(pipe_child_ready[0], buf, sizeof(buf));
 550	close(pipe_child_ready[0]);
 551	ASSERT_GT(ret, 0);
 552	handle = (struct file_handle *)buf;
 553
 554	TH_LOG("Parent: received %d bytes, handle size=%u", ret, handle->handle_bytes);
 555
 556	/* Open the child's namespace while it's still alive */
 557	snprintf(proc_path, sizeof(proc_path), "/proc/%d/ns/net", pid);
 558	TH_LOG("Parent: opening child's namespace at %s", proc_path);
 559	nsfd = open(proc_path, O_RDONLY);
 560	if (nsfd < 0) {
 561		TH_LOG("Parent: failed to open %s: %s", proc_path, strerror(errno));
 562		close(pipe_parent_ready[1]);
 563		kill(pid, SIGKILL);
 564		waitpid(pid, NULL, 0);
 565		SKIP(return, "Failed to open child's namespace");
 566	}
 567
 568	TH_LOG("Parent: opened child's namespace, got fd %d", nsfd);
 569
 570	/* Signal child that we have the fd */
 571	sync_byte = 'G';
 572	write(pipe_parent_ready[1], &sync_byte, 1);
 573	close(pipe_parent_ready[1]);
 574	TH_LOG("Parent: signaled child that we have the fd");
 575
 576	/* Wait for child to exit */
 577	waitpid(pid, &status, 0);
 578	ASSERT_TRUE(WIFEXITED(status));
 579	ASSERT_EQ(WEXITSTATUS(status), 0);
 580
 581	TH_LOG("Child exited, parent holds fd %d to namespace", nsfd);
 582
 583	/*
 584	 * Namespace should still be ACTIVE because we hold an fd.
 585	 * We should be able to reopen it via file handle.
 586	 */
 587	TH_LOG("Attempting to reopen namespace via file handle (should succeed - fd held)");
 588	int fd2 = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
 589	ASSERT_GE(fd2, 0);
 590
 591	TH_LOG("Successfully reopened namespace via file handle, got fd %d", fd2);
 592
 593	/* Verify it's the same namespace */
 594	struct stat st1, st2;
 595	ASSERT_EQ(fstat(nsfd, &st1), 0);
 596	ASSERT_EQ(fstat(fd2, &st2), 0);
 597	TH_LOG("Namespace inodes: nsfd=%lu, fd2=%lu", st1.st_ino, st2.st_ino);
 598	ASSERT_EQ(st1.st_ino, st2.st_ino);
 599	close(fd2);
 600
 601	/* Now close the fd - namespace should become inactive */
 602	TH_LOG("Closing fd %d - namespace should become inactive", nsfd);
 603	close(nsfd);
 604
 605	/* Now reopening should fail - namespace is inactive */
 606	TH_LOG("Attempting to reopen namespace via file handle (should fail - inactive)");
 607	fd2 = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
 608	ASSERT_LT(fd2, 0);
 609	/* Should fail with ENOENT (inactive) or ESTALE (gone) */
 610	TH_LOG("Reopen failed as expected: %s (errno=%d)", strerror(errno), errno);
 611	ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
 612}
 613
 614/*
 615 * Test hierarchical active reference propagation.
 616 * When a child namespace is active, its owning user namespace should also
 617 * be active automatically due to hierarchical active reference propagation.
 618 * This ensures parents are always reachable when children are active.
 619 */
 620TEST(ns_parent_always_reachable)
 621{
 622	struct file_handle *parent_handle, *child_handle;
 623	int ret;
 624	int child_nsfd;
 625	int pipefd[2];
 626	pid_t pid;
 627	int status;
 628	__u64 parent_id, child_id;
 629	char parent_buf[sizeof(*parent_handle) + MAX_HANDLE_SZ];
 630	char child_buf[sizeof(*child_handle) + MAX_HANDLE_SZ];
 631
 632	ASSERT_EQ(pipe(pipefd), 0);
 633
 634	pid = fork();
 635	ASSERT_GE(pid, 0);
 636
 637	if (pid == 0) {
 638		/* Child process */
 639		close(pipefd[0]);
 640
 641		TH_LOG("Child: creating parent user namespace and setting up mappings");
 642
 643		/* Create parent user namespace with mappings */
 644		ret = setup_userns();
 645		if (ret < 0) {
 646			TH_LOG("Child: setup_userns() for parent failed: %s", strerror(errno));
 647			close(pipefd[1]);
 648			exit(1);
 649		}
 650
 651		TH_LOG("Child: parent user namespace created, now uid=%d gid=%d", getuid(), getgid());
 652
 653		/* Get namespace ID for parent user namespace */
 654		int parent_fd = open("/proc/self/ns/user", O_RDONLY);
 655		if (parent_fd < 0) {
 656			TH_LOG("Child: failed to open parent /proc/self/ns/user: %s", strerror(errno));
 657			close(pipefd[1]);
 658			exit(1);
 659		}
 660
 661		TH_LOG("Child: opened parent userns fd %d", parent_fd);
 662
 663		if (ioctl(parent_fd, NS_GET_ID, &parent_id) < 0) {
 664			TH_LOG("Child: NS_GET_ID for parent failed: %s", strerror(errno));
 665			close(parent_fd);
 666			close(pipefd[1]);
 667			exit(1);
 668		}
 669		close(parent_fd);
 670
 671		TH_LOG("Child: got parent namespace ID %llu", (unsigned long long)parent_id);
 672
 673		/* Create child user namespace within parent */
 674		TH_LOG("Child: creating nested child user namespace");
 675		ret = setup_userns();
 676		if (ret < 0) {
 677			TH_LOG("Child: setup_userns() for child failed: %s", strerror(errno));
 678			close(pipefd[1]);
 679			exit(1);
 680		}
 681
 682		TH_LOG("Child: nested child user namespace created, uid=%d gid=%d", getuid(), getgid());
 683
 684		/* Get namespace ID for child user namespace */
 685		int child_fd = open("/proc/self/ns/user", O_RDONLY);
 686		if (child_fd < 0) {
 687			TH_LOG("Child: failed to open child /proc/self/ns/user: %s", strerror(errno));
 688			close(pipefd[1]);
 689			exit(1);
 690		}
 691
 692		TH_LOG("Child: opened child userns fd %d", child_fd);
 693
 694		if (ioctl(child_fd, NS_GET_ID, &child_id) < 0) {
 695			TH_LOG("Child: NS_GET_ID for child failed: %s", strerror(errno));
 696			close(child_fd);
 697			close(pipefd[1]);
 698			exit(1);
 699		}
 700		close(child_fd);
 701
 702		TH_LOG("Child: got child namespace ID %llu", (unsigned long long)child_id);
 703
 704		/* Send both namespace IDs to parent */
 705		TH_LOG("Child: sending both namespace IDs to parent");
 706		write(pipefd[1], &parent_id, sizeof(parent_id));
 707		write(pipefd[1], &child_id, sizeof(child_id));
 708		close(pipefd[1]);
 709
 710		TH_LOG("Child: exiting - parent userns should become inactive");
 711		/* Exit - parent user namespace should become inactive */
 712		exit(0);
 713	}
 714
 715	/* Parent process */
 716	close(pipefd[1]);
 717
 718	TH_LOG("Parent: reading both namespace IDs from child");
 719
 720	/* Read both namespace IDs - fixed size, no parsing needed */
 721	ret = read(pipefd[0], &parent_id, sizeof(parent_id));
 722	if (ret != sizeof(parent_id)) {
 723		close(pipefd[0]);
 724		waitpid(pid, NULL, 0);
 725		SKIP(return, "Failed to read parent namespace ID from child");
 726	}
 727
 728	ret = read(pipefd[0], &child_id, sizeof(child_id));
 729	close(pipefd[0]);
 730	if (ret != sizeof(child_id)) {
 731		waitpid(pid, NULL, 0);
 732		SKIP(return, "Failed to read child namespace ID from child");
 733	}
 734
 735	TH_LOG("Parent: received parent_id=%llu, child_id=%llu",
 736	       (unsigned long long)parent_id, (unsigned long long)child_id);
 737
 738	/* Construct file handles from namespace IDs */
 739	parent_handle = (struct file_handle *)parent_buf;
 740	parent_handle->handle_bytes = sizeof(struct nsfs_file_handle);
 741	parent_handle->handle_type = FILEID_NSFS;
 742	struct nsfs_file_handle *parent_fh = (struct nsfs_file_handle *)parent_handle->f_handle;
 743	parent_fh->ns_id = parent_id;
 744	parent_fh->ns_type = 0;
 745	parent_fh->ns_inum = 0;
 746
 747	child_handle = (struct file_handle *)child_buf;
 748	child_handle->handle_bytes = sizeof(struct nsfs_file_handle);
 749	child_handle->handle_type = FILEID_NSFS;
 750	struct nsfs_file_handle *child_fh = (struct nsfs_file_handle *)child_handle->f_handle;
 751	child_fh->ns_id = child_id;
 752	child_fh->ns_type = 0;
 753	child_fh->ns_inum = 0;
 754
 755	TH_LOG("Parent: opening child namespace BEFORE child exits");
 756
 757	/* Open child namespace while child is still alive to keep it active */
 758	child_nsfd = open_by_handle_at(FD_NSFS_ROOT, child_handle, O_RDONLY);
 759	if (child_nsfd < 0) {
 760		TH_LOG("Failed to open child namespace: %s (errno=%d)", strerror(errno), errno);
 761		waitpid(pid, NULL, 0);
 762		SKIP(return, "Failed to open child namespace");
 763	}
 764
 765	TH_LOG("Opened child namespace fd %d", child_nsfd);
 766
 767	/* Now wait for child to exit */
 768	TH_LOG("Parent: waiting for child to exit");
 769	waitpid(pid, &status, 0);
 770	ASSERT_TRUE(WIFEXITED(status));
 771	ASSERT_EQ(WEXITSTATUS(status), 0);
 772
 773	TH_LOG("Child process exited, parent holds fd to child namespace");
 774
 775	/*
 776	 * With hierarchical active reference propagation:
 777	 * Since the child namespace is active (parent process holds fd),
 778	 * the parent user namespace should ALSO be active automatically.
 779	 * This is because when we took an active reference on the child,
 780	 * it propagated up to the owning user namespace.
 781	 */
 782	TH_LOG("Attempting to reopen parent namespace (should SUCCEED - hierarchical propagation)");
 783	int parent_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
 784	ASSERT_GE(parent_fd, 0);
 785
 786	TH_LOG("SUCCESS: Parent namespace is active (fd=%d) due to active child", parent_fd);
 787
 788	/* Verify we can also get parent via NS_GET_USERNS */
 789	TH_LOG("Verifying NS_GET_USERNS also works");
 790	int parent_fd2 = ioctl(child_nsfd, NS_GET_USERNS);
 791	if (parent_fd2 < 0) {
 792		close(parent_fd);
 793		close(child_nsfd);
 794		TH_LOG("NS_GET_USERNS failed: %s (errno=%d)", strerror(errno), errno);
 795		SKIP(return, "NS_GET_USERNS not supported or failed");
 796	}
 797
 798	TH_LOG("NS_GET_USERNS succeeded, got parent fd %d", parent_fd2);
 799
 800	/* Verify both methods give us the same namespace */
 801	struct stat st1, st2;
 802	ASSERT_EQ(fstat(parent_fd, &st1), 0);
 803	ASSERT_EQ(fstat(parent_fd2, &st2), 0);
 804	TH_LOG("Parent namespace inodes: parent_fd=%lu, parent_fd2=%lu", st1.st_ino, st2.st_ino);
 805	ASSERT_EQ(st1.st_ino, st2.st_ino);
 806
 807	/*
 808	 * Close child fd - parent should remain active because we still
 809	 * hold direct references to it (parent_fd and parent_fd2).
 810	 */
 811	TH_LOG("Closing child fd - parent should remain active (direct refs held)");
 812	close(child_nsfd);
 813
 814	/* Parent should still be openable */
 815	TH_LOG("Verifying parent still active via file handle");
 816	int parent_fd3 = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
 817	ASSERT_GE(parent_fd3, 0);
 818	close(parent_fd3);
 819
 820	TH_LOG("Closing all fds to parent namespace");
 821	close(parent_fd);
 822	close(parent_fd2);
 823
 824	/* Both should now be inactive */
 825	TH_LOG("Attempting to reopen parent (should fail - inactive, no refs)");
 826	parent_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
 827	ASSERT_LT(parent_fd, 0);
 828	TH_LOG("Parent inactive as expected: %s (errno=%d)", strerror(errno), errno);
 829	ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
 830}
 831
 832/*
 833 * Test that bind mounts keep namespaces in the tree even when inactive
 834 */
 835TEST(ns_bind_mount_keeps_in_tree)
 836{
 837	struct file_handle *handle;
 838	int mount_id;
 839	int ret;
 840	int fd;
 841	int pipefd[2];
 842	pid_t pid;
 843	int status;
 844	char buf[sizeof(*handle) + MAX_HANDLE_SZ];
 845	char tmpfile[] = "/tmp/ns-test-XXXXXX";
 846	int tmpfd;
 847
 848	/* Create temporary file for bind mount */
 849	tmpfd = mkstemp(tmpfile);
 850	if (tmpfd < 0) {
 851		SKIP(return, "Cannot create temporary file");
 852	}
 853	close(tmpfd);
 854
 855	ASSERT_EQ(pipe(pipefd), 0);
 856
 857	pid = fork();
 858	ASSERT_GE(pid, 0);
 859
 860	if (pid == 0) {
 861		/* Child process */
 862		close(pipefd[0]);
 863
 864		/* Unshare mount namespace and make mounts private to avoid propagation */
 865		ret = unshare(CLONE_NEWNS);
 866		if (ret < 0) {
 867			close(pipefd[1]);
 868			unlink(tmpfile);
 869			exit(1);
 870		}
 871		ret = mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL);
 872		if (ret < 0) {
 873			close(pipefd[1]);
 874			unlink(tmpfile);
 875			exit(1);
 876		}
 877
 878		/* Create new network namespace */
 879		ret = unshare(CLONE_NEWNET);
 880		if (ret < 0) {
 881			close(pipefd[1]);
 882			unlink(tmpfile);
 883			exit(1);
 884		}
 885
 886		/* Bind mount the namespace */
 887		ret = mount("/proc/self/ns/net", tmpfile, NULL, MS_BIND, NULL);
 888		if (ret < 0) {
 889			close(pipefd[1]);
 890			unlink(tmpfile);
 891			exit(1);
 892		}
 893
 894		/* Get file handle */
 895		fd = open("/proc/self/ns/net", O_RDONLY);
 896		if (fd < 0) {
 897			umount(tmpfile);
 898			close(pipefd[1]);
 899			unlink(tmpfile);
 900			exit(1);
 901		}
 902
 903		handle = (struct file_handle *)buf;
 904		handle->handle_bytes = MAX_HANDLE_SZ;
 905		ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
 906		close(fd);
 907
 908		if (ret < 0) {
 909			umount(tmpfile);
 910			close(pipefd[1]);
 911			unlink(tmpfile);
 912			exit(1);
 913		}
 914
 915		/* Send handle to parent */
 916		write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
 917		close(pipefd[1]);
 918		exit(0);
 919	}
 920
 921	/* Parent */
 922	close(pipefd[1]);
 923	ret = read(pipefd[0], buf, sizeof(buf));
 924	close(pipefd[0]);
 925
 926	waitpid(pid, &status, 0);
 927	ASSERT_TRUE(WIFEXITED(status));
 928	ASSERT_EQ(WEXITSTATUS(status), 0);
 929
 930	ASSERT_GT(ret, 0);
 931	handle = (struct file_handle *)buf;
 932
 933	/*
 934	 * Namespace should be inactive but still in tree due to bind mount.
 935	 * Reopening should fail with ENOENT (inactive) not ESTALE (not in tree).
 936	 */
 937	fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
 938	ASSERT_LT(fd, 0);
 939	/* Should be ENOENT (inactive) since bind mount keeps it in tree */
 940	if (errno != ENOENT && errno != ESTALE) {
 941		TH_LOG("Unexpected error: %d", errno);
 942	}
 943
 944	/* Cleanup */
 945	umount(tmpfile);
 946	unlink(tmpfile);
 947}
 948
 949/*
 950 * Test multi-level hierarchy (3+ levels deep).
 951 * Grandparent → Parent → Child
 952 * When child is active, both parent AND grandparent should be active.
 953 */
 954TEST(ns_multilevel_hierarchy)
 955{
 956	struct file_handle *gp_handle, *p_handle, *c_handle;
 957	int ret, pipefd[2];
 958	pid_t pid;
 959	int status;
 960	__u64 gp_id, p_id, c_id;
 961	char gp_buf[sizeof(*gp_handle) + MAX_HANDLE_SZ];
 962	char p_buf[sizeof(*p_handle) + MAX_HANDLE_SZ];
 963	char c_buf[sizeof(*c_handle) + MAX_HANDLE_SZ];
 964
 965	ASSERT_EQ(pipe(pipefd), 0);
 966	pid = fork();
 967	ASSERT_GE(pid, 0);
 968
 969	if (pid == 0) {
 970		close(pipefd[0]);
 971
 972		/* Create grandparent user namespace */
 973		if (setup_userns() < 0) {
 974			close(pipefd[1]);
 975			exit(1);
 976		}
 977
 978		int gp_fd = open("/proc/self/ns/user", O_RDONLY);
 979		if (gp_fd < 0) {
 980			close(pipefd[1]);
 981			exit(1);
 982		}
 983		if (ioctl(gp_fd, NS_GET_ID, &gp_id) < 0) {
 984			close(gp_fd);
 985			close(pipefd[1]);
 986			exit(1);
 987		}
 988		close(gp_fd);
 989
 990		/* Create parent user namespace */
 991		if (setup_userns() < 0) {
 992			close(pipefd[1]);
 993			exit(1);
 994		}
 995
 996		int p_fd = open("/proc/self/ns/user", O_RDONLY);
 997		if (p_fd < 0) {
 998			close(pipefd[1]);
 999			exit(1);
1000		}
1001		if (ioctl(p_fd, NS_GET_ID, &p_id) < 0) {
1002			close(p_fd);
1003			close(pipefd[1]);
1004			exit(1);
1005		}
1006		close(p_fd);
1007
1008		/* Create child user namespace */
1009		if (setup_userns() < 0) {
1010			close(pipefd[1]);
1011			exit(1);
1012		}
1013
1014		int c_fd = open("/proc/self/ns/user", O_RDONLY);
1015		if (c_fd < 0) {
1016			close(pipefd[1]);
1017			exit(1);
1018		}
1019		if (ioctl(c_fd, NS_GET_ID, &c_id) < 0) {
1020			close(c_fd);
1021			close(pipefd[1]);
1022			exit(1);
1023		}
1024		close(c_fd);
1025
1026		/* Send all three namespace IDs */
1027		write(pipefd[1], &gp_id, sizeof(gp_id));
1028		write(pipefd[1], &p_id, sizeof(p_id));
1029		write(pipefd[1], &c_id, sizeof(c_id));
1030		close(pipefd[1]);
1031		exit(0);
1032	}
1033
1034	close(pipefd[1]);
1035
1036	/* Read all three namespace IDs - fixed size, no parsing needed */
1037	ret = read(pipefd[0], &gp_id, sizeof(gp_id));
1038	if (ret != sizeof(gp_id)) {
1039		close(pipefd[0]);
1040		waitpid(pid, NULL, 0);
1041		SKIP(return, "Failed to read grandparent namespace ID from child");
1042	}
1043
1044	ret = read(pipefd[0], &p_id, sizeof(p_id));
1045	if (ret != sizeof(p_id)) {
1046		close(pipefd[0]);
1047		waitpid(pid, NULL, 0);
1048		SKIP(return, "Failed to read parent namespace ID from child");
1049	}
1050
1051	ret = read(pipefd[0], &c_id, sizeof(c_id));
1052	close(pipefd[0]);
1053	if (ret != sizeof(c_id)) {
1054		waitpid(pid, NULL, 0);
1055		SKIP(return, "Failed to read child namespace ID from child");
1056	}
1057
1058	/* Construct file handles from namespace IDs */
1059	gp_handle = (struct file_handle *)gp_buf;
1060	gp_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1061	gp_handle->handle_type = FILEID_NSFS;
1062	struct nsfs_file_handle *gp_fh = (struct nsfs_file_handle *)gp_handle->f_handle;
1063	gp_fh->ns_id = gp_id;
1064	gp_fh->ns_type = 0;
1065	gp_fh->ns_inum = 0;
1066
1067	p_handle = (struct file_handle *)p_buf;
1068	p_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1069	p_handle->handle_type = FILEID_NSFS;
1070	struct nsfs_file_handle *p_fh = (struct nsfs_file_handle *)p_handle->f_handle;
1071	p_fh->ns_id = p_id;
1072	p_fh->ns_type = 0;
1073	p_fh->ns_inum = 0;
1074
1075	c_handle = (struct file_handle *)c_buf;
1076	c_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1077	c_handle->handle_type = FILEID_NSFS;
1078	struct nsfs_file_handle *c_fh = (struct nsfs_file_handle *)c_handle->f_handle;
1079	c_fh->ns_id = c_id;
1080	c_fh->ns_type = 0;
1081	c_fh->ns_inum = 0;
1082
1083	/* Open child before process exits */
1084	int c_fd = open_by_handle_at(FD_NSFS_ROOT, c_handle, O_RDONLY);
1085	if (c_fd < 0) {
1086		waitpid(pid, NULL, 0);
1087		SKIP(return, "Failed to open child namespace");
1088	}
1089
1090	waitpid(pid, &status, 0);
1091	ASSERT_TRUE(WIFEXITED(status));
1092	ASSERT_EQ(WEXITSTATUS(status), 0);
1093
1094	/*
1095	 * With 3-level hierarchy and child active:
1096	 * - Child is active (we hold fd)
1097	 * - Parent should be active (propagated from child)
1098	 * - Grandparent should be active (propagated from parent)
1099	 */
1100	TH_LOG("Testing parent active when child is active");
1101	int p_fd = open_by_handle_at(FD_NSFS_ROOT, p_handle, O_RDONLY);
1102	ASSERT_GE(p_fd, 0);
1103
1104	TH_LOG("Testing grandparent active when child is active");
1105	int gp_fd = open_by_handle_at(FD_NSFS_ROOT, gp_handle, O_RDONLY);
1106	ASSERT_GE(gp_fd, 0);
1107
1108	close(c_fd);
1109	close(p_fd);
1110	close(gp_fd);
1111}
1112
1113/*
1114 * Test multiple children sharing same parent.
1115 * Parent should stay active as long as ANY child is active.
1116 */
1117TEST(ns_multiple_children_same_parent)
1118{
1119	struct file_handle *p_handle, *c1_handle, *c2_handle;
1120	int ret, pipefd[2];
1121	pid_t pid;
1122	int status;
1123	__u64 p_id, c1_id, c2_id;
1124	char p_buf[sizeof(*p_handle) + MAX_HANDLE_SZ];
1125	char c1_buf[sizeof(*c1_handle) + MAX_HANDLE_SZ];
1126	char c2_buf[sizeof(*c2_handle) + MAX_HANDLE_SZ];
1127
1128	ASSERT_EQ(pipe(pipefd), 0);
1129	pid = fork();
1130	ASSERT_GE(pid, 0);
1131
1132	if (pid == 0) {
1133		close(pipefd[0]);
1134
1135		/* Create parent user namespace */
1136		if (setup_userns() < 0) {
1137			close(pipefd[1]);
1138			exit(1);
1139		}
1140
1141		int p_fd = open("/proc/self/ns/user", O_RDONLY);
1142		if (p_fd < 0) {
1143			close(pipefd[1]);
1144			exit(1);
1145		}
1146		if (ioctl(p_fd, NS_GET_ID, &p_id) < 0) {
1147			close(p_fd);
1148			close(pipefd[1]);
1149			exit(1);
1150		}
1151		close(p_fd);
1152
1153		/* Create first child user namespace */
1154		if (setup_userns() < 0) {
1155			close(pipefd[1]);
1156			exit(1);
1157		}
1158
1159		int c1_fd = open("/proc/self/ns/user", O_RDONLY);
1160		if (c1_fd < 0) {
1161			close(pipefd[1]);
1162			exit(1);
1163		}
1164		if (ioctl(c1_fd, NS_GET_ID, &c1_id) < 0) {
1165			close(c1_fd);
1166			close(pipefd[1]);
1167			exit(1);
1168		}
1169		close(c1_fd);
1170
1171		/* Return to parent user namespace and create second child */
1172		/* We can't actually do this easily, so let's create a sibling namespace
1173		 * by creating a network namespace instead */
1174		if (unshare(CLONE_NEWNET) < 0) {
1175			close(pipefd[1]);
1176			exit(1);
1177		}
1178
1179		int c2_fd = open("/proc/self/ns/net", O_RDONLY);
1180		if (c2_fd < 0) {
1181			close(pipefd[1]);
1182			exit(1);
1183		}
1184		if (ioctl(c2_fd, NS_GET_ID, &c2_id) < 0) {
1185			close(c2_fd);
1186			close(pipefd[1]);
1187			exit(1);
1188		}
1189		close(c2_fd);
1190
1191		/* Send all namespace IDs */
1192		write(pipefd[1], &p_id, sizeof(p_id));
1193		write(pipefd[1], &c1_id, sizeof(c1_id));
1194		write(pipefd[1], &c2_id, sizeof(c2_id));
1195		close(pipefd[1]);
1196		exit(0);
1197	}
1198
1199	close(pipefd[1]);
1200
1201	/* Read all three namespace IDs - fixed size, no parsing needed */
1202	ret = read(pipefd[0], &p_id, sizeof(p_id));
1203	if (ret != sizeof(p_id)) {
1204		close(pipefd[0]);
1205		waitpid(pid, NULL, 0);
1206		SKIP(return, "Failed to read parent namespace ID");
1207	}
1208
1209	ret = read(pipefd[0], &c1_id, sizeof(c1_id));
1210	if (ret != sizeof(c1_id)) {
1211		close(pipefd[0]);
1212		waitpid(pid, NULL, 0);
1213		SKIP(return, "Failed to read first child namespace ID");
1214	}
1215
1216	ret = read(pipefd[0], &c2_id, sizeof(c2_id));
1217	close(pipefd[0]);
1218	if (ret != sizeof(c2_id)) {
1219		waitpid(pid, NULL, 0);
1220		SKIP(return, "Failed to read second child namespace ID");
1221	}
1222
1223	/* Construct file handles from namespace IDs */
1224	p_handle = (struct file_handle *)p_buf;
1225	p_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1226	p_handle->handle_type = FILEID_NSFS;
1227	struct nsfs_file_handle *p_fh = (struct nsfs_file_handle *)p_handle->f_handle;
1228	p_fh->ns_id = p_id;
1229	p_fh->ns_type = 0;
1230	p_fh->ns_inum = 0;
1231
1232	c1_handle = (struct file_handle *)c1_buf;
1233	c1_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1234	c1_handle->handle_type = FILEID_NSFS;
1235	struct nsfs_file_handle *c1_fh = (struct nsfs_file_handle *)c1_handle->f_handle;
1236	c1_fh->ns_id = c1_id;
1237	c1_fh->ns_type = 0;
1238	c1_fh->ns_inum = 0;
1239
1240	c2_handle = (struct file_handle *)c2_buf;
1241	c2_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1242	c2_handle->handle_type = FILEID_NSFS;
1243	struct nsfs_file_handle *c2_fh = (struct nsfs_file_handle *)c2_handle->f_handle;
1244	c2_fh->ns_id = c2_id;
1245	c2_fh->ns_type = 0;
1246	c2_fh->ns_inum = 0;
1247
1248	/* Open both children before process exits */
1249	int c1_fd = open_by_handle_at(FD_NSFS_ROOT, c1_handle, O_RDONLY);
1250	int c2_fd = open_by_handle_at(FD_NSFS_ROOT, c2_handle, O_RDONLY);
1251
1252	if (c1_fd < 0 || c2_fd < 0) {
1253		if (c1_fd >= 0) close(c1_fd);
1254		if (c2_fd >= 0) close(c2_fd);
1255		waitpid(pid, NULL, 0);
1256		SKIP(return, "Failed to open child namespaces");
1257	}
1258
1259	waitpid(pid, &status, 0);
1260	ASSERT_TRUE(WIFEXITED(status));
1261	ASSERT_EQ(WEXITSTATUS(status), 0);
1262
1263	/* Parent should be active (both children active) */
1264	TH_LOG("Both children active - parent should be active");
1265	int p_fd = open_by_handle_at(FD_NSFS_ROOT, p_handle, O_RDONLY);
1266	ASSERT_GE(p_fd, 0);
1267	close(p_fd);
1268
1269	/* Close first child - parent should STILL be active */
1270	TH_LOG("Closing first child - parent should still be active");
1271	close(c1_fd);
1272	p_fd = open_by_handle_at(FD_NSFS_ROOT, p_handle, O_RDONLY);
1273	ASSERT_GE(p_fd, 0);
1274	close(p_fd);
1275
1276	/* Close second child - NOW parent should become inactive */
1277	TH_LOG("Closing second child - parent should become inactive");
1278	close(c2_fd);
1279	p_fd = open_by_handle_at(FD_NSFS_ROOT, p_handle, O_RDONLY);
1280	ASSERT_LT(p_fd, 0);
1281}
1282
1283/*
1284 * Test that different namespace types with same owner all contribute
1285 * active references to the owning user namespace.
1286 */
1287TEST(ns_different_types_same_owner)
1288{
1289	struct file_handle *u_handle, *n_handle, *ut_handle;
1290	int ret, pipefd[2];
1291	pid_t pid;
1292	int status;
1293	__u64 u_id, n_id, ut_id;
1294	char u_buf[sizeof(*u_handle) + MAX_HANDLE_SZ];
1295	char n_buf[sizeof(*n_handle) + MAX_HANDLE_SZ];
1296	char ut_buf[sizeof(*ut_handle) + MAX_HANDLE_SZ];
1297
1298	ASSERT_EQ(pipe(pipefd), 0);
1299	pid = fork();
1300	ASSERT_GE(pid, 0);
1301
1302	if (pid == 0) {
1303		close(pipefd[0]);
1304
1305		/* Create user namespace */
1306		if (setup_userns() < 0) {
1307			close(pipefd[1]);
1308			exit(1);
1309		}
1310
1311		int u_fd = open("/proc/self/ns/user", O_RDONLY);
1312		if (u_fd < 0) {
1313			close(pipefd[1]);
1314			exit(1);
1315		}
1316		if (ioctl(u_fd, NS_GET_ID, &u_id) < 0) {
1317			close(u_fd);
1318			close(pipefd[1]);
1319			exit(1);
1320		}
1321		close(u_fd);
1322
1323		/* Create network namespace (owned by user namespace) */
1324		if (unshare(CLONE_NEWNET) < 0) {
1325			close(pipefd[1]);
1326			exit(1);
1327		}
1328
1329		int n_fd = open("/proc/self/ns/net", O_RDONLY);
1330		if (n_fd < 0) {
1331			close(pipefd[1]);
1332			exit(1);
1333		}
1334		if (ioctl(n_fd, NS_GET_ID, &n_id) < 0) {
1335			close(n_fd);
1336			close(pipefd[1]);
1337			exit(1);
1338		}
1339		close(n_fd);
1340
1341		/* Create UTS namespace (also owned by user namespace) */
1342		if (unshare(CLONE_NEWUTS) < 0) {
1343			close(pipefd[1]);
1344			exit(1);
1345		}
1346
1347		int ut_fd = open("/proc/self/ns/uts", O_RDONLY);
1348		if (ut_fd < 0) {
1349			close(pipefd[1]);
1350			exit(1);
1351		}
1352		if (ioctl(ut_fd, NS_GET_ID, &ut_id) < 0) {
1353			close(ut_fd);
1354			close(pipefd[1]);
1355			exit(1);
1356		}
1357		close(ut_fd);
1358
1359		/* Send all namespace IDs */
1360		write(pipefd[1], &u_id, sizeof(u_id));
1361		write(pipefd[1], &n_id, sizeof(n_id));
1362		write(pipefd[1], &ut_id, sizeof(ut_id));
1363		close(pipefd[1]);
1364		exit(0);
1365	}
1366
1367	close(pipefd[1]);
1368
1369	/* Read all three namespace IDs - fixed size, no parsing needed */
1370	ret = read(pipefd[0], &u_id, sizeof(u_id));
1371	if (ret != sizeof(u_id)) {
1372		close(pipefd[0]);
1373		waitpid(pid, NULL, 0);
1374		SKIP(return, "Failed to read user namespace ID");
1375	}
1376
1377	ret = read(pipefd[0], &n_id, sizeof(n_id));
1378	if (ret != sizeof(n_id)) {
1379		close(pipefd[0]);
1380		waitpid(pid, NULL, 0);
1381		SKIP(return, "Failed to read network namespace ID");
1382	}
1383
1384	ret = read(pipefd[0], &ut_id, sizeof(ut_id));
1385	close(pipefd[0]);
1386	if (ret != sizeof(ut_id)) {
1387		waitpid(pid, NULL, 0);
1388		SKIP(return, "Failed to read UTS namespace ID");
1389	}
1390
1391	/* Construct file handles from namespace IDs */
1392	u_handle = (struct file_handle *)u_buf;
1393	u_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1394	u_handle->handle_type = FILEID_NSFS;
1395	struct nsfs_file_handle *u_fh = (struct nsfs_file_handle *)u_handle->f_handle;
1396	u_fh->ns_id = u_id;
1397	u_fh->ns_type = 0;
1398	u_fh->ns_inum = 0;
1399
1400	n_handle = (struct file_handle *)n_buf;
1401	n_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1402	n_handle->handle_type = FILEID_NSFS;
1403	struct nsfs_file_handle *n_fh = (struct nsfs_file_handle *)n_handle->f_handle;
1404	n_fh->ns_id = n_id;
1405	n_fh->ns_type = 0;
1406	n_fh->ns_inum = 0;
1407
1408	ut_handle = (struct file_handle *)ut_buf;
1409	ut_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1410	ut_handle->handle_type = FILEID_NSFS;
1411	struct nsfs_file_handle *ut_fh = (struct nsfs_file_handle *)ut_handle->f_handle;
1412	ut_fh->ns_id = ut_id;
1413	ut_fh->ns_type = 0;
1414	ut_fh->ns_inum = 0;
1415
1416	/* Open both non-user namespaces before process exits */
1417	int n_fd = open_by_handle_at(FD_NSFS_ROOT, n_handle, O_RDONLY);
1418	int ut_fd = open_by_handle_at(FD_NSFS_ROOT, ut_handle, O_RDONLY);
1419
1420	if (n_fd < 0 || ut_fd < 0) {
1421		if (n_fd >= 0) close(n_fd);
1422		if (ut_fd >= 0) close(ut_fd);
1423		waitpid(pid, NULL, 0);
1424		SKIP(return, "Failed to open namespaces");
1425	}
1426
1427	waitpid(pid, &status, 0);
1428	ASSERT_TRUE(WIFEXITED(status));
1429	ASSERT_EQ(WEXITSTATUS(status), 0);
1430
1431	/*
1432	 * Both network and UTS namespaces are active.
1433	 * User namespace should be active (gets 2 active refs).
1434	 */
1435	TH_LOG("Both net and uts active - user namespace should be active");
1436	int u_fd = open_by_handle_at(FD_NSFS_ROOT, u_handle, O_RDONLY);
1437	ASSERT_GE(u_fd, 0);
1438	close(u_fd);
1439
1440	/* Close network namespace - user namespace should STILL be active */
1441	TH_LOG("Closing network ns - user ns should still be active (uts still active)");
1442	close(n_fd);
1443	u_fd = open_by_handle_at(FD_NSFS_ROOT, u_handle, O_RDONLY);
1444	ASSERT_GE(u_fd, 0);
1445	close(u_fd);
1446
1447	/* Close UTS namespace - user namespace should become inactive */
1448	TH_LOG("Closing uts ns - user ns should become inactive");
1449	close(ut_fd);
1450	u_fd = open_by_handle_at(FD_NSFS_ROOT, u_handle, O_RDONLY);
1451	ASSERT_LT(u_fd, 0);
1452}
1453
1454/*
1455 * Test hierarchical propagation with deep namespace hierarchy.
1456 * Create: init_user_ns -> user_A -> user_B -> net_ns
1457 * When net_ns is active, both user_A and user_B should be active.
1458 * This verifies the conditional recursion in __ns_ref_active_put() works.
1459 */
1460TEST(ns_deep_hierarchy_propagation)
1461{
1462	struct file_handle *ua_handle, *ub_handle, *net_handle;
1463	int ret, pipefd[2];
1464	pid_t pid;
1465	int status;
1466	__u64 ua_id, ub_id, net_id;
1467	char ua_buf[sizeof(*ua_handle) + MAX_HANDLE_SZ];
1468	char ub_buf[sizeof(*ub_handle) + MAX_HANDLE_SZ];
1469	char net_buf[sizeof(*net_handle) + MAX_HANDLE_SZ];
1470
1471	ASSERT_EQ(pipe(pipefd), 0);
1472	pid = fork();
1473	ASSERT_GE(pid, 0);
1474
1475	if (pid == 0) {
1476		close(pipefd[0]);
1477
1478		/* Create user_A -> user_B -> net hierarchy */
1479		if (setup_userns() < 0) {
1480			close(pipefd[1]);
1481			exit(1);
1482		}
1483
1484		int ua_fd = open("/proc/self/ns/user", O_RDONLY);
1485		if (ua_fd < 0) {
1486			close(pipefd[1]);
1487			exit(1);
1488		}
1489		if (ioctl(ua_fd, NS_GET_ID, &ua_id) < 0) {
1490			close(ua_fd);
1491			close(pipefd[1]);
1492			exit(1);
1493		}
1494		close(ua_fd);
1495
1496		if (setup_userns() < 0) {
1497			close(pipefd[1]);
1498			exit(1);
1499		}
1500
1501		int ub_fd = open("/proc/self/ns/user", O_RDONLY);
1502		if (ub_fd < 0) {
1503			close(pipefd[1]);
1504			exit(1);
1505		}
1506		if (ioctl(ub_fd, NS_GET_ID, &ub_id) < 0) {
1507			close(ub_fd);
1508			close(pipefd[1]);
1509			exit(1);
1510		}
1511		close(ub_fd);
1512
1513		if (unshare(CLONE_NEWNET) < 0) {
1514			close(pipefd[1]);
1515			exit(1);
1516		}
1517
1518		int net_fd = open("/proc/self/ns/net", O_RDONLY);
1519		if (net_fd < 0) {
1520			close(pipefd[1]);
1521			exit(1);
1522		}
1523		if (ioctl(net_fd, NS_GET_ID, &net_id) < 0) {
1524			close(net_fd);
1525			close(pipefd[1]);
1526			exit(1);
1527		}
1528		close(net_fd);
1529
1530		/* Send all three namespace IDs */
1531		write(pipefd[1], &ua_id, sizeof(ua_id));
1532		write(pipefd[1], &ub_id, sizeof(ub_id));
1533		write(pipefd[1], &net_id, sizeof(net_id));
1534		close(pipefd[1]);
1535		exit(0);
1536	}
1537
1538	close(pipefd[1]);
1539
1540	/* Read all three namespace IDs - fixed size, no parsing needed */
1541	ret = read(pipefd[0], &ua_id, sizeof(ua_id));
1542	if (ret != sizeof(ua_id)) {
1543		close(pipefd[0]);
1544		waitpid(pid, NULL, 0);
1545		SKIP(return, "Failed to read user_A namespace ID");
1546	}
1547
1548	ret = read(pipefd[0], &ub_id, sizeof(ub_id));
1549	if (ret != sizeof(ub_id)) {
1550		close(pipefd[0]);
1551		waitpid(pid, NULL, 0);
1552		SKIP(return, "Failed to read user_B namespace ID");
1553	}
1554
1555	ret = read(pipefd[0], &net_id, sizeof(net_id));
1556	close(pipefd[0]);
1557	if (ret != sizeof(net_id)) {
1558		waitpid(pid, NULL, 0);
1559		SKIP(return, "Failed to read network namespace ID");
1560	}
1561
1562	/* Construct file handles from namespace IDs */
1563	ua_handle = (struct file_handle *)ua_buf;
1564	ua_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1565	ua_handle->handle_type = FILEID_NSFS;
1566	struct nsfs_file_handle *ua_fh = (struct nsfs_file_handle *)ua_handle->f_handle;
1567	ua_fh->ns_id = ua_id;
1568	ua_fh->ns_type = 0;
1569	ua_fh->ns_inum = 0;
1570
1571	ub_handle = (struct file_handle *)ub_buf;
1572	ub_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1573	ub_handle->handle_type = FILEID_NSFS;
1574	struct nsfs_file_handle *ub_fh = (struct nsfs_file_handle *)ub_handle->f_handle;
1575	ub_fh->ns_id = ub_id;
1576	ub_fh->ns_type = 0;
1577	ub_fh->ns_inum = 0;
1578
1579	net_handle = (struct file_handle *)net_buf;
1580	net_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1581	net_handle->handle_type = FILEID_NSFS;
1582	struct nsfs_file_handle *net_fh = (struct nsfs_file_handle *)net_handle->f_handle;
1583	net_fh->ns_id = net_id;
1584	net_fh->ns_type = 0;
1585	net_fh->ns_inum = 0;
1586
1587	/* Open net_ns before child exits to keep it active */
1588	int net_fd = open_by_handle_at(FD_NSFS_ROOT, net_handle, O_RDONLY);
1589	if (net_fd < 0) {
1590		waitpid(pid, NULL, 0);
1591		SKIP(return, "Failed to open network namespace");
1592	}
1593
1594	waitpid(pid, &status, 0);
1595	ASSERT_TRUE(WIFEXITED(status));
1596	ASSERT_EQ(WEXITSTATUS(status), 0);
1597
1598	/* With net_ns active, both user_A and user_B should be active */
1599	TH_LOG("Testing user_B active (net_ns active causes propagation)");
1600	int ub_fd = open_by_handle_at(FD_NSFS_ROOT, ub_handle, O_RDONLY);
1601	ASSERT_GE(ub_fd, 0);
1602
1603	TH_LOG("Testing user_A active (propagated through user_B)");
1604	int ua_fd = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
1605	ASSERT_GE(ua_fd, 0);
1606
1607	/* Close net_ns - user_B should stay active (we hold direct ref) */
1608	TH_LOG("Closing net_ns, user_B should remain active (direct ref held)");
1609	close(net_fd);
1610	int ub_fd2 = open_by_handle_at(FD_NSFS_ROOT, ub_handle, O_RDONLY);
1611	ASSERT_GE(ub_fd2, 0);
1612	close(ub_fd2);
1613
1614	/* Close user_B - user_A should stay active (we hold direct ref) */
1615	TH_LOG("Closing user_B, user_A should remain active (direct ref held)");
1616	close(ub_fd);
1617	int ua_fd2 = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
1618	ASSERT_GE(ua_fd2, 0);
1619	close(ua_fd2);
1620
1621	/* Close user_A - everything should become inactive */
1622	TH_LOG("Closing user_A, all should become inactive");
1623	close(ua_fd);
1624
1625	/* All should now be inactive */
1626	ua_fd = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
1627	ASSERT_LT(ua_fd, 0);
1628}
1629
1630/*
1631 * Test that parent stays active as long as ANY child is active.
1632 * Create parent user namespace with two child net namespaces.
1633 * Parent should remain active until BOTH children are inactive.
1634 */
1635TEST(ns_parent_multiple_children_refcount)
1636{
1637	struct file_handle *parent_handle, *net1_handle, *net2_handle;
1638	int ret, pipefd[2], syncpipe[2];
1639	pid_t pid;
1640	int status;
1641	__u64 p_id, n1_id, n2_id;
1642	char p_buf[sizeof(*parent_handle) + MAX_HANDLE_SZ];
1643	char n1_buf[sizeof(*net1_handle) + MAX_HANDLE_SZ];
1644	char n2_buf[sizeof(*net2_handle) + MAX_HANDLE_SZ];
1645	char sync_byte;
1646
1647	ASSERT_EQ(pipe(pipefd), 0);
1648	ASSERT_EQ(pipe(syncpipe), 0);
1649	pid = fork();
1650	ASSERT_GE(pid, 0);
1651
1652	if (pid == 0) {
1653		close(pipefd[0]);
1654		close(syncpipe[1]);
1655
1656		/* Create parent user namespace */
1657		if (setup_userns() < 0) {
1658			close(pipefd[1]);
1659			exit(1);
1660		}
1661
1662		int p_fd = open("/proc/self/ns/user", O_RDONLY);
1663		if (p_fd < 0) {
1664			close(pipefd[1]);
1665			exit(1);
1666		}
1667		if (ioctl(p_fd, NS_GET_ID, &p_id) < 0) {
1668			close(p_fd);
1669			close(pipefd[1]);
1670			exit(1);
1671		}
1672		close(p_fd);
1673
1674		/* Create first network namespace */
1675		if (unshare(CLONE_NEWNET) < 0) {
1676			close(pipefd[1]);
1677			close(syncpipe[0]);
1678			exit(1);
1679		}
1680
1681		int n1_fd = open("/proc/self/ns/net", O_RDONLY);
1682		if (n1_fd < 0) {
1683			close(pipefd[1]);
1684			close(syncpipe[0]);
1685			exit(1);
1686		}
1687		if (ioctl(n1_fd, NS_GET_ID, &n1_id) < 0) {
1688			close(n1_fd);
1689			close(pipefd[1]);
1690			close(syncpipe[0]);
1691			exit(1);
1692		}
1693		/* Keep n1_fd open so first namespace stays active */
1694
1695		/* Create second network namespace */
1696		if (unshare(CLONE_NEWNET) < 0) {
1697			close(n1_fd);
1698			close(pipefd[1]);
1699			close(syncpipe[0]);
1700			exit(1);
1701		}
1702
1703		int n2_fd = open("/proc/self/ns/net", O_RDONLY);
1704		if (n2_fd < 0) {
1705			close(n1_fd);
1706			close(pipefd[1]);
1707			close(syncpipe[0]);
1708			exit(1);
1709		}
1710		if (ioctl(n2_fd, NS_GET_ID, &n2_id) < 0) {
1711			close(n1_fd);
1712			close(n2_fd);
1713			close(pipefd[1]);
1714			close(syncpipe[0]);
1715			exit(1);
1716		}
1717		/* Keep both n1_fd and n2_fd open */
1718
1719		/* Send all namespace IDs */
1720		write(pipefd[1], &p_id, sizeof(p_id));
1721		write(pipefd[1], &n1_id, sizeof(n1_id));
1722		write(pipefd[1], &n2_id, sizeof(n2_id));
1723		close(pipefd[1]);
1724
1725		/* Wait for parent to signal before exiting */
1726		read(syncpipe[0], &sync_byte, 1);
1727		close(syncpipe[0]);
1728		exit(0);
1729	}
1730
1731	close(pipefd[1]);
1732	close(syncpipe[0]);
1733
1734	/* Read all three namespace IDs - fixed size, no parsing needed */
1735	ret = read(pipefd[0], &p_id, sizeof(p_id));
1736	if (ret != sizeof(p_id)) {
1737		close(pipefd[0]);
1738		waitpid(pid, NULL, 0);
1739		SKIP(return, "Failed to read parent namespace ID");
1740	}
1741
1742	ret = read(pipefd[0], &n1_id, sizeof(n1_id));
1743	if (ret != sizeof(n1_id)) {
1744		close(pipefd[0]);
1745		waitpid(pid, NULL, 0);
1746		SKIP(return, "Failed to read first network namespace ID");
1747	}
1748
1749	ret = read(pipefd[0], &n2_id, sizeof(n2_id));
1750	close(pipefd[0]);
1751	if (ret != sizeof(n2_id)) {
1752		waitpid(pid, NULL, 0);
1753		SKIP(return, "Failed to read second network namespace ID");
1754	}
1755
1756	/* Construct file handles from namespace IDs */
1757	parent_handle = (struct file_handle *)p_buf;
1758	parent_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1759	parent_handle->handle_type = FILEID_NSFS;
1760	struct nsfs_file_handle *p_fh = (struct nsfs_file_handle *)parent_handle->f_handle;
1761	p_fh->ns_id = p_id;
1762	p_fh->ns_type = 0;
1763	p_fh->ns_inum = 0;
1764
1765	net1_handle = (struct file_handle *)n1_buf;
1766	net1_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1767	net1_handle->handle_type = FILEID_NSFS;
1768	struct nsfs_file_handle *n1_fh = (struct nsfs_file_handle *)net1_handle->f_handle;
1769	n1_fh->ns_id = n1_id;
1770	n1_fh->ns_type = 0;
1771	n1_fh->ns_inum = 0;
1772
1773	net2_handle = (struct file_handle *)n2_buf;
1774	net2_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1775	net2_handle->handle_type = FILEID_NSFS;
1776	struct nsfs_file_handle *n2_fh = (struct nsfs_file_handle *)net2_handle->f_handle;
1777	n2_fh->ns_id = n2_id;
1778	n2_fh->ns_type = 0;
1779	n2_fh->ns_inum = 0;
1780
1781	/* Open both net namespaces while child is still alive */
1782	int n1_fd = open_by_handle_at(FD_NSFS_ROOT, net1_handle, O_RDONLY);
1783	int n2_fd = open_by_handle_at(FD_NSFS_ROOT, net2_handle, O_RDONLY);
1784	if (n1_fd < 0 || n2_fd < 0) {
1785		if (n1_fd >= 0) close(n1_fd);
1786		if (n2_fd >= 0) close(n2_fd);
1787		sync_byte = 'G';
1788		write(syncpipe[1], &sync_byte, 1);
1789		close(syncpipe[1]);
1790		waitpid(pid, NULL, 0);
1791		SKIP(return, "Failed to open net namespaces");
1792	}
1793
1794	/* Signal child that we have opened the namespaces */
1795	sync_byte = 'G';
1796	write(syncpipe[1], &sync_byte, 1);
1797	close(syncpipe[1]);
1798
1799	/* Wait for child to exit */
1800	waitpid(pid, &status, 0);
1801	ASSERT_TRUE(WIFEXITED(status));
1802	ASSERT_EQ(WEXITSTATUS(status), 0);
1803
1804	/* Parent should be active (has 2 active children) */
1805	TH_LOG("Both net namespaces active - parent should be active");
1806	int p_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
1807	ASSERT_GE(p_fd, 0);
1808	close(p_fd);
1809
1810	/* Close first net namespace - parent should STILL be active */
1811	TH_LOG("Closing first net ns - parent should still be active");
1812	close(n1_fd);
1813	p_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
1814	ASSERT_GE(p_fd, 0);
1815	close(p_fd);
1816
1817	/* Close second net namespace - parent should become inactive */
1818	TH_LOG("Closing second net ns - parent should become inactive");
1819	close(n2_fd);
1820	p_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
1821	ASSERT_LT(p_fd, 0);
1822}
1823
1824/*
1825 * Test that user namespace as a child also propagates correctly.
1826 * Create user_A -> user_B, verify when user_B is active that user_A
1827 * is also active. This is different from non-user namespace children.
1828 */
1829TEST(ns_userns_child_propagation)
1830{
1831	struct file_handle *ua_handle, *ub_handle;
1832	int ret, pipefd[2];
1833	pid_t pid;
1834	int status;
1835	__u64 ua_id, ub_id;
1836	char ua_buf[sizeof(*ua_handle) + MAX_HANDLE_SZ];
1837	char ub_buf[sizeof(*ub_handle) + MAX_HANDLE_SZ];
1838
1839	ASSERT_EQ(pipe(pipefd), 0);
1840	pid = fork();
1841	ASSERT_GE(pid, 0);
1842
1843	if (pid == 0) {
1844		close(pipefd[0]);
1845
1846		/* Create user_A */
1847		if (setup_userns() < 0) {
1848			close(pipefd[1]);
1849			exit(1);
1850		}
1851
1852		int ua_fd = open("/proc/self/ns/user", O_RDONLY);
1853		if (ua_fd < 0) {
1854			close(pipefd[1]);
1855			exit(1);
1856		}
1857		if (ioctl(ua_fd, NS_GET_ID, &ua_id) < 0) {
1858			close(ua_fd);
1859			close(pipefd[1]);
1860			exit(1);
1861		}
1862		close(ua_fd);
1863
1864		/* Create user_B (child of user_A) */
1865		if (setup_userns() < 0) {
1866			close(pipefd[1]);
1867			exit(1);
1868		}
1869
1870		int ub_fd = open("/proc/self/ns/user", O_RDONLY);
1871		if (ub_fd < 0) {
1872			close(pipefd[1]);
1873			exit(1);
1874		}
1875		if (ioctl(ub_fd, NS_GET_ID, &ub_id) < 0) {
1876			close(ub_fd);
1877			close(pipefd[1]);
1878			exit(1);
1879		}
1880		close(ub_fd);
1881
1882		/* Send both namespace IDs */
1883		write(pipefd[1], &ua_id, sizeof(ua_id));
1884		write(pipefd[1], &ub_id, sizeof(ub_id));
1885		close(pipefd[1]);
1886		exit(0);
1887	}
1888
1889	close(pipefd[1]);
1890
1891	/* Read both namespace IDs - fixed size, no parsing needed */
1892	ret = read(pipefd[0], &ua_id, sizeof(ua_id));
1893	if (ret != sizeof(ua_id)) {
1894		close(pipefd[0]);
1895		waitpid(pid, NULL, 0);
1896		SKIP(return, "Failed to read user_A namespace ID");
1897	}
1898
1899	ret = read(pipefd[0], &ub_id, sizeof(ub_id));
1900	close(pipefd[0]);
1901	if (ret != sizeof(ub_id)) {
1902		waitpid(pid, NULL, 0);
1903		SKIP(return, "Failed to read user_B namespace ID");
1904	}
1905
1906	/* Construct file handles from namespace IDs */
1907	ua_handle = (struct file_handle *)ua_buf;
1908	ua_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1909	ua_handle->handle_type = FILEID_NSFS;
1910	struct nsfs_file_handle *ua_fh = (struct nsfs_file_handle *)ua_handle->f_handle;
1911	ua_fh->ns_id = ua_id;
1912	ua_fh->ns_type = 0;
1913	ua_fh->ns_inum = 0;
1914
1915	ub_handle = (struct file_handle *)ub_buf;
1916	ub_handle->handle_bytes = sizeof(struct nsfs_file_handle);
1917	ub_handle->handle_type = FILEID_NSFS;
1918	struct nsfs_file_handle *ub_fh = (struct nsfs_file_handle *)ub_handle->f_handle;
1919	ub_fh->ns_id = ub_id;
1920	ub_fh->ns_type = 0;
1921	ub_fh->ns_inum = 0;
1922
1923	/* Open user_B before child exits */
1924	int ub_fd = open_by_handle_at(FD_NSFS_ROOT, ub_handle, O_RDONLY);
1925	if (ub_fd < 0) {
1926		waitpid(pid, NULL, 0);
1927		SKIP(return, "Failed to open user_B");
1928	}
1929
1930	waitpid(pid, &status, 0);
1931	ASSERT_TRUE(WIFEXITED(status));
1932	ASSERT_EQ(WEXITSTATUS(status), 0);
1933
1934	/* With user_B active, user_A should also be active */
1935	TH_LOG("Testing user_A active when child user_B is active");
1936	int ua_fd = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
1937	ASSERT_GE(ua_fd, 0);
1938
1939	/* Close user_B */
1940	TH_LOG("Closing user_B");
1941	close(ub_fd);
1942
1943	/* user_A should remain active (we hold direct ref) */
1944	int ua_fd2 = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
1945	ASSERT_GE(ua_fd2, 0);
1946	close(ua_fd2);
1947
1948	/* Close user_A - should become inactive */
1949	TH_LOG("Closing user_A - should become inactive");
1950	close(ua_fd);
1951
1952	ua_fd = open_by_handle_at(FD_NSFS_ROOT, ua_handle, O_RDONLY);
1953	ASSERT_LT(ua_fd, 0);
1954}
1955
1956/*
1957 * Test different namespace types (net, uts, ipc) all contributing
1958 * active references to the same owning user namespace.
1959 */
1960TEST(ns_mixed_types_same_owner)
1961{
1962	struct file_handle *user_handle, *net_handle, *uts_handle;
1963	int ret, pipefd[2];
1964	pid_t pid;
1965	int status;
1966	__u64 u_id, n_id, ut_id;
1967	char u_buf[sizeof(*user_handle) + MAX_HANDLE_SZ];
1968	char n_buf[sizeof(*net_handle) + MAX_HANDLE_SZ];
1969	char ut_buf[sizeof(*uts_handle) + MAX_HANDLE_SZ];
1970
1971	ASSERT_EQ(pipe(pipefd), 0);
1972	pid = fork();
1973	ASSERT_GE(pid, 0);
1974
1975	if (pid == 0) {
1976		close(pipefd[0]);
1977
1978		if (setup_userns() < 0) {
1979			close(pipefd[1]);
1980			exit(1);
1981		}
1982
1983		int u_fd = open("/proc/self/ns/user", O_RDONLY);
1984		if (u_fd < 0) {
1985			close(pipefd[1]);
1986			exit(1);
1987		}
1988		if (ioctl(u_fd, NS_GET_ID, &u_id) < 0) {
1989			close(u_fd);
1990			close(pipefd[1]);
1991			exit(1);
1992		}
1993		close(u_fd);
1994
1995		if (unshare(CLONE_NEWNET) < 0) {
1996			close(pipefd[1]);
1997			exit(1);
1998		}
1999
2000		int n_fd = open("/proc/self/ns/net", O_RDONLY);
2001		if (n_fd < 0) {
2002			close(pipefd[1]);
2003			exit(1);
2004		}
2005		if (ioctl(n_fd, NS_GET_ID, &n_id) < 0) {
2006			close(n_fd);
2007			close(pipefd[1]);
2008			exit(1);
2009		}
2010		close(n_fd);
2011
2012		if (unshare(CLONE_NEWUTS) < 0) {
2013			close(pipefd[1]);
2014			exit(1);
2015		}
2016
2017		int ut_fd = open("/proc/self/ns/uts", O_RDONLY);
2018		if (ut_fd < 0) {
2019			close(pipefd[1]);
2020			exit(1);
2021		}
2022		if (ioctl(ut_fd, NS_GET_ID, &ut_id) < 0) {
2023			close(ut_fd);
2024			close(pipefd[1]);
2025			exit(1);
2026		}
2027		close(ut_fd);
2028
2029		/* Send all namespace IDs */
2030		write(pipefd[1], &u_id, sizeof(u_id));
2031		write(pipefd[1], &n_id, sizeof(n_id));
2032		write(pipefd[1], &ut_id, sizeof(ut_id));
2033		close(pipefd[1]);
2034		exit(0);
2035	}
2036
2037	close(pipefd[1]);
2038
2039	/* Read all three namespace IDs - fixed size, no parsing needed */
2040	ret = read(pipefd[0], &u_id, sizeof(u_id));
2041	if (ret != sizeof(u_id)) {
2042		close(pipefd[0]);
2043		waitpid(pid, NULL, 0);
2044		SKIP(return, "Failed to read user namespace ID");
2045	}
2046
2047	ret = read(pipefd[0], &n_id, sizeof(n_id));
2048	if (ret != sizeof(n_id)) {
2049		close(pipefd[0]);
2050		waitpid(pid, NULL, 0);
2051		SKIP(return, "Failed to read network namespace ID");
2052	}
2053
2054	ret = read(pipefd[0], &ut_id, sizeof(ut_id));
2055	close(pipefd[0]);
2056	if (ret != sizeof(ut_id)) {
2057		waitpid(pid, NULL, 0);
2058		SKIP(return, "Failed to read UTS namespace ID");
2059	}
2060
2061	/* Construct file handles from namespace IDs */
2062	user_handle = (struct file_handle *)u_buf;
2063	user_handle->handle_bytes = sizeof(struct nsfs_file_handle);
2064	user_handle->handle_type = FILEID_NSFS;
2065	struct nsfs_file_handle *u_fh = (struct nsfs_file_handle *)user_handle->f_handle;
2066	u_fh->ns_id = u_id;
2067	u_fh->ns_type = 0;
2068	u_fh->ns_inum = 0;
2069
2070	net_handle = (struct file_handle *)n_buf;
2071	net_handle->handle_bytes = sizeof(struct nsfs_file_handle);
2072	net_handle->handle_type = FILEID_NSFS;
2073	struct nsfs_file_handle *n_fh = (struct nsfs_file_handle *)net_handle->f_handle;
2074	n_fh->ns_id = n_id;
2075	n_fh->ns_type = 0;
2076	n_fh->ns_inum = 0;
2077
2078	uts_handle = (struct file_handle *)ut_buf;
2079	uts_handle->handle_bytes = sizeof(struct nsfs_file_handle);
2080	uts_handle->handle_type = FILEID_NSFS;
2081	struct nsfs_file_handle *ut_fh = (struct nsfs_file_handle *)uts_handle->f_handle;
2082	ut_fh->ns_id = ut_id;
2083	ut_fh->ns_type = 0;
2084	ut_fh->ns_inum = 0;
2085
2086	/* Open both non-user namespaces */
2087	int n_fd = open_by_handle_at(FD_NSFS_ROOT, net_handle, O_RDONLY);
2088	int ut_fd = open_by_handle_at(FD_NSFS_ROOT, uts_handle, O_RDONLY);
2089	if (n_fd < 0 || ut_fd < 0) {
2090		if (n_fd >= 0) close(n_fd);
2091		if (ut_fd >= 0) close(ut_fd);
2092		waitpid(pid, NULL, 0);
2093		SKIP(return, "Failed to open namespaces");
2094	}
2095
2096	waitpid(pid, &status, 0);
2097	ASSERT_TRUE(WIFEXITED(status));
2098	ASSERT_EQ(WEXITSTATUS(status), 0);
2099
2100	/* User namespace should be active (2 active children) */
2101	TH_LOG("Both net and uts active - user ns should be active");
2102	int u_fd = open_by_handle_at(FD_NSFS_ROOT, user_handle, O_RDONLY);
2103	ASSERT_GE(u_fd, 0);
2104	close(u_fd);
2105
2106	/* Close net - user ns should STILL be active (uts still active) */
2107	TH_LOG("Closing net - user ns should still be active");
2108	close(n_fd);
2109	u_fd = open_by_handle_at(FD_NSFS_ROOT, user_handle, O_RDONLY);
2110	ASSERT_GE(u_fd, 0);
2111	close(u_fd);
2112
2113	/* Close uts - user ns should become inactive */
2114	TH_LOG("Closing uts - user ns should become inactive");
2115	close(ut_fd);
2116	u_fd = open_by_handle_at(FD_NSFS_ROOT, user_handle, O_RDONLY);
2117	ASSERT_LT(u_fd, 0);
2118}
2119
2120/* Thread test helpers and structures */
2121struct thread_ns_info {
2122	__u64 ns_id;
2123	int pipefd;
2124	int syncfd_read;
2125	int syncfd_write;
2126	int exit_code;
2127};
2128
2129static void *thread_create_namespace(void *arg)
2130{
2131	struct thread_ns_info *info = (struct thread_ns_info *)arg;
2132	int ret;
2133
2134	/* Create new network namespace */
2135	ret = unshare(CLONE_NEWNET);
2136	if (ret < 0) {
2137		info->exit_code = 1;
2138		return NULL;
2139	}
2140
2141	/* Get namespace ID */
2142	int fd = open("/proc/thread-self/ns/net", O_RDONLY);
2143	if (fd < 0) {
2144		info->exit_code = 2;
2145		return NULL;
2146	}
2147
2148	ret = ioctl(fd, NS_GET_ID, &info->ns_id);
2149	close(fd);
2150	if (ret < 0) {
2151		info->exit_code = 3;
2152		return NULL;
2153	}
2154
2155	/* Send namespace ID to main thread */
2156	if (write(info->pipefd, &info->ns_id, sizeof(info->ns_id)) != sizeof(info->ns_id)) {
2157		info->exit_code = 4;
2158		return NULL;
2159	}
2160
2161	/* Wait for signal to exit */
2162	char sync_byte;
2163	if (read(info->syncfd_read, &sync_byte, 1) != 1) {
2164		info->exit_code = 5;
2165		return NULL;
2166	}
2167
2168	info->exit_code = 0;
2169	return NULL;
2170}
2171
2172/*
2173 * Test that namespace becomes inactive after thread exits.
2174 * This verifies active reference counting works with threads, not just processes.
2175 */
2176TEST(thread_ns_inactive_after_exit)
2177{
2178	pthread_t thread;
2179	struct thread_ns_info info;
2180	struct file_handle *handle;
2181	int pipefd[2];
2182	int syncpipe[2];
2183	int ret;
2184	char sync_byte;
2185	char buf[sizeof(*handle) + MAX_HANDLE_SZ];
2186
2187	ASSERT_EQ(pipe(pipefd), 0);
2188	ASSERT_EQ(pipe(syncpipe), 0);
2189
2190	info.pipefd = pipefd[1];
2191	info.syncfd_read = syncpipe[0];
2192	info.syncfd_write = -1;
2193	info.exit_code = -1;
2194
2195	/* Create thread that will create a namespace */
2196	ret = pthread_create(&thread, NULL, thread_create_namespace, &info);
2197	ASSERT_EQ(ret, 0);
2198
2199	/* Read namespace ID from thread */
2200	__u64 ns_id;
2201	ret = read(pipefd[0], &ns_id, sizeof(ns_id));
2202	if (ret != sizeof(ns_id)) {
2203		sync_byte = 'X';
2204		write(syncpipe[1], &sync_byte, 1);
2205		pthread_join(thread, NULL);
2206		close(pipefd[0]);
2207		close(pipefd[1]);
2208		close(syncpipe[0]);
2209		close(syncpipe[1]);
2210		SKIP(return, "Failed to read namespace ID from thread");
2211	}
2212
2213	TH_LOG("Thread created namespace with ID %llu", (unsigned long long)ns_id);
2214
2215	/* Construct file handle */
2216	handle = (struct file_handle *)buf;
2217	handle->handle_bytes = sizeof(struct nsfs_file_handle);
2218	handle->handle_type = FILEID_NSFS;
2219	struct nsfs_file_handle *fh = (struct nsfs_file_handle *)handle->f_handle;
2220	fh->ns_id = ns_id;
2221	fh->ns_type = 0;
2222	fh->ns_inum = 0;
2223
2224	/* Namespace should be active while thread is alive */
2225	TH_LOG("Attempting to open namespace while thread is alive (should succeed)");
2226	int nsfd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
2227	ASSERT_GE(nsfd, 0);
2228	close(nsfd);
2229
2230	/* Signal thread to exit */
2231	TH_LOG("Signaling thread to exit");
2232	sync_byte = 'X';
2233	ASSERT_EQ(write(syncpipe[1], &sync_byte, 1), 1);
2234	close(syncpipe[1]);
2235
2236	/* Wait for thread to exit */
2237	ASSERT_EQ(pthread_join(thread, NULL), 0);
2238	close(pipefd[0]);
2239	close(pipefd[1]);
2240	close(syncpipe[0]);
2241
2242	if (info.exit_code != 0)
2243		SKIP(return, "Thread failed to create namespace");
2244
2245	TH_LOG("Thread exited, namespace should be inactive");
2246
2247	/* Namespace should now be inactive */
2248	nsfd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
2249	ASSERT_LT(nsfd, 0);
2250	/* Should fail with ENOENT (inactive) or ESTALE (gone) */
2251	TH_LOG("Namespace inactive as expected: %s (errno=%d)", strerror(errno), errno);
2252	ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
2253}
2254
2255/*
2256 * Test that a namespace remains active while a thread holds an fd to it.
2257 * Even after the thread exits, the namespace should remain active as long as
2258 * another thread holds a file descriptor to it.
2259 */
2260TEST(thread_ns_fd_keeps_active)
2261{
2262	pthread_t thread;
2263	struct thread_ns_info info;
2264	struct file_handle *handle;
2265	int pipefd[2];
2266	int syncpipe[2];
2267	int ret;
2268	char sync_byte;
2269	char buf[sizeof(*handle) + MAX_HANDLE_SZ];
2270
2271	ASSERT_EQ(pipe(pipefd), 0);
2272	ASSERT_EQ(pipe(syncpipe), 0);
2273
2274	info.pipefd = pipefd[1];
2275	info.syncfd_read = syncpipe[0];
2276	info.syncfd_write = -1;
2277	info.exit_code = -1;
2278
2279	/* Create thread that will create a namespace */
2280	ret = pthread_create(&thread, NULL, thread_create_namespace, &info);
2281	ASSERT_EQ(ret, 0);
2282
2283	/* Read namespace ID from thread */
2284	__u64 ns_id;
2285	ret = read(pipefd[0], &ns_id, sizeof(ns_id));
2286	if (ret != sizeof(ns_id)) {
2287		sync_byte = 'X';
2288		write(syncpipe[1], &sync_byte, 1);
2289		pthread_join(thread, NULL);
2290		close(pipefd[0]);
2291		close(pipefd[1]);
2292		close(syncpipe[0]);
2293		close(syncpipe[1]);
2294		SKIP(return, "Failed to read namespace ID from thread");
2295	}
2296
2297	TH_LOG("Thread created namespace with ID %llu", (unsigned long long)ns_id);
2298
2299	/* Construct file handle */
2300	handle = (struct file_handle *)buf;
2301	handle->handle_bytes = sizeof(struct nsfs_file_handle);
2302	handle->handle_type = FILEID_NSFS;
2303	struct nsfs_file_handle *fh = (struct nsfs_file_handle *)handle->f_handle;
2304	fh->ns_id = ns_id;
2305	fh->ns_type = 0;
2306	fh->ns_inum = 0;
2307
2308	/* Open namespace while thread is alive */
2309	TH_LOG("Opening namespace while thread is alive");
2310	int nsfd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
2311	ASSERT_GE(nsfd, 0);
2312
2313	/* Signal thread to exit */
2314	TH_LOG("Signaling thread to exit");
2315	sync_byte = 'X';
2316	write(syncpipe[1], &sync_byte, 1);
2317	close(syncpipe[1]);
2318
2319	/* Wait for thread to exit */
2320	pthread_join(thread, NULL);
2321	close(pipefd[0]);
2322	close(pipefd[1]);
2323	close(syncpipe[0]);
2324
2325	if (info.exit_code != 0) {
2326		close(nsfd);
2327		SKIP(return, "Thread failed to create namespace");
2328	}
2329
2330	TH_LOG("Thread exited, but main thread holds fd - namespace should remain active");
2331
2332	/* Namespace should still be active because we hold an fd */
2333	int nsfd2 = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
2334	ASSERT_GE(nsfd2, 0);
2335
2336	/* Verify it's the same namespace */
2337	struct stat st1, st2;
2338	ASSERT_EQ(fstat(nsfd, &st1), 0);
2339	ASSERT_EQ(fstat(nsfd2, &st2), 0);
2340	ASSERT_EQ(st1.st_ino, st2.st_ino);
2341	close(nsfd2);
2342
2343	TH_LOG("Closing fd - namespace should become inactive");
2344	close(nsfd);
2345
2346	/* Now namespace should be inactive */
2347	nsfd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
2348	ASSERT_LT(nsfd, 0);
2349	/* Should fail with ENOENT (inactive) or ESTALE (gone) */
2350	TH_LOG("Namespace inactive as expected: %s (errno=%d)", strerror(errno), errno);
2351	ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
2352}
2353
2354/* Structure for thread data in subprocess */
2355struct thread_sleep_data {
2356	int syncfd_read;
2357};
2358
2359static 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 */
2376TEST(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
2672TEST_HARNESS_MAIN
~/ Read-only file preview.