1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 /*
4  * Copyright 2021 Google LLC.
5  */
6 
7 #include <test_progs.h>
8 #include <cgroup_helpers.h>
9 #include <network_helpers.h>
10 
11 #include "cgroup_getset_retval_setsockopt.skel.h"
12 #include "cgroup_getset_retval_getsockopt.skel.h"
13 
14 #define SOL_CUSTOM	0xdeadbeef
15 
16 static int zero;
17 
18 static void test_setsockopt_set(int cgroup_fd, int sock_fd)
19 {
20 	struct cgroup_getset_retval_setsockopt *obj;
21 	struct bpf_link *link_set_eunatch = NULL;
22 
23 	obj = cgroup_getset_retval_setsockopt__open_and_load();
24 	if (!ASSERT_OK_PTR(obj, "skel-load"))
25 		return;
26 
27 	/* Attach setsockopt that sets EUNATCH, assert that
28 	 * we actually get that error when we run setsockopt()
29 	 */
30 	link_set_eunatch = bpf_program__attach_cgroup(obj->progs.set_eunatch,
31 						      cgroup_fd);
32 	if (!ASSERT_OK_PTR(link_set_eunatch, "cg-attach-set_eunatch"))
33 		goto close_bpf_object;
34 
35 	if (!ASSERT_ERR(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
36 				   &zero, sizeof(int)), "setsockopt"))
37 		goto close_bpf_object;
38 	if (!ASSERT_EQ(errno, EUNATCH, "setsockopt-errno"))
39 		goto close_bpf_object;
40 
41 	if (!ASSERT_EQ(obj->bss->invocations, 1, "invocations"))
42 		goto close_bpf_object;
43 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
44 		goto close_bpf_object;
45 
46 close_bpf_object:
47 	bpf_link__destroy(link_set_eunatch);
48 
49 	cgroup_getset_retval_setsockopt__destroy(obj);
50 }
51 
52 static void test_setsockopt_set_and_get(int cgroup_fd, int sock_fd)
53 {
54 	struct cgroup_getset_retval_setsockopt *obj;
55 	struct bpf_link *link_set_eunatch = NULL, *link_get_retval = NULL;
56 
57 	obj = cgroup_getset_retval_setsockopt__open_and_load();
58 	if (!ASSERT_OK_PTR(obj, "skel-load"))
59 		return;
60 
61 	/* Attach setsockopt that sets EUNATCH, and one that gets the
62 	 * previously set errno. Assert that we get the same errno back.
63 	 */
64 	link_set_eunatch = bpf_program__attach_cgroup(obj->progs.set_eunatch,
65 						      cgroup_fd);
66 	if (!ASSERT_OK_PTR(link_set_eunatch, "cg-attach-set_eunatch"))
67 		goto close_bpf_object;
68 	link_get_retval = bpf_program__attach_cgroup(obj->progs.get_retval,
69 						     cgroup_fd);
70 	if (!ASSERT_OK_PTR(link_get_retval, "cg-attach-get_retval"))
71 		goto close_bpf_object;
72 
73 	if (!ASSERT_ERR(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
74 				   &zero, sizeof(int)), "setsockopt"))
75 		goto close_bpf_object;
76 	if (!ASSERT_EQ(errno, EUNATCH, "setsockopt-errno"))
77 		goto close_bpf_object;
78 
79 	if (!ASSERT_EQ(obj->bss->invocations, 2, "invocations"))
80 		goto close_bpf_object;
81 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
82 		goto close_bpf_object;
83 	if (!ASSERT_EQ(obj->bss->retval_value, -EUNATCH, "retval_value"))
84 		goto close_bpf_object;
85 
86 close_bpf_object:
87 	bpf_link__destroy(link_set_eunatch);
88 	bpf_link__destroy(link_get_retval);
89 
90 	cgroup_getset_retval_setsockopt__destroy(obj);
91 }
92 
93 static void test_setsockopt_default_zero(int cgroup_fd, int sock_fd)
94 {
95 	struct cgroup_getset_retval_setsockopt *obj;
96 	struct bpf_link *link_get_retval = NULL;
97 
98 	obj = cgroup_getset_retval_setsockopt__open_and_load();
99 	if (!ASSERT_OK_PTR(obj, "skel-load"))
100 		return;
101 
102 	/* Attach setsockopt that gets the previously set errno.
103 	 * Assert that, without anything setting one, we get 0.
104 	 */
105 	link_get_retval = bpf_program__attach_cgroup(obj->progs.get_retval,
106 						     cgroup_fd);
107 	if (!ASSERT_OK_PTR(link_get_retval, "cg-attach-get_retval"))
108 		goto close_bpf_object;
109 
110 	if (!ASSERT_OK(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
111 				  &zero, sizeof(int)), "setsockopt"))
112 		goto close_bpf_object;
113 
114 	if (!ASSERT_EQ(obj->bss->invocations, 1, "invocations"))
115 		goto close_bpf_object;
116 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
117 		goto close_bpf_object;
118 	if (!ASSERT_EQ(obj->bss->retval_value, 0, "retval_value"))
119 		goto close_bpf_object;
120 
121 close_bpf_object:
122 	bpf_link__destroy(link_get_retval);
123 
124 	cgroup_getset_retval_setsockopt__destroy(obj);
125 }
126 
127 static void test_setsockopt_default_zero_and_set(int cgroup_fd, int sock_fd)
128 {
129 	struct cgroup_getset_retval_setsockopt *obj;
130 	struct bpf_link *link_get_retval = NULL, *link_set_eunatch = NULL;
131 
132 	obj = cgroup_getset_retval_setsockopt__open_and_load();
133 	if (!ASSERT_OK_PTR(obj, "skel-load"))
134 		return;
135 
136 	/* Attach setsockopt that gets the previously set errno, and then
137 	 * one that sets the errno to EUNATCH. Assert that the get does not
138 	 * see EUNATCH set later, and does not prevent EUNATCH from being set.
139 	 */
140 	link_get_retval = bpf_program__attach_cgroup(obj->progs.get_retval,
141 						     cgroup_fd);
142 	if (!ASSERT_OK_PTR(link_get_retval, "cg-attach-get_retval"))
143 		goto close_bpf_object;
144 	link_set_eunatch = bpf_program__attach_cgroup(obj->progs.set_eunatch,
145 						      cgroup_fd);
146 	if (!ASSERT_OK_PTR(link_set_eunatch, "cg-attach-set_eunatch"))
147 		goto close_bpf_object;
148 
149 	if (!ASSERT_ERR(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
150 				   &zero, sizeof(int)), "setsockopt"))
151 		goto close_bpf_object;
152 	if (!ASSERT_EQ(errno, EUNATCH, "setsockopt-errno"))
153 		goto close_bpf_object;
154 
155 	if (!ASSERT_EQ(obj->bss->invocations, 2, "invocations"))
156 		goto close_bpf_object;
157 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
158 		goto close_bpf_object;
159 	if (!ASSERT_EQ(obj->bss->retval_value, 0, "retval_value"))
160 		goto close_bpf_object;
161 
162 close_bpf_object:
163 	bpf_link__destroy(link_get_retval);
164 	bpf_link__destroy(link_set_eunatch);
165 
166 	cgroup_getset_retval_setsockopt__destroy(obj);
167 }
168 
169 static void test_setsockopt_override(int cgroup_fd, int sock_fd)
170 {
171 	struct cgroup_getset_retval_setsockopt *obj;
172 	struct bpf_link *link_set_eunatch = NULL, *link_set_eisconn = NULL;
173 	struct bpf_link *link_get_retval = NULL;
174 
175 	obj = cgroup_getset_retval_setsockopt__open_and_load();
176 	if (!ASSERT_OK_PTR(obj, "skel-load"))
177 		return;
178 
179 	/* Attach setsockopt that sets EUNATCH, then one that sets EISCONN,
180 	 * and then one that gets the exported errno. Assert both the syscall
181 	 * and the helper sees the last set errno.
182 	 */
183 	link_set_eunatch = bpf_program__attach_cgroup(obj->progs.set_eunatch,
184 						      cgroup_fd);
185 	if (!ASSERT_OK_PTR(link_set_eunatch, "cg-attach-set_eunatch"))
186 		goto close_bpf_object;
187 	link_set_eisconn = bpf_program__attach_cgroup(obj->progs.set_eisconn,
188 						      cgroup_fd);
189 	if (!ASSERT_OK_PTR(link_set_eisconn, "cg-attach-set_eisconn"))
190 		goto close_bpf_object;
191 	link_get_retval = bpf_program__attach_cgroup(obj->progs.get_retval,
192 						     cgroup_fd);
193 	if (!ASSERT_OK_PTR(link_get_retval, "cg-attach-get_retval"))
194 		goto close_bpf_object;
195 
196 	if (!ASSERT_ERR(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
197 				   &zero, sizeof(int)), "setsockopt"))
198 		goto close_bpf_object;
199 	if (!ASSERT_EQ(errno, EISCONN, "setsockopt-errno"))
200 		goto close_bpf_object;
201 
202 	if (!ASSERT_EQ(obj->bss->invocations, 3, "invocations"))
203 		goto close_bpf_object;
204 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
205 		goto close_bpf_object;
206 	if (!ASSERT_EQ(obj->bss->retval_value, -EISCONN, "retval_value"))
207 		goto close_bpf_object;
208 
209 close_bpf_object:
210 	bpf_link__destroy(link_set_eunatch);
211 	bpf_link__destroy(link_set_eisconn);
212 	bpf_link__destroy(link_get_retval);
213 
214 	cgroup_getset_retval_setsockopt__destroy(obj);
215 }
216 
217 static void test_setsockopt_legacy_eperm(int cgroup_fd, int sock_fd)
218 {
219 	struct cgroup_getset_retval_setsockopt *obj;
220 	struct bpf_link *link_legacy_eperm = NULL, *link_get_retval = NULL;
221 
222 	obj = cgroup_getset_retval_setsockopt__open_and_load();
223 	if (!ASSERT_OK_PTR(obj, "skel-load"))
224 		return;
225 
226 	/* Attach setsockopt that return a reject without setting errno
227 	 * (legacy reject), and one that gets the errno. Assert that for
228 	 * backward compatibility the syscall result in EPERM, and this
229 	 * is also visible to the helper.
230 	 */
231 	link_legacy_eperm = bpf_program__attach_cgroup(obj->progs.legacy_eperm,
232 						       cgroup_fd);
233 	if (!ASSERT_OK_PTR(link_legacy_eperm, "cg-attach-legacy_eperm"))
234 		goto close_bpf_object;
235 	link_get_retval = bpf_program__attach_cgroup(obj->progs.get_retval,
236 						     cgroup_fd);
237 	if (!ASSERT_OK_PTR(link_get_retval, "cg-attach-get_retval"))
238 		goto close_bpf_object;
239 
240 	if (!ASSERT_ERR(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
241 				   &zero, sizeof(int)), "setsockopt"))
242 		goto close_bpf_object;
243 	if (!ASSERT_EQ(errno, EPERM, "setsockopt-errno"))
244 		goto close_bpf_object;
245 
246 	if (!ASSERT_EQ(obj->bss->invocations, 2, "invocations"))
247 		goto close_bpf_object;
248 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
249 		goto close_bpf_object;
250 	if (!ASSERT_EQ(obj->bss->retval_value, -EPERM, "retval_value"))
251 		goto close_bpf_object;
252 
253 close_bpf_object:
254 	bpf_link__destroy(link_legacy_eperm);
255 	bpf_link__destroy(link_get_retval);
256 
257 	cgroup_getset_retval_setsockopt__destroy(obj);
258 }
259 
260 static void test_setsockopt_legacy_no_override(int cgroup_fd, int sock_fd)
261 {
262 	struct cgroup_getset_retval_setsockopt *obj;
263 	struct bpf_link *link_set_eunatch = NULL, *link_legacy_eperm = NULL;
264 	struct bpf_link *link_get_retval = NULL;
265 
266 	obj = cgroup_getset_retval_setsockopt__open_and_load();
267 	if (!ASSERT_OK_PTR(obj, "skel-load"))
268 		return;
269 
270 	/* Attach setsockopt that sets EUNATCH, then one that return a reject
271 	 * without setting errno, and then one that gets the exported errno.
272 	 * Assert both the syscall and the helper's errno are unaffected by
273 	 * the second prog (i.e. legacy rejects does not override the errno
274 	 * to EPERM).
275 	 */
276 	link_set_eunatch = bpf_program__attach_cgroup(obj->progs.set_eunatch,
277 						      cgroup_fd);
278 	if (!ASSERT_OK_PTR(link_set_eunatch, "cg-attach-set_eunatch"))
279 		goto close_bpf_object;
280 	link_legacy_eperm = bpf_program__attach_cgroup(obj->progs.legacy_eperm,
281 						       cgroup_fd);
282 	if (!ASSERT_OK_PTR(link_legacy_eperm, "cg-attach-legacy_eperm"))
283 		goto close_bpf_object;
284 	link_get_retval = bpf_program__attach_cgroup(obj->progs.get_retval,
285 						     cgroup_fd);
286 	if (!ASSERT_OK_PTR(link_get_retval, "cg-attach-get_retval"))
287 		goto close_bpf_object;
288 
289 	if (!ASSERT_ERR(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
290 				   &zero, sizeof(int)), "setsockopt"))
291 		goto close_bpf_object;
292 	if (!ASSERT_EQ(errno, EUNATCH, "setsockopt-errno"))
293 		goto close_bpf_object;
294 
295 	if (!ASSERT_EQ(obj->bss->invocations, 3, "invocations"))
296 		goto close_bpf_object;
297 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
298 		goto close_bpf_object;
299 	if (!ASSERT_EQ(obj->bss->retval_value, -EUNATCH, "retval_value"))
300 		goto close_bpf_object;
301 
302 close_bpf_object:
303 	bpf_link__destroy(link_set_eunatch);
304 	bpf_link__destroy(link_legacy_eperm);
305 	bpf_link__destroy(link_get_retval);
306 
307 	cgroup_getset_retval_setsockopt__destroy(obj);
308 }
309 
310 static void test_getsockopt_get(int cgroup_fd, int sock_fd)
311 {
312 	struct cgroup_getset_retval_getsockopt *obj;
313 	struct bpf_link *link_get_retval = NULL;
314 	int buf;
315 	socklen_t optlen = sizeof(buf);
316 
317 	obj = cgroup_getset_retval_getsockopt__open_and_load();
318 	if (!ASSERT_OK_PTR(obj, "skel-load"))
319 		return;
320 
321 	/* Attach getsockopt that gets previously set errno. Assert that the
322 	 * error from kernel is in both ctx_retval_value and retval_value.
323 	 */
324 	link_get_retval = bpf_program__attach_cgroup(obj->progs.get_retval,
325 						     cgroup_fd);
326 	if (!ASSERT_OK_PTR(link_get_retval, "cg-attach-get_retval"))
327 		goto close_bpf_object;
328 
329 	if (!ASSERT_ERR(getsockopt(sock_fd, SOL_CUSTOM, 0,
330 				   &buf, &optlen), "getsockopt"))
331 		goto close_bpf_object;
332 	if (!ASSERT_EQ(errno, EOPNOTSUPP, "getsockopt-errno"))
333 		goto close_bpf_object;
334 
335 	if (!ASSERT_EQ(obj->bss->invocations, 1, "invocations"))
336 		goto close_bpf_object;
337 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
338 		goto close_bpf_object;
339 	if (!ASSERT_EQ(obj->bss->retval_value, -EOPNOTSUPP, "retval_value"))
340 		goto close_bpf_object;
341 	if (!ASSERT_EQ(obj->bss->ctx_retval_value, -EOPNOTSUPP, "ctx_retval_value"))
342 		goto close_bpf_object;
343 
344 close_bpf_object:
345 	bpf_link__destroy(link_get_retval);
346 
347 	cgroup_getset_retval_getsockopt__destroy(obj);
348 }
349 
350 static void test_getsockopt_override(int cgroup_fd, int sock_fd)
351 {
352 	struct cgroup_getset_retval_getsockopt *obj;
353 	struct bpf_link *link_set_eisconn = NULL;
354 	int buf;
355 	socklen_t optlen = sizeof(buf);
356 
357 	obj = cgroup_getset_retval_getsockopt__open_and_load();
358 	if (!ASSERT_OK_PTR(obj, "skel-load"))
359 		return;
360 
361 	/* Attach getsockopt that sets retval to -EISCONN. Assert that this
362 	 * overrides the value from kernel.
363 	 */
364 	link_set_eisconn = bpf_program__attach_cgroup(obj->progs.set_eisconn,
365 						      cgroup_fd);
366 	if (!ASSERT_OK_PTR(link_set_eisconn, "cg-attach-set_eisconn"))
367 		goto close_bpf_object;
368 
369 	if (!ASSERT_ERR(getsockopt(sock_fd, SOL_CUSTOM, 0,
370 				   &buf, &optlen), "getsockopt"))
371 		goto close_bpf_object;
372 	if (!ASSERT_EQ(errno, EISCONN, "getsockopt-errno"))
373 		goto close_bpf_object;
374 
375 	if (!ASSERT_EQ(obj->bss->invocations, 1, "invocations"))
376 		goto close_bpf_object;
377 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
378 		goto close_bpf_object;
379 
380 close_bpf_object:
381 	bpf_link__destroy(link_set_eisconn);
382 
383 	cgroup_getset_retval_getsockopt__destroy(obj);
384 }
385 
386 static void test_getsockopt_retval_sync(int cgroup_fd, int sock_fd)
387 {
388 	struct cgroup_getset_retval_getsockopt *obj;
389 	struct bpf_link *link_set_eisconn = NULL, *link_clear_retval = NULL;
390 	struct bpf_link *link_get_retval = NULL;
391 	int buf;
392 	socklen_t optlen = sizeof(buf);
393 
394 	obj = cgroup_getset_retval_getsockopt__open_and_load();
395 	if (!ASSERT_OK_PTR(obj, "skel-load"))
396 		return;
397 
398 	/* Attach getsockopt that sets retval to -EISCONN, and one that clears
399 	 * ctx retval. Assert that the clearing ctx retval is synced to helper
400 	 * and clears any errors both from kernel and BPF..
401 	 */
402 	link_set_eisconn = bpf_program__attach_cgroup(obj->progs.set_eisconn,
403 						      cgroup_fd);
404 	if (!ASSERT_OK_PTR(link_set_eisconn, "cg-attach-set_eisconn"))
405 		goto close_bpf_object;
406 	link_clear_retval = bpf_program__attach_cgroup(obj->progs.clear_retval,
407 						       cgroup_fd);
408 	if (!ASSERT_OK_PTR(link_clear_retval, "cg-attach-clear_retval"))
409 		goto close_bpf_object;
410 	link_get_retval = bpf_program__attach_cgroup(obj->progs.get_retval,
411 						     cgroup_fd);
412 	if (!ASSERT_OK_PTR(link_get_retval, "cg-attach-get_retval"))
413 		goto close_bpf_object;
414 
415 	if (!ASSERT_OK(getsockopt(sock_fd, SOL_CUSTOM, 0,
416 				  &buf, &optlen), "getsockopt"))
417 		goto close_bpf_object;
418 
419 	if (!ASSERT_EQ(obj->bss->invocations, 3, "invocations"))
420 		goto close_bpf_object;
421 	if (!ASSERT_FALSE(obj->bss->assertion_error, "assertion_error"))
422 		goto close_bpf_object;
423 	if (!ASSERT_EQ(obj->bss->retval_value, 0, "retval_value"))
424 		goto close_bpf_object;
425 	if (!ASSERT_EQ(obj->bss->ctx_retval_value, 0, "ctx_retval_value"))
426 		goto close_bpf_object;
427 
428 close_bpf_object:
429 	bpf_link__destroy(link_set_eisconn);
430 	bpf_link__destroy(link_clear_retval);
431 	bpf_link__destroy(link_get_retval);
432 
433 	cgroup_getset_retval_getsockopt__destroy(obj);
434 }
435 
436 void test_cgroup_getset_retval(void)
437 {
438 	int cgroup_fd = -1;
439 	int sock_fd = -1;
440 
441 	cgroup_fd = test__join_cgroup("/cgroup_getset_retval");
442 	if (!ASSERT_GE(cgroup_fd, 0, "cg-create"))
443 		goto close_fd;
444 
445 	sock_fd = start_server(AF_INET, SOCK_DGRAM, NULL, 0, 0);
446 	if (!ASSERT_GE(sock_fd, 0, "start-server"))
447 		goto close_fd;
448 
449 	if (test__start_subtest("setsockopt-set"))
450 		test_setsockopt_set(cgroup_fd, sock_fd);
451 
452 	if (test__start_subtest("setsockopt-set_and_get"))
453 		test_setsockopt_set_and_get(cgroup_fd, sock_fd);
454 
455 	if (test__start_subtest("setsockopt-default_zero"))
456 		test_setsockopt_default_zero(cgroup_fd, sock_fd);
457 
458 	if (test__start_subtest("setsockopt-default_zero_and_set"))
459 		test_setsockopt_default_zero_and_set(cgroup_fd, sock_fd);
460 
461 	if (test__start_subtest("setsockopt-override"))
462 		test_setsockopt_override(cgroup_fd, sock_fd);
463 
464 	if (test__start_subtest("setsockopt-legacy_eperm"))
465 		test_setsockopt_legacy_eperm(cgroup_fd, sock_fd);
466 
467 	if (test__start_subtest("setsockopt-legacy_no_override"))
468 		test_setsockopt_legacy_no_override(cgroup_fd, sock_fd);
469 
470 	if (test__start_subtest("getsockopt-get"))
471 		test_getsockopt_get(cgroup_fd, sock_fd);
472 
473 	if (test__start_subtest("getsockopt-override"))
474 		test_getsockopt_override(cgroup_fd, sock_fd);
475 
476 	if (test__start_subtest("getsockopt-retval_sync"))
477 		test_getsockopt_retval_sync(cgroup_fd, sock_fd);
478 
479 close_fd:
480 	close(cgroup_fd);
481 }
482