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