xref: /linux/tools/testing/selftests/namespaces/ns_active_ref_test.c (revision 8ebfb9896c97ab609222460e705f425cb3f0aad0)
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  */
35 TEST(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  */
87 TEST(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  */
167 TEST(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  */
290 TEST(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  */
380 TEST(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  */
464 TEST(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  */
620 TEST(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  */
835 TEST(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  */
954 TEST(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  */
1117 TEST(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  */
1287 TEST(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  */
1460 TEST(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  */
1635 TEST(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  */
1829 TEST(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  */
1960 TEST(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 */
2121 struct thread_ns_info {
2122 	__u64 ns_id;
2123 	int pipefd;
2124 	int syncfd_read;
2125 	int syncfd_write;
2126 	int exit_code;
2127 };
2128 
2129 static 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  */
2176 TEST(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  */
2260 TEST(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 */
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 
2672 TEST_HARNESS_MAIN
2673