Add C based test for a few bpf_sysctl_* helpers and bpf_strtoul. Make sure that sysctl can be identified by name and that multiple integers can be parsed from sysctl value with bpf_strtoul. net/ipv4/tcp_mem is chosen as a testing sysctl, it contains 3 unsigned longs, they all are parsed and compared (val[0] < val[1] < val[2]). One verifier limitation is being worked around in the test: variable stack access. Since such an access is unsupported, offset, returned by bpf_strtoul while parsing current ulong, can't be used directly when specifying 'buf' pointer to the next bpf_strtoul. That's why instead of 'base + offset' where offset non-const tnum, the test uses brute force search to find static number, that matches with 'offset', and use 'base + static' to access the stack. Example of output: # ./test_sysctl ... Test case: C prog: deny all writes .. [PASS] Test case: C prog: deny access by name .. [PASS] Test case: C prog: read tcp_mem .. [PASS] Summary: 39 PASSED, 0 FAILED Signed-off-by: Andrey Ignatov <rdna@xxxxxx> --- .../selftests/bpf/progs/test_sysctl_prog.c | 89 +++++++++++++++++++ tools/testing/selftests/bpf/test_sysctl.c | 57 +++++++++++- 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/bpf/progs/test_sysctl_prog.c diff --git a/tools/testing/selftests/bpf/progs/test_sysctl_prog.c b/tools/testing/selftests/bpf/progs/test_sysctl_prog.c new file mode 100644 index 000000000000..29c02b6a0979 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_sysctl_prog.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Facebook + +#include <stdint.h> +#include <string.h> + +#include <linux/stddef.h> +#include <linux/bpf.h> + +#include "bpf_helpers.h" + +/* Min/max supported lengths of a string with unsigned long in base 10. */ +#define MIN_ULONG_STR_LEN 2 +#define MAX_ULONG_STR_LEN 15 + +static __always_inline int is_tcp_mem(struct bpf_sysctl *ctx) +{ + char tcp_mem_name[] = "net/ipv4/tcp_mem"; + unsigned char i; + char name[64]; + int ret; + + memset(name, 0, sizeof(name)); + ret = bpf_sysctl_get_name(ctx, name, sizeof(name), 0); + if (ret < 0 || ret != sizeof(tcp_mem_name) - 1) + return 0; + +#pragma clang loop unroll(full) + for (i = 0; i < sizeof(tcp_mem_name); ++i) + if (name[i] != tcp_mem_name[i]) + return 0; + + return 1; +} + +SEC("cgroup/sysctl") +int sysctl_tcp_mem(struct bpf_sysctl *ctx) +{ + unsigned long mem_min = 0, mem_pressure = 0, mem_max = 0; + unsigned char i, off; + char value[64]; + int ret; + + if (ctx->write) + return 0; + + if (!is_tcp_mem(ctx)) + return 0; + + ret = bpf_sysctl_get_current_value(ctx, value, sizeof(value)); + if (ret < 0 || ret >= sizeof(value)) + return 0; + + ret = bpf_strtoul(value, sizeof(value), 0, &mem_min); + if (ret <= 0 || ret > MAX_ULONG_STR_LEN) + return 0; + off = ret; + ret = -1; + + /* Make verifier happy, prevent "invalid variable stack read R1 + * var_off=(0x0; 0xf)" by using static offset for value. + */ +#pragma clang loop unroll(full) + for (i = MIN_ULONG_STR_LEN; i <= MAX_ULONG_STR_LEN; ++i) + if (off == i) { + ret = bpf_strtoul(value + i, sizeof(value) - i, 0, + &mem_pressure); + break; + } + if (ret <= 0 || ret > MAX_ULONG_STR_LEN) + return 0; + off += ret; + ret = -1; + + /* Same here. */ +#pragma clang loop unroll(full) + for (i = MIN_ULONG_STR_LEN * 2; i <= MAX_ULONG_STR_LEN * 2 + 2; ++i) + if (off == i) { + ret = bpf_strtoul(value + i, sizeof(value) - i, 0, + &mem_max); + break; + } + if (ret <= 0 || ret > MAX_ULONG_STR_LEN) + return 0; + + return mem_min < mem_pressure && mem_pressure < mem_max; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/test_sysctl.c b/tools/testing/selftests/bpf/test_sysctl.c index 885675480af9..a3bebd7c68dd 100644 --- a/tools/testing/selftests/bpf/test_sysctl.c +++ b/tools/testing/selftests/bpf/test_sysctl.c @@ -11,6 +11,7 @@ #include <linux/filter.h> #include <bpf/bpf.h> +#include <bpf/libbpf.h> #include "bpf_rlimit.h" #include "bpf_util.h" @@ -26,6 +27,7 @@ struct sysctl_test { const char *descr; size_t fixup_value_insn; struct bpf_insn insns[MAX_INSNS]; + const char *prog_file; enum bpf_attach_type attach_type; const char *sysctl; int open_flags; @@ -1302,6 +1304,31 @@ static struct sysctl_test tests[] = { .open_flags = O_RDONLY, .result = SUCCESS, }, + { + "C prog: deny all writes", + .prog_file = "./test_sysctl_prog.o", + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_WRONLY, + .newval = "123 456 789", + .result = OP_EPERM, + }, + { + "C prog: deny access by name", + .prog_file = "./test_sysctl_prog.o", + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/route/mtu_expires", + .open_flags = O_RDONLY, + .result = OP_EPERM, + }, + { + "C prog: read tcp_mem", + .prog_file = "./test_sysctl_prog.o", + .attach_type = BPF_CGROUP_SYSCTL, + .sysctl = "net/ipv4/tcp_mem", + .open_flags = O_RDONLY, + .result = SUCCESS, + }, }; static size_t probe_prog_length(const struct bpf_insn *fp) @@ -1335,7 +1362,8 @@ static int fixup_sysctl_value(const char *buf, size_t buf_len, return 0; } -static int load_sysctl_prog(struct sysctl_test *test, const char *sysctl_path) +static int load_sysctl_prog_insns(struct sysctl_test *test, + const char *sysctl_path) { struct bpf_insn *prog = test->insns; struct bpf_load_program_attr attr; @@ -1377,6 +1405,33 @@ static int load_sysctl_prog(struct sysctl_test *test, const char *sysctl_path) return ret; } +static int load_sysctl_prog_file(struct sysctl_test *test) +{ + struct bpf_prog_load_attr attr; + struct bpf_object *obj; + int prog_fd; + + memset(&attr, 0, sizeof(struct bpf_prog_load_attr)); + attr.file = test->prog_file; + attr.prog_type = BPF_PROG_TYPE_CGROUP_SYSCTL; + + if (bpf_prog_load_xattr(&attr, &obj, &prog_fd)) { + if (test->result != LOAD_REJECT) + log_err(">>> Loading program (%s) error.\n", + test->prog_file); + return -1; + } + + return prog_fd; +} + +static int load_sysctl_prog(struct sysctl_test *test, const char *sysctl_path) +{ + return test->prog_file + ? load_sysctl_prog_file(test) + : load_sysctl_prog_insns(test, sysctl_path); +} + static int access_sysctl(const char *sysctl_path, const struct sysctl_test *test) { -- 2.17.1