All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Emil Tsalapatis <emil@etsalapatis.com>,
	kkd@meta.com, kernel-team@meta.com
Subject: [RFC PATCH bpf-next v1 10/16] bpf: Let typed_arena_cast copy pointers the verifier already trusts
Date: Sun, 27 Sep 2026 01:34:48 +0200	[thread overview]
Message-ID: <20260926233503.3114147-11-memxor@gmail.com> (raw)
In-Reply-To: <20260926233503.3114147-1-memxor@gmail.com>

The compiler inserts the cast at every use of a pointer to a struct with
special fields, so that programs never write one, and it cannot tell what
the pointer is: the same struct lives in typed arenas, in allocated objects,
in map values and on the stack. So the verifier decides per path what the
cast takes. A scalar, a raw arena pointer, a typed arena pointer loaded from
a field and not yet sanitized, one of another struct, or one that
arithmetic moved inside its object is sanitized as before. A sanitized
pointer of the struct's own typed arena at offset zero is already what the
cast produces, so it is copied, and since the sequence is a no-op on it,
such a path may share the instruction with sanitizing ones. Any other
verified pointer, to an allocated object, a map value, the stack or
anything else, is copied with its state, offset and references: the cast
has nothing to add to what the verifier knows, so it needs neither an arena
map nor a registration, and a program that only allocates objects of the
struct loads as before. The lowering is per instruction and runs on every
path through it, so a path that copies such a pointer cannot share the
instruction with one that sanitizes, since the sequence would mask a
pointer that is not in the slice; that program is rejected. An instruction
that only copied lowers to a move, or to nothing when dst is src, which
takes removing the instruction, as the nop removal pass has already run.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 kernel/bpf/fixups.c   | 35 ++++++++++++++++++++++--------
 kernel/bpf/verifier.c | 50 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 76 insertions(+), 9 deletions(-)

diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index fd0ba8376c1c..a0856bd18ac2 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -897,17 +897,34 @@ int bpf_lower_typed_arena_insns(struct bpf_verifier_env *env)
 	for (i = 0; i < insn_cnt; i++, insn++) {
 		aux = &env->insn_aux_data[i + delta];
 		cnt = 0;
-		if (aux->sanitize_needed)
-			cnt = bpf_typed_arena_cast_insns(aux->sanitize_arena, aux->sanitize_reg,
-							 aux->sanitize_reg, insn_buf);
 		if (insn_is_typed_arena_cast(insn)) {
-			if (!aux->typed_arena) {
-				verifier_bug(env, "typed_arena_cast at insn %d has no typed arena", i);
-				return -EFAULT;
+			/*
+			 * A cast that sanitized on some path lowers to the
+			 * sequence, a no-op on the paths that brought a sanitized
+			 * pointer of the same typed arena. One that only copied
+			 * verified pointers is a move, or nothing at all.
+			 */
+			if (aux->sanitize_needed) {
+				if (!aux->typed_arena) {
+					verifier_bug(env, "typed_arena_cast at insn %d has no typed arena", i);
+					return -EFAULT;
+				}
+				cnt = bpf_typed_arena_cast_insns(aux->typed_arena, insn->dst_reg,
+								 insn->src_reg, insn_buf);
+			} else if (insn->dst_reg != insn->src_reg) {
+				insn_buf[cnt++] = BPF_MOV64_REG(insn->dst_reg, insn->src_reg);
+			} else {
+				int err = verifier_remove_insns(env, i + delta, 1);
+
+				if (err)
+					return err;
+				delta--;
+				insn = env->prog->insnsi + i + delta;
+				continue;
 			}
-			cnt += bpf_typed_arena_cast_insns(aux->typed_arena, insn->dst_reg,
-							  insn->src_reg, insn_buf + cnt);
-		} else if (cnt) {
+		} else if (aux->sanitize_needed) {
+			cnt = bpf_typed_arena_cast_insns(aux->sanitize_arena, aux->sanitize_reg,
+							 aux->sanitize_reg, insn_buf);
 			insn_buf[cnt++] = *insn;
 		}
 		if (!cnt)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index b85d99f81ef5..aceb17e62948 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -17299,13 +17299,51 @@ static struct bpf_typed_arena *typed_arena_register(struct bpf_verifier_env *env
  * of the instruction, so an instruction casts to one type on every path, and
  * the lowering can be chosen once.
  */
+/*
+ * dst = typed_arena_cast(src, imm) makes a trusted pointer to an object of the
+ * struct imm names out of the value in src. What that takes depends on the
+ * value, and the compiler that inserts the cast at every use of a pointer to
+ * the struct cannot tell, since the same struct lives in typed arenas, in
+ * allocated objects, in map values and on the stack. So the verifier decides
+ * per path. A scalar, a raw arena pointer, or a typed arena pointer that is
+ * unsanitized, of another struct, or moved inside its object is sanitized:
+ * dst becomes a pointer to the start of an object of the struct's typed
+ * arena, and the lowering masks and rebases the value. A sanitized pointer of
+ * that typed arena at offset zero is already what the cast makes, so dst is a
+ * copy of src; the sequence is a no-op on it, which lets such a path share
+ * the instruction with sanitizing ones. Any other verified pointer, to an
+ * allocated object, a map value, the stack, or anything else, is copied as it
+ * is, with its state, offset and references: the cast has nothing to add to
+ * what the verifier already knows, and needs no arena and no registration. A
+ * typed arena pointer that may be NULL, an allocation's result, is one of
+ * these: sanitizing it would turn a failed allocation into object 0, so it is
+ * copied and keeps its check.
+ *
+ * The lowering is per instruction and runs on every path through it, so a
+ * path that copies a verified pointer of another kind cannot share the
+ * instruction with a path that sanitizes: the sequence would mask a pointer
+ * that is not in the slice. Such a program is rejected.
+ */
 static int check_typed_arena_cast(struct bpf_verifier_env *env, struct bpf_insn *insn)
 {
 	struct bpf_insn_aux_data *aux = &env->insn_aux_data[env->insn_idx];
 	struct bpf_reg_state *regs = cur_regs(env);
+	struct bpf_reg_state *src = &regs[insn->src_reg];
 	struct bpf_reg_state *dst = &regs[insn->dst_reg];
 	struct bpf_typed_arena *ta;
 
+	if (src->type != SCALAR_VALUE && base_type(src->type) != PTR_TO_ARENA &&
+	    (!type_is_typed_arena_obj(src->type) || type_flag(src->type) & PTR_MAYBE_NULL)) {
+		if (aux->sanitize_needed) {
+			verbose(env, "insn %d casts values that need different treatment on different paths\n",
+				env->insn_idx);
+			return -EINVAL;
+		}
+		aux->sanitize_plain |= BIT(insn->src_reg);
+		*dst = *src;
+		return 0;
+	}
+
 	/* The arena itself needs CAP_PERFMON, so the leak rules already permit a kernel pointer. */
 	if (!env->prog->aux->arena) {
 		verbose(env, "typed_arena_cast insn can only be used in a program that has an associated arena\n");
@@ -17320,6 +17358,18 @@ static int check_typed_arena_cast(struct bpf_verifier_env *env, struct bpf_insn
 		return PTR_ERR(ta);
 	aux->typed_arena = ta;
 
+	if (src->type == (PTR_TO_BTF_ID | MEM_ARENA) && src->btf_id == ta->btf_id &&
+	    tnum_is_const(src->var_off) && !src->var_off.value) {
+		*dst = *src;
+		return 0;
+	}
+	if (aux->sanitize_plain) {
+		verbose(env, "insn %d casts values that need different treatment on different paths\n",
+			env->insn_idx);
+		return -EINVAL;
+	}
+	aux->sanitize_needed = true;
+
 	mark_reg_known_zero(env, regs, insn->dst_reg);
 	dst->type = PTR_TO_BTF_ID | MEM_ARENA;
 	dst->btf = env->prog->aux->btf;
-- 
2.53.0


  parent reply	other threads:[~2026-09-26 23:35 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-26 23:34 [RFC PATCH bpf-next v1 00/16] BPF typed arenas Kumar Kartikeya Dwivedi
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 01/16] mm/vmalloc: Add get_vm_area_align() Kumar Kartikeya Dwivedi
2026-09-26 23:42   ` sashiko-bot
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 02/16] bpf: Introduce BPF typed arenas Kumar Kartikeya Dwivedi
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 03/16] bpf: Back typed arena chunks with scratch on demand Kumar Kartikeya Dwivedi
2026-09-26 23:56   ` sashiko-bot
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 04/16] bpf: Add the typed_arena_cast instruction Kumar Kartikeya Dwivedi
2026-09-26 23:55   ` sashiko-bot
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 05/16] bpf: Allow scalar and atomic access to typed arena objects Kumar Kartikeya Dwivedi
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 06/16] bpf: Support special fields in " Kumar Kartikeya Dwivedi
2026-09-26 23:59   ` sashiko-bot
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 07/16] bpf: Trust typed pointer fields of " Kumar Kartikeya Dwivedi
2026-09-27  0:03   ` sashiko-bot
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 08/16] bpf: Canonicalize loaded typed arena pointers where they are used Kumar Kartikeya Dwivedi
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 09/16] bpf: Add typed arena page allocation and release kfuncs Kumar Kartikeya Dwivedi
2026-09-26 23:55   ` sashiko-bot
2026-09-26 23:34 ` Kumar Kartikeya Dwivedi [this message]
2026-09-26 23:49   ` [RFC PATCH bpf-next v1 10/16] bpf: Let typed_arena_cast copy pointers the verifier already trusts sashiko-bot
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 11/16] libbpf: Support the typed_arena_cast instruction Kumar Kartikeya Dwivedi
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 12/16] selftests/bpf: Build BPF objects with compiler-inserted typed arena casts Kumar Kartikeya Dwivedi
2026-09-26 23:46   ` sashiko-bot
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 13/16] selftests/bpf: Test typed arena casts and registration Kumar Kartikeya Dwivedi
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 14/16] selftests/bpf: Test typed arena object access, kptrs and typed pointer fields Kumar Kartikeya Dwivedi
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 15/16] selftests/bpf: Test typed arena page allocation and release Kumar Kartikeya Dwivedi
2026-09-26 23:46   ` sashiko-bot
2026-09-26 23:34 ` [RFC PATCH bpf-next v1 16/16] selftests/bpf: Exercise typed arenas at run time Kumar Kartikeya Dwivedi
2026-09-26 23:50   ` sashiko-bot

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=20260926233503.3114147-11-memxor@gmail.com \
    --to=memxor@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=emil@etsalapatis.com \
    --cc=kernel-team@meta.com \
    --cc=kkd@meta.com \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.