From: Yonghong Song <yonghong.song@linux.dev>
To: bot+bpf-ci@kernel.org, bpf@vger.kernel.org
Cc: ast@kernel.org, andrii@kernel.org, daniel@iogearbox.net,
eddyz87@gmail.com, kernel-team@fb.com, martin.lau@kernel.org,
clm@meta.com, ihor.solodrai@linux.dev
Subject: Re: [PATCH bpf-next v5 08/11] bpf: Reject register-pair returns when the subprog BTF is unreliable
Date: Sun, 16 Aug 2026 20:47:42 -0700 [thread overview]
Message-ID: <ac9bda02-46ad-468f-86b8-11b781804849@linux.dev> (raw)
In-Reply-To: <1ce46a68f49276d02c4085b05dedb72549b0cdebf33ff4576d670b5bd1e6069f@mail.kernel.org>
On 8/13/26 1:49 PM, bot+bpf-ci@kernel.org wrote:
>> btf_check_subprog_call() can decide, at a call site, that this BTF is not
>> to be trusted and mark the subprogram unreliable, which happens when
>> compiler optimizations remove arguments from a static function or when a
>> mismatched type is passed to a global one. Verification carries on, but
>> the prototype the return convention was read from is one the verifier has
>> already declared not to describe the compiled code.
>>
>> Rather than keep tracking R2 on the strength of a discarded signature,
>> reject a return value larger than 8 bytes as soon as the prototype it was
>> derived from becomes unreliable. Add subprog_ret_pair_unreliable() and
>> test it in check_func_call() on the path to a static subprogram, which is
>> where the flag can be observed while the call still proceeds. For a static
>> callee this only triggers on a genuine argument mismatch, since the
>> previous patch stopped btf_validate_return_type() from marking a local
>> function unreliable for returning an aggregate.
>>
>> No check is needed anywhere else:
>> - a global subprogram is already rejected by the existing "Caller passes
>> invalid args into func#N" path, because btf_check_subprog_call()
>> returns an error both when it marks the BTF unreliable and on every
>> later call;
>> - the main program does not use the convention at all: its return value
>> is the program's exit code, read out of R0, so nothing looks at R2
>> there;
> Does this enumeration cover all call sites? btf_check_subprog_call() has
> two callers: check_func_call() and push_callback_call() in verifier.c.
>
> push_callback_call() also swallows every non-EFAULT error:
>
> err = btf_check_subprog_call(env, subprog, caller->regs);
> if (err == -EFAULT)
> return err;
>
> so a callback subprogram's BTF can be marked unreliable while the call
> still proceeds, exactly the situation described above for the static
> check_func_call() path. The two bullets given (global subprogram, main
> program) do not cover it.
>
> It interacts with the sticky nature of the flag: a subprogram marked
> unreliable by a callback call site will subsequently be rejected by the
> new check at an unrelated direct call site whose own arguments were fine,
> since btf_check_subprog_call() returns -EINVAL for every later call once
> the flag is set (the early return in btf_check_subprog_call()).
>
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index b23be0fa95af..0ffb3bed1649 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -441,6 +441,23 @@ static void bpf_compute_subprog_ret_regs(struct bpf_verifier_env *env)
>> }
>> }
>>
>> +/*
>> + * A >8 byte BPF return changes the calling convention to R0:R2, and the
>> + * verifier derives that convention from the subprogram's BTF prototype
>> + * alone. Once that prototype is marked unreliable it is known not to
>> + * describe the compiled code, so the convention read from it cannot be
>> + * trusted either: reject the call rather than keep tracking R2 on the
>> + * strength of a signature the verifier has already discarded.
>> + */
>> +static bool subprog_ret_pair_unreliable(struct bpf_verifier_env *env, int subprog)
>> +{
>> + struct bpf_prog_aux *aux = env->prog->aux;
>> +
>> + return bpf_ret_reg_pair(env, subprog) &&
>> + aux->func_info_aux &&
>> + aux->func_info_aux[subprog].unreliable;
>> +}
>> +
> [ ... ]
>> @@ -9527,6 +9544,12 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>> return 0;
>> }
>>
>> + if (subprog_ret_pair_unreliable(env, subprog)) {
>> + verbose(env, "Func#%d ('%s') returns >8 bytes, which requires reliable BTF\n",
>> + subprog, subprog_name(env, subprog));
>> + return -EINVAL;
>> + }
>> +
> Can this gate reject valid programs? For a static callee, func_info_aux[]
> .unreliable is set for reasons that have nothing to do with an argument
> mismatch.
>
> The path is: do_check_insn() calls check_func_call(), which calls
> btf_check_subprog_call(), which calls btf_check_func_arg_match().
>
> btf_check_func_arg_match() in kernel/bpf/verifier.c starts with:
>
> ret = btf_prepare_func_args(env, subprog);
> if (ret) {
> if (bpf_in_stack_arg_cnt(sub) > 0) ...
> return ret;
> }
>
> Then btf_check_subprog_call() does unconditionally on any non-zero err:
>
> if (err)
> prog->aux->func_info_aux[subprog].unreliable = true;
>
> btf_prepare_func_args() returns -EINVAL for a non-global subprogram in the
> ordinary case of an untagged pointer argument. In the argument loop
> (kernel/bpf/btf.c), a pointer arg that is not the prog ctx type, not a
> dynptr, and carries no __arg_trusted/__arg_untrusted/__arg_arena tag skips
> the generic-user-data-pointer branch and falls through to:
>
> skip_pointer:
> if (tags) { ... return -EINVAL; }
> if (btf_type_is_int(t) || btf_is_any_enum(t)) { ... }
> if (!is_global)
> return -EINVAL;
>
> A by-value struct argument takes the same exit. This is expected,
> documented behaviour for static callees since btf_prepare_func_args() even
> comments "not fatal for static funcs" at kernel/bpf/btf.c, because a
> static subprogram is verified inline so the verifier never needs a usable
> BTF argument model for it.
>
> Concrete example that verifies fine at the parent commit but is now
> rejected:
>
> struct pair { __u64 a, b; };
>
> static __noinline struct pair make_pair(__u64 *p)
> {
> struct pair r = { *p, 1 };
> return r;
> }
>
> SEC("tc") int prog(struct __sk_buff *ctx)
> {
> __u64 v = 1;
> struct pair p = make_pair(&v);
> return p.a + p.b;
> }
>
> This program is rejected: "Func#1 ('make_pair') returns >8 bytes, which
> requires reliable BTF".
>
> The flow is: bpf_compute_subprog_ret_regs() sees struct pair, size 16,
> and sets ret_reg_pair = true. btf_validate_return_type() accepts it via
> the local_func path. The argument loop then returns -EINVAL for __u64 *p,
> setting unreliable = true. The new check fires.
>
> The rejection is also unnecessary for soundness on this path: a static
> callee is verified inline, so prepare_func_exit() copies the callee's
> actual tracked R0 and R2 into the caller. R2 tracking does not rest on the
> BTF prototype the way it does for a global callee.
>
> The commit message premise appears incorrect: "For a static callee this
> only triggers on a genuine argument mismatch, since the previous patch
> stopped btf_validate_return_type() from marking a local function
> unreliable for returning an aggregate." Relaxing btf_validate_return_type()
> removed only one of two independent -EINVAL exits inside
> btf_prepare_func_args(); the argument-conversion exit at kernel/bpf/btf.c
> is untouched and is the common one. The new comment in verifier.c encodes
> the same premise: "Once that prototype is marked unreliable it is known
> not to describe the compiled code" - but the flag also means merely "the
> verifier cannot express these argument types".
>
> Should the gate be restricted to cases where the return convention itself
> is untrustworthy, rather than whenever any argument cannot be expressed in
> BTF?
Good point. Let us remove this patch as it is too conservative.
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/31740414277
next prev parent reply other threads:[~2026-08-17 3:47 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-13 20:02 [PATCH bpf-next v5 00/11] bpf: Support aggregate return values up to 16 bytes Yonghong Song
2026-08-13 20:02 ` [PATCH bpf-next v5 01/11] bpf: Factor check_global_ret_scalar_reg() out of the global return check Yonghong Song
2026-08-14 22:24 ` Eduard Zingerman
2026-08-13 20:02 ` [PATCH bpf-next v5 02/11] bpf: Add helpers to describe the R0:R2 return register pair Yonghong Song
2026-08-13 21:11 ` bot+bpf-ci
2026-08-17 3:25 ` Yonghong Song
2026-08-14 22:43 ` Eduard Zingerman
2026-08-17 3:28 ` Yonghong Song
2026-08-13 20:02 ` [PATCH bpf-next v5 03/11] bpf: Wire up JIT support for 16-byte kfunc returns Yonghong Song
2026-08-14 1:20 ` sashiko-bot
2026-08-17 3:30 ` Yonghong Song
2026-08-13 20:02 ` [PATCH bpf-next v5 04/11] bpf: Track R2 of register-pair returns in precision backtracking Yonghong Song
2026-08-13 20:49 ` bot+bpf-ci
2026-08-17 3:32 ` Yonghong Song
2026-08-14 23:37 ` Eduard Zingerman
2026-08-17 3:33 ` Yonghong Song
2026-08-13 20:02 ` [PATCH bpf-next v5 05/11] bpf: Account R2 of register-pair returns in live register analysis Yonghong Song
2026-08-14 1:50 ` sashiko-bot
2026-08-17 3:35 ` Yonghong Song
2026-08-13 20:02 ` [PATCH bpf-next v5 06/11] bpf: Add verifier support for 16-byte returns in R0:R2 Yonghong Song
2026-08-13 21:11 ` bot+bpf-ci
2026-08-17 3:43 ` Yonghong Song
2026-08-14 2:26 ` sashiko-bot
2026-08-17 3:44 ` Yonghong Song
2026-08-14 23:53 ` Eduard Zingerman
2026-08-17 3:44 ` Yonghong Song
2026-08-13 20:02 ` [PATCH bpf-next v5 07/11] bpf: Enable aggregate return types up to 16 bytes Yonghong Song
2026-08-13 20:02 ` [PATCH bpf-next v5 08/11] bpf: Reject register-pair returns when the subprog BTF is unreliable Yonghong Song
2026-08-13 20:49 ` bot+bpf-ci
2026-08-17 3:47 ` Yonghong Song [this message]
2026-08-14 3:02 ` sashiko-bot
2026-08-17 3:49 ` Yonghong Song
2026-08-13 20:02 ` [PATCH bpf-next v5 09/11] selftests/bpf: Add C tests for 16-byte returns in R0:R2 Yonghong Song
2026-08-13 21:11 ` bot+bpf-ci
2026-08-17 3:50 ` Yonghong Song
2026-08-15 0:47 ` Eduard Zingerman
2026-08-17 3:53 ` Yonghong Song
2026-08-13 20:03 ` [PATCH bpf-next v5 10/11] selftests/bpf: Add inline-asm and subprog tests for R0:R2 returns Yonghong Song
2026-08-13 21:11 ` bot+bpf-ci
2026-08-17 3:59 ` Yonghong Song
2026-08-15 1:03 ` Eduard Zingerman
2026-08-17 4:00 ` Yonghong Song
2026-08-13 20:03 ` [PATCH bpf-next v5 11/11] Documentation/bpf: Document up to 16-byte kfunc return values in R0:R2 Yonghong Song
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ac9bda02-46ad-468f-86b8-11b781804849@linux.dev \
--to=yonghong.song@linux.dev \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bot+bpf-ci@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=clm@meta.com \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=ihor.solodrai@linux.dev \
--cc=kernel-team@fb.com \
--cc=martin.lau@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox