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