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