mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-31 16:38:12 +00:00
4939b2847d
Nearly every user of cgroup helpers does the same sequence of API calls. So push these into a single helper cgroup_setup_and_join. The cases that do a bit of extra logic are test_progs which currently uses an env variable to decide if it needs to setup the cgroup environment or can use an existingi environment. And then tests that are doing cgroup tests themselves. We skip these cases for now. Signed-off-by: John Fastabend <john.fastabend@gmail.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Acked-by: Andrii Nakryiko <andriin@fb.com> Link: https://lore.kernel.org/bpf/159623335418.30208.15807461815525100199.stgit@john-XPS-13-9370
481 lines
9.1 KiB
C
481 lines
9.1 KiB
C
// 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;
|
|
}
|