xref: /linux/tools/testing/selftests/bpf/prog_tests/sockopt.c (revision 0e2b2a76278153d1ac312b0691cb65dabb9aef3e)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <test_progs.h>
3 #include "cgroup_helpers.h"
4 
5 static char bpf_log_buf[4096];
6 static bool verbose;
7 
8 #ifndef PAGE_SIZE
9 #define PAGE_SIZE 4096
10 #endif
11 
12 enum sockopt_test_error {
13 	OK = 0,
14 	DENY_LOAD,
15 	DENY_ATTACH,
16 	EOPNOTSUPP_GETSOCKOPT,
17 	EPERM_GETSOCKOPT,
18 	EFAULT_GETSOCKOPT,
19 	EPERM_SETSOCKOPT,
20 	EFAULT_SETSOCKOPT,
21 };
22 
23 static struct sockopt_test {
24 	const char			*descr;
25 	const struct bpf_insn		insns[64];
26 	enum bpf_attach_type		attach_type;
27 	enum bpf_attach_type		expected_attach_type;
28 
29 	int				set_optname;
30 	int				set_level;
31 	const char			set_optval[64];
32 	socklen_t			set_optlen;
33 
34 	int				get_optname;
35 	int				get_level;
36 	const char			get_optval[64];
37 	socklen_t			get_optlen;
38 	socklen_t			get_optlen_ret;
39 
40 	enum sockopt_test_error		error;
41 } tests[] = {
42 
43 	/* ==================== getsockopt ====================  */
44 
45 	{
46 		.descr = "getsockopt: no expected_attach_type",
47 		.insns = {
48 			/* return 1 */
49 			BPF_MOV64_IMM(BPF_REG_0, 1),
50 			BPF_EXIT_INSN(),
51 
52 		},
53 		.attach_type = BPF_CGROUP_GETSOCKOPT,
54 		.expected_attach_type = 0,
55 		.error = DENY_LOAD,
56 	},
57 	{
58 		.descr = "getsockopt: wrong expected_attach_type",
59 		.insns = {
60 			/* return 1 */
61 			BPF_MOV64_IMM(BPF_REG_0, 1),
62 			BPF_EXIT_INSN(),
63 
64 		},
65 		.attach_type = BPF_CGROUP_GETSOCKOPT,
66 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
67 		.error = DENY_ATTACH,
68 	},
69 	{
70 		.descr = "getsockopt: bypass bpf hook",
71 		.insns = {
72 			/* return 1 */
73 			BPF_MOV64_IMM(BPF_REG_0, 1),
74 			BPF_EXIT_INSN(),
75 		},
76 		.attach_type = BPF_CGROUP_GETSOCKOPT,
77 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
78 
79 		.get_level = SOL_IP,
80 		.set_level = SOL_IP,
81 
82 		.get_optname = IP_TOS,
83 		.set_optname = IP_TOS,
84 
85 		.set_optval = { 1 << 3 },
86 		.set_optlen = 1,
87 
88 		.get_optval = { 1 << 3 },
89 		.get_optlen = 1,
90 	},
91 	{
92 		.descr = "getsockopt: return EPERM from bpf hook",
93 		.insns = {
94 			BPF_MOV64_IMM(BPF_REG_0, 0),
95 			BPF_EXIT_INSN(),
96 		},
97 		.attach_type = BPF_CGROUP_GETSOCKOPT,
98 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
99 
100 		.get_level = SOL_IP,
101 		.get_optname = IP_TOS,
102 
103 		.get_optlen = 1,
104 		.error = EPERM_GETSOCKOPT,
105 	},
106 	{
107 		.descr = "getsockopt: no optval bounds check, deny loading",
108 		.insns = {
109 			/* r6 = ctx->optval */
110 			BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
111 				    offsetof(struct bpf_sockopt, optval)),
112 
113 			/* ctx->optval[0] = 0x80 */
114 			BPF_MOV64_IMM(BPF_REG_0, 0x80),
115 			BPF_STX_MEM(BPF_W, BPF_REG_6, BPF_REG_0, 0),
116 
117 			/* return 1 */
118 			BPF_MOV64_IMM(BPF_REG_0, 1),
119 			BPF_EXIT_INSN(),
120 		},
121 		.attach_type = BPF_CGROUP_GETSOCKOPT,
122 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
123 		.error = DENY_LOAD,
124 	},
125 	{
126 		.descr = "getsockopt: read ctx->level",
127 		.insns = {
128 			/* r6 = ctx->level */
129 			BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
130 				    offsetof(struct bpf_sockopt, level)),
131 
132 			/* if (ctx->level == 123) { */
133 			BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 123, 4),
134 			/* ctx->retval = 0 */
135 			BPF_MOV64_IMM(BPF_REG_0, 0),
136 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
137 				    offsetof(struct bpf_sockopt, retval)),
138 			/* return 1 */
139 			BPF_MOV64_IMM(BPF_REG_0, 1),
140 			BPF_JMP_A(1),
141 			/* } else { */
142 			/* return 0 */
143 			BPF_MOV64_IMM(BPF_REG_0, 0),
144 			/* } */
145 			BPF_EXIT_INSN(),
146 		},
147 		.attach_type = BPF_CGROUP_GETSOCKOPT,
148 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
149 
150 		.get_level = 123,
151 
152 		.get_optlen = 1,
153 	},
154 	{
155 		.descr = "getsockopt: deny writing to ctx->level",
156 		.insns = {
157 			/* ctx->level = 1 */
158 			BPF_MOV64_IMM(BPF_REG_0, 1),
159 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
160 				    offsetof(struct bpf_sockopt, level)),
161 			BPF_EXIT_INSN(),
162 		},
163 		.attach_type = BPF_CGROUP_GETSOCKOPT,
164 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
165 
166 		.error = DENY_LOAD,
167 	},
168 	{
169 		.descr = "getsockopt: read ctx->optname",
170 		.insns = {
171 			/* r6 = ctx->optname */
172 			BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
173 				    offsetof(struct bpf_sockopt, optname)),
174 
175 			/* if (ctx->optname == 123) { */
176 			BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 123, 4),
177 			/* ctx->retval = 0 */
178 			BPF_MOV64_IMM(BPF_REG_0, 0),
179 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
180 				    offsetof(struct bpf_sockopt, retval)),
181 			/* return 1 */
182 			BPF_MOV64_IMM(BPF_REG_0, 1),
183 			BPF_JMP_A(1),
184 			/* } else { */
185 			/* return 0 */
186 			BPF_MOV64_IMM(BPF_REG_0, 0),
187 			/* } */
188 			BPF_EXIT_INSN(),
189 		},
190 		.attach_type = BPF_CGROUP_GETSOCKOPT,
191 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
192 
193 		.get_optname = 123,
194 
195 		.get_optlen = 1,
196 	},
197 	{
198 		.descr = "getsockopt: read ctx->retval",
199 		.insns = {
200 			/* r6 = ctx->retval */
201 			BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
202 				    offsetof(struct bpf_sockopt, retval)),
203 
204 			/* return 1 */
205 			BPF_MOV64_IMM(BPF_REG_0, 1),
206 			BPF_EXIT_INSN(),
207 		},
208 		.attach_type = BPF_CGROUP_GETSOCKOPT,
209 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
210 
211 		.get_level = SOL_IP,
212 		.get_optname = IP_TOS,
213 		.get_optlen = 1,
214 	},
215 	{
216 		.descr = "getsockopt: deny writing to ctx->optname",
217 		.insns = {
218 			/* ctx->optname = 1 */
219 			BPF_MOV64_IMM(BPF_REG_0, 1),
220 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
221 				    offsetof(struct bpf_sockopt, optname)),
222 			BPF_EXIT_INSN(),
223 		},
224 		.attach_type = BPF_CGROUP_GETSOCKOPT,
225 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
226 
227 		.error = DENY_LOAD,
228 	},
229 	{
230 		.descr = "getsockopt: read ctx->optlen",
231 		.insns = {
232 			/* r6 = ctx->optlen */
233 			BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
234 				    offsetof(struct bpf_sockopt, optlen)),
235 
236 			/* if (ctx->optlen == 64) { */
237 			BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 64, 4),
238 			/* ctx->retval = 0 */
239 			BPF_MOV64_IMM(BPF_REG_0, 0),
240 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
241 				    offsetof(struct bpf_sockopt, retval)),
242 			/* return 1 */
243 			BPF_MOV64_IMM(BPF_REG_0, 1),
244 			BPF_JMP_A(1),
245 			/* } else { */
246 			/* return 0 */
247 			BPF_MOV64_IMM(BPF_REG_0, 0),
248 			/* } */
249 			BPF_EXIT_INSN(),
250 		},
251 		.attach_type = BPF_CGROUP_GETSOCKOPT,
252 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
253 
254 		.get_optlen = 64,
255 	},
256 	{
257 		.descr = "getsockopt: deny bigger ctx->optlen",
258 		.insns = {
259 			/* ctx->optlen = 65 */
260 			BPF_MOV64_IMM(BPF_REG_0, 65),
261 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
262 				    offsetof(struct bpf_sockopt, optlen)),
263 
264 			/* ctx->retval = 0 */
265 			BPF_MOV64_IMM(BPF_REG_0, 0),
266 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
267 				    offsetof(struct bpf_sockopt, retval)),
268 
269 			/* return 1 */
270 			BPF_MOV64_IMM(BPF_REG_0, 1),
271 			BPF_EXIT_INSN(),
272 		},
273 		.attach_type = BPF_CGROUP_GETSOCKOPT,
274 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
275 
276 		.get_optlen = 64,
277 
278 		.error = EFAULT_GETSOCKOPT,
279 	},
280 	{
281 		.descr = "getsockopt: ignore >PAGE_SIZE optlen",
282 		.insns = {
283 			/* write 0xFF to the first optval byte */
284 
285 			/* r6 = ctx->optval */
286 			BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
287 				    offsetof(struct bpf_sockopt, optval)),
288 			/* r2 = ctx->optval */
289 			BPF_MOV64_REG(BPF_REG_2, BPF_REG_6),
290 			/* r6 = ctx->optval + 1 */
291 			BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1),
292 
293 			/* r7 = ctx->optval_end */
294 			BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_1,
295 				    offsetof(struct bpf_sockopt, optval_end)),
296 
297 			/* if (ctx->optval + 1 <= ctx->optval_end) { */
298 			BPF_JMP_REG(BPF_JGT, BPF_REG_6, BPF_REG_7, 1),
299 			/* ctx->optval[0] = 0xF0 */
300 			BPF_ST_MEM(BPF_B, BPF_REG_2, 0, 0xFF),
301 			/* } */
302 
303 			/* retval changes are ignored */
304 			/* ctx->retval = 5 */
305 			BPF_MOV64_IMM(BPF_REG_0, 5),
306 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
307 				    offsetof(struct bpf_sockopt, retval)),
308 
309 			/* return 1 */
310 			BPF_MOV64_IMM(BPF_REG_0, 1),
311 			BPF_EXIT_INSN(),
312 		},
313 		.attach_type = BPF_CGROUP_GETSOCKOPT,
314 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
315 
316 		.get_level = 1234,
317 		.get_optname = 5678,
318 		.get_optval = {}, /* the changes are ignored */
319 		.get_optlen = PAGE_SIZE + 1,
320 		.error = EOPNOTSUPP_GETSOCKOPT,
321 	},
322 	{
323 		.descr = "getsockopt: support smaller ctx->optlen",
324 		.insns = {
325 			/* ctx->optlen = 32 */
326 			BPF_MOV64_IMM(BPF_REG_0, 32),
327 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
328 				    offsetof(struct bpf_sockopt, optlen)),
329 			/* ctx->retval = 0 */
330 			BPF_MOV64_IMM(BPF_REG_0, 0),
331 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
332 				    offsetof(struct bpf_sockopt, retval)),
333 			/* return 1 */
334 			BPF_MOV64_IMM(BPF_REG_0, 1),
335 			BPF_EXIT_INSN(),
336 		},
337 		.attach_type = BPF_CGROUP_GETSOCKOPT,
338 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
339 
340 		.get_optlen = 64,
341 		.get_optlen_ret = 32,
342 	},
343 	{
344 		.descr = "getsockopt: deny writing to ctx->optval",
345 		.insns = {
346 			/* ctx->optval = 1 */
347 			BPF_MOV64_IMM(BPF_REG_0, 1),
348 			BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0,
349 				    offsetof(struct bpf_sockopt, optval)),
350 			BPF_EXIT_INSN(),
351 		},
352 		.attach_type = BPF_CGROUP_GETSOCKOPT,
353 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
354 
355 		.error = DENY_LOAD,
356 	},
357 	{
358 		.descr = "getsockopt: deny writing to ctx->optval_end",
359 		.insns = {
360 			/* ctx->optval_end = 1 */
361 			BPF_MOV64_IMM(BPF_REG_0, 1),
362 			BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0,
363 				    offsetof(struct bpf_sockopt, optval_end)),
364 			BPF_EXIT_INSN(),
365 		},
366 		.attach_type = BPF_CGROUP_GETSOCKOPT,
367 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
368 
369 		.error = DENY_LOAD,
370 	},
371 	{
372 		.descr = "getsockopt: rewrite value",
373 		.insns = {
374 			/* r6 = ctx->optval */
375 			BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
376 				    offsetof(struct bpf_sockopt, optval)),
377 			/* r2 = ctx->optval */
378 			BPF_MOV64_REG(BPF_REG_2, BPF_REG_6),
379 			/* r6 = ctx->optval + 1 */
380 			BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1),
381 
382 			/* r7 = ctx->optval_end */
383 			BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_1,
384 				    offsetof(struct bpf_sockopt, optval_end)),
385 
386 			/* if (ctx->optval + 1 <= ctx->optval_end) { */
387 			BPF_JMP_REG(BPF_JGT, BPF_REG_6, BPF_REG_7, 1),
388 			/* ctx->optval[0] = 0xF0 */
389 			BPF_ST_MEM(BPF_B, BPF_REG_2, 0, 0xF0),
390 			/* } */
391 
392 			/* ctx->retval = 0 */
393 			BPF_MOV64_IMM(BPF_REG_0, 0),
394 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
395 				    offsetof(struct bpf_sockopt, retval)),
396 
397 			/* return 1*/
398 			BPF_MOV64_IMM(BPF_REG_0, 1),
399 			BPF_EXIT_INSN(),
400 		},
401 		.attach_type = BPF_CGROUP_GETSOCKOPT,
402 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
403 
404 		.get_level = SOL_IP,
405 		.get_optname = IP_TOS,
406 
407 		.get_optval = { 0xF0 },
408 		.get_optlen = 1,
409 	},
410 
411 	/* ==================== setsockopt ====================  */
412 
413 	{
414 		.descr = "setsockopt: no expected_attach_type",
415 		.insns = {
416 			/* return 1 */
417 			BPF_MOV64_IMM(BPF_REG_0, 1),
418 			BPF_EXIT_INSN(),
419 
420 		},
421 		.attach_type = BPF_CGROUP_SETSOCKOPT,
422 		.expected_attach_type = 0,
423 		.error = DENY_LOAD,
424 	},
425 	{
426 		.descr = "setsockopt: wrong expected_attach_type",
427 		.insns = {
428 			/* return 1 */
429 			BPF_MOV64_IMM(BPF_REG_0, 1),
430 			BPF_EXIT_INSN(),
431 
432 		},
433 		.attach_type = BPF_CGROUP_SETSOCKOPT,
434 		.expected_attach_type = BPF_CGROUP_GETSOCKOPT,
435 		.error = DENY_ATTACH,
436 	},
437 	{
438 		.descr = "setsockopt: bypass bpf hook",
439 		.insns = {
440 			/* return 1 */
441 			BPF_MOV64_IMM(BPF_REG_0, 1),
442 			BPF_EXIT_INSN(),
443 		},
444 		.attach_type = BPF_CGROUP_SETSOCKOPT,
445 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
446 
447 		.get_level = SOL_IP,
448 		.set_level = SOL_IP,
449 
450 		.get_optname = IP_TOS,
451 		.set_optname = IP_TOS,
452 
453 		.set_optval = { 1 << 3 },
454 		.set_optlen = 1,
455 
456 		.get_optval = { 1 << 3 },
457 		.get_optlen = 1,
458 	},
459 	{
460 		.descr = "setsockopt: return EPERM from bpf hook",
461 		.insns = {
462 			/* return 0 */
463 			BPF_MOV64_IMM(BPF_REG_0, 0),
464 			BPF_EXIT_INSN(),
465 		},
466 		.attach_type = BPF_CGROUP_SETSOCKOPT,
467 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
468 
469 		.set_level = SOL_IP,
470 		.set_optname = IP_TOS,
471 
472 		.set_optlen = 1,
473 		.error = EPERM_SETSOCKOPT,
474 	},
475 	{
476 		.descr = "setsockopt: no optval bounds check, deny loading",
477 		.insns = {
478 			/* r6 = ctx->optval */
479 			BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
480 				    offsetof(struct bpf_sockopt, optval)),
481 
482 			/* r0 = ctx->optval[0] */
483 			BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_6, 0),
484 
485 			/* return 1 */
486 			BPF_MOV64_IMM(BPF_REG_0, 1),
487 			BPF_EXIT_INSN(),
488 		},
489 		.attach_type = BPF_CGROUP_SETSOCKOPT,
490 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
491 		.error = DENY_LOAD,
492 	},
493 	{
494 		.descr = "setsockopt: read ctx->level",
495 		.insns = {
496 			/* r6 = ctx->level */
497 			BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
498 				    offsetof(struct bpf_sockopt, level)),
499 
500 			/* if (ctx->level == 123) { */
501 			BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 123, 4),
502 			/* ctx->optlen = -1 */
503 			BPF_MOV64_IMM(BPF_REG_0, -1),
504 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
505 				    offsetof(struct bpf_sockopt, optlen)),
506 			/* return 1 */
507 			BPF_MOV64_IMM(BPF_REG_0, 1),
508 			BPF_JMP_A(1),
509 			/* } else { */
510 			/* return 0 */
511 			BPF_MOV64_IMM(BPF_REG_0, 0),
512 			/* } */
513 			BPF_EXIT_INSN(),
514 		},
515 		.attach_type = BPF_CGROUP_SETSOCKOPT,
516 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
517 
518 		.set_level = 123,
519 
520 		.set_optlen = 1,
521 	},
522 	{
523 		.descr = "setsockopt: allow changing ctx->level",
524 		.insns = {
525 			/* ctx->level = SOL_IP */
526 			BPF_MOV64_IMM(BPF_REG_0, SOL_IP),
527 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
528 				    offsetof(struct bpf_sockopt, level)),
529 			/* return 1 */
530 			BPF_MOV64_IMM(BPF_REG_0, 1),
531 			BPF_EXIT_INSN(),
532 		},
533 		.attach_type = BPF_CGROUP_SETSOCKOPT,
534 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
535 
536 		.get_level = SOL_IP,
537 		.set_level = 234, /* should be rewritten to SOL_IP */
538 
539 		.get_optname = IP_TOS,
540 		.set_optname = IP_TOS,
541 
542 		.set_optval = { 1 << 3 },
543 		.set_optlen = 1,
544 		.get_optval = { 1 << 3 },
545 		.get_optlen = 1,
546 	},
547 	{
548 		.descr = "setsockopt: read ctx->optname",
549 		.insns = {
550 			/* r6 = ctx->optname */
551 			BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
552 				    offsetof(struct bpf_sockopt, optname)),
553 
554 			/* if (ctx->optname == 123) { */
555 			BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 123, 4),
556 			/* ctx->optlen = -1 */
557 			BPF_MOV64_IMM(BPF_REG_0, -1),
558 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
559 				    offsetof(struct bpf_sockopt, optlen)),
560 			/* return 1 */
561 			BPF_MOV64_IMM(BPF_REG_0, 1),
562 			BPF_JMP_A(1),
563 			/* } else { */
564 			/* return 0 */
565 			BPF_MOV64_IMM(BPF_REG_0, 0),
566 			/* } */
567 			BPF_EXIT_INSN(),
568 		},
569 		.attach_type = BPF_CGROUP_SETSOCKOPT,
570 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
571 
572 		.set_optname = 123,
573 
574 		.set_optlen = 1,
575 	},
576 	{
577 		.descr = "setsockopt: allow changing ctx->optname",
578 		.insns = {
579 			/* ctx->optname = IP_TOS */
580 			BPF_MOV64_IMM(BPF_REG_0, IP_TOS),
581 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
582 				    offsetof(struct bpf_sockopt, optname)),
583 			/* return 1 */
584 			BPF_MOV64_IMM(BPF_REG_0, 1),
585 			BPF_EXIT_INSN(),
586 		},
587 		.attach_type = BPF_CGROUP_SETSOCKOPT,
588 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
589 
590 		.get_level = SOL_IP,
591 		.set_level = SOL_IP,
592 
593 		.get_optname = IP_TOS,
594 		.set_optname = 456, /* should be rewritten to IP_TOS */
595 
596 		.set_optval = { 1 << 3 },
597 		.set_optlen = 1,
598 		.get_optval = { 1 << 3 },
599 		.get_optlen = 1,
600 	},
601 	{
602 		.descr = "setsockopt: read ctx->optlen",
603 		.insns = {
604 			/* r6 = ctx->optlen */
605 			BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
606 				    offsetof(struct bpf_sockopt, optlen)),
607 
608 			/* if (ctx->optlen == 64) { */
609 			BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 64, 4),
610 			/* ctx->optlen = -1 */
611 			BPF_MOV64_IMM(BPF_REG_0, -1),
612 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
613 				    offsetof(struct bpf_sockopt, optlen)),
614 			/* return 1 */
615 			BPF_MOV64_IMM(BPF_REG_0, 1),
616 			BPF_JMP_A(1),
617 			/* } else { */
618 			/* return 0 */
619 			BPF_MOV64_IMM(BPF_REG_0, 0),
620 			/* } */
621 			BPF_EXIT_INSN(),
622 		},
623 		.attach_type = BPF_CGROUP_SETSOCKOPT,
624 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
625 
626 		.set_optlen = 64,
627 	},
628 	{
629 		.descr = "setsockopt: ctx->optlen == -1 is ok",
630 		.insns = {
631 			/* ctx->optlen = -1 */
632 			BPF_MOV64_IMM(BPF_REG_0, -1),
633 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
634 				    offsetof(struct bpf_sockopt, optlen)),
635 			/* return 1 */
636 			BPF_MOV64_IMM(BPF_REG_0, 1),
637 			BPF_EXIT_INSN(),
638 		},
639 		.attach_type = BPF_CGROUP_SETSOCKOPT,
640 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
641 
642 		.set_optlen = 64,
643 	},
644 	{
645 		.descr = "setsockopt: deny ctx->optlen < 0 (except -1)",
646 		.insns = {
647 			/* ctx->optlen = -2 */
648 			BPF_MOV64_IMM(BPF_REG_0, -2),
649 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
650 				    offsetof(struct bpf_sockopt, optlen)),
651 			/* return 1 */
652 			BPF_MOV64_IMM(BPF_REG_0, 1),
653 			BPF_EXIT_INSN(),
654 		},
655 		.attach_type = BPF_CGROUP_SETSOCKOPT,
656 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
657 
658 		.set_optlen = 4,
659 
660 		.error = EFAULT_SETSOCKOPT,
661 	},
662 	{
663 		.descr = "setsockopt: deny ctx->optlen > input optlen",
664 		.insns = {
665 			/* ctx->optlen = 65 */
666 			BPF_MOV64_IMM(BPF_REG_0, 65),
667 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
668 				    offsetof(struct bpf_sockopt, optlen)),
669 			BPF_MOV64_IMM(BPF_REG_0, 1),
670 			BPF_EXIT_INSN(),
671 		},
672 		.attach_type = BPF_CGROUP_SETSOCKOPT,
673 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
674 
675 		.set_optlen = 64,
676 
677 		.error = EFAULT_SETSOCKOPT,
678 	},
679 	{
680 		.descr = "setsockopt: ignore >PAGE_SIZE optlen",
681 		.insns = {
682 			/* write 0xFF to the first optval byte */
683 
684 			/* r6 = ctx->optval */
685 			BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
686 				    offsetof(struct bpf_sockopt, optval)),
687 			/* r2 = ctx->optval */
688 			BPF_MOV64_REG(BPF_REG_2, BPF_REG_6),
689 			/* r6 = ctx->optval + 1 */
690 			BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1),
691 
692 			/* r7 = ctx->optval_end */
693 			BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_1,
694 				    offsetof(struct bpf_sockopt, optval_end)),
695 
696 			/* if (ctx->optval + 1 <= ctx->optval_end) { */
697 			BPF_JMP_REG(BPF_JGT, BPF_REG_6, BPF_REG_7, 1),
698 			/* ctx->optval[0] = 0xF0 */
699 			BPF_ST_MEM(BPF_B, BPF_REG_2, 0, 0xF0),
700 			/* } */
701 
702 			BPF_MOV64_IMM(BPF_REG_0, 1),
703 			BPF_EXIT_INSN(),
704 		},
705 		.attach_type = BPF_CGROUP_SETSOCKOPT,
706 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
707 
708 		.set_level = SOL_IP,
709 		.set_optname = IP_TOS,
710 		.set_optval = {},
711 		.set_optlen = PAGE_SIZE + 1,
712 
713 		.get_level = SOL_IP,
714 		.get_optname = IP_TOS,
715 		.get_optval = {}, /* the changes are ignored */
716 		.get_optlen = 4,
717 	},
718 	{
719 		.descr = "setsockopt: allow changing ctx->optlen within bounds",
720 		.insns = {
721 			/* r6 = ctx->optval */
722 			BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
723 				    offsetof(struct bpf_sockopt, optval)),
724 			/* r2 = ctx->optval */
725 			BPF_MOV64_REG(BPF_REG_2, BPF_REG_6),
726 			/* r6 = ctx->optval + 1 */
727 			BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1),
728 
729 			/* r7 = ctx->optval_end */
730 			BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_1,
731 				    offsetof(struct bpf_sockopt, optval_end)),
732 
733 			/* if (ctx->optval + 1 <= ctx->optval_end) { */
734 			BPF_JMP_REG(BPF_JGT, BPF_REG_6, BPF_REG_7, 1),
735 			/* ctx->optval[0] = 1 << 3 */
736 			BPF_ST_MEM(BPF_B, BPF_REG_2, 0, 1 << 3),
737 			/* } */
738 
739 			/* ctx->optlen = 1 */
740 			BPF_MOV64_IMM(BPF_REG_0, 1),
741 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
742 				    offsetof(struct bpf_sockopt, optlen)),
743 
744 			/* return 1*/
745 			BPF_MOV64_IMM(BPF_REG_0, 1),
746 			BPF_EXIT_INSN(),
747 		},
748 		.attach_type = BPF_CGROUP_SETSOCKOPT,
749 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
750 
751 		.get_level = SOL_IP,
752 		.set_level = SOL_IP,
753 
754 		.get_optname = IP_TOS,
755 		.set_optname = IP_TOS,
756 
757 		.set_optval = { 1, 1, 1, 1 },
758 		.set_optlen = 4,
759 		.get_optval = { 1 << 3 },
760 		.get_optlen = 1,
761 	},
762 	{
763 		.descr = "setsockopt: deny write ctx->retval",
764 		.insns = {
765 			/* ctx->retval = 0 */
766 			BPF_MOV64_IMM(BPF_REG_0, 0),
767 			BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_0,
768 				    offsetof(struct bpf_sockopt, retval)),
769 
770 			/* return 1 */
771 			BPF_MOV64_IMM(BPF_REG_0, 1),
772 			BPF_EXIT_INSN(),
773 		},
774 		.attach_type = BPF_CGROUP_SETSOCKOPT,
775 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
776 
777 		.error = DENY_LOAD,
778 	},
779 	{
780 		.descr = "setsockopt: deny read ctx->retval",
781 		.insns = {
782 			/* r6 = ctx->retval */
783 			BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_1,
784 				    offsetof(struct bpf_sockopt, retval)),
785 
786 			/* return 1 */
787 			BPF_MOV64_IMM(BPF_REG_0, 1),
788 			BPF_EXIT_INSN(),
789 		},
790 		.attach_type = BPF_CGROUP_SETSOCKOPT,
791 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
792 
793 		.error = DENY_LOAD,
794 	},
795 	{
796 		.descr = "setsockopt: deny writing to ctx->optval",
797 		.insns = {
798 			/* ctx->optval = 1 */
799 			BPF_MOV64_IMM(BPF_REG_0, 1),
800 			BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0,
801 				    offsetof(struct bpf_sockopt, optval)),
802 			BPF_EXIT_INSN(),
803 		},
804 		.attach_type = BPF_CGROUP_SETSOCKOPT,
805 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
806 
807 		.error = DENY_LOAD,
808 	},
809 	{
810 		.descr = "setsockopt: deny writing to ctx->optval_end",
811 		.insns = {
812 			/* ctx->optval_end = 1 */
813 			BPF_MOV64_IMM(BPF_REG_0, 1),
814 			BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0,
815 				    offsetof(struct bpf_sockopt, optval_end)),
816 			BPF_EXIT_INSN(),
817 		},
818 		.attach_type = BPF_CGROUP_SETSOCKOPT,
819 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
820 
821 		.error = DENY_LOAD,
822 	},
823 	{
824 		.descr = "setsockopt: allow IP_TOS <= 128",
825 		.insns = {
826 			/* r6 = ctx->optval */
827 			BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
828 				    offsetof(struct bpf_sockopt, optval)),
829 			/* r7 = ctx->optval + 1 */
830 			BPF_MOV64_REG(BPF_REG_7, BPF_REG_6),
831 			BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
832 
833 			/* r8 = ctx->optval_end */
834 			BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_1,
835 				    offsetof(struct bpf_sockopt, optval_end)),
836 
837 			/* if (ctx->optval + 1 <= ctx->optval_end) { */
838 			BPF_JMP_REG(BPF_JGT, BPF_REG_7, BPF_REG_8, 4),
839 
840 			/* r9 = ctx->optval[0] */
841 			BPF_LDX_MEM(BPF_B, BPF_REG_9, BPF_REG_6, 0),
842 
843 			/* if (ctx->optval[0] < 128) */
844 			BPF_JMP_IMM(BPF_JGT, BPF_REG_9, 128, 2),
845 			BPF_MOV64_IMM(BPF_REG_0, 1),
846 			BPF_JMP_A(1),
847 			/* } */
848 
849 			/* } else { */
850 			BPF_MOV64_IMM(BPF_REG_0, 0),
851 			/* } */
852 
853 			BPF_EXIT_INSN(),
854 		},
855 		.attach_type = BPF_CGROUP_SETSOCKOPT,
856 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
857 
858 		.get_level = SOL_IP,
859 		.set_level = SOL_IP,
860 
861 		.get_optname = IP_TOS,
862 		.set_optname = IP_TOS,
863 
864 		.set_optval = { 0x80 },
865 		.set_optlen = 1,
866 		.get_optval = { 0x80 },
867 		.get_optlen = 1,
868 	},
869 	{
870 		.descr = "setsockopt: deny IP_TOS > 128",
871 		.insns = {
872 			/* r6 = ctx->optval */
873 			BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
874 				    offsetof(struct bpf_sockopt, optval)),
875 			/* r7 = ctx->optval + 1 */
876 			BPF_MOV64_REG(BPF_REG_7, BPF_REG_6),
877 			BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
878 
879 			/* r8 = ctx->optval_end */
880 			BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_1,
881 				    offsetof(struct bpf_sockopt, optval_end)),
882 
883 			/* if (ctx->optval + 1 <= ctx->optval_end) { */
884 			BPF_JMP_REG(BPF_JGT, BPF_REG_7, BPF_REG_8, 4),
885 
886 			/* r9 = ctx->optval[0] */
887 			BPF_LDX_MEM(BPF_B, BPF_REG_9, BPF_REG_6, 0),
888 
889 			/* if (ctx->optval[0] < 128) */
890 			BPF_JMP_IMM(BPF_JGT, BPF_REG_9, 128, 2),
891 			BPF_MOV64_IMM(BPF_REG_0, 1),
892 			BPF_JMP_A(1),
893 			/* } */
894 
895 			/* } else { */
896 			BPF_MOV64_IMM(BPF_REG_0, 0),
897 			/* } */
898 
899 			BPF_EXIT_INSN(),
900 		},
901 		.attach_type = BPF_CGROUP_SETSOCKOPT,
902 		.expected_attach_type = BPF_CGROUP_SETSOCKOPT,
903 
904 		.get_level = SOL_IP,
905 		.set_level = SOL_IP,
906 
907 		.get_optname = IP_TOS,
908 		.set_optname = IP_TOS,
909 
910 		.set_optval = { 0x81 },
911 		.set_optlen = 1,
912 		.get_optval = { 0x00 },
913 		.get_optlen = 1,
914 
915 		.error = EPERM_SETSOCKOPT,
916 	},
917 };
918 
919 static int load_prog(const struct bpf_insn *insns,
920 		     enum bpf_attach_type expected_attach_type)
921 {
922 	LIBBPF_OPTS(bpf_prog_load_opts, opts,
923 		.expected_attach_type = expected_attach_type,
924 		.log_level = 2,
925 		.log_buf = bpf_log_buf,
926 		.log_size = sizeof(bpf_log_buf),
927 	);
928 	int fd, insns_cnt = 0;
929 
930 	for (;
931 	     insns[insns_cnt].code != (BPF_JMP | BPF_EXIT);
932 	     insns_cnt++) {
933 	}
934 	insns_cnt++;
935 
936 	fd = bpf_prog_load(BPF_PROG_TYPE_CGROUP_SOCKOPT, NULL, "GPL", insns, insns_cnt, &opts);
937 	if (verbose && fd < 0)
938 		fprintf(stderr, "%s\n", bpf_log_buf);
939 
940 	return fd;
941 }
942 
943 static int run_test(int cgroup_fd, struct sockopt_test *test)
944 {
945 	int sock_fd, err, prog_fd;
946 	void *optval = NULL;
947 	int ret = 0;
948 
949 	prog_fd = load_prog(test->insns, test->expected_attach_type);
950 	if (prog_fd < 0) {
951 		if (test->error == DENY_LOAD)
952 			return 0;
953 
954 		log_err("Failed to load BPF program");
955 		return -1;
956 	}
957 
958 	err = bpf_prog_attach(prog_fd, cgroup_fd, test->attach_type, 0);
959 	if (err < 0) {
960 		if (test->error == DENY_ATTACH)
961 			goto close_prog_fd;
962 
963 		log_err("Failed to attach BPF program");
964 		ret = -1;
965 		goto close_prog_fd;
966 	}
967 
968 	sock_fd = socket(AF_INET, SOCK_STREAM, 0);
969 	if (sock_fd < 0) {
970 		log_err("Failed to create AF_INET socket");
971 		ret = -1;
972 		goto detach_prog;
973 	}
974 
975 	if (test->set_optlen) {
976 		if (test->set_optlen >= PAGE_SIZE) {
977 			int num_pages = test->set_optlen / PAGE_SIZE;
978 			int remainder = test->set_optlen % PAGE_SIZE;
979 
980 			test->set_optlen = num_pages * sysconf(_SC_PAGESIZE) + remainder;
981 		}
982 
983 		err = setsockopt(sock_fd, test->set_level, test->set_optname,
984 				 test->set_optval, test->set_optlen);
985 		if (err) {
986 			if (errno == EPERM && test->error == EPERM_SETSOCKOPT)
987 				goto close_sock_fd;
988 			if (errno == EFAULT && test->error == EFAULT_SETSOCKOPT)
989 				goto free_optval;
990 
991 			log_err("Failed to call setsockopt");
992 			ret = -1;
993 			goto close_sock_fd;
994 		}
995 	}
996 
997 	if (test->get_optlen) {
998 		if (test->get_optlen >= PAGE_SIZE) {
999 			int num_pages = test->get_optlen / PAGE_SIZE;
1000 			int remainder = test->get_optlen % PAGE_SIZE;
1001 
1002 			test->get_optlen = num_pages * sysconf(_SC_PAGESIZE) + remainder;
1003 		}
1004 
1005 		optval = malloc(test->get_optlen);
1006 		memset(optval, 0, test->get_optlen);
1007 		socklen_t optlen = test->get_optlen;
1008 		socklen_t expected_get_optlen = test->get_optlen_ret ?:
1009 			test->get_optlen;
1010 
1011 		err = getsockopt(sock_fd, test->get_level, test->get_optname,
1012 				 optval, &optlen);
1013 		if (err) {
1014 			if (errno == EOPNOTSUPP && test->error == EOPNOTSUPP_GETSOCKOPT)
1015 				goto free_optval;
1016 			if (errno == EPERM && test->error == EPERM_GETSOCKOPT)
1017 				goto free_optval;
1018 			if (errno == EFAULT && test->error == EFAULT_GETSOCKOPT)
1019 				goto free_optval;
1020 
1021 			log_err("Failed to call getsockopt");
1022 			ret = -1;
1023 			goto free_optval;
1024 		}
1025 
1026 		if (optlen != expected_get_optlen) {
1027 			errno = 0;
1028 			log_err("getsockopt returned unexpected optlen");
1029 			ret = -1;
1030 			goto free_optval;
1031 		}
1032 
1033 		if (memcmp(optval, test->get_optval, optlen) != 0) {
1034 			errno = 0;
1035 			log_err("getsockopt returned unexpected optval");
1036 			ret = -1;
1037 			goto free_optval;
1038 		}
1039 	}
1040 
1041 	ret = test->error != OK;
1042 
1043 free_optval:
1044 	free(optval);
1045 close_sock_fd:
1046 	close(sock_fd);
1047 detach_prog:
1048 	bpf_prog_detach2(prog_fd, cgroup_fd, test->attach_type);
1049 close_prog_fd:
1050 	close(prog_fd);
1051 	return ret;
1052 }
1053 
1054 void test_sockopt(void)
1055 {
1056 	int cgroup_fd, i;
1057 
1058 	cgroup_fd = test__join_cgroup("/sockopt");
1059 	if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup"))
1060 		return;
1061 
1062 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
1063 		if (!test__start_subtest(tests[i].descr))
1064 			continue;
1065 
1066 		ASSERT_OK(run_test(cgroup_fd, &tests[i]), tests[i].descr);
1067 	}
1068 
1069 	close(cgroup_fd);
1070 }
1071