xref: /linux/tools/testing/selftests/namespaces/ns_active_ref_test.c (revision 721c7e41b184714cb173b04365a168d58522f9fc)
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 <unistd.h>
16 #include "../kselftest_harness.h"
17 #include "../filesystems/utils.h"
18 
19 #ifndef FD_NSFS_ROOT
20 #define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */
21 #endif
22 
23 /*
24  * Test that initial namespaces can be reopened via file handle.
25  * Initial namespaces should have active ref count of 1 from boot.
26  */
27 TEST(init_ns_always_active)
28 {
29 	struct file_handle *handle;
30 	int mount_id;
31 	int ret;
32 	int fd1, fd2;
33 	struct stat st1, st2;
34 
35 	handle = malloc(sizeof(*handle) + MAX_HANDLE_SZ);
36 	ASSERT_NE(handle, NULL);
37 
38 	/* Open initial network namespace */
39 	fd1 = open("/proc/1/ns/net", O_RDONLY);
40 	ASSERT_GE(fd1, 0);
41 
42 	/* Get file handle for initial namespace */
43 	handle->handle_bytes = MAX_HANDLE_SZ;
44 	ret = name_to_handle_at(fd1, "", handle, &mount_id, AT_EMPTY_PATH);
45 	if (ret < 0 && errno == EOPNOTSUPP) {
46 		SKIP(free(handle); close(fd1);
47 		     return, "nsfs doesn't support file handles");
48 	}
49 	ASSERT_EQ(ret, 0);
50 
51 	/* Close the namespace fd */
52 	close(fd1);
53 
54 	/* Try to reopen via file handle - should succeed since init ns is always active */
55 	fd2 = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
56 	if (fd2 < 0 && (errno == EINVAL || errno == EOPNOTSUPP)) {
57 		SKIP(free(handle);
58 		     return, "open_by_handle_at with FD_NSFS_ROOT not supported");
59 	}
60 	ASSERT_GE(fd2, 0);
61 
62 	/* Verify we opened the same namespace */
63 	fd1 = open("/proc/1/ns/net", O_RDONLY);
64 	ASSERT_GE(fd1, 0);
65 	ASSERT_EQ(fstat(fd1, &st1), 0);
66 	ASSERT_EQ(fstat(fd2, &st2), 0);
67 	ASSERT_EQ(st1.st_ino, st2.st_ino);
68 
69 	close(fd1);
70 	close(fd2);
71 	free(handle);
72 }
73 
74 /*
75  * Test namespace lifecycle: create a namespace in a child process,
76  * get a file handle while it's active, then try to reopen after
77  * the process exits (namespace becomes inactive).
78  */
79 TEST(ns_inactive_after_exit)
80 {
81 	struct file_handle *handle;
82 	int mount_id;
83 	int ret;
84 	int fd;
85 	int pipefd[2];
86 	pid_t pid;
87 	int status;
88 	char buf[sizeof(*handle) + MAX_HANDLE_SZ];
89 
90 	/* Create pipe for passing file handle from child */
91 	ASSERT_EQ(pipe(pipefd), 0);
92 
93 	pid = fork();
94 	ASSERT_GE(pid, 0);
95 
96 	if (pid == 0) {
97 		/* Child process */
98 		close(pipefd[0]);
99 
100 		/* Create new network namespace */
101 		ret = unshare(CLONE_NEWNET);
102 		if (ret < 0) {
103 			close(pipefd[1]);
104 			exit(1);
105 		}
106 
107 		/* Open our new namespace */
108 		fd = open("/proc/self/ns/net", O_RDONLY);
109 		if (fd < 0) {
110 			close(pipefd[1]);
111 			exit(1);
112 		}
113 
114 		/* Get file handle for the namespace */
115 		handle = (struct file_handle *)buf;
116 		handle->handle_bytes = MAX_HANDLE_SZ;
117 		ret = name_to_handle_at(fd, "", handle, &mount_id, AT_EMPTY_PATH);
118 		close(fd);
119 
120 		if (ret < 0) {
121 			close(pipefd[1]);
122 			exit(1);
123 		}
124 
125 		/* Send handle to parent */
126 		write(pipefd[1], buf, sizeof(*handle) + handle->handle_bytes);
127 		close(pipefd[1]);
128 
129 		/* Exit - namespace should become inactive */
130 		exit(0);
131 	}
132 
133 	/* Parent process */
134 	close(pipefd[1]);
135 
136 	/* Read file handle from child */
137 	ret = read(pipefd[0], buf, sizeof(buf));
138 	close(pipefd[0]);
139 
140 	/* Wait for child to exit */
141 	waitpid(pid, &status, 0);
142 	ASSERT_TRUE(WIFEXITED(status));
143 	ASSERT_EQ(WEXITSTATUS(status), 0);
144 
145 	ASSERT_GT(ret, 0);
146 	handle = (struct file_handle *)buf;
147 
148 	/* Try to reopen namespace - should fail with ENOENT since it's inactive */
149 	fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
150 	ASSERT_LT(fd, 0);
151 	/* Should fail with ENOENT (namespace inactive) or ESTALE */
152 	ASSERT_TRUE(errno == ENOENT || errno == ESTALE);
153 }
154 
155 TEST_HARNESS_MAIN
156