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_rlimit.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 	/* Expected test result */
38 	enum {
39 		LOAD_REJECT,
40 		ATTACH_REJECT,
41 		BIND_REJECT,
42 		SUCCESS,
43 	} result;
44 };
45 
46 static struct sock_test tests[] = {
47 	{
48 		"bind4 load with invalid access: src_ip6",
49 		.insns = {
50 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
51 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
52 				    offsetof(struct bpf_sock, src_ip6[0])),
53 			BPF_MOV64_IMM(BPF_REG_0, 1),
54 			BPF_EXIT_INSN(),
55 		},
56 		BPF_CGROUP_INET4_POST_BIND,
57 		BPF_CGROUP_INET4_POST_BIND,
58 		0,
59 		0,
60 		NULL,
61 		0,
62 		LOAD_REJECT,
63 	},
64 	{
65 		"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 		BPF_CGROUP_INET4_POST_BIND,
74 		BPF_CGROUP_INET4_POST_BIND,
75 		0,
76 		0,
77 		NULL,
78 		0,
79 		LOAD_REJECT,
80 	},
81 	{
82 		"bind6 load with invalid access: src_ip4",
83 		.insns = {
84 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
85 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
86 				    offsetof(struct bpf_sock, src_ip4)),
87 			BPF_MOV64_IMM(BPF_REG_0, 1),
88 			BPF_EXIT_INSN(),
89 		},
90 		BPF_CGROUP_INET6_POST_BIND,
91 		BPF_CGROUP_INET6_POST_BIND,
92 		0,
93 		0,
94 		NULL,
95 		0,
96 		LOAD_REJECT,
97 	},
98 	{
99 		"sock_create load with invalid access: src_port",
100 		.insns = {
101 			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
102 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
103 				    offsetof(struct bpf_sock, src_port)),
104 			BPF_MOV64_IMM(BPF_REG_0, 1),
105 			BPF_EXIT_INSN(),
106 		},
107 		BPF_CGROUP_INET_SOCK_CREATE,
108 		BPF_CGROUP_INET_SOCK_CREATE,
109 		0,
110 		0,
111 		NULL,
112 		0,
113 		LOAD_REJECT,
114 	},
115 	{
116 		"sock_create load w/o expected_attach_type (compat mode)",
117 		.insns = {
118 			BPF_MOV64_IMM(BPF_REG_0, 1),
119 			BPF_EXIT_INSN(),
120 		},
121 		0,
122 		BPF_CGROUP_INET_SOCK_CREATE,
123 		AF_INET,
124 		SOCK_STREAM,
125 		"127.0.0.1",
126 		8097,
127 		SUCCESS,
128 	},
129 	{
130 		"sock_create load w/ expected_attach_type",
131 		.insns = {
132 			BPF_MOV64_IMM(BPF_REG_0, 1),
133 			BPF_EXIT_INSN(),
134 		},
135 		BPF_CGROUP_INET_SOCK_CREATE,
136 		BPF_CGROUP_INET_SOCK_CREATE,
137 		AF_INET,
138 		SOCK_STREAM,
139 		"127.0.0.1",
140 		8097,
141 		SUCCESS,
142 	},
143 	{
144 		"attach type mismatch bind4 vs bind6",
145 		.insns = {
146 			BPF_MOV64_IMM(BPF_REG_0, 1),
147 			BPF_EXIT_INSN(),
148 		},
149 		BPF_CGROUP_INET4_POST_BIND,
150 		BPF_CGROUP_INET6_POST_BIND,
151 		0,
152 		0,
153 		NULL,
154 		0,
155 		ATTACH_REJECT,
156 	},
157 	{
158 		"attach type mismatch bind6 vs bind4",
159 		.insns = {
160 			BPF_MOV64_IMM(BPF_REG_0, 1),
161 			BPF_EXIT_INSN(),
162 		},
163 		BPF_CGROUP_INET6_POST_BIND,
164 		BPF_CGROUP_INET4_POST_BIND,
165 		0,
166 		0,
167 		NULL,
168 		0,
169 		ATTACH_REJECT,
170 	},
171 	{
172 		"attach type mismatch default vs bind4",
173 		.insns = {
174 			BPF_MOV64_IMM(BPF_REG_0, 1),
175 			BPF_EXIT_INSN(),
176 		},
177 		0,
178 		BPF_CGROUP_INET4_POST_BIND,
179 		0,
180 		0,
181 		NULL,
182 		0,
183 		ATTACH_REJECT,
184 	},
185 	{
186 		"attach type mismatch bind6 vs sock_create",
187 		.insns = {
188 			BPF_MOV64_IMM(BPF_REG_0, 1),
189 			BPF_EXIT_INSN(),
190 		},
191 		BPF_CGROUP_INET6_POST_BIND,
192 		BPF_CGROUP_INET_SOCK_CREATE,
193 		0,
194 		0,
195 		NULL,
196 		0,
197 		ATTACH_REJECT,
198 	},
199 	{
200 		"bind4 reject all",
201 		.insns = {
202 			BPF_MOV64_IMM(BPF_REG_0, 0),
203 			BPF_EXIT_INSN(),
204 		},
205 		BPF_CGROUP_INET4_POST_BIND,
206 		BPF_CGROUP_INET4_POST_BIND,
207 		AF_INET,
208 		SOCK_STREAM,
209 		"0.0.0.0",
210 		0,
211 		BIND_REJECT,
212 	},
213 	{
214 		"bind6 reject all",
215 		.insns = {
216 			BPF_MOV64_IMM(BPF_REG_0, 0),
217 			BPF_EXIT_INSN(),
218 		},
219 		BPF_CGROUP_INET6_POST_BIND,
220 		BPF_CGROUP_INET6_POST_BIND,
221 		AF_INET6,
222 		SOCK_STREAM,
223 		"::",
224 		0,
225 		BIND_REJECT,
226 	},
227 	{
228 		"bind6 deny 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_ip6[3])),
235 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0x01000000, 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, 0x2001, 2),
239 
240 			/* return DENY; */
241 			BPF_MOV64_IMM(BPF_REG_0, 0),
242 			BPF_JMP_A(1),
243 
244 			/* else return ALLOW; */
245 			BPF_MOV64_IMM(BPF_REG_0, 1),
246 			BPF_EXIT_INSN(),
247 		},
248 		BPF_CGROUP_INET6_POST_BIND,
249 		BPF_CGROUP_INET6_POST_BIND,
250 		AF_INET6,
251 		SOCK_STREAM,
252 		"::1",
253 		8193,
254 		BIND_REJECT,
255 	},
256 	{
257 		"bind4 allow specific IP & port",
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, 0x0100007F, 4),
265 			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
266 				    offsetof(struct bpf_sock, src_port)),
267 			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0x1002, 2),
268 
269 			/* return ALLOW; */
270 			BPF_MOV64_IMM(BPF_REG_0, 1),
271 			BPF_JMP_A(1),
272 
273 			/* else return DENY; */
274 			BPF_MOV64_IMM(BPF_REG_0, 0),
275 			BPF_EXIT_INSN(),
276 		},
277 		BPF_CGROUP_INET4_POST_BIND,
278 		BPF_CGROUP_INET4_POST_BIND,
279 		AF_INET,
280 		SOCK_STREAM,
281 		"127.0.0.1",
282 		4098,
283 		SUCCESS,
284 	},
285 	{
286 		"bind4 allow all",
287 		.insns = {
288 			BPF_MOV64_IMM(BPF_REG_0, 1),
289 			BPF_EXIT_INSN(),
290 		},
291 		BPF_CGROUP_INET4_POST_BIND,
292 		BPF_CGROUP_INET4_POST_BIND,
293 		AF_INET,
294 		SOCK_STREAM,
295 		"0.0.0.0",
296 		0,
297 		SUCCESS,
298 	},
299 	{
300 		"bind6 allow all",
301 		.insns = {
302 			BPF_MOV64_IMM(BPF_REG_0, 1),
303 			BPF_EXIT_INSN(),
304 		},
305 		BPF_CGROUP_INET6_POST_BIND,
306 		BPF_CGROUP_INET6_POST_BIND,
307 		AF_INET6,
308 		SOCK_STREAM,
309 		"::",
310 		0,
311 		SUCCESS,
312 	},
313 };
314 
315 static size_t probe_prog_length(const struct bpf_insn *fp)
316 {
317 	size_t len;
318 
319 	for (len = MAX_INSNS - 1; len > 0; --len)
320 		if (fp[len].code != 0 || fp[len].imm != 0)
321 			break;
322 	return len + 1;
323 }
324 
325 static int load_sock_prog(const struct bpf_insn *prog,
326 			  enum bpf_attach_type attach_type)
327 {
328 	struct bpf_load_program_attr attr;
329 	int ret;
330 
331 	memset(&attr, 0, sizeof(struct bpf_load_program_attr));
332 	attr.prog_type = BPF_PROG_TYPE_CGROUP_SOCK;
333 	attr.expected_attach_type = attach_type;
334 	attr.insns = prog;
335 	attr.insns_cnt = probe_prog_length(attr.insns);
336 	attr.license = "GPL";
337 	attr.log_level = 2;
338 
339 	ret = bpf_load_program_xattr(&attr, bpf_log_buf, BPF_LOG_BUF_SIZE);
340 	if (verbose && ret < 0)
341 		fprintf(stderr, "%s\n", bpf_log_buf);
342 
343 	return ret;
344 }
345 
346 static int attach_sock_prog(int cgfd, int progfd,
347 			    enum bpf_attach_type attach_type)
348 {
349 	return bpf_prog_attach(progfd, cgfd, attach_type, BPF_F_ALLOW_OVERRIDE);
350 }
351 
352 static int bind_sock(int domain, int type, const char *ip, unsigned short port)
353 {
354 	struct sockaddr_storage addr;
355 	struct sockaddr_in6 *addr6;
356 	struct sockaddr_in *addr4;
357 	int sockfd = -1;
358 	socklen_t len;
359 	int err = 0;
360 
361 	sockfd = socket(domain, type, 0);
362 	if (sockfd < 0)
363 		goto err;
364 
365 	memset(&addr, 0, sizeof(addr));
366 
367 	if (domain == AF_INET) {
368 		len = sizeof(struct sockaddr_in);
369 		addr4 = (struct sockaddr_in *)&addr;
370 		addr4->sin_family = domain;
371 		addr4->sin_port = htons(port);
372 		if (inet_pton(domain, ip, (void *)&addr4->sin_addr) != 1)
373 			goto err;
374 	} else if (domain == AF_INET6) {
375 		len = sizeof(struct sockaddr_in6);
376 		addr6 = (struct sockaddr_in6 *)&addr;
377 		addr6->sin6_family = domain;
378 		addr6->sin6_port = htons(port);
379 		if (inet_pton(domain, ip, (void *)&addr6->sin6_addr) != 1)
380 			goto err;
381 	} else {
382 		goto err;
383 	}
384 
385 	if (bind(sockfd, (const struct sockaddr *)&addr, len) == -1)
386 		goto err;
387 
388 	goto out;
389 err:
390 	err = -1;
391 out:
392 	close(sockfd);
393 	return err;
394 }
395 
396 static int run_test_case(int cgfd, const struct sock_test *test)
397 {
398 	int progfd = -1;
399 	int err = 0;
400 
401 	printf("Test case: %s .. ", test->descr);
402 	progfd = load_sock_prog(test->insns, test->expected_attach_type);
403 	if (progfd < 0) {
404 		if (test->result == LOAD_REJECT)
405 			goto out;
406 		else
407 			goto err;
408 	}
409 
410 	if (attach_sock_prog(cgfd, progfd, test->attach_type) == -1) {
411 		if (test->result == ATTACH_REJECT)
412 			goto out;
413 		else
414 			goto err;
415 	}
416 
417 	if (bind_sock(test->domain, test->type, test->ip, test->port) == -1) {
418 		/* sys_bind() may fail for different reasons, errno has to be
419 		 * checked to confirm that BPF program rejected it.
420 		 */
421 		if (test->result == BIND_REJECT && errno == EPERM)
422 			goto out;
423 		else
424 			goto err;
425 	}
426 
427 
428 	if (test->result != SUCCESS)
429 		goto err;
430 
431 	goto out;
432 err:
433 	err = -1;
434 out:
435 	/* Detaching w/o checking return code: best effort attempt. */
436 	if (progfd != -1)
437 		bpf_prog_detach(cgfd, test->attach_type);
438 	close(progfd);
439 	printf("[%s]\n", err ? "FAIL" : "PASS");
440 	return err;
441 }
442 
443 static int run_tests(int cgfd)
444 {
445 	int passes = 0;
446 	int fails = 0;
447 	int i;
448 
449 	for (i = 0; i < ARRAY_SIZE(tests); ++i) {
450 		if (run_test_case(cgfd, &tests[i]))
451 			++fails;
452 		else
453 			++passes;
454 	}
455 	printf("Summary: %d PASSED, %d FAILED\n", passes, fails);
456 	return fails ? -1 : 0;
457 }
458 
459 int main(int argc, char **argv)
460 {
461 	int cgfd = -1;
462 	int err = 0;
463 
464 	if (setup_cgroup_environment())
465 		goto err;
466 
467 	cgfd = create_and_get_cgroup(CG_PATH);
468 	if (cgfd < 0)
469 		goto err;
470 
471 	if (join_cgroup(CG_PATH))
472 		goto err;
473 
474 	if (run_tests(cgfd))
475 		goto err;
476 
477 	goto out;
478 err:
479 	err = -1;
480 out:
481 	close(cgfd);
482 	cleanup_cgroup_environment();
483 	return err;
484 }
485