1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (c) 2018 Facebook
3 
4 #include <stdio.h>
5 #include <unistd.h>
6 
7 #include <arpa/inet.h>
8 #include <sys/types.h>
9 #include <sys/socket.h>
10 
11 #include <linux/filter.h>
12 
13 #include <bpf/bpf.h>
14 
15 #include "cgroup_helpers.h"
16 #include <bpf/bpf_endian.h>
17 #include "bpf_util.h"
18 
19 #define CG_PATH		"/foo"
20 #define MAX_INSNS	512
21 
22 char bpf_log_buf[BPF_LOG_BUF_SIZE];
23 static bool verbose = false;
24 
25 struct sock_test {
26 	const char *descr;
27 	/* BPF prog properties */
28 	struct bpf_insn	insns[MAX_INSNS];
29 	enum bpf_attach_type expected_attach_type;
30 	enum bpf_attach_type attach_type;
31 	/* Socket properties */
32 	int domain;
33 	int type;
34 	/* Endpoint to bind() to */
35 	const char *ip;
36 	unsigned short port;
37 	unsigned short port_retry;
38 	/* Expected test result */
39 	enum {
40 		LOAD_REJECT,
41 		ATTACH_REJECT,
42 		BIND_REJECT,
43 		SUCCESS,
44 		RETRY_SUCCESS,
45 		RETRY_REJECT
46 	} result;
47 };
48 
49 static struct sock_test tests[] = {
50 	{
51 		.descr = "bind4 load with invalid access: src_ip6",
52 		.insns = {
53 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
54 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
55 				    offsetof(struct bpf_sock, src_ip6[0])),
56 			BPF_MOV64_IMM(BPF_REG_0, 1),
57 			BPF_EXIT_INSN(),
58 		},
59 		.expected_attach_type = BPF_CGROUP_INET4_POST_BIND,
60 		.attach_type = BPF_CGROUP_INET4_POST_BIND,
61 		.result = LOAD_REJECT,
62 	},
63 	{
64 		.descr = "bind4 load with invalid access: mark",
65 		.insns = {
66 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
67 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
68 				    offsetof(struct bpf_sock, mark)),
69 			BPF_MOV64_IMM(BPF_REG_0, 1),
70 			BPF_EXIT_INSN(),
71 		},
72 		.expected_attach_type = BPF_CGROUP_INET4_POST_BIND,
73 		.attach_type = BPF_CGROUP_INET4_POST_BIND,
74 		.result = LOAD_REJECT,
75 	},
76 	{
77 		.descr = "bind6 load with invalid access: src_ip4",
78 		.insns = {
79 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
80 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
81 				    offsetof(struct bpf_sock, src_ip4)),
82 			BPF_MOV64_IMM(BPF_REG_0, 1),
83 			BPF_EXIT_INSN(),
84 		},
85 		.expected_attach_type = BPF_CGROUP_INET6_POST_BIND,
86 		.attach_type = BPF_CGROUP_INET6_POST_BIND,
87 		.result = LOAD_REJECT,
88 	},
89 	{
90 		.descr = "sock_create load with invalid access: src_port",
91 		.insns = {
92 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
93 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
94 				    offsetof(struct bpf_sock, src_port)),
95 			BPF_MOV64_IMM(BPF_REG_0, 1),
96 			BPF_EXIT_INSN(),
97 		},
98 		.expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE,
99 		.attach_type = BPF_CGROUP_INET_SOCK_CREATE,
100 		.result = LOAD_REJECT,
101 	},
102 	{
103 		.descr = "sock_create load w/o expected_attach_type (compat mode)",
104 		.insns = {
105 			BPF_MOV64_IMM(BPF_REG_0, 1),
106 			BPF_EXIT_INSN(),
107 		},
108 		.expected_attach_type = 0,
109 		.attach_type = BPF_CGROUP_INET_SOCK_CREATE,
110 		.domain = AF_INET,
111 		.type = SOCK_STREAM,
112 		.ip = "127.0.0.1",
113 		.port = 8097,
114 		.result = SUCCESS,
115 	},
116 	{
117 		.descr = "sock_create load w/ expected_attach_type",
118 		.insns = {
119 			BPF_MOV64_IMM(BPF_REG_0, 1),
120 			BPF_EXIT_INSN(),
121 		},
122 		.expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE,
123 		.attach_type = BPF_CGROUP_INET_SOCK_CREATE,
124 		.domain = AF_INET,
125 		.type = SOCK_STREAM,
126 		.ip = "127.0.0.1",
127 		.port = 8097,
128 		.result = SUCCESS,
129 	},
130 	{
131 		.descr = "attach type mismatch bind4 vs bind6",
132 		.insns = {
133 			BPF_MOV64_IMM(BPF_REG_0, 1),
134 			BPF_EXIT_INSN(),
135 		},
136 		.expected_attach_type = BPF_CGROUP_INET4_POST_BIND,
137 		.attach_type = BPF_CGROUP_INET6_POST_BIND,
138 		.result = ATTACH_REJECT,
139 	},
140 	{
141 		.descr = "attach type mismatch bind6 vs bind4",
142 		.insns = {
143 			BPF_MOV64_IMM(BPF_REG_0, 1),
144 			BPF_EXIT_INSN(),
145 		},
146 		.expected_attach_type = BPF_CGROUP_INET6_POST_BIND,
147 		.attach_type = BPF_CGROUP_INET4_POST_BIND,
148 		.result = ATTACH_REJECT,
149 	},
150 	{
151 		.descr = "attach type mismatch default vs bind4",
152 		.insns = {
153 			BPF_MOV64_IMM(BPF_REG_0, 1),
154 			BPF_EXIT_INSN(),
155 		},
156 		.expected_attach_type = 0,
157 		.attach_type = BPF_CGROUP_INET4_POST_BIND,
158 		.result = ATTACH_REJECT,
159 	},
160 	{
161 		.descr = "attach type mismatch bind6 vs sock_create",
162 		.insns = {
163 			BPF_MOV64_IMM(BPF_REG_0, 1),
164 			BPF_EXIT_INSN(),
165 		},
166 		.expected_attach_type = BPF_CGROUP_INET6_POST_BIND,
167 		.attach_type = BPF_CGROUP_INET_SOCK_CREATE,
168 		.result = ATTACH_REJECT,
169 	},
170 	{
171 		.descr = "bind4 reject all",
172 		.insns = {
173 			BPF_MOV64_IMM(BPF_REG_0, 0),
174 			BPF_EXIT_INSN(),
175 		},
176 		.expected_attach_type = BPF_CGROUP_INET4_POST_BIND,
177 		.attach_type = BPF_CGROUP_INET4_POST_BIND,
178 		.domain = AF_INET,
179 		.type = SOCK_STREAM,
180 		.ip = "0.0.0.0",
181 		.result = BIND_REJECT,
182 	},
183 	{
184 		.descr = "bind6 reject all",
185 		.insns = {
186 			BPF_MOV64_IMM(BPF_REG_0, 0),
187 			BPF_EXIT_INSN(),
188 		},
189 		.expected_attach_type = BPF_CGROUP_INET6_POST_BIND,
190 		.attach_type = BPF_CGROUP_INET6_POST_BIND,
191 		.domain = AF_INET6,
192 		.type = SOCK_STREAM,
193 		.ip = "::",
194 		.result = BIND_REJECT,
195 	},
196 	{
197 		.descr = "bind6 deny specific IP & port",
198 		.insns = {
199 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
200 
201 			/* if (ip == expected && port == expected) */
202 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
203 				    offsetof(struct bpf_sock, src_ip6[3])),
204 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7,
205 				    __bpf_constant_ntohl(0x00000001), 4),
206 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
207 				    offsetof(struct bpf_sock, src_port)),
208 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0x2001, 2),
209 
210 			/* return DENY; */
211 			BPF_MOV64_IMM(BPF_REG_0, 0),
212 			BPF_JMP_A(1),
213 
214 			/* else return ALLOW; */
215 			BPF_MOV64_IMM(BPF_REG_0, 1),
216 			BPF_EXIT_INSN(),
217 		},
218 		.expected_attach_type = BPF_CGROUP_INET6_POST_BIND,
219 		.attach_type = BPF_CGROUP_INET6_POST_BIND,
220 		.domain = AF_INET6,
221 		.type = SOCK_STREAM,
222 		.ip = "::1",
223 		.port = 8193,
224 		.result = BIND_REJECT,
225 	},
226 	{
227 		.descr = "bind4 allow specific IP & port",
228 		.insns = {
229 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
230 
231 			/* if (ip == expected && port == expected) */
232 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
233 				    offsetof(struct bpf_sock, src_ip4)),
234 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7,
235 				    __bpf_constant_ntohl(0x7F000001), 4),
236 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
237 				    offsetof(struct bpf_sock, src_port)),
238 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0x1002, 2),
239 
240 			/* return ALLOW; */
241 			BPF_MOV64_IMM(BPF_REG_0, 1),
242 			BPF_JMP_A(1),
243 
244 			/* else return DENY; */
245 			BPF_MOV64_IMM(BPF_REG_0, 0),
246 			BPF_EXIT_INSN(),
247 		},
248 		.expected_attach_type = BPF_CGROUP_INET4_POST_BIND,
249 		.attach_type = BPF_CGROUP_INET4_POST_BIND,
250 		.domain = AF_INET,
251 		.type = SOCK_STREAM,
252 		.ip = "127.0.0.1",
253 		.port = 4098,
254 		.result = SUCCESS,
255 	},
256 	{
257 		.descr = "bind4 deny specific IP & port of TCP, and retry",
258 		.insns = {
259 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
260 
261 			/* if (ip == expected && port == expected) */
262 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
263 				    offsetof(struct bpf_sock, src_ip4)),
264 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7,
265 				    __bpf_constant_ntohl(0x7F000001), 4),
266 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
267 				    offsetof(struct bpf_sock, src_port)),
268 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0x1002, 2),
269 
270 			/* return DENY; */
271 			BPF_MOV64_IMM(BPF_REG_0, 0),
272 			BPF_JMP_A(1),
273 
274 			/* else return ALLOW; */
275 			BPF_MOV64_IMM(BPF_REG_0, 1),
276 			BPF_EXIT_INSN(),
277 		},
278 		.expected_attach_type = BPF_CGROUP_INET4_POST_BIND,
279 		.attach_type = BPF_CGROUP_INET4_POST_BIND,
280 		.domain = AF_INET,
281 		.type = SOCK_STREAM,
282 		.ip = "127.0.0.1",
283 		.port = 4098,
284 		.port_retry = 5000,
285 		.result = RETRY_SUCCESS,
286 	},
287 	{
288 		.descr = "bind4 deny specific IP & port of UDP, and retry",
289 		.insns = {
290 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
291 
292 			/* if (ip == expected && port == expected) */
293 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
294 				    offsetof(struct bpf_sock, src_ip4)),
295 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7,
296 				    __bpf_constant_ntohl(0x7F000001), 4),
297 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
298 				    offsetof(struct bpf_sock, src_port)),
299 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0x1002, 2),
300 
301 			/* return DENY; */
302 			BPF_MOV64_IMM(BPF_REG_0, 0),
303 			BPF_JMP_A(1),
304 
305 			/* else return ALLOW; */
306 			BPF_MOV64_IMM(BPF_REG_0, 1),
307 			BPF_EXIT_INSN(),
308 		},
309 		.expected_attach_type = BPF_CGROUP_INET4_POST_BIND,
310 		.attach_type = BPF_CGROUP_INET4_POST_BIND,
311 		.domain = AF_INET,
312 		.type = SOCK_DGRAM,
313 		.ip = "127.0.0.1",
314 		.port = 4098,
315 		.port_retry = 5000,
316 		.result = RETRY_SUCCESS,
317 	},
318 	{
319 		.descr = "bind6 deny specific IP & port, and retry",
320 		.insns = {
321 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
322 
323 			/* if (ip == expected && port == expected) */
324 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
325 				    offsetof(struct bpf_sock, src_ip6[3])),
326 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7,
327 				    __bpf_constant_ntohl(0x00000001), 4),
328 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
329 				    offsetof(struct bpf_sock, src_port)),
330 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0x2001, 2),
331 
332 			/* return DENY; */
333 			BPF_MOV64_IMM(BPF_REG_0, 0),
334 			BPF_JMP_A(1),
335 
336 			/* else return ALLOW; */
337 			BPF_MOV64_IMM(BPF_REG_0, 1),
338 			BPF_EXIT_INSN(),
339 		},
340 		.expected_attach_type = BPF_CGROUP_INET6_POST_BIND,
341 		.attach_type = BPF_CGROUP_INET6_POST_BIND,
342 		.domain = AF_INET6,
343 		.type = SOCK_STREAM,
344 		.ip = "::1",
345 		.port = 8193,
346 		.port_retry = 9000,
347 		.result = RETRY_SUCCESS,
348 	},
349 	{
350 		.descr = "bind4 allow all",
351 		.insns = {
352 			BPF_MOV64_IMM(BPF_REG_0, 1),
353 			BPF_EXIT_INSN(),
354 		},
355 		.expected_attach_type = BPF_CGROUP_INET4_POST_BIND,
356 		.attach_type = BPF_CGROUP_INET4_POST_BIND,
357 		.domain = AF_INET,
358 		.type = SOCK_STREAM,
359 		.ip = "0.0.0.0",
360 		.result = SUCCESS,
361 	},
362 	{
363 		.descr = "bind6 allow all",
364 		.insns = {
365 			BPF_MOV64_IMM(BPF_REG_0, 1),
366 			BPF_EXIT_INSN(),
367 		},
368 		.expected_attach_type = BPF_CGROUP_INET6_POST_BIND,
369 		.attach_type = BPF_CGROUP_INET6_POST_BIND,
370 		.domain = AF_INET6,
371 		.type = SOCK_STREAM,
372 		.ip = "::",
373 		.result = SUCCESS,
374 	},
375 };
376 
377 static size_t probe_prog_length(const struct bpf_insn *fp)
378 {
379 	size_t len;
380 
381 	for (len = MAX_INSNS - 1; len > 0; --len)
382 		if (fp[len].code != 0 || fp[len].imm != 0)
383 			break;
384 	return len + 1;
385 }
386 
387 static int load_sock_prog(const struct bpf_insn *prog,
388 			  enum bpf_attach_type attach_type)
389 {
390 	LIBBPF_OPTS(bpf_prog_load_opts, opts);
391 	int ret, insn_cnt;
392 
393 	insn_cnt = probe_prog_length(prog);
394 
395 	opts.expected_attach_type = attach_type;
396 	opts.log_buf = bpf_log_buf;
397 	opts.log_size = BPF_LOG_BUF_SIZE;
398 	opts.log_level = 2;
399 
400 	ret = bpf_prog_load(BPF_PROG_TYPE_CGROUP_SOCK, NULL, "GPL", prog, insn_cnt, &opts);
401 	if (verbose && ret < 0)
402 		fprintf(stderr, "%s\n", bpf_log_buf);
403 
404 	return ret;
405 }
406 
407 static int attach_sock_prog(int cgfd, int progfd,
408 			    enum bpf_attach_type attach_type)
409 {
410 	return bpf_prog_attach(progfd, cgfd, attach_type, BPF_F_ALLOW_OVERRIDE);
411 }
412 
413 static int bind_sock(int domain, int type, const char *ip,
414 		     unsigned short port, unsigned short port_retry)
415 {
416 	struct sockaddr_storage addr;
417 	struct sockaddr_in6 *addr6;
418 	struct sockaddr_in *addr4;
419 	int sockfd = -1;
420 	socklen_t len;
421 	int res = SUCCESS;
422 
423 	sockfd = socket(domain, type, 0);
424 	if (sockfd < 0)
425 		goto err;
426 
427 	memset(&addr, 0, sizeof(addr));
428 
429 	if (domain == AF_INET) {
430 		len = sizeof(struct sockaddr_in);
431 		addr4 = (struct sockaddr_in *)&addr;
432 		addr4->sin_family = domain;
433 		addr4->sin_port = htons(port);
434 		if (inet_pton(domain, ip, (void *)&addr4->sin_addr) != 1)
435 			goto err;
436 	} else if (domain == AF_INET6) {
437 		len = sizeof(struct sockaddr_in6);
438 		addr6 = (struct sockaddr_in6 *)&addr;
439 		addr6->sin6_family = domain;
440 		addr6->sin6_port = htons(port);
441 		if (inet_pton(domain, ip, (void *)&addr6->sin6_addr) != 1)
442 			goto err;
443 	} else {
444 		goto err;
445 	}
446 
447 	if (bind(sockfd, (const struct sockaddr *)&addr, len) == -1) {
448 		/* sys_bind() may fail for different reasons, errno has to be
449 		 * checked to confirm that BPF program rejected it.
450 		 */
451 		if (errno != EPERM)
452 			goto err;
453 		if (port_retry)
454 			goto retry;
455 		res = BIND_REJECT;
456 		goto out;
457 	}
458 
459 	goto out;
460 retry:
461 	if (domain == AF_INET)
462 		addr4->sin_port = htons(port_retry);
463 	else
464 		addr6->sin6_port = htons(port_retry);
465 	if (bind(sockfd, (const struct sockaddr *)&addr, len) == -1) {
466 		if (errno != EPERM)
467 			goto err;
468 		res = RETRY_REJECT;
469 	} else {
470 		res = RETRY_SUCCESS;
471 	}
472 	goto out;
473 err:
474 	res = -1;
475 out:
476 	close(sockfd);
477 	return res;
478 }
479 
480 static int run_test_case(int cgfd, const struct sock_test *test)
481 {
482 	int progfd = -1;
483 	int err = 0;
484 	int res;
485 
486 	printf("Test case: %s .. ", test->descr);
487 	progfd = load_sock_prog(test->insns, test->expected_attach_type);
488 	if (progfd < 0) {
489 		if (test->result == LOAD_REJECT)
490 			goto out;
491 		else
492 			goto err;
493 	}
494 
495 	if (attach_sock_prog(cgfd, progfd, test->attach_type) < 0) {
496 		if (test->result == ATTACH_REJECT)
497 			goto out;
498 		else
499 			goto err;
500 	}
501 
502 	res = bind_sock(test->domain, test->type, test->ip, test->port,
503 			test->port_retry);
504 	if (res > 0 && test->result == res)
505 		goto out;
506 
507 err:
508 	err = -1;
509 out:
510 	/* Detaching w/o checking return code: best effort attempt. */
511 	if (progfd != -1)
512 		bpf_prog_detach(cgfd, test->attach_type);
513 	close(progfd);
514 	printf("[%s]\n", err ? "FAIL" : "PASS");
515 	return err;
516 }
517 
518 static int run_tests(int cgfd)
519 {
520 	int passes = 0;
521 	int fails = 0;
522 	int i;
523 
524 	for (i = 0; i < ARRAY_SIZE(tests); ++i) {
525 		if (run_test_case(cgfd, &tests[i]))
526 			++fails;
527 		else
528 			++passes;
529 	}
530 	printf("Summary: %d PASSED, %d FAILED\n", passes, fails);
531 	return fails ? -1 : 0;
532 }
533 
534 int main(int argc, char **argv)
535 {
536 	int cgfd = -1;
537 	int err = 0;
538 
539 	cgfd = cgroup_setup_and_join(CG_PATH);
540 	if (cgfd < 0)
541 		goto err;
542 
543 	/* Use libbpf 1.0 API mode */
544 	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
545 
546 	if (run_tests(cgfd))
547 		goto err;
548 
549 	goto out;
550 err:
551 	err = -1;
552 out:
553 	close(cgfd);
554 	cleanup_cgroup_environment();
555 	return err;
556 }
557