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; Signed-off-by: Yonghong Song --- kernel/bpf/verifier.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) 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; +} + static const char *subprog_name(const struct bpf_verifier_env *env, int subprog) { struct bpf_func_info *info; @@ -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; + } + /* * Track caller's total stack arg count (incoming + max outgoing). * This is needed so the JIT knows how much stack arg space to allocate. -- 2.53.0-Meta