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 */
TEST(init_ns_always_active)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 */
TEST(ns_inactive_after_exit)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 */
TEST(ns_active_with_multiple_processes)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 */
TEST(userns_active_ref_lifecycle)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 */
TEST(pidns_active_ref_lifecycle)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 */
TEST(ns_fd_keeps_active)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 */
TEST(ns_parent_always_reachable)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 */
TEST(ns_bind_mount_keeps_in_tree)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 */
TEST(ns_multilevel_hierarchy)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 */
TEST(ns_multiple_children_same_parent)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 */
TEST(ns_different_types_same_owner)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 */
TEST(ns_deep_hierarchy_propagation)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 */
TEST(ns_parent_multiple_children_refcount)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 */
TEST(ns_userns_child_propagation)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 */
TEST(ns_mixed_types_same_owner)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
thread_create_namespace(void * arg)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 */
TEST(thread_ns_inactive_after_exit)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 */
TEST(thread_ns_fd_keeps_active)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
thread_sleep_and_wait(void * arg)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 */
TEST(thread_subprocess_ns_inactive_after_all_exit)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