[PATCH bpf-next v3 08/13] bpf: Reject register-pair returns when the subprog BTF is unreliable

Yonghong Song <[email protected]>
Newsgroups org.kernel.vger.bpf
Message-ID <[email protected]>
The R0:R2 return convention is derived from the BTF function prototype:
bpf_compute_subprog_ret_regs() inspects the return type of every
subprogram and records whether its value comes back in a register pair.

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. From that point on the
verifier falls back to conservative, R0-only, semantics for the
subprogram, while the compiled code keeps returning a pair and leaves
the upper half in R2 behind the verifier's back.

Rather than silently mistracking R2, 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 at the two places that can
observe the flag: check_func_call(), for the call itself, and
prepare_func_exit(), for the return from an inlined static subprogram.

Note that the main program needs no such check: a >8 byte return from
subprog 0 is rejected at BPF_EXIT regardless of whether its BTF is
reliable. Callbacks need none either: a callback address only becomes a
PTR_TO_FUNC through check_ld_imm(), which already rejects any callback
returning more than 8 bytes.

Signed-off-by: Yonghong Song <[email protected]>
---
 kernel/bpf/verifier.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 41c47bcc3b0a..a01c8ecd9073 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -438,6 +438,21 @@ static void bpf_compute_subprog_ret_regs(struct bpf_verifier_env *env)
 	}
 }
 
+/*
+ * A >8 byte BPF return changes the calling convention to R0:R2, so the
+ * verifier can only allow it while the subprogram's prototype remains
+ * reliable. Once BTF is marked unreliable, reject the feature instead of
+ * silently falling back to R0-only semantics.
+ */
+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;
@@ -9459,6 +9474,11 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 	err = btf_check_subprog_call(env, subprog, caller->regs);
 	if (err == -EFAULT)
 		return err;
+	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;
+	}
 	if (bpf_subprog_is_global(env, subprog)) {
 		const char *sub_name = subprog_name(env, subprog);
 
@@ -9832,6 +9852,11 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
 
 	callee = state->frame[state->curframe];
 	r0 = &callee->regs[BPF_REG_0];
+	if (subprog_ret_pair_unreliable(env, callee->subprogno)) {
+		verbose(env, "Func#%d ('%s') returns >8 bytes, which requires reliable BTF\n",
+			callee->subprogno, subprog_name(env, callee->subprogno));
+		return -EINVAL;
+	}
 	nregs = bpf_ret_reg_pair(env, callee->subprogno) ? 2 : 1;
 	if (nregs > 1)
 		env->prog->jit_required = 1;
-- 
2.53.0-Meta
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.