Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: Fix htab_update/reenter_update selftest failure

Since commit 31158ad02ddb ("rqspinlock: Add deadlock detection
and recovery") the updated path on re-entrancy now reports deadlock
via -EDEADLK instead of the previous -EBUSY.

Also, the way reentrancy was exercised (via fentry/lookup_elem_raw)
has been fragile because lookup_elem_raw may be inlined
(find_kernel_btf_id() will return -ESRCH).

To fix this fentry is attached to bpf_obj_free_fields() instead of
lookup_elem_raw() and:

- The htab map is made to use a BTF-described struct val with a
struct bpf_timer so that check_and_free_fields() reliably calls
bpf_obj_free_fields() on element replacement.

- The selftest is updated to do two updates to the same key (insert +
replace) in prog_test.

- The selftest is updated to align with expected errno with the
kernel’s current behavior.

Signed-off-by: Saket Kumar Bhaskar <skb99@linux.ibm.com>
Tested-by: Venkat Rao Bagalkote <venkat88@linux.ibm.com>
Link: https://lore.kernel.org/r/20251117060752.129648-1-skb99@linux.ibm.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Saket Kumar Bhaskar and committed by
Alexei Starovoitov
590699d8 4617b306

+41 -15
+28 -9
tools/testing/selftests/bpf/prog_tests/htab_update.c
··· 15 15 static void test_reenter_update(void) 16 16 { 17 17 struct htab_update *skel; 18 - unsigned int key, value; 18 + void *value = NULL; 19 + unsigned int key, value_size; 19 20 int err; 20 21 21 22 skel = htab_update__open(); 22 23 if (!ASSERT_OK_PTR(skel, "htab_update__open")) 23 24 return; 24 25 25 - /* lookup_elem_raw() may be inlined and find_kernel_btf_id() will return -ESRCH */ 26 - bpf_program__set_autoload(skel->progs.lookup_elem_raw, true); 26 + bpf_program__set_autoload(skel->progs.bpf_obj_free_fields, true); 27 27 err = htab_update__load(skel); 28 - if (!ASSERT_TRUE(!err || err == -ESRCH, "htab_update__load") || err) 28 + if (!ASSERT_TRUE(!err, "htab_update__load") || err) 29 29 goto out; 30 30 31 31 skel->bss->pid = getpid(); ··· 33 33 if (!ASSERT_OK(err, "htab_update__attach")) 34 34 goto out; 35 35 36 - /* Will trigger the reentrancy of bpf_map_update_elem() */ 36 + value_size = bpf_map__value_size(skel->maps.htab); 37 + 38 + value = calloc(1, value_size); 39 + if (!ASSERT_OK_PTR(value, "calloc value")) 40 + goto out; 41 + /* 42 + * First update: plain insert. This should NOT trigger the re-entrancy 43 + * path, because there is no old element to free yet. 44 + */ 37 45 key = 0; 38 - value = 0; 39 - err = bpf_map_update_elem(bpf_map__fd(skel->maps.htab), &key, &value, 0); 40 - if (!ASSERT_OK(err, "add element")) 46 + err = bpf_map_update_elem(bpf_map__fd(skel->maps.htab), &key, value, BPF_ANY); 47 + if (!ASSERT_OK(err, "first update (insert)")) 41 48 goto out; 42 49 43 - ASSERT_EQ(skel->bss->update_err, -EBUSY, "no reentrancy"); 50 + /* 51 + * Second update: replace existing element with same key and trigger 52 + * the reentrancy of bpf_map_update_elem(). 53 + * check_and_free_fields() calls bpf_obj_free_fields() on the old 54 + * value, which is where fentry program runs and performs a nested 55 + * bpf_map_update_elem(), triggering -EDEADLK. 56 + */ 57 + memset(value, 0, value_size); 58 + err = bpf_map_update_elem(bpf_map__fd(skel->maps.htab), &key, value, BPF_ANY); 59 + if (!ASSERT_OK(err, "second update (replace)")) 60 + goto out; 61 + 62 + ASSERT_EQ(skel->bss->update_err, -EDEADLK, "no reentrancy"); 44 63 out: 45 64 htab_update__destroy(skel); 46 65 }
+13 -6
tools/testing/selftests/bpf/progs/htab_update.c
··· 6 6 7 7 char _license[] SEC("license") = "GPL"; 8 8 9 + /* Map value type: has BTF-managed field (bpf_timer) */ 10 + struct val { 11 + struct bpf_timer t; 12 + __u64 payload; 13 + }; 14 + 9 15 struct { 10 16 __uint(type, BPF_MAP_TYPE_HASH); 11 17 __uint(max_entries, 1); 12 - __uint(key_size, sizeof(__u32)); 13 - __uint(value_size, sizeof(__u32)); 18 + __type(key, __u32); 19 + __type(value, struct val); 14 20 } htab SEC(".maps"); 15 21 16 22 int pid = 0; 17 23 int update_err = 0; 18 24 19 - SEC("?fentry/lookup_elem_raw") 20 - int lookup_elem_raw(void *ctx) 25 + SEC("?fentry/bpf_obj_free_fields") 26 + int bpf_obj_free_fields(void *ctx) 21 27 { 22 - __u32 key = 0, value = 1; 28 + __u32 key = 0; 29 + struct val value = { .payload = 1 }; 23 30 24 31 if ((bpf_get_current_pid_tgid() >> 32) != pid) 25 32 return 0; 26 33 27 - update_err = bpf_map_update_elem(&htab, &key, &value, 0); 34 + update_err = bpf_map_update_elem(&htab, &key, &value, BPF_ANY); 28 35 return 0; 29 36 }