bpf: selftests: Add selftests for module kfunc support

This adds selftests that tests the success and failure path for modules
kfuncs (in presence of invalid kfunc calls) for both libbpf and
gen_loader. It also adds a prog_test kfunc_btf_id_list so that we can
add module BTF ID set from bpf_testmod.

This also introduces  a couple of test cases to verifier selftests for
validating whether we get an error or not depending on if invalid kfunc
call remains after elimination of unreachable instructions.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20211002011757.311265-10-memxor@gmail.com
This commit is contained in:
Kumar Kartikeya Dwivedi 2021-10-02 06:47:57 +05:30 committed by Alexei Starovoitov
parent 18f4fccbf3
commit c48e51c8b0
9 changed files with 132 additions and 31 deletions

View File

@ -275,5 +275,6 @@ static inline bool bpf_check_mod_kfunc_call(struct kfunc_btf_id_list *klist,
THIS_MODULE } THIS_MODULE }
extern struct kfunc_btf_id_list bpf_tcp_ca_kfunc_list; extern struct kfunc_btf_id_list bpf_tcp_ca_kfunc_list;
extern struct kfunc_btf_id_list prog_test_kfunc_list;
#endif #endif

View File

@ -6397,3 +6397,4 @@ bool bpf_check_mod_kfunc_call(struct kfunc_btf_id_list *klist, u32 kfunc_id,
EXPORT_SYMBOL_GPL(name) EXPORT_SYMBOL_GPL(name)
DEFINE_KFUNC_BTF_ID_LIST(bpf_tcp_ca_kfunc_list); DEFINE_KFUNC_BTF_ID_LIST(bpf_tcp_ca_kfunc_list);
DEFINE_KFUNC_BTF_ID_LIST(prog_test_kfunc_list);

View File

@ -2,6 +2,7 @@
/* Copyright (c) 2017 Facebook /* Copyright (c) 2017 Facebook
*/ */
#include <linux/bpf.h> #include <linux/bpf.h>
#include <linux/btf.h>
#include <linux/btf_ids.h> #include <linux/btf_ids.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
@ -243,7 +244,9 @@ BTF_SET_END(test_sk_kfunc_ids)
bool bpf_prog_test_check_kfunc_call(u32 kfunc_id, struct module *owner) bool bpf_prog_test_check_kfunc_call(u32 kfunc_id, struct module *owner)
{ {
return btf_id_set_contains(&test_sk_kfunc_ids, kfunc_id); if (btf_id_set_contains(&test_sk_kfunc_ids, kfunc_id))
return true;
return bpf_check_mod_kfunc_call(&prog_test_kfunc_list, kfunc_id, owner);
} }
static void *bpf_test_init(const union bpf_attr *kattr, u32 size, static void *bpf_test_init(const union bpf_attr *kattr, u32 size,

View File

@ -315,8 +315,9 @@ LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h \
linked_vars.skel.h linked_maps.skel.h linked_vars.skel.h linked_maps.skel.h
LSKELS := kfunc_call_test.c fentry_test.c fexit_test.c fexit_sleep.c \ LSKELS := kfunc_call_test.c fentry_test.c fexit_test.c fexit_sleep.c \
test_ksyms_module.c test_ringbuf.c atomics.c trace_printk.c \ test_ringbuf.c atomics.c trace_printk.c trace_vprintk.c
trace_vprintk.c # Generate both light skeleton and libbpf skeleton for these
LSKELS_EXTRA := test_ksyms_module.c
SKEL_BLACKLIST += $$(LSKELS) SKEL_BLACKLIST += $$(LSKELS)
test_static_linked.skel.h-deps := test_static_linked1.o test_static_linked2.o test_static_linked.skel.h-deps := test_static_linked1.o test_static_linked2.o
@ -346,7 +347,7 @@ TRUNNER_BPF_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.o, $$(TRUNNER_BPF_SRCS)
TRUNNER_BPF_SKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.skel.h, \ TRUNNER_BPF_SKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.skel.h, \
$$(filter-out $(SKEL_BLACKLIST) $(LINKED_BPF_SRCS),\ $$(filter-out $(SKEL_BLACKLIST) $(LINKED_BPF_SRCS),\
$$(TRUNNER_BPF_SRCS))) $$(TRUNNER_BPF_SRCS)))
TRUNNER_BPF_LSKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS)) TRUNNER_BPF_LSKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS) $$(LSKELS_EXTRA))
TRUNNER_BPF_SKELS_LINKED := $$(addprefix $$(TRUNNER_OUTPUT)/,$(LINKED_SKELS)) TRUNNER_BPF_SKELS_LINKED := $$(addprefix $$(TRUNNER_OUTPUT)/,$(LINKED_SKELS))
TEST_GEN_FILES += $$(TRUNNER_BPF_OBJS) TEST_GEN_FILES += $$(TRUNNER_BPF_OBJS)

View File

@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2020 Facebook */ /* Copyright (c) 2020 Facebook */
#include <linux/btf.h>
#include <linux/btf_ids.h>
#include <linux/error-injection.h> #include <linux/error-injection.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/module.h> #include <linux/module.h>
@ -13,6 +15,12 @@
DEFINE_PER_CPU(int, bpf_testmod_ksym_percpu) = 123; DEFINE_PER_CPU(int, bpf_testmod_ksym_percpu) = 123;
noinline void
bpf_testmod_test_mod_kfunc(int i)
{
*(int *)this_cpu_ptr(&bpf_testmod_ksym_percpu) = i;
}
noinline int bpf_testmod_loop_test(int n) noinline int bpf_testmod_loop_test(int n)
{ {
int i, sum = 0; int i, sum = 0;
@ -71,13 +79,26 @@ static struct bin_attribute bin_attr_bpf_testmod_file __ro_after_init = {
.write = bpf_testmod_test_write, .write = bpf_testmod_test_write,
}; };
BTF_SET_START(bpf_testmod_kfunc_ids)
BTF_ID(func, bpf_testmod_test_mod_kfunc)
BTF_SET_END(bpf_testmod_kfunc_ids)
static DEFINE_KFUNC_BTF_ID_SET(&bpf_testmod_kfunc_ids, bpf_testmod_kfunc_btf_set);
static int bpf_testmod_init(void) static int bpf_testmod_init(void)
{ {
return sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file); int ret;
ret = sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
if (ret)
return ret;
register_kfunc_btf_id_set(&prog_test_kfunc_list, &bpf_testmod_kfunc_btf_set);
return 0;
} }
static void bpf_testmod_exit(void) static void bpf_testmod_exit(void)
{ {
unregister_kfunc_btf_id_set(&prog_test_kfunc_list, &bpf_testmod_kfunc_btf_set);
return sysfs_remove_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file); return sysfs_remove_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file);
} }

View File

@ -2,30 +2,29 @@
/* Copyright (c) 2021 Facebook */ /* Copyright (c) 2021 Facebook */
#include <test_progs.h> #include <test_progs.h>
#include <bpf/libbpf.h> #include <network_helpers.h>
#include <bpf/btf.h>
#include "test_ksyms_module.lskel.h" #include "test_ksyms_module.lskel.h"
static int duration;
void test_ksyms_module(void) void test_ksyms_module(void)
{ {
struct test_ksyms_module* skel; struct test_ksyms_module *skel;
int retval;
int err; int err;
skel = test_ksyms_module__open_and_load(); if (!env.has_testmod) {
if (CHECK(!skel, "skel_open", "failed to open skeleton\n")) test__skip();
return; return;
}
err = test_ksyms_module__attach(skel); skel = test_ksyms_module__open_and_load();
if (CHECK(err, "skel_attach", "skeleton attach failed: %d\n", err)) if (!ASSERT_OK_PTR(skel, "test_ksyms_module__open_and_load"))
return;
err = bpf_prog_test_run(skel->progs.load.prog_fd, 1, &pkt_v4, sizeof(pkt_v4),
NULL, NULL, (__u32 *)&retval, NULL);
if (!ASSERT_OK(err, "bpf_prog_test_run"))
goto cleanup; goto cleanup;
ASSERT_EQ(retval, 0, "retval");
usleep(1); ASSERT_EQ(skel->bss->out_bpf_testmod_ksym, 42, "bpf_testmod_ksym");
ASSERT_EQ(skel->bss->triggered, true, "triggered");
ASSERT_EQ(skel->bss->out_mod_ksym_global, 123, "global_ksym_val");
cleanup: cleanup:
test_ksyms_module__destroy(skel); test_ksyms_module__destroy(skel);
} }

View File

@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include <network_helpers.h>
#include "test_ksyms_module.skel.h"
void test_ksyms_module_libbpf(void)
{
struct test_ksyms_module *skel;
int retval, err;
if (!env.has_testmod) {
test__skip();
return;
}
skel = test_ksyms_module__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_ksyms_module__open"))
return;
err = bpf_prog_test_run(bpf_program__fd(skel->progs.load), 1, &pkt_v4,
sizeof(pkt_v4), NULL, NULL, (__u32 *)&retval, NULL);
if (!ASSERT_OK(err, "bpf_prog_test_run"))
goto cleanup;
ASSERT_EQ(retval, 0, "retval");
ASSERT_EQ(skel->bss->out_bpf_testmod_ksym, 42, "bpf_testmod_ksym");
cleanup:
test_ksyms_module__destroy(skel);
}

View File

@ -2,24 +2,48 @@
/* Copyright (c) 2021 Facebook */ /* Copyright (c) 2021 Facebook */
#include "vmlinux.h" #include "vmlinux.h"
#include <bpf/bpf_helpers.h> #include <bpf/bpf_helpers.h>
#define X_0(x)
#define X_1(x) x X_0(x)
#define X_2(x) x X_1(x)
#define X_3(x) x X_2(x)
#define X_4(x) x X_3(x)
#define X_5(x) x X_4(x)
#define X_6(x) x X_5(x)
#define X_7(x) x X_6(x)
#define X_8(x) x X_7(x)
#define X_9(x) x X_8(x)
#define X_10(x) x X_9(x)
#define REPEAT_256(Y) X_2(X_10(X_10(Y))) X_5(X_10(Y)) X_6(Y)
extern const int bpf_testmod_ksym_percpu __ksym; extern const int bpf_testmod_ksym_percpu __ksym;
extern void bpf_testmod_test_mod_kfunc(int i) __ksym;
extern void bpf_testmod_invalid_mod_kfunc(void) __ksym __weak;
int out_mod_ksym_global = 0; int out_bpf_testmod_ksym = 0;
bool triggered = false; const volatile int x = 0;
SEC("raw_tp/sys_enter") SEC("tc")
int handler(const void *ctx) int load(struct __sk_buff *skb)
{ {
int *val; /* This will be kept by clang, but removed by verifier. Since it is
__u32 cpu; * marked as __weak, libbpf and gen_loader don't error out if BTF ID
* is not found for it, instead imm and off is set to 0 for it.
val = (int *)bpf_this_cpu_ptr(&bpf_testmod_ksym_percpu); */
out_mod_ksym_global = *val; if (x)
triggered = true; bpf_testmod_invalid_mod_kfunc();
bpf_testmod_test_mod_kfunc(42);
out_bpf_testmod_ksym = *(int *)bpf_this_cpu_ptr(&bpf_testmod_ksym_percpu);
return 0;
}
SEC("tc")
int load_256(struct __sk_buff *skb)
{
/* this will fail if kfunc doesn't reuse its own btf fd index */
REPEAT_256(bpf_testmod_test_mod_kfunc(42););
bpf_testmod_test_mod_kfunc(42);
return 0; return 0;
} }

View File

@ -1,3 +1,26 @@
{
"calls: invalid kfunc call not eliminated",
.insns = {
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 0, 0),
BPF_MOV64_IMM(BPF_REG_0, 1),
BPF_EXIT_INSN(),
},
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
.result = REJECT,
.errstr = "invalid kernel function call not eliminated in verifier pass",
},
{
"calls: invalid kfunc call unreachable",
.insns = {
BPF_MOV64_IMM(BPF_REG_0, 1),
BPF_JMP_IMM(BPF_JGT, BPF_REG_0, 0, 2),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 0, 0),
BPF_MOV64_IMM(BPF_REG_0, 1),
BPF_EXIT_INSN(),
},
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
.result = ACCEPT,
},
{ {
"calls: basic sanity", "calls: basic sanity",
.insns = { .insns = {