// SPDX-License-Identifier: GPL-2.0 #include #include #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); }