forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
481 lines
9.1 KiB
481 lines
9.1 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
// Copyright (c) 2018 Facebook |
|
|
|
#include <stdio.h> |
|
#include <unistd.h> |
|
|
|
#include <arpa/inet.h> |
|
#include <sys/types.h> |
|
#include <sys/socket.h> |
|
|
|
#include <linux/filter.h> |
|
|
|
#include <bpf/bpf.h> |
|
|
|
#include "cgroup_helpers.h" |
|
#include <bpf/bpf_endian.h> |
|
#include "bpf_rlimit.h" |
|
#include "bpf_util.h" |
|
|
|
#define CG_PATH "/foo" |
|
#define MAX_INSNS 512 |
|
|
|
char bpf_log_buf[BPF_LOG_BUF_SIZE]; |
|
static bool verbose = false; |
|
|
|
struct sock_test { |
|
const char *descr; |
|
/* BPF prog properties */ |
|
struct bpf_insn insns[MAX_INSNS]; |
|
enum bpf_attach_type expected_attach_type; |
|
enum bpf_attach_type attach_type; |
|
/* Socket properties */ |
|
int domain; |
|
int type; |
|
/* Endpoint to bind() to */ |
|
const char *ip; |
|
unsigned short port; |
|
/* Expected test result */ |
|
enum { |
|
LOAD_REJECT, |
|
ATTACH_REJECT, |
|
BIND_REJECT, |
|
SUCCESS, |
|
} result; |
|
}; |
|
|
|
static struct sock_test tests[] = { |
|
{ |
|
"bind4 load with invalid access: src_ip6", |
|
.insns = { |
|
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), |
|
BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, |
|
offsetof(struct bpf_sock, src_ip6[0])), |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
0, |
|
0, |
|
NULL, |
|
0, |
|
LOAD_REJECT, |
|
}, |
|
{ |
|
"bind4 load with invalid access: mark", |
|
.insns = { |
|
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), |
|
BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, |
|
offsetof(struct bpf_sock, mark)), |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
0, |
|
0, |
|
NULL, |
|
0, |
|
LOAD_REJECT, |
|
}, |
|
{ |
|
"bind6 load with invalid access: src_ip4", |
|
.insns = { |
|
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), |
|
BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, |
|
offsetof(struct bpf_sock, src_ip4)), |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
0, |
|
0, |
|
NULL, |
|
0, |
|
LOAD_REJECT, |
|
}, |
|
{ |
|
"sock_create load with invalid access: src_port", |
|
.insns = { |
|
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), |
|
BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, |
|
offsetof(struct bpf_sock, src_port)), |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET_SOCK_CREATE, |
|
BPF_CGROUP_INET_SOCK_CREATE, |
|
0, |
|
0, |
|
NULL, |
|
0, |
|
LOAD_REJECT, |
|
}, |
|
{ |
|
"sock_create load w/o expected_attach_type (compat mode)", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
0, |
|
BPF_CGROUP_INET_SOCK_CREATE, |
|
AF_INET, |
|
SOCK_STREAM, |
|
"127.0.0.1", |
|
8097, |
|
SUCCESS, |
|
}, |
|
{ |
|
"sock_create load w/ expected_attach_type", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET_SOCK_CREATE, |
|
BPF_CGROUP_INET_SOCK_CREATE, |
|
AF_INET, |
|
SOCK_STREAM, |
|
"127.0.0.1", |
|
8097, |
|
SUCCESS, |
|
}, |
|
{ |
|
"attach type mismatch bind4 vs bind6", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
0, |
|
0, |
|
NULL, |
|
0, |
|
ATTACH_REJECT, |
|
}, |
|
{ |
|
"attach type mismatch bind6 vs bind4", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
0, |
|
0, |
|
NULL, |
|
0, |
|
ATTACH_REJECT, |
|
}, |
|
{ |
|
"attach type mismatch default vs bind4", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
0, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
0, |
|
0, |
|
NULL, |
|
0, |
|
ATTACH_REJECT, |
|
}, |
|
{ |
|
"attach type mismatch bind6 vs sock_create", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
BPF_CGROUP_INET_SOCK_CREATE, |
|
0, |
|
0, |
|
NULL, |
|
0, |
|
ATTACH_REJECT, |
|
}, |
|
{ |
|
"bind4 reject all", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 0), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
AF_INET, |
|
SOCK_STREAM, |
|
"0.0.0.0", |
|
0, |
|
BIND_REJECT, |
|
}, |
|
{ |
|
"bind6 reject all", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 0), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
AF_INET6, |
|
SOCK_STREAM, |
|
"::", |
|
0, |
|
BIND_REJECT, |
|
}, |
|
{ |
|
"bind6 deny specific IP & port", |
|
.insns = { |
|
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), |
|
|
|
/* if (ip == expected && port == expected) */ |
|
BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, |
|
offsetof(struct bpf_sock, src_ip6[3])), |
|
BPF_JMP_IMM(BPF_JNE, BPF_REG_7, |
|
__bpf_constant_ntohl(0x00000001), 4), |
|
BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, |
|
offsetof(struct bpf_sock, src_port)), |
|
BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0x2001, 2), |
|
|
|
/* return DENY; */ |
|
BPF_MOV64_IMM(BPF_REG_0, 0), |
|
BPF_JMP_A(1), |
|
|
|
/* else return ALLOW; */ |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
AF_INET6, |
|
SOCK_STREAM, |
|
"::1", |
|
8193, |
|
BIND_REJECT, |
|
}, |
|
{ |
|
"bind4 allow specific IP & port", |
|
.insns = { |
|
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), |
|
|
|
/* if (ip == expected && port == expected) */ |
|
BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, |
|
offsetof(struct bpf_sock, src_ip4)), |
|
BPF_JMP_IMM(BPF_JNE, BPF_REG_7, |
|
__bpf_constant_ntohl(0x7F000001), 4), |
|
BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6, |
|
offsetof(struct bpf_sock, src_port)), |
|
BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0x1002, 2), |
|
|
|
/* return ALLOW; */ |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_JMP_A(1), |
|
|
|
/* else return DENY; */ |
|
BPF_MOV64_IMM(BPF_REG_0, 0), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
AF_INET, |
|
SOCK_STREAM, |
|
"127.0.0.1", |
|
4098, |
|
SUCCESS, |
|
}, |
|
{ |
|
"bind4 allow all", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
BPF_CGROUP_INET4_POST_BIND, |
|
AF_INET, |
|
SOCK_STREAM, |
|
"0.0.0.0", |
|
0, |
|
SUCCESS, |
|
}, |
|
{ |
|
"bind6 allow all", |
|
.insns = { |
|
BPF_MOV64_IMM(BPF_REG_0, 1), |
|
BPF_EXIT_INSN(), |
|
}, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
BPF_CGROUP_INET6_POST_BIND, |
|
AF_INET6, |
|
SOCK_STREAM, |
|
"::", |
|
0, |
|
SUCCESS, |
|
}, |
|
}; |
|
|
|
static size_t probe_prog_length(const struct bpf_insn *fp) |
|
{ |
|
size_t len; |
|
|
|
for (len = MAX_INSNS - 1; len > 0; --len) |
|
if (fp[len].code != 0 || fp[len].imm != 0) |
|
break; |
|
return len + 1; |
|
} |
|
|
|
static int load_sock_prog(const struct bpf_insn *prog, |
|
enum bpf_attach_type attach_type) |
|
{ |
|
struct bpf_load_program_attr attr; |
|
int ret; |
|
|
|
memset(&attr, 0, sizeof(struct bpf_load_program_attr)); |
|
attr.prog_type = BPF_PROG_TYPE_CGROUP_SOCK; |
|
attr.expected_attach_type = attach_type; |
|
attr.insns = prog; |
|
attr.insns_cnt = probe_prog_length(attr.insns); |
|
attr.license = "GPL"; |
|
attr.log_level = 2; |
|
|
|
ret = bpf_load_program_xattr(&attr, bpf_log_buf, BPF_LOG_BUF_SIZE); |
|
if (verbose && ret < 0) |
|
fprintf(stderr, "%s\n", bpf_log_buf); |
|
|
|
return ret; |
|
} |
|
|
|
static int attach_sock_prog(int cgfd, int progfd, |
|
enum bpf_attach_type attach_type) |
|
{ |
|
return bpf_prog_attach(progfd, cgfd, attach_type, BPF_F_ALLOW_OVERRIDE); |
|
} |
|
|
|
static int bind_sock(int domain, int type, const char *ip, unsigned short port) |
|
{ |
|
struct sockaddr_storage addr; |
|
struct sockaddr_in6 *addr6; |
|
struct sockaddr_in *addr4; |
|
int sockfd = -1; |
|
socklen_t len; |
|
int err = 0; |
|
|
|
sockfd = socket(domain, type, 0); |
|
if (sockfd < 0) |
|
goto err; |
|
|
|
memset(&addr, 0, sizeof(addr)); |
|
|
|
if (domain == AF_INET) { |
|
len = sizeof(struct sockaddr_in); |
|
addr4 = (struct sockaddr_in *)&addr; |
|
addr4->sin_family = domain; |
|
addr4->sin_port = htons(port); |
|
if (inet_pton(domain, ip, (void *)&addr4->sin_addr) != 1) |
|
goto err; |
|
} else if (domain == AF_INET6) { |
|
len = sizeof(struct sockaddr_in6); |
|
addr6 = (struct sockaddr_in6 *)&addr; |
|
addr6->sin6_family = domain; |
|
addr6->sin6_port = htons(port); |
|
if (inet_pton(domain, ip, (void *)&addr6->sin6_addr) != 1) |
|
goto err; |
|
} else { |
|
goto err; |
|
} |
|
|
|
if (bind(sockfd, (const struct sockaddr *)&addr, len) == -1) |
|
goto err; |
|
|
|
goto out; |
|
err: |
|
err = -1; |
|
out: |
|
close(sockfd); |
|
return err; |
|
} |
|
|
|
static int run_test_case(int cgfd, const struct sock_test *test) |
|
{ |
|
int progfd = -1; |
|
int err = 0; |
|
|
|
printf("Test case: %s .. ", test->descr); |
|
progfd = load_sock_prog(test->insns, test->expected_attach_type); |
|
if (progfd < 0) { |
|
if (test->result == LOAD_REJECT) |
|
goto out; |
|
else |
|
goto err; |
|
} |
|
|
|
if (attach_sock_prog(cgfd, progfd, test->attach_type) == -1) { |
|
if (test->result == ATTACH_REJECT) |
|
goto out; |
|
else |
|
goto err; |
|
} |
|
|
|
if (bind_sock(test->domain, test->type, test->ip, test->port) == -1) { |
|
/* sys_bind() may fail for different reasons, errno has to be |
|
* checked to confirm that BPF program rejected it. |
|
*/ |
|
if (test->result == BIND_REJECT && errno == EPERM) |
|
goto out; |
|
else |
|
goto err; |
|
} |
|
|
|
|
|
if (test->result != SUCCESS) |
|
goto err; |
|
|
|
goto out; |
|
err: |
|
err = -1; |
|
out: |
|
/* Detaching w/o checking return code: best effort attempt. */ |
|
if (progfd != -1) |
|
bpf_prog_detach(cgfd, test->attach_type); |
|
close(progfd); |
|
printf("[%s]\n", err ? "FAIL" : "PASS"); |
|
return err; |
|
} |
|
|
|
static int run_tests(int cgfd) |
|
{ |
|
int passes = 0; |
|
int fails = 0; |
|
int i; |
|
|
|
for (i = 0; i < ARRAY_SIZE(tests); ++i) { |
|
if (run_test_case(cgfd, &tests[i])) |
|
++fails; |
|
else |
|
++passes; |
|
} |
|
printf("Summary: %d PASSED, %d FAILED\n", passes, fails); |
|
return fails ? -1 : 0; |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
int cgfd = -1; |
|
int err = 0; |
|
|
|
cgfd = cgroup_setup_and_join(CG_PATH); |
|
if (cgfd < 0) |
|
goto err; |
|
|
|
if (run_tests(cgfd)) |
|
goto err; |
|
|
|
goto out; |
|
err: |
|
err = -1; |
|
out: |
|
close(cgfd); |
|
cleanup_cgroup_environment(); |
|
return err; |
|
}
|
|
|