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.
Unified · Split
| @@ -275,5 +275,6 @@ static inline bool bpf_check_mod_kfunc_call(struct kfunc_btf_id_list *klist, · 274 unmodified lines | |||
| 275 | THIS_MODULE } | 275 | THIS_MODULE } |
| 276 | 276 | ||
| 277 | extern struct kfunc_btf_id_list bpf_tcp_ca_kfunc_list; | 277 | extern struct kfunc_btf_id_list bpf_tcp_ca_kfunc_list; |
| 278 | extern struct kfunc_btf_id_list prog_test_kfunc_list; | ||
| 278 | 279 | ||
| 279 | #endif | 280 | #endif |
| @@ -6397,3 +6397,4 @@ bool bpf_check_mod_kfunc_call(struct kfunc_btf_id_list *klist, u32 kfunc_id, · 6396 unmodified lines | |||
| 6397 | EXPORT_SYMBOL_GPL(name) | 6397 | EXPORT_SYMBOL_GPL(name) |
| 6398 | 6398 | ||
| 6399 | DEFINE_KFUNC_BTF_ID_LIST(bpf_tcp_ca_kfunc_list); | 6399 | DEFINE_KFUNC_BTF_ID_LIST(bpf_tcp_ca_kfunc_list); |
| 6400 | DEFINE_KFUNC_BTF_ID_LIST(prog_test_kfunc_list); | ||
| @@ -2,6 +2,7 @@ · 1 unmodified line | |||
| 2 | /* Copyright (c) 2017 Facebook | 2 | /* Copyright (c) 2017 Facebook |
| 3 | */ | 3 | */ |
| 4 | #include <linux/bpf.h> | 4 | #include <linux/bpf.h> |
| 5 | #include <linux/btf.h> | ||
| 5 | #include <linux/btf_ids.h> | 6 | #include <linux/btf_ids.h> |
| 6 | #include <linux/slab.h> | 7 | #include <linux/slab.h> |
| 7 | #include <linux/vmalloc.h> | 8 | #include <linux/vmalloc.h> |
| @@ -243,7 +244,9 @@ BTF_SET_END(test_sk_kfunc_ids) · 235 unmodified lines | |||
| 243 | 244 | ||
| 244 | bool bpf_prog_test_check_kfunc_call(u32 kfunc_id, struct module *owner) | 245 | bool bpf_prog_test_check_kfunc_call(u32 kfunc_id, struct module *owner) |
| 245 | { | 246 | { |
| 246 | return btf_id_set_contains(&test_sk_kfunc_ids, kfunc_id); | 247 | if (btf_id_set_contains(&test_sk_kfunc_ids, kfunc_id)) |
| 248 | return true; | ||
| 249 | return bpf_check_mod_kfunc_call(&prog_test_kfunc_list, kfunc_id, owner); | ||
| 247 | } | 250 | } |
| 248 | 251 | ||
| 249 | static void *bpf_test_init(const union bpf_attr *kattr, u32 size, | 252 | static void *bpf_test_init(const union bpf_attr *kattr, u32 size, |
| @@ -315,8 +315,9 @@ LINKED_SKELS := test_static_linked.skel.h linked_funcs.skel.h \ · 314 unmodified lines | |||
| 315 | linked_vars.skel.h linked_maps.skel.h | 315 | linked_vars.skel.h linked_maps.skel.h |
| 316 | 316 | ||
| 317 | LSKELS := kfunc_call_test.c fentry_test.c fexit_test.c fexit_sleep.c \ | 317 | LSKELS := kfunc_call_test.c fentry_test.c fexit_test.c fexit_sleep.c \ |
| 318 | test_ksyms_module.c test_ringbuf.c atomics.c trace_printk.c \ | 318 | test_ringbuf.c atomics.c trace_printk.c trace_vprintk.c |
| 319 | trace_vprintk.c | 319 | # Generate both light skeleton and libbpf skeleton for these |
| 320 | LSKELS_EXTRA := test_ksyms_module.c | ||
| 320 | SKEL_BLACKLIST += $$(LSKELS) | 321 | SKEL_BLACKLIST += $$(LSKELS) |
| 321 | 322 | ||
| 322 | test_static_linked.skel.h-deps := test_static_linked1.o test_static_linked2.o | 323 | 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) · 23 unmodified lines | |||
| 346 | TRUNNER_BPF_SKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.skel.h, \ | 347 | TRUNNER_BPF_SKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.skel.h, \ |
| 347 | $$(filter-out $(SKEL_BLACKLIST) $(LINKED_BPF_SRCS),\ | 348 | $$(filter-out $(SKEL_BLACKLIST) $(LINKED_BPF_SRCS),\ |
| 348 | $$(TRUNNER_BPF_SRCS))) | 349 | $$(TRUNNER_BPF_SRCS))) |
| 349 | TRUNNER_BPF_LSKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS)) | 350 | TRUNNER_BPF_LSKELS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.lskel.h, $$(LSKELS) $$(LSKELS_EXTRA)) |
| 350 | TRUNNER_BPF_SKELS_LINKED := $$(addprefix $$(TRUNNER_OUTPUT)/,$(LINKED_SKELS)) | 351 | TRUNNER_BPF_SKELS_LINKED := $$(addprefix $$(TRUNNER_OUTPUT)/,$(LINKED_SKELS)) |
| 351 | TEST_GEN_FILES += $$(TRUNNER_BPF_OBJS) | 352 | TEST_GEN_FILES += $$(TRUNNER_BPF_OBJS) |
| 352 | 353 | ||
| @@ -1,5 +1,7 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* Copyright (c) 2020 Facebook */ | 2 | /* Copyright (c) 2020 Facebook */ |
| 3 | #include <linux/btf.h> | ||
| 4 | #include <linux/btf_ids.h> | ||
| 3 | #include <linux/error-injection.h> | 5 | #include <linux/error-injection.h> |
| 4 | #include <linux/init.h> | 6 | #include <linux/init.h> |
| 5 | #include <linux/module.h> | 7 | #include <linux/module.h> |
| @@ -13,6 +15,12 @@ · 7 unmodified lines | |||
| 13 | 15 | ||
| 14 | DEFINE_PER_CPU(int, bpf_testmod_ksym_percpu) = 123; | 16 | DEFINE_PER_CPU(int, bpf_testmod_ksym_percpu) = 123; |
| 15 | 17 | ||
| 18 | noinline void | ||
| 19 | bpf_testmod_test_mod_kfunc(int i) | ||
| 20 | { | ||
| 21 | *(int *)this_cpu_ptr(&bpf_testmod_ksym_percpu) = i; | ||
| 22 | } | ||
| 23 | |||
| 16 | noinline int bpf_testmod_loop_test(int n) | 24 | noinline int bpf_testmod_loop_test(int n) |
| 17 | { | 25 | { |
| 18 | int i, sum = 0; | 26 | int i, sum = 0; |
| @@ -71,13 +79,26 @@ static struct bin_attribute bin_attr_bpf_testmod_file __ro_after_init = { · 52 unmodified lines | |||
| 71 | .write = bpf_testmod_test_write, | 79 | .write = bpf_testmod_test_write, |
| 72 | }; | 80 | }; |
| 73 | 81 | ||
| 82 | BTF_SET_START(bpf_testmod_kfunc_ids) | ||
| 83 | BTF_ID(func, bpf_testmod_test_mod_kfunc) | ||
| 84 | BTF_SET_END(bpf_testmod_kfunc_ids) | ||
| 85 | |||
| 86 | static DEFINE_KFUNC_BTF_ID_SET(&bpf_testmod_kfunc_ids, bpf_testmod_kfunc_btf_set); | ||
| 87 | |||
| 74 | static int bpf_testmod_init(void) | 88 | static int bpf_testmod_init(void) |
| 75 | { | 89 | { |
| 76 | return sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file); | 90 | int ret; |
| 91 | |||
| 92 | ret = sysfs_create_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file); | ||
| 93 | if (ret) | ||
| 94 | return ret; | ||
| 95 | register_kfunc_btf_id_set(&prog_test_kfunc_list, &bpf_testmod_kfunc_btf_set); | ||
| 96 | return 0; | ||
| 77 | } | 97 | } |
| 78 | 98 | ||
| 79 | static void bpf_testmod_exit(void) | 99 | static void bpf_testmod_exit(void) |
| 80 | { | 100 | { |
| 101 | unregister_kfunc_btf_id_set(&prog_test_kfunc_list, &bpf_testmod_kfunc_btf_set); | ||
| 81 | return sysfs_remove_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file); | 102 | return sysfs_remove_bin_file(kernel_kobj, &bin_attr_bpf_testmod_file); |
| 82 | } | 103 | } |
| 83 | 104 | ||
| @@ -2,30 +2,29 @@ · 1 unmodified line | |||
| 2 | /* Copyright (c) 2021 Facebook */ | 2 | /* Copyright (c) 2021 Facebook */ |
| 3 | 3 | ||
| 4 | #include <test_progs.h> | 4 | #include <test_progs.h> |
| 5 | #include <bpf/libbpf.h> | 5 | #include <network_helpers.h> |
| 6 | #include <bpf/btf.h> | ||
| 7 | #include "test_ksyms_module.lskel.h" | 6 | #include "test_ksyms_module.lskel.h" |
| 8 | 7 | ||
| 9 | static int duration; | ||
| 10 | |||
| 11 | void test_ksyms_module(void) | 8 | void test_ksyms_module(void) |
| 12 | { | 9 | { |
| 13 | struct test_ksyms_module* skel; | 10 | struct test_ksyms_module *skel; |
| 11 | int retval; | ||
| 14 | int err; | 12 | int err; |
| 15 | 13 | ||
| 16 | skel = test_ksyms_module__open_and_load(); | 14 | if (!env.has_testmod) { |
| 17 | if (CHECK(!skel, "skel_open", "failed to open skeleton\n")) | 15 | test__skip(); |
| 18 | return; | 16 | return; |
| 17 | } | ||
| 19 | 18 | ||
| 20 | err = test_ksyms_module__attach(skel); | 19 | skel = test_ksyms_module__open_and_load(); |
| 21 | if (CHECK(err, "skel_attach", "skeleton attach failed: %d\n", err)) | 20 | if (!ASSERT_OK_PTR(skel, "test_ksyms_module__open_and_load")) |
| 21 | return; | ||
| 22 | err = bpf_prog_test_run(skel->progs.load.prog_fd, 1, &pkt_v4, sizeof(pkt_v4), | ||
| 23 | NULL, NULL, (__u32 *)&retval, NULL); | ||
| 24 | if (!ASSERT_OK(err, "bpf_prog_test_run")) | ||
| 22 | goto cleanup; | 25 | goto cleanup; |
| 23 | 26 | ASSERT_EQ(retval, 0, "retval"); | |
| 24 | usleep(1); | 27 | ASSERT_EQ(skel->bss->out_bpf_testmod_ksym, 42, "bpf_testmod_ksym"); |
| 25 | |||
| 26 | ASSERT_EQ(skel->bss->triggered, true, "triggered"); | ||
| 27 | ASSERT_EQ(skel->bss->out_mod_ksym_global, 123, "global_ksym_val"); | ||
| 28 | |||
| 29 | cleanup: | 28 | cleanup: |
| 30 | test_ksyms_module__destroy(skel); | 29 | test_ksyms_module__destroy(skel); |
| 31 | } | 30 | } |
| @@ -0,0 +1,28 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | |||
| 3 | #include <test_progs.h> | ||
| 4 | #include <network_helpers.h> | ||
| 5 | #include "test_ksyms_module.skel.h" | ||
| 6 | |||
| 7 | void test_ksyms_module_libbpf(void) | ||
| 8 | { | ||
| 9 | struct test_ksyms_module *skel; | ||
| 10 | int retval, err; | ||
| 11 | |||
| 12 | if (!env.has_testmod) { | ||
| 13 | test__skip(); | ||
| 14 | return; | ||
| 15 | } | ||
| 16 | |||
| 17 | skel = test_ksyms_module__open_and_load(); | ||
| 18 | if (!ASSERT_OK_PTR(skel, "test_ksyms_module__open")) | ||
| 19 | return; | ||
| 20 | err = bpf_prog_test_run(bpf_program__fd(skel->progs.load), 1, &pkt_v4, | ||
| 21 | sizeof(pkt_v4), NULL, NULL, (__u32 *)&retval, NULL); | ||
| 22 | if (!ASSERT_OK(err, "bpf_prog_test_run")) | ||
| 23 | goto cleanup; | ||
| 24 | ASSERT_EQ(retval, 0, "retval"); | ||
| 25 | ASSERT_EQ(skel->bss->out_bpf_testmod_ksym, 42, "bpf_testmod_ksym"); | ||
| 26 | cleanup: | ||
| 27 | test_ksyms_module__destroy(skel); | ||
| 28 | } | ||
| @@ -2,24 +2,48 @@ · 1 unmodified line | |||
| 2 | /* Copyright (c) 2021 Facebook */ | 2 | /* Copyright (c) 2021 Facebook */ |
| 3 | 3 | ||
| 4 | #include "vmlinux.h" | 4 | #include "vmlinux.h" |
| 5 | |||
| 6 | #include <bpf/bpf_helpers.h> | 5 | #include <bpf/bpf_helpers.h> |
| 7 | 6 | ||
| 7 | #define X_0(x) | ||
| 8 | #define X_1(x) x X_0(x) | ||
| 9 | #define X_2(x) x X_1(x) | ||
| 10 | #define X_3(x) x X_2(x) | ||
| 11 | #define X_4(x) x X_3(x) | ||
| 12 | #define X_5(x) x X_4(x) | ||
| 13 | #define X_6(x) x X_5(x) | ||
| 14 | #define X_7(x) x X_6(x) | ||
| 15 | #define X_8(x) x X_7(x) | ||
| 16 | #define X_9(x) x X_8(x) | ||
| 17 | #define X_10(x) x X_9(x) | ||
| 18 | #define REPEAT_256(Y) X_2(X_10(X_10(Y))) X_5(X_10(Y)) X_6(Y) | ||
| 19 | |||
| 8 | extern const int bpf_testmod_ksym_percpu __ksym; | 20 | extern const int bpf_testmod_ksym_percpu __ksym; |
| 21 | extern void bpf_testmod_test_mod_kfunc(int i) __ksym; | ||
| 22 | extern void bpf_testmod_invalid_mod_kfunc(void) __ksym __weak; | ||
| 9 | 23 | ||
| 10 | int out_mod_ksym_global = 0; | 24 | int out_bpf_testmod_ksym = 0; |
| 11 | bool triggered = false; | 25 | const volatile int x = 0; |
| 12 | 26 | ||
| 13 | SEC("raw_tp/sys_enter") | 27 | SEC("tc") |
| 14 | int handler(const void *ctx) | 28 | int load(struct __sk_buff *skb) |
| 15 | { | 29 | { |
| 16 | int *val; | 30 | /* This will be kept by clang, but removed by verifier. Since it is |
| 17 | __u32 cpu; | 31 | * marked as __weak, libbpf and gen_loader don't error out if BTF ID |
| 18 | 32 | * is not found for it, instead imm and off is set to 0 for it. | |
| 19 | val = (int *)bpf_this_cpu_ptr(&bpf_testmod_ksym_percpu); | 33 | */ |
| 20 | out_mod_ksym_global = *val; | 34 | if (x) |
| 21 | triggered = true; | 35 | bpf_testmod_invalid_mod_kfunc(); |
| 36 | bpf_testmod_test_mod_kfunc(42); | ||
| 37 | out_bpf_testmod_ksym = *(int *)bpf_this_cpu_ptr(&bpf_testmod_ksym_percpu); | ||
| 38 | return 0; | ||
| 39 | } | ||
| 22 | 40 | ||
| 41 | SEC("tc") | ||
| 42 | int load_256(struct __sk_buff *skb) | ||
| 43 | { | ||
| 44 | /* this will fail if kfunc doesn't reuse its own btf fd index */ | ||
| 45 | REPEAT_256(bpf_testmod_test_mod_kfunc(42);); | ||
| 46 | bpf_testmod_test_mod_kfunc(42); | ||
| 23 | return 0; | 47 | return 0; |
| 24 | } | 48 | } |
| 25 | 49 | ||
| @@ -1,3 +1,26 @@ | |||
| 1 | { | ||
| 2 | "calls: invalid kfunc call not eliminated", | ||
| 3 | .insns = { | ||
| 4 | BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 0, 0), | ||
| 5 | BPF_MOV64_IMM(BPF_REG_0, 1), | ||
| 6 | BPF_EXIT_INSN(), | ||
| 7 | }, | ||
| 8 | .prog_type = BPF_PROG_TYPE_TRACEPOINT, | ||
| 9 | .result = REJECT, | ||
| 10 | .errstr = "invalid kernel function call not eliminated in verifier pass", | ||
| 11 | }, | ||
| 12 | { | ||
| 13 | "calls: invalid kfunc call unreachable", | ||
| 14 | .insns = { | ||
| 15 | BPF_MOV64_IMM(BPF_REG_0, 1), | ||
| 16 | BPF_JMP_IMM(BPF_JGT, BPF_REG_0, 0, 2), | ||
| 17 | BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 0, 0), | ||
| 18 | BPF_MOV64_IMM(BPF_REG_0, 1), | ||
| 19 | BPF_EXIT_INSN(), | ||
| 20 | }, | ||
| 21 | .prog_type = BPF_PROG_TYPE_TRACEPOINT, | ||
| 22 | .result = ACCEPT, | ||
| 23 | }, | ||
| 1 | { | 24 | { |
| 2 | "calls: basic sanity", | 25 | "calls: basic sanity", |
| 3 | .insns = { | 26 | .insns = { |