1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
3 #define _GNU_SOURCE
4 #include <test_progs.h>
5 #include <bpf/btf.h>
6 #include <fcntl.h>
7 #include <unistd.h>
8 #include <linux/unistd.h>
9 #include <linux/mount.h>
10 #include <sys/syscall.h>
11 
12 static inline int sys_fsopen(const char *fsname, unsigned flags)
13 {
14 	return syscall(__NR_fsopen, fsname, flags);
15 }
16 
17 static inline int sys_fsconfig(int fs_fd, unsigned cmd, const char *key, const void *val, int aux)
18 {
19 	return syscall(__NR_fsconfig, fs_fd, cmd, key, val, aux);
20 }
21 
22 static inline int sys_fsmount(int fs_fd, unsigned flags, unsigned ms_flags)
23 {
24 	return syscall(__NR_fsmount, fs_fd, flags, ms_flags);
25 }
26 
27 __attribute__((unused))
28 static inline int sys_move_mount(int from_dfd, const char *from_path,
29 			         int to_dfd, const char *to_path,
30 			         unsigned int ms_flags)
31 {
32 	return syscall(__NR_move_mount, from_dfd, from_path, to_dfd, to_path, ms_flags);
33 }
34 
35 static void bpf_obj_pinning_detached(void)
36 {
37 	LIBBPF_OPTS(bpf_obj_pin_opts, pin_opts);
38 	LIBBPF_OPTS(bpf_obj_get_opts, get_opts);
39 	int fs_fd = -1, mnt_fd = -1;
40 	int map_fd = -1, map_fd2 = -1;
41 	int zero = 0, src_value, dst_value, err;
42 	const char *map_name = "fsmount_map";
43 
44 	/* A bunch of below UAPI calls are constructed based on reading:
45 	 * https://brauner.io/2023/02/28/mounting-into-mount-namespaces.html
46 	 */
47 
48 	/* create VFS context */
49 	fs_fd = sys_fsopen("bpf", 0);
50 	if (!ASSERT_GE(fs_fd, 0, "fs_fd"))
51 		goto cleanup;
52 
53 	/* instantiate FS object */
54 	err = sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
55 	if (!ASSERT_OK(err, "fs_create"))
56 		goto cleanup;
57 
58 	/* create O_PATH fd for detached mount */
59 	mnt_fd = sys_fsmount(fs_fd, 0, 0);
60 	if (!ASSERT_GE(mnt_fd, 0, "mnt_fd"))
61 		goto cleanup;
62 
63 	/* If we wanted to expose detached mount in the file system, we'd do
64 	 * something like below. But the whole point is that we actually don't
65 	 * even have to expose BPF FS in the file system to be able to work
66 	 * (pin/get objects) with it.
67 	 *
68 	 * err = sys_move_mount(mnt_fd, "", -EBADF, mnt_path, MOVE_MOUNT_F_EMPTY_PATH);
69 	 * if (!ASSERT_OK(err, "move_mount"))
70 	 *	goto cleanup;
71 	 */
72 
73 	/* create BPF map to pin */
74 	map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, map_name, 4, 4, 1, NULL);
75 	if (!ASSERT_GE(map_fd, 0, "map_fd"))
76 		goto cleanup;
77 
78 	/* pin BPF map into detached BPF FS through mnt_fd */
79 	pin_opts.file_flags = BPF_F_PATH_FD;
80 	pin_opts.path_fd = mnt_fd;
81 	err = bpf_obj_pin_opts(map_fd, map_name, &pin_opts);
82 	if (!ASSERT_OK(err, "map_pin"))
83 		goto cleanup;
84 
85 	/* get BPF map from detached BPF FS through mnt_fd */
86 	get_opts.file_flags = BPF_F_PATH_FD;
87 	get_opts.path_fd = mnt_fd;
88 	map_fd2 = bpf_obj_get_opts(map_name, &get_opts);
89 	if (!ASSERT_GE(map_fd2, 0, "map_get"))
90 		goto cleanup;
91 
92 	/* update map through one FD */
93 	src_value = 0xcafebeef;
94 	err = bpf_map_update_elem(map_fd, &zero, &src_value, 0);
95 	ASSERT_OK(err, "map_update");
96 
97 	/* check values written/read through different FDs do match */
98 	dst_value = 0;
99 	err = bpf_map_lookup_elem(map_fd2, &zero, &dst_value);
100 	ASSERT_OK(err, "map_lookup");
101 	ASSERT_EQ(dst_value, src_value, "map_value_eq1");
102 	ASSERT_EQ(dst_value, 0xcafebeef, "map_value_eq2");
103 
104 cleanup:
105 	if (map_fd >= 0)
106 		ASSERT_OK(close(map_fd), "close_map_fd");
107 	if (map_fd2 >= 0)
108 		ASSERT_OK(close(map_fd2), "close_map_fd2");
109 	if (fs_fd >= 0)
110 		ASSERT_OK(close(fs_fd), "close_fs_fd");
111 	if (mnt_fd >= 0)
112 		ASSERT_OK(close(mnt_fd), "close_mnt_fd");
113 }
114 
115 enum path_kind
116 {
117 	PATH_STR_ABS,
118 	PATH_STR_REL,
119 	PATH_FD_REL,
120 };
121 
122 static void validate_pin(int map_fd, const char *map_name, int src_value,
123 			 enum path_kind path_kind)
124 {
125 	LIBBPF_OPTS(bpf_obj_pin_opts, pin_opts);
126 	char abs_path[PATH_MAX], old_cwd[PATH_MAX];
127 	const char *pin_path = NULL;
128 	int zero = 0, dst_value, map_fd2, err;
129 
130 	snprintf(abs_path, sizeof(abs_path), "/sys/fs/bpf/%s", map_name);
131 	old_cwd[0] = '\0';
132 
133 	switch (path_kind) {
134 	case PATH_STR_ABS:
135 		/* absolute path */
136 		pin_path = abs_path;
137 		break;
138 	case PATH_STR_REL:
139 		/* cwd + relative path */
140 		ASSERT_OK_PTR(getcwd(old_cwd, sizeof(old_cwd)), "getcwd");
141 		ASSERT_OK(chdir("/sys/fs/bpf"), "chdir");
142 		pin_path = map_name;
143 		break;
144 	case PATH_FD_REL:
145 		/* dir fd + relative path */
146 		pin_opts.file_flags = BPF_F_PATH_FD;
147 		pin_opts.path_fd = open("/sys/fs/bpf", O_PATH);
148 		ASSERT_GE(pin_opts.path_fd, 0, "path_fd");
149 		pin_path = map_name;
150 		break;
151 	}
152 
153 	/* pin BPF map using specified path definition */
154 	err = bpf_obj_pin_opts(map_fd, pin_path, &pin_opts);
155 	ASSERT_OK(err, "obj_pin");
156 
157 	/* cleanup */
158 	if (pin_opts.path_fd >= 0)
159 		close(pin_opts.path_fd);
160 	if (old_cwd[0])
161 		ASSERT_OK(chdir(old_cwd), "restore_cwd");
162 
163 	map_fd2 = bpf_obj_get(abs_path);
164 	if (!ASSERT_GE(map_fd2, 0, "map_get"))
165 		goto cleanup;
166 
167 	/* update map through one FD */
168 	err = bpf_map_update_elem(map_fd, &zero, &src_value, 0);
169 	ASSERT_OK(err, "map_update");
170 
171 	/* check values written/read through different FDs do match */
172 	dst_value = 0;
173 	err = bpf_map_lookup_elem(map_fd2, &zero, &dst_value);
174 	ASSERT_OK(err, "map_lookup");
175 	ASSERT_EQ(dst_value, src_value, "map_value_eq");
176 cleanup:
177 	if (map_fd2 >= 0)
178 		ASSERT_OK(close(map_fd2), "close_map_fd2");
179 	unlink(abs_path);
180 }
181 
182 static void validate_get(int map_fd, const char *map_name, int src_value,
183 			 enum path_kind path_kind)
184 {
185 	LIBBPF_OPTS(bpf_obj_get_opts, get_opts);
186 	char abs_path[PATH_MAX], old_cwd[PATH_MAX];
187 	const char *pin_path = NULL;
188 	int zero = 0, dst_value, map_fd2, err;
189 
190 	snprintf(abs_path, sizeof(abs_path), "/sys/fs/bpf/%s", map_name);
191 	/* pin BPF map using specified path definition */
192 	err = bpf_obj_pin(map_fd, abs_path);
193 	if (!ASSERT_OK(err, "pin_map"))
194 		return;
195 
196 	old_cwd[0] = '\0';
197 
198 	switch (path_kind) {
199 	case PATH_STR_ABS:
200 		/* absolute path */
201 		pin_path = abs_path;
202 		break;
203 	case PATH_STR_REL:
204 		/* cwd + relative path */
205 		ASSERT_OK_PTR(getcwd(old_cwd, sizeof(old_cwd)), "getcwd");
206 		ASSERT_OK(chdir("/sys/fs/bpf"), "chdir");
207 		pin_path = map_name;
208 		break;
209 	case PATH_FD_REL:
210 		/* dir fd + relative path */
211 		get_opts.file_flags = BPF_F_PATH_FD;
212 		get_opts.path_fd = open("/sys/fs/bpf", O_PATH);
213 		ASSERT_GE(get_opts.path_fd, 0, "path_fd");
214 		pin_path = map_name;
215 		break;
216 	}
217 
218 	map_fd2 = bpf_obj_get_opts(pin_path, &get_opts);
219 	if (!ASSERT_GE(map_fd2, 0, "map_get"))
220 		goto cleanup;
221 
222 	/* cleanup */
223 	if (get_opts.path_fd >= 0)
224 		close(get_opts.path_fd);
225 	if (old_cwd[0])
226 		ASSERT_OK(chdir(old_cwd), "restore_cwd");
227 
228 	/* update map through one FD */
229 	err = bpf_map_update_elem(map_fd, &zero, &src_value, 0);
230 	ASSERT_OK(err, "map_update");
231 
232 	/* check values written/read through different FDs do match */
233 	dst_value = 0;
234 	err = bpf_map_lookup_elem(map_fd2, &zero, &dst_value);
235 	ASSERT_OK(err, "map_lookup");
236 	ASSERT_EQ(dst_value, src_value, "map_value_eq");
237 cleanup:
238 	if (map_fd2 >= 0)
239 		ASSERT_OK(close(map_fd2), "close_map_fd2");
240 	unlink(abs_path);
241 }
242 
243 static void bpf_obj_pinning_mounted(enum path_kind path_kind)
244 {
245 	const char *map_name = "mounted_map";
246 	int map_fd;
247 
248 	/* create BPF map to pin */
249 	map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, map_name, 4, 4, 1, NULL);
250 	if (!ASSERT_GE(map_fd, 0, "map_fd"))
251 		return;
252 
253 	validate_pin(map_fd, map_name, 100 + (int)path_kind, path_kind);
254 	validate_get(map_fd, map_name, 200 + (int)path_kind, path_kind);
255 	ASSERT_OK(close(map_fd), "close_map_fd");
256 }
257 
258 void test_bpf_obj_pinning()
259 {
260 	if (test__start_subtest("detached"))
261 		bpf_obj_pinning_detached();
262 	if (test__start_subtest("mounted-str-abs"))
263 		bpf_obj_pinning_mounted(PATH_STR_ABS);
264 	if (test__start_subtest("mounted-str-rel"))
265 		bpf_obj_pinning_mounted(PATH_STR_REL);
266 	if (test__start_subtest("mounted-fd-rel"))
267 		bpf_obj_pinning_mounted(PATH_FD_REL);
268 }
269