| // SPDX-License-Identifier: GPL-2.0 | |
| #include <linux/bpf.h> | |
| #include <test_progs.h> | |
| #include "cgroup_helpers.h" | |
| static char bpf_log_buf[4096]; | |
| static bool verbose; | |
| enum sock_create_test_error { | |
| OK = 0, | |
| DENY_CREATE, | |
| }; | |
| static struct sock_create_test { | |
| const char *descr; | |
| const struct bpf_insn insns[64]; | |
| enum bpf_attach_type attach_type; | |
| enum bpf_attach_type expected_attach_type; | |
| int domain; | |
| int type; | |
| int protocol; | |
| int optname; | |
| int optval; | |
| enum sock_create_test_error error; | |
| } tests[] = { | |
| { | |
| .descr = "AF_INET set priority", | |
| .insns = { | |
| /* r3 = 123 (priority) */ | |
| BPF_MOV64_IMM(BPF_REG_3, 123), | |
| BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3, | |
| offsetof(struct bpf_sock, priority)), | |
| /* return 1 */ | |
| BPF_MOV64_IMM(BPF_REG_0, 1), | |
| BPF_EXIT_INSN(), | |
| }, | |
| .expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .domain = AF_INET, | |
| .type = SOCK_DGRAM, | |
| .optname = SO_PRIORITY, | |
| .optval = 123, | |
| }, | |
| { | |
| .descr = "AF_INET6 set priority", | |
| .insns = { | |
| /* r3 = 123 (priority) */ | |
| BPF_MOV64_IMM(BPF_REG_3, 123), | |
| BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3, | |
| offsetof(struct bpf_sock, priority)), | |
| /* return 1 */ | |
| BPF_MOV64_IMM(BPF_REG_0, 1), | |
| BPF_EXIT_INSN(), | |
| }, | |
| .expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .domain = AF_INET6, | |
| .type = SOCK_DGRAM, | |
| .optname = SO_PRIORITY, | |
| .optval = 123, | |
| }, | |
| { | |
| .descr = "AF_INET set mark", | |
| .insns = { | |
| BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), | |
| /* get uid of process */ | |
| BPF_EMIT_CALL(BPF_FUNC_get_current_uid_gid), | |
| BPF_ALU64_IMM(BPF_AND, BPF_REG_0, 0xffffffff), | |
| /* if uid is 0, use given mark(666), else use uid as the mark */ | |
| BPF_MOV64_REG(BPF_REG_3, BPF_REG_0), | |
| BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), | |
| BPF_MOV64_IMM(BPF_REG_3, 666), | |
| BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), | |
| BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3, | |
| offsetof(struct bpf_sock, mark)), | |
| /* return 1 */ | |
| BPF_MOV64_IMM(BPF_REG_0, 1), | |
| BPF_EXIT_INSN(), | |
| }, | |
| .expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .domain = AF_INET, | |
| .type = SOCK_DGRAM, | |
| .optname = SO_MARK, | |
| .optval = 666, | |
| }, | |
| { | |
| .descr = "AF_INET6 set mark", | |
| .insns = { | |
| BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), | |
| /* get uid of process */ | |
| BPF_EMIT_CALL(BPF_FUNC_get_current_uid_gid), | |
| BPF_ALU64_IMM(BPF_AND, BPF_REG_0, 0xffffffff), | |
| /* if uid is 0, use given mark(666), else use uid as the mark */ | |
| BPF_MOV64_REG(BPF_REG_3, BPF_REG_0), | |
| BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), | |
| BPF_MOV64_IMM(BPF_REG_3, 666), | |
| BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), | |
| BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3, | |
| offsetof(struct bpf_sock, mark)), | |
| /* return 1 */ | |
| BPF_MOV64_IMM(BPF_REG_0, 1), | |
| BPF_EXIT_INSN(), | |
| }, | |
| .expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .domain = AF_INET6, | |
| .type = SOCK_DGRAM, | |
| .optname = SO_MARK, | |
| .optval = 666, | |
| }, | |
| { | |
| .descr = "AF_INET bound to iface", | |
| .insns = { | |
| /* r3 = 1 (lo interface) */ | |
| BPF_MOV64_IMM(BPF_REG_3, 1), | |
| BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3, | |
| offsetof(struct bpf_sock, bound_dev_if)), | |
| /* return 1 */ | |
| BPF_MOV64_IMM(BPF_REG_0, 1), | |
| BPF_EXIT_INSN(), | |
| }, | |
| .expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .domain = AF_INET, | |
| .type = SOCK_DGRAM, | |
| .optname = SO_BINDTOIFINDEX, | |
| .optval = 1, | |
| }, | |
| { | |
| .descr = "AF_INET6 bound to iface", | |
| .insns = { | |
| /* r3 = 1 (lo interface) */ | |
| BPF_MOV64_IMM(BPF_REG_3, 1), | |
| BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3, | |
| offsetof(struct bpf_sock, bound_dev_if)), | |
| /* return 1 */ | |
| BPF_MOV64_IMM(BPF_REG_0, 1), | |
| BPF_EXIT_INSN(), | |
| }, | |
| .expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .domain = AF_INET6, | |
| .type = SOCK_DGRAM, | |
| .optname = SO_BINDTOIFINDEX, | |
| .optval = 1, | |
| }, | |
| { | |
| .descr = "block AF_INET, SOCK_DGRAM, IPPROTO_ICMP socket", | |
| .insns = { | |
| BPF_MOV64_IMM(BPF_REG_0, 1), /* r0 = verdict */ | |
| /* sock->family == AF_INET */ | |
| BPF_LDX_MEM(BPF_H, BPF_REG_2, BPF_REG_1, | |
| offsetof(struct bpf_sock, family)), | |
| BPF_JMP_IMM(BPF_JNE, BPF_REG_2, AF_INET, 5), | |
| /* sock->type == SOCK_DGRAM */ | |
| BPF_LDX_MEM(BPF_H, BPF_REG_2, BPF_REG_1, | |
| offsetof(struct bpf_sock, type)), | |
| BPF_JMP_IMM(BPF_JNE, BPF_REG_2, SOCK_DGRAM, 3), | |
| /* sock->protocol == IPPROTO_ICMP */ | |
| BPF_LDX_MEM(BPF_H, BPF_REG_2, BPF_REG_1, | |
| offsetof(struct bpf_sock, protocol)), | |
| BPF_JMP_IMM(BPF_JNE, BPF_REG_2, IPPROTO_ICMP, 1), | |
| /* return 0 (block) */ | |
| BPF_MOV64_IMM(BPF_REG_0, 0), | |
| BPF_EXIT_INSN(), | |
| }, | |
| .expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .domain = AF_INET, | |
| .type = SOCK_DGRAM, | |
| .protocol = IPPROTO_ICMP, | |
| .error = DENY_CREATE, | |
| }, | |
| { | |
| .descr = "block AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6 socket", | |
| .insns = { | |
| BPF_MOV64_IMM(BPF_REG_0, 1), /* r0 = verdict */ | |
| /* sock->family == AF_INET6 */ | |
| BPF_LDX_MEM(BPF_H, BPF_REG_2, BPF_REG_1, | |
| offsetof(struct bpf_sock, family)), | |
| BPF_JMP_IMM(BPF_JNE, BPF_REG_2, AF_INET6, 5), | |
| /* sock->type == SOCK_DGRAM */ | |
| BPF_LDX_MEM(BPF_H, BPF_REG_2, BPF_REG_1, | |
| offsetof(struct bpf_sock, type)), | |
| BPF_JMP_IMM(BPF_JNE, BPF_REG_2, SOCK_DGRAM, 3), | |
| /* sock->protocol == IPPROTO_ICMPV6 */ | |
| BPF_LDX_MEM(BPF_H, BPF_REG_2, BPF_REG_1, | |
| offsetof(struct bpf_sock, protocol)), | |
| BPF_JMP_IMM(BPF_JNE, BPF_REG_2, IPPROTO_ICMPV6, 1), | |
| /* return 0 (block) */ | |
| BPF_MOV64_IMM(BPF_REG_0, 0), | |
| BPF_EXIT_INSN(), | |
| }, | |
| .expected_attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .domain = AF_INET, | |
| .type = SOCK_DGRAM, | |
| .protocol = IPPROTO_ICMPV6, | |
| .error = DENY_CREATE, | |
| }, | |
| { | |
| .descr = "load w/o expected_attach_type (compat mode)", | |
| .insns = { | |
| /* return 1 */ | |
| BPF_MOV64_IMM(BPF_REG_0, 1), | |
| BPF_EXIT_INSN(), | |
| }, | |
| .expected_attach_type = 0, | |
| .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
| .domain = AF_INET, | |
| .type = SOCK_STREAM, | |
| }, | |
| }; | |
| static int load_prog(const struct bpf_insn *insns, | |
| enum bpf_attach_type expected_attach_type) | |
| { | |
| LIBBPF_OPTS(bpf_prog_load_opts, opts, | |
| .expected_attach_type = expected_attach_type, | |
| .log_level = 2, | |
| .log_buf = bpf_log_buf, | |
| .log_size = sizeof(bpf_log_buf), | |
| ); | |
| int fd, insns_cnt = 0; | |
| for (; | |
| insns[insns_cnt].code != (BPF_JMP | BPF_EXIT); | |
| insns_cnt++) { | |
| } | |
| insns_cnt++; | |
| fd = bpf_prog_load(BPF_PROG_TYPE_CGROUP_SOCK, NULL, "GPL", insns, | |
| insns_cnt, &opts); | |
| if (verbose && fd < 0) | |
| fprintf(stderr, "%s\n", bpf_log_buf); | |
| return fd; | |
| } | |
| static int run_test(int cgroup_fd, struct sock_create_test *test) | |
| { | |
| int sock_fd, err, prog_fd, optval, ret = -1; | |
| socklen_t optlen = sizeof(optval); | |
| prog_fd = load_prog(test->insns, test->expected_attach_type); | |
| if (prog_fd < 0) { | |
| log_err("Failed to load BPF program"); | |
| return -1; | |
| } | |
| err = bpf_prog_attach(prog_fd, cgroup_fd, test->attach_type, 0); | |
| if (err < 0) { | |
| log_err("Failed to attach BPF program"); | |
| goto close_prog_fd; | |
| } | |
| sock_fd = socket(test->domain, test->type, test->protocol); | |
| if (sock_fd < 0) { | |
| if (test->error == DENY_CREATE) | |
| ret = 0; | |
| else | |
| log_err("Failed to create socket"); | |
| goto detach_prog; | |
| } | |
| if (test->optname) { | |
| err = getsockopt(sock_fd, SOL_SOCKET, test->optname, &optval, &optlen); | |
| if (err) { | |
| log_err("Failed to call getsockopt"); | |
| goto cleanup; | |
| } | |
| if (optval != test->optval) { | |
| errno = 0; | |
| log_err("getsockopt returned unexpected optval"); | |
| goto cleanup; | |
| } | |
| } | |
| ret = test->error != OK; | |
| cleanup: | |
| close(sock_fd); | |
| detach_prog: | |
| bpf_prog_detach2(prog_fd, cgroup_fd, test->attach_type); | |
| close_prog_fd: | |
| close(prog_fd); | |
| return ret; | |
| } | |
| void test_sock_create(void) | |
| { | |
| int cgroup_fd, i; | |
| cgroup_fd = test__join_cgroup("/sock_create"); | |
| if (!ASSERT_GE(cgroup_fd, 0, "join_cgroup")) | |
| return; | |
| for (i = 0; i < ARRAY_SIZE(tests); i++) { | |
| if (!test__start_subtest(tests[i].descr)) | |
| continue; | |
| ASSERT_OK(run_test(cgroup_fd, &tests[i]), tests[i].descr); | |
| } | |
| close(cgroup_fd); | |
| } |