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