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