LLVM23 ([1]) supports 'true' function signature in BTF. The return type
of the caller of a tailcall must be an 'int'. Otherwise, verification will
fail (see check_btf_func() in check_btf.c). So with llvm23, it is possible
that the compiler may change the caller's return type from 'int' to 'void'.
To prevent this, barrier_var() and __sink() are used to avoid returning
a constant prone to be optimized.
[1] https://github.com/llvm/llvm-project/pull/198426
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
Link: https://lore.kernel.org/r/20260609233407.2711577-1-yonghong.song@linux.dev
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
static __noinline
int subprog_tail(struct __sk_buff *skb)
{
+ int ret = 1;
+
if (load_byte(skb, 0))
bpf_tail_call_static(skb, &jmp_table, 1);
else
bpf_tail_call_static(skb, &jmp_table, 0);
- return 1;
+ barrier_var(ret);
+ return ret;
}
int count = 0;
static __noinline
int subprog_tail(struct __sk_buff *skb)
{
+ int ret = 0;
+
bpf_tail_call_static(skb, &jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
SEC("tc")
int entry(struct __sk_buff *skb)
{
- int ret = 1;
+ int ret = 1, ret1, ret2;
clobber_regs_stack();
count++;
- subprog_tail(skb);
- subprog_tail(skb);
+ ret1 = subprog_tail(skb);
+ ret2 = subprog_tail(skb);
+ __sink(ret1);
+ __sink(ret2);
return ret;
}
static __noinline
int subprog_tail0(struct __sk_buff *skb)
{
+ int ret = 0;
+
bpf_tail_call_static(skb, &jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
__auxiliary
static __noinline
int subprog_tail1(struct __sk_buff *skb)
{
+ int ret = 0;
+
bpf_tail_call_static(skb, &jmp_table, 1);
- return 0;
+ barrier_var(ret);
+ return ret;
}
__auxiliary
SEC("tc")
int classifier_1(struct __sk_buff *skb)
{
+ int ret;
+
count1++;
- subprog_tail1(skb);
+ ret = subprog_tail1(skb);
+ __sink(ret);
return 0;
}
SEC("tc")
int tailcall_bpf2bpf_hierarchy_2(struct __sk_buff *skb)
{
- int ret = 0;
+ int ret = 0, ret1, ret2;
clobber_regs_stack();
- subprog_tail0(skb);
- subprog_tail1(skb);
-
+ ret1 = subprog_tail0(skb);
+ ret2 = subprog_tail1(skb);
+ __sink(ret1);
+ __sink(ret2);
__sink(ret);
return (count1 << 16) | count0;
}
static __noinline
int subprog_tail(struct __sk_buff *skb, void *jmp_table)
{
+ int ret = 0;
+
bpf_tail_call_static(skb, jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
__auxiliary
SEC("tc")
int classifier_0(struct __sk_buff *skb)
{
+ int ret1, ret2;
+
count++;
- subprog_tail(skb, &jmp_table0);
- subprog_tail(skb, &jmp_table1);
+ ret1 = subprog_tail(skb, &jmp_table0);
+ ret2 = subprog_tail(skb, &jmp_table1);
+ __sink(ret1);
+ __sink(ret2);
return count;
}
static __noinline
int subprog_tail(void *ctx)
{
+ int ret = 0;
+
bpf_tail_call_static(ctx, &jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
SEC("fentry/dummy")
int BPF_PROG(fentry, struct sk_buff *skb)
{
+ int ret1, ret2;
+
clobber_regs_stack();
count++;
- subprog_tail(ctx);
- subprog_tail(ctx);
+ ret1 = subprog_tail(ctx);
+ ret2 = subprog_tail(ctx);
+ __sink(ret1);
+ __sink(ret2);
return 0;
}
static __noinline
int static_tail_call(struct __sk_buff *sk)
{
+ int ret = 0;
+
bpf_tail_call_static(sk, &jmp_table, 0);
- return 0;
+ barrier_var(ret);
+ return ret;
}
/* Tail calls in sub-programs invalidate packet pointers. */
int invalidate_pkt_pointers_by_static_tail_call(struct __sk_buff *sk)
{
int *p = (void *)(long)sk->data;
+ int ret;
if ((void *)(p + 1) > (void *)(long)sk->data_end)
return TCX_DROP;
- static_tail_call(sk);
+ ret = static_tail_call(sk);
+ __sink(ret);
*p = 42; /* this is unsafe */
return TCX_PASS;
}