Add a regression test to check against invalid check_and_init_map_value call inside prealloc_lru_pop. To actually observe a kind of problem this can cause, set debug to 1 when running the test locally without the fix. Then, just observe the refcount which keeps increasing on each run of the test. With timers or spin locks, it would cause unpredictable results when racing. ... bash-5.1# ./test_progs -t lru_bug test_progs-192 [000] d..21 354.838821: bpf_trace_printk: ref: 4 test_progs-192 [000] d..21 354.842824: bpf_trace_printk: ref: 5 bash-5.1# ./test_progs -t lru_bug test_progs-193 [000] d..21 356.722813: bpf_trace_printk: ref: 5 test_progs-193 [000] d..21 356.727071: bpf_trace_printk: ref: 6 ... and so on. Signed-off-by: Kumar Kartikeya Dwivedi <memxor@xxxxxxxxx> --- .../selftests/bpf/prog_tests/lru_bug.c | 19 ++++++ tools/testing/selftests/bpf/progs/lru_bug.c | 67 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/lru_bug.c create mode 100644 tools/testing/selftests/bpf/progs/lru_bug.c diff --git a/tools/testing/selftests/bpf/prog_tests/lru_bug.c b/tools/testing/selftests/bpf/prog_tests/lru_bug.c new file mode 100644 index 000000000000..3bcb5bc62d5a --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/lru_bug.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <test_progs.h> + +#include "lru_bug.skel.h" + +void serial_test_lru_bug(void) +{ + struct lru_bug *skel; + int ret; + + skel = lru_bug__open_and_load(); + if (!ASSERT_OK_PTR(skel, "lru_bug__open_and_load")) + return; + ret = lru_bug__attach(skel); + if (!ASSERT_OK(ret, "lru_bug__attach")) + return; + usleep(1); + ASSERT_OK(skel->data->result, "prealloc_lru_pop doesn't call check_and_init_map_value"); +} diff --git a/tools/testing/selftests/bpf/progs/lru_bug.c b/tools/testing/selftests/bpf/progs/lru_bug.c new file mode 100644 index 000000000000..35cbbe7aba9e --- /dev/null +++ b/tools/testing/selftests/bpf/progs/lru_bug.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <vmlinux.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_helpers.h> + +struct map_value { + struct prog_test_ref_kfunc __kptr_ref *ptr; +}; + +struct { + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 1); + __type(key, int); + __type(value, struct map_value); +} lru_map SEC(".maps"); + +extern struct prog_test_ref_kfunc *bpf_kfunc_call_test_acquire(unsigned long *sp) __ksym; +extern void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *s) __ksym; + +int pid = 0; +const volatile int debug = 0; +int result = 1; + +SEC("fentry/bpf_ktime_get_ns") +int printk(void *ctx) +{ + struct map_value v = {}; + + if (pid == bpf_get_current_task_btf()->pid) + bpf_map_update_elem(&lru_map, &(int){0}, &v, 0); + return 0; +} + +SEC("fentry/do_nanosleep") +int nanosleep(void *ctx) +{ + struct map_value val = {}, *v; + struct prog_test_ref_kfunc *s; + unsigned long l = 0; + + bpf_map_update_elem(&lru_map, &(int){0}, &val, 0); + v = bpf_map_lookup_elem(&lru_map, &(int){0}); + if (!v) + return 0; + bpf_map_delete_elem(&lru_map, &(int){0}); + s = bpf_kfunc_call_test_acquire(&l); + if (!s) + return 0; + if (debug) + bpf_printk("ref: %d\n", s->cnt.refs.counter); + s = bpf_kptr_xchg(&v->ptr, s); + if (s) + bpf_kfunc_call_test_release(s); + pid = bpf_get_current_task_btf()->pid; + bpf_ktime_get_ns(); + if (debug) { + s = bpf_kfunc_call_test_acquire(&l); + if (!s) + return 0; + bpf_printk("ref: %d\n", s->cnt.refs.counter); + bpf_kfunc_call_test_release(s); + } + result = !v->ptr; + return 0; +} + +char _license[] SEC("license") = "GPL"; -- 2.34.1