On Sat, Sep 6, 2025 at 12:55 AM Yonghong Song <yonghong.song@xxxxxxxxx> wrote: > > > > On 9/4/25 10:18 PM, Hoyeon Lee wrote: > > Add a compile-time check to bpf_tail_call_static() to warn when a > > constant slot(index) >= map->max_entries. This uses a small > > BPF_MAP_ENTRIES() macro together with Clang's diagnose_if attribute. > > > > Clang front-end keeps the map type with a '(*max_entries)[N]' field, > > so the expression > > > > sizeof(*(m)->max_entries) / sizeof(**(m)->max_entries) > > > > is resolved to N entirely at compile time. This allows diagnose_if() > > to emit a warning when a constant slot index is out of range. > > > > Out-of-bounds tail calls are currently silent no-ops at runtime, so > > emitting a compile-time warning helps detect logic errors earlier. > > This is currently limited to Clang (due to diagnose_if) and only for > > constant indices, but should still catch the common cases. > > > > Signed-off-by: Hoyeon Lee <hoyeon.lee@xxxxxxxx> > > --- > > Changes in V2: > > - add function definition for __bpf_tail_call_warn for compile error > > > > tools/lib/bpf/bpf_helpers.h | 21 +++++++++++++++++++++ > > 1 file changed, 21 insertions(+) > > > > diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h > > index 80c028540656..98bc1536c497 100644 > > --- a/tools/lib/bpf/bpf_helpers.h > > +++ b/tools/lib/bpf/bpf_helpers.h > > @@ -173,6 +173,27 @@ bpf_tail_call_static(void *ctx, const void *map, const __u32 slot) > > :: [ctx]"r"(ctx), [map]"r"(map), [slot]"i"(slot) > > : "r0", "r1", "r2", "r3", "r4", "r5"); > > } > > + > > +#if __has_attribute(diagnose_if) > > +static __always_inline void __bpf_tail_call_warn(int oob) > > + __attribute__((diagnose_if(oob, "bpf_tail_call: slot >= max_entries", > > + "warning"))) {}; > > + > > +#define BPF_MAP_ENTRIES(m) \ > > + ((__u32)(sizeof(*(m)->max_entries) / sizeof(**(m)->max_entries))) > > + > > +#ifndef bpf_tail_call_static > > +#define bpf_tail_call_static(ctx, map, slot) \ > > +({ \ > > + /* wrapped to avoid double evaluation. */ \ > > + const __u32 __slot = (slot); \ > > + __bpf_tail_call_warn(__slot >= BPF_MAP_ENTRIES(map)); \ > > + /* Avoid re-expand & invoke original as (bpf_tail_call_static)(..) */ \ > > + (bpf_tail_call_static)(ctx, map, __slot); \ > > +}) > > +#endif /* bpf_tail_call_static */ > > +#endif > > I got the following error with llvm21. > > progs/tailcall_bpf2bpf3.c:20:3: error: bpf_tail_call: slot >= max_entries [-Werror,-Wuser-defined-warnings] > 20 | bpf_tail_call_static(skb, &jmp_table,progs/tailcall_bpf2bpf2.c:17:3 10); > | : ^ > /home/yhs/work/bpf-next/tools/testing/selftests/bpf/tools/include/bpf/bpf_helpers.h:190:53: note: expanded from macro > 'bpf_tail_call_static' > 190 | __bpf_tail_call_warn(__slot >= BPF_MAP_ENTRIES(map)); \ > | ^ > /home/yhs/work/bpf-next/tools/testing/selftests/bpf/tools/include/bpf/bpf_helpers.h:179:17: note: from 'diagnose_if' > attribute on '__bpf_tail_call_warn': > 179 | __attribute__((diagnose_if(oob, "bpf_tail_call: slot >= max_entries", > | ^ ~~~ > error: bpf_tail_call: slot >= max_entries [-Werror,-Wuser-defined-warnings] > 17 | bpf_tail_call_static(skb, &jmp_table, 1); > | ^ > /home/yhs/work/bpf-next/tools/testing/selftests/bpf/tools/include/bpf/bpf_helpers.h:190:53: note: expanded from macro > 'bpf_tail_call_static' > 190 | __bpf_tail_call_warn(__slot >= BPF_MAP_ENTRIES(map)); \ > | ^ > /home/yhs/work/bpf-next/tools/testing/selftests/bpf/tools/include/bpf/bpf_helpers.h:179:17: note: from 'diagnose_if' > attribute on '__bpf_tail_call_warn': > 179 | __attribute__((diagnose_if(oob, "bpf_tail_call: slot >= max_entries", > | ^ ~~~ > CLNG-BPF [test_progs] tailcall_poke.bpf.o > 1 error generated. > make: *** [Makefile:733: /home/yhs/work/bpf-next/tools/testing/selftests/bpf/tailcall_bpf2bpf3.bpf.o] Error 1 > Thank you for sharing build results! Checked BPF CI, and found 2 issues: 1. selftests/bpf promote warnings to errors (-Werror) For bpf2bpf tail-call variant progs that intentionally calls OOB trigger this diagnostic, relaxing just those files keeps CI green while still showing the warning: # tools/testing/selftests/bpf/Makefile progs/tailcall_bpf2bpf%.c-CFLAGS := -Wno-error=user-defined-warnings 2. 'void *' maps build error (bpf2bpf / map-in-map) The proposed warning is meant only for typed .maps objects. When a prog passes a void * map, BPF_MAP_ENTRIES() must not attempt member access. A _Generic gate fixes this by filtering only for typed maps and yielding 0U for void* families: # from BPF-CI build error # function prototype: # int subprog_tail(struct __sk_buff *skb, void *jmp_table) progs/tailcall_bpf2bpf_hierarchy3.c:36:2: error: member reference base type 'void' is not a structure or union 36 | bpf_tail_call_static(skb, jmp_table, 0); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ fixes: #define BPF_MAP_ENTRIES(m) _Generic((m), \ void *: 0U, \ const void *: 0U, \ volatile void *: 0U, \ const volatile void *: 0U, \ default: ((__u32)(sizeof(*(m)->max_entries) / sizeof(**(m)->max_entries))) \ ) This avoids the compile error, but this is not a very clean solution. As Andrii Nakryiko noted, map->max_entries can be changed at runtime, which makes this compile-time approach misleading. I hadn’t considered that, so this RFC seems less practical to pursue further. Still, I’m sharing the troubleshooting above in case parts of it may be useful for future attempts. > > + > > #endif > > #endif > > > > -- > > 2.51.0 >