selftests/bpf: Keep int return type for tailcall subprogs

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>
This commit is contained in:
Yonghong Song 2026-06-09 16:34:07 -07:00 committed by Alexei Starovoitov
parent c15261b1bb
commit be1d838b88
6 changed files with 57 additions and 20 deletions

View File

@ -13,11 +13,14 @@ struct {
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;

View File

@ -16,20 +16,25 @@ 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;
}

View File

@ -25,8 +25,11 @@ int count1 = 0;
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
@ -41,16 +44,22 @@ int classifier_0(struct __sk_buff *skb)
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;
}
@ -59,13 +68,14 @@ __retval(33)
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;
}

View File

@ -33,17 +33,24 @@ int count = 0;
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;
}

View File

@ -18,18 +18,25 @@ int count = 0;
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;
}

View File

@ -1120,8 +1120,11 @@ int tail_call(struct __sk_buff *sk)
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. */
@ -1144,10 +1147,12 @@ __failure __msg("invalid mem access")
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;
}