1// SPDX-License-Identifier: GPL-2.0
2#include <test_progs.h>
3#include "cgroup_helpers.h"
4
5static char bpf_log_buf[4096];
6static bool verbose;
7
8#ifndef PAGE_SIZE
9#define PAGE_SIZE 4096
10#endif
11
12enum 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
23static 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
919static 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
943static 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
1043free_optval:
1044	free(optval);
1045close_sock_fd:
1046	close(sock_fd);
1047detach_prog:
1048	bpf_prog_detach2(prog_fd, cgroup_fd, test->attach_type);
1049close_prog_fd:
1050	close(prog_fd);
1051	return ret;
1052}
1053
1054void 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