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