1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
3 #include <test_progs.h>
4 #include <time.h>
5 
6 #include <sys/epoll.h>
7 
8 #include "struct_ops_module.skel.h"
9 #include "struct_ops_nulled_out_cb.skel.h"
10 #include "struct_ops_forgotten_cb.skel.h"
11 #include "struct_ops_detach.skel.h"
12 #include "unsupported_ops.skel.h"
13 
check_map_info(struct bpf_map_info * info)14 static void check_map_info(struct bpf_map_info *info)
15 {
16 	struct bpf_btf_info btf_info;
17 	char btf_name[256];
18 	u32 btf_info_len = sizeof(btf_info);
19 	int err, fd;
20 
21 	fd = bpf_btf_get_fd_by_id(info->btf_vmlinux_id);
22 	if (!ASSERT_GE(fd, 0, "get_value_type_btf_obj_fd"))
23 		return;
24 
25 	memset(&btf_info, 0, sizeof(btf_info));
26 	btf_info.name = ptr_to_u64(btf_name);
27 	btf_info.name_len = sizeof(btf_name);
28 	err = bpf_btf_get_info_by_fd(fd, &btf_info, &btf_info_len);
29 	if (!ASSERT_OK(err, "get_value_type_btf_obj_info"))
30 		goto cleanup;
31 
32 	if (!ASSERT_EQ(strcmp(btf_name, "bpf_testmod"), 0, "get_value_type_btf_obj_name"))
33 		goto cleanup;
34 
35 cleanup:
36 	close(fd);
37 }
38 
attach_ops_and_check(struct struct_ops_module * skel,struct bpf_map * map,int expected_test_2_result)39 static int attach_ops_and_check(struct struct_ops_module *skel,
40 				struct bpf_map *map,
41 				int expected_test_2_result)
42 {
43 	struct bpf_link *link;
44 
45 	link = bpf_map__attach_struct_ops(map);
46 	ASSERT_OK_PTR(link, "attach_test_mod_1");
47 	if (!link)
48 		return -1;
49 
50 	/* test_{1,2}() would be called from bpf_dummy_reg() in bpf_testmod.c */
51 	ASSERT_EQ(skel->bss->test_1_result, 0xdeadbeef, "test_1_result");
52 	ASSERT_EQ(skel->bss->test_2_result, expected_test_2_result, "test_2_result");
53 
54 	bpf_link__destroy(link);
55 	return 0;
56 }
57 
test_struct_ops_load(void)58 static void test_struct_ops_load(void)
59 {
60 	struct struct_ops_module *skel;
61 	struct bpf_map_info info = {};
62 	int err;
63 	u32 len;
64 
65 	skel = struct_ops_module__open();
66 	if (!ASSERT_OK_PTR(skel, "struct_ops_module_open"))
67 		return;
68 
69 	skel->struct_ops.testmod_1->data = 13;
70 	skel->struct_ops.testmod_1->test_2 = skel->progs.test_3;
71 	/* Since test_2() is not being used, it should be disabled from
72 	 * auto-loading, or it will fail to load.
73 	 */
74 	bpf_program__set_autoload(skel->progs.test_2, false);
75 	bpf_map__set_autocreate(skel->maps.testmod_zeroed, false);
76 
77 	err = struct_ops_module__load(skel);
78 	if (!ASSERT_OK(err, "struct_ops_module_load"))
79 		goto cleanup;
80 
81 	len = sizeof(info);
82 	err = bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.testmod_1), &info,
83 				     &len);
84 	if (!ASSERT_OK(err, "bpf_map_get_info_by_fd"))
85 		goto cleanup;
86 
87 	check_map_info(&info);
88 	/* test_3() will be called from bpf_dummy_reg() in bpf_testmod.c
89 	 *
90 	 * In bpf_testmod.c it will pass 4 and 13 (the value of data) to
91 	 * .test_2.  So, the value of test_2_result should be 20 (4 + 13 +
92 	 * 3).
93 	 */
94 	if (!attach_ops_and_check(skel, skel->maps.testmod_1, 20))
95 		goto cleanup;
96 	if (!attach_ops_and_check(skel, skel->maps.testmod_2, 12))
97 		goto cleanup;
98 
99 cleanup:
100 	struct_ops_module__destroy(skel);
101 }
102 
test_struct_ops_not_zeroed(void)103 static void test_struct_ops_not_zeroed(void)
104 {
105 	struct struct_ops_module *skel;
106 	int err;
107 
108 	/* zeroed is 0, and zeroed_op is null */
109 	skel = struct_ops_module__open();
110 	if (!ASSERT_OK_PTR(skel, "struct_ops_module_open"))
111 		return;
112 
113 	skel->struct_ops.testmod_zeroed->zeroed = 0;
114 	/* zeroed_op prog should be not loaded automatically now */
115 	skel->struct_ops.testmod_zeroed->zeroed_op = NULL;
116 
117 	err = struct_ops_module__load(skel);
118 	ASSERT_OK(err, "struct_ops_module_load");
119 
120 	struct_ops_module__destroy(skel);
121 
122 	/* zeroed is not 0 */
123 	skel = struct_ops_module__open();
124 	if (!ASSERT_OK_PTR(skel, "struct_ops_module_open_not_zeroed"))
125 		return;
126 
127 	/* libbpf should reject the testmod_zeroed since struct
128 	 * bpf_testmod_ops in the kernel has no "zeroed" field and the
129 	 * value of "zeroed" is non-zero.
130 	 */
131 	skel->struct_ops.testmod_zeroed->zeroed = 0xdeadbeef;
132 	skel->struct_ops.testmod_zeroed->zeroed_op = NULL;
133 	err = struct_ops_module__load(skel);
134 	ASSERT_ERR(err, "struct_ops_module_load_not_zeroed");
135 
136 	struct_ops_module__destroy(skel);
137 
138 	/* zeroed_op is not null */
139 	skel = struct_ops_module__open();
140 	if (!ASSERT_OK_PTR(skel, "struct_ops_module_open_not_zeroed_op"))
141 		return;
142 
143 	/* libbpf should reject the testmod_zeroed since the value of its
144 	 * "zeroed_op" is not null.
145 	 */
146 	skel->struct_ops.testmod_zeroed->zeroed_op = skel->progs.test_3;
147 	err = struct_ops_module__load(skel);
148 	ASSERT_ERR(err, "struct_ops_module_load_not_zeroed_op");
149 
150 	struct_ops_module__destroy(skel);
151 }
152 
153 /* The signature of an implementation might not match the signature of the
154  * function pointer prototype defined in the BPF program. This mismatch
155  * should be allowed as long as the behavior of the operator program
156  * adheres to the signature in the kernel. Libbpf should not enforce the
157  * signature; rather, let the kernel verifier handle the enforcement.
158  */
test_struct_ops_incompatible(void)159 static void test_struct_ops_incompatible(void)
160 {
161 	struct struct_ops_module *skel;
162 	struct bpf_link *link;
163 	int err;
164 
165 	skel = struct_ops_module__open();
166 	if (!ASSERT_OK_PTR(skel, "struct_ops_module_open"))
167 		return;
168 
169 	bpf_map__set_autocreate(skel->maps.testmod_zeroed, false);
170 
171 	err = struct_ops_module__load(skel);
172 	if (!ASSERT_OK(err, "skel_load"))
173 		goto cleanup;
174 
175 	link = bpf_map__attach_struct_ops(skel->maps.testmod_incompatible);
176 	if (ASSERT_OK_PTR(link, "attach_struct_ops"))
177 		bpf_link__destroy(link);
178 
179 cleanup:
180 	struct_ops_module__destroy(skel);
181 }
182 
183 /* validate that it's ok to "turn off" callback that kernel supports */
test_struct_ops_nulled_out_cb(void)184 static void test_struct_ops_nulled_out_cb(void)
185 {
186 	struct struct_ops_nulled_out_cb *skel;
187 	int err;
188 
189 	skel = struct_ops_nulled_out_cb__open();
190 	if (!ASSERT_OK_PTR(skel, "skel_open"))
191 		return;
192 
193 	/* kernel knows about test_1, but we still null it out */
194 	skel->struct_ops.ops->test_1 = NULL;
195 
196 	err = struct_ops_nulled_out_cb__load(skel);
197 	if (!ASSERT_OK(err, "skel_load"))
198 		goto cleanup;
199 
200 	ASSERT_FALSE(bpf_program__autoload(skel->progs.test_1_turn_off), "prog_autoload");
201 	ASSERT_LT(bpf_program__fd(skel->progs.test_1_turn_off), 0, "prog_fd");
202 
203 cleanup:
204 	struct_ops_nulled_out_cb__destroy(skel);
205 }
206 
207 /* validate that libbpf generates reasonable error message if struct_ops is
208  * not referenced in any struct_ops map
209  */
test_struct_ops_forgotten_cb(void)210 static void test_struct_ops_forgotten_cb(void)
211 {
212 	struct struct_ops_forgotten_cb *skel;
213 	char *log;
214 	int err;
215 
216 	skel = struct_ops_forgotten_cb__open();
217 	if (!ASSERT_OK_PTR(skel, "skel_open"))
218 		return;
219 
220 	start_libbpf_log_capture();
221 
222 	err = struct_ops_forgotten_cb__load(skel);
223 	if (!ASSERT_ERR(err, "skel_load"))
224 		goto cleanup;
225 
226 	log = stop_libbpf_log_capture();
227 	ASSERT_HAS_SUBSTR(log,
228 			  "prog 'test_1_forgotten': SEC(\"struct_ops\") program isn't referenced anywhere, did you forget to use it?",
229 			  "libbpf_log");
230 	free(log);
231 
232 	struct_ops_forgotten_cb__destroy(skel);
233 
234 	/* now let's programmatically use it, we should be fine now */
235 	skel = struct_ops_forgotten_cb__open();
236 	if (!ASSERT_OK_PTR(skel, "skel_open"))
237 		return;
238 
239 	skel->struct_ops.ops->test_1 = skel->progs.test_1_forgotten; /* not anymore */
240 
241 	err = struct_ops_forgotten_cb__load(skel);
242 	if (!ASSERT_OK(err, "skel_load"))
243 		goto cleanup;
244 
245 cleanup:
246 	struct_ops_forgotten_cb__destroy(skel);
247 }
248 
249 /* Detach a link from a user space program */
test_detach_link(void)250 static void test_detach_link(void)
251 {
252 	struct epoll_event ev, events[2];
253 	struct struct_ops_detach *skel;
254 	struct bpf_link *link = NULL;
255 	int fd, epollfd = -1, nfds;
256 	int err;
257 
258 	skel = struct_ops_detach__open_and_load();
259 	if (!ASSERT_OK_PTR(skel, "struct_ops_detach__open_and_load"))
260 		return;
261 
262 	link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
263 	if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
264 		goto cleanup;
265 
266 	fd = bpf_link__fd(link);
267 	if (!ASSERT_GE(fd, 0, "link_fd"))
268 		goto cleanup;
269 
270 	epollfd = epoll_create1(0);
271 	if (!ASSERT_GE(epollfd, 0, "epoll_create1"))
272 		goto cleanup;
273 
274 	ev.events = EPOLLHUP;
275 	ev.data.fd = fd;
276 	err = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
277 	if (!ASSERT_OK(err, "epoll_ctl"))
278 		goto cleanup;
279 
280 	err = bpf_link__detach(link);
281 	if (!ASSERT_OK(err, "detach_link"))
282 		goto cleanup;
283 
284 	/* Wait for EPOLLHUP */
285 	nfds = epoll_wait(epollfd, events, 2, 500);
286 	if (!ASSERT_EQ(nfds, 1, "epoll_wait"))
287 		goto cleanup;
288 
289 	if (!ASSERT_EQ(events[0].data.fd, fd, "epoll_wait_fd"))
290 		goto cleanup;
291 	if (!ASSERT_TRUE(events[0].events & EPOLLHUP, "events[0].events"))
292 		goto cleanup;
293 
294 cleanup:
295 	if (epollfd >= 0)
296 		close(epollfd);
297 	bpf_link__destroy(link);
298 	struct_ops_detach__destroy(skel);
299 }
300 
serial_test_struct_ops_module(void)301 void serial_test_struct_ops_module(void)
302 {
303 	if (test__start_subtest("struct_ops_load"))
304 		test_struct_ops_load();
305 	if (test__start_subtest("struct_ops_not_zeroed"))
306 		test_struct_ops_not_zeroed();
307 	if (test__start_subtest("struct_ops_incompatible"))
308 		test_struct_ops_incompatible();
309 	if (test__start_subtest("struct_ops_null_out_cb"))
310 		test_struct_ops_nulled_out_cb();
311 	if (test__start_subtest("struct_ops_forgotten_cb"))
312 		test_struct_ops_forgotten_cb();
313 	if (test__start_subtest("test_detach_link"))
314 		test_detach_link();
315 	RUN_TESTS(unsupported_ops);
316 }
317 
318