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 = ®s[insn->src_reg];
struct bpf_reg_state *dst = ®s[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
next prev 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox