BPF List
 help / color / mirror / Atom feed
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


  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