BPF List
 help / color / mirror / Atom feed
From: Eduard Zingerman <eddyz87@gmail.com>
To: Kumar Kartikeya Dwivedi <memxor@gmail.com>, bpf@vger.kernel.org
Cc: Tejun Heo <tj@kernel.org>, Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Emil Tsalapatis	 <emil@etsalapatis.com>,
	kkd@meta.com, kernel-team@meta.com
Subject: Re: [PATCH bpf-next v3 2/9] bpf: Support __arena and __arena_nullable on struct_ops arguments
Date: Tue, 04 Aug 2026 22:14:41 -0700	[thread overview]
Message-ID: <0a7bc56a3753c9122ab421aa14d2daae08b08e30.camel@gmail.com> (raw)
In-Reply-To: <018d033499f61651ec8761fe0f7ac9662ef3c096.camel@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 3822 bytes --]

On Tue, 2026-08-04 at 16:55 -0700, Eduard Zingerman wrote:
> On Mon, 2026-08-03 at 14:51 +0200, Kumar Kartikeya Dwivedi wrote:
> 
> Lgtm except for several nits below.
> 
> ...
> 
> > diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
> 
> ...
> 
> > @@ -226,26 +229,30 @@ static int prepare_arg_info(struct btf *btf,
> >  	info = info_buf;
> >  	for (arg_no = 0; arg_no < nargs; arg_no++) {
> >  		/* Skip arguments that is not suffixed with
> > -		 * "__nullable or __ref".
> > +		 * "__nullable", "__ref", "__arena" or "__arena_nullable".
> >  		 */
> >  		is_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
> >  						     MAYBE_NULL_SUFFIX);
> >  		is_refcounted = btf_param_match_suffix(btf, &stub_args[arg_no],
> >  						       REFCOUNTED_SUFFIX);
> > +		is_arena_nullable = btf_param_match_suffix(btf, &stub_args[arg_no],
> > +							   ARENA_MAYBE_NULL_SUFFIX);
> > +		is_arena = btf_param_match_suffix(btf, &stub_args[arg_no], ARENA_SUFFIX);
> >  
> >  		if (is_nullable)
> >  			suffix = MAYBE_NULL_SUFFIX;
> >  		else if (is_refcounted)
> >  			suffix = REFCOUNTED_SUFFIX;
> > +		else if (is_arena_nullable)
> > +			suffix = ARENA_MAYBE_NULL_SUFFIX;
> > +		else if (is_arena)
> > +			suffix = ARENA_SUFFIX;
> 
> As discussed already, that's unfortunate that p__arena and p__nullable
> are valid, but p__arena__nullable is not, substituted by
> p__arena_nullable. Same goes for global subprogs.
> 
> >  		else
> >  			continue;
> >  
> > -		/* Should be a pointer to struct */
> > -		pointed_type = btf_type_resolve_ptr(btf,
> > -						    args[arg_no].type,
> > -						    &arg_btf_id);
> > -		if (!pointed_type ||
> > -		    !btf_type_is_struct(pointed_type)) {
> > +		/* Should be a pointer to struct, or any pointer for __arena/__arena_nullable */
> > +		pointed_type = btf_type_resolve_ptr(btf, args[arg_no].type, &arg_btf_id);
> > +		if (!pointed_type || (!is_arena && !is_arena_nullable && !btf_type_is_struct(pointed_type))) {
> >  			pr_warn("stub function %s has %s tagging to an unsupported type\n",
> >  				stub_fname, suffix);
> >  			goto err_out;
> 
> ...
> 
> > diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> > index ed7999ad6c66..d2d7a2904345 100644
> > --- a/kernel/bpf/trampoline.c
> > +++ b/kernel/bpf/trampoline.c
> 
> ...
> 
> > @@ -695,6 +743,22 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
> >  		goto out;
> >  	}
> >  
> > +	/*
> > +	 * Arena ctx args are converted only by the struct_ops indirect
> > +	 * trampoline, which dispatches to a single known prog. Generic
> > +	 * trampolines can mix progs with different arenas, so no conversion
> > +	 * is possible here. Not reachable today: only struct_ops progs get
> > +	 * arena ctx args and they never ride generic trampolines.
> > +	 */
> > +	for (kind = 0; kind < BPF_TRAMP_MAX; kind++) {
> > +		for (i = 0; i < tnodes[kind].nr_nodes; i++) {
> > +			if (bpf_prog_has_arena_ctx_arg(tnodes[kind].nodes[i]->link->prog)) {
> > +				err = -ENOTSUPP;
> > +				goto out;
> > +			}
> > +		}
> > +	}
> 
> Nit: should this be done in __bpf_trampoline_link_prog() or
>      bpf_trampoline_add_prog()? To avoid doing the same check on unlink.
>      Since this is an invariant violation, do we want to add WARN_ONCE?
> 
> > +
> >  	/* clear all bits except SHARE_IPMODIFY and TAIL_CALL_CTX */
> >  	tr->flags &= (BPF_TRAMP_F_SHARE_IPMODIFY | BPF_TRAMP_F_TAIL_CALL_CTX);
> >  
> 
> ...

Thinking a bit more about this. Since kfuncs already use flags in
btf_func_model, I think it would be nice to use them for struct_ops as
well. E.g. as in the patch attached (applied on top of this series).
Wdyt?

[-- Attachment #2: struct-ops-flags.diff --]
[-- Type: text/x-patch, Size: 9423 bytes --]

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 4b349ae35ebf..dcc68eb5632a 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -3071,12 +3071,12 @@ static void emit_arena_arg_conv(u8 **pprog, u32 src_reg, bool nullable, u32 base
 
 static void save_args(const struct btf_func_model *m, u8 **prog,
 		      int stack_size, bool for_call_origin, u32 flags,
-		      const struct bpf_tramp_arena_args *aargs)
+		      u64 arena_base)
 {
 	int arg_regs, first_off = 0, nr_regs = 0, nr_stack_slots = 0;
 	bool use_jmp = bpf_trampoline_use_jmp(flags);
 	int stack_args_off = (use_jmp || (flags & BPF_TRAMP_F_INDIRECT)) ? 16 : 24;
-	int i, j, slot = 0;
+	int i, j;
 
 	/* Store function arguments to stack.
 	 * For a function that accepts two pointers the sequence will be:
@@ -3084,6 +3084,9 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 	 * mov QWORD PTR [rbp-0x8],rsi
 	 */
 	for (i = 0; i < min_t(int, m->nr_args, MAX_BPF_FUNC_ARGS); i++) {
+		bool arena_arg = arena_base && (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG);
+		bool nullable = m->arg_flags[i] & BTF_FMODEL_NULLABLE_ARG;
+
 		arg_regs = (m->arg_size[i] + 7) / 8;
 
 		/* According to the research of Yonghong, struct members
@@ -3117,10 +3120,9 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 			for (j = 0; j < arg_regs; j++) {
 				emit_ldx(prog, BPF_DW, BPF_REG_0, BPF_REG_FP,
 					 nr_stack_slots * 8 + stack_args_off);
-				if (aargs && (aargs->slots & BIT(slot)))
-					emit_arena_arg_conv(prog, BPF_REG_0,
-							    aargs->nullable_slots & BIT(slot),
-							    (u32)aargs->kern_vm_start);
+				if (arena_arg)
+					emit_arena_arg_conv(prog, BPF_REG_0, nullable,
+							    (u32)arena_base);
 				emit_stx(prog, BPF_DW, BPF_REG_FP, BPF_REG_0,
 					 -stack_size);
 
@@ -3128,7 +3130,6 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 					first_off = stack_size;
 				stack_size -= 8;
 				nr_stack_slots++;
-				slot++;
 			}
 		} else {
 			/* Only copy the arguments on-stack to current
@@ -3137,7 +3138,6 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 			 */
 			if (for_call_origin) {
 				nr_regs += arg_regs;
-				slot += arg_regs;
 				continue;
 			}
 
@@ -3145,16 +3145,13 @@ static void save_args(const struct btf_func_model *m, u8 **prog,
 			for (j = 0; j < arg_regs; j++) {
 				u32 src = nr_regs == 5 ? X86_REG_R9 : BPF_REG_1 + nr_regs;
 
-				if (aargs && (aargs->slots & BIT(slot))) {
-					emit_arena_arg_conv(prog, src,
-							    aargs->nullable_slots & BIT(slot),
-							    (u32)aargs->kern_vm_start);
+				if (arena_arg) {
+					emit_arena_arg_conv(prog, src, nullable, (u32)arena_base);
 					src = BPF_REG_0;
 				}
 				emit_stx(prog, BPF_DW, BPF_REG_FP, src, -stack_size);
 				stack_size -= 8;
 				nr_regs++;
-				slot++;
 			}
 		}
 	}
@@ -3445,13 +3442,12 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	struct bpf_tramp_nodes *fentry = &tnodes[BPF_TRAMP_FENTRY];
 	struct bpf_tramp_nodes *fexit = &tnodes[BPF_TRAMP_FEXIT];
 	struct bpf_tramp_nodes *fmod_ret = &tnodes[BPF_TRAMP_MODIFY_RETURN];
-	struct bpf_tramp_arena_args aargs;
 	void *orig_call = func_addr;
 	int cookie_off, cookie_cnt;
 	u8 **branches = NULL;
+	u64 arena_base;
 	u64 func_meta;
 	u8 *prog;
-	bool has_aargs;
 	bool save_ret;
 
 	/*
@@ -3462,7 +3458,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 	WARN_ON_ONCE((flags & BPF_TRAMP_F_INDIRECT) &&
 		     (flags & ~(BPF_TRAMP_F_INDIRECT | BPF_TRAMP_F_RET_FENTRY_RET)));
 
-	has_aargs = bpf_tramp_collect_arena_args(tnodes, flags, &aargs);
+	arena_base = bpf_tramp_arena_base(m, tnodes, flags);
 
 	/* extra registers for struct arguments */
 	for (i = 0; i < m->nr_args; i++) {
@@ -3601,8 +3597,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 		emit_store_stack_imm64(&prog, BPF_REG_0, -ip_off, (long)func_addr);
 	}
 
-	save_args(m, &prog, regs_off, false, flags,
-		  has_aargs ? &aargs : NULL);
+	save_args(m, &prog, regs_off, false, flags, arena_base);
 
 	if (flags & BPF_TRAMP_F_CALL_ORIG) {
 		/* arg1: mov rdi, im */
@@ -3644,7 +3639,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
 
 	if (flags & BPF_TRAMP_F_CALL_ORIG) {
 		restore_regs(m, &prog, regs_off);
-		save_args(m, &prog, arg_stack_off, true, flags, NULL);
+		save_args(m, &prog, arg_stack_off, true, flags, 0);
 
 		if (flags & BPF_TRAMP_F_TAIL_CALL_CTX) {
 			/* Before calling the original function, load the
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index c839f039729a..a959efc3468f 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1293,18 +1293,13 @@ struct bpf_tramp_nodes {
 };
 
 /*
- * Which 8-byte ctx slots of a struct_ops trampoline hold arena kernel
- * pointers that save_args() converts to the arena pointer form,
- * ctx[slot] = (u32)(kaddr - kern_vm_start).
+ * The arena base against which a struct_ops trampoline converts the
+ * arguments marked with BTF_FMODEL_ARENA_ARG while saving them into the BPF
+ * ctx, ctx[arg] = (u32)(kaddr - kern_vm_start). Zero when the trampoline
+ * converts nothing.
  */
-struct bpf_tramp_arena_args {
-	u32 slots;
-	u32 nullable_slots;	/* subset of @slots where NULL is preserved */
-	u64 kern_vm_start;
-};
-
-bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,
-				  struct bpf_tramp_arena_args *aargs);
+u64 bpf_tramp_arena_base(const struct btf_func_model *m,
+			 struct bpf_tramp_nodes *tnodes, u32 flags);
 
 struct bpf_tramp_run_ctx;
 
@@ -1707,11 +1702,6 @@ struct bpf_ctx_arg_aux {
 	u32 btf_id;
 	u32 ref_id;
 	bool refcounted;
-	/*
-	 * We don't encode NULL-ness in the type for the program, but still need
-	 * to distinguish it for the purposes of telling JITs what sequence to emit.
-	 */
-	bool arena_nullable;
 };
 
 struct btf_mod_pair {
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 827a6216a620..c6a7c0213bb0 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -174,6 +174,7 @@ static int prepare_arg_info(struct btf *btf,
 			    const char *st_ops_name,
 			    const char *member_name,
 			    const struct btf_type *func_proto, void *stub_func_addr,
+			    struct btf_func_model *model,
 			    struct bpf_struct_ops_arg_info *arg_info)
 {
 	const struct btf_type *stub_func_proto, *pointed_type;
@@ -288,7 +289,9 @@ static int prepare_arg_info(struct btf *btf,
 			 * precision around it, since it has no safety implication.
 			 */
 			info->reg_type = PTR_TO_ARENA;
-			info->arena_nullable = is_arena_nullable;
+			model->arg_flags[arg_no] |= BTF_FMODEL_ARENA_ARG;
+			if (is_arena_nullable)
+				model->arg_flags[arg_no] |= BTF_FMODEL_NULLABLE_ARG;
 		}
 
 		info++;
@@ -476,6 +479,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
 		stub_func_addr = *(void **)(st_ops->cfi_stubs + moff);
 		err = prepare_arg_info(btf, st_ops->name, mname,
 				       func_proto, stub_func_addr,
+				       &st_ops->func_models[i],
 				       arg_info + i);
 		if (err)
 			goto errout;
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index d2d7a2904345..412a7d32e94a 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -540,40 +540,33 @@ static bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
 }
 
 /*
- * Collect which ctx slots of a struct_ops trampoline hold arena kernel
- * pointers that save_args() must convert to the arena pointer form. Only
- * the struct_ops indirect trampoline converts: it dispatches to a single
- * prog whose arena is known at generation time. Return false when there
- * is nothing to convert.
+ * The arena base against which save_args() converts the arguments marked
+ * with BTF_FMODEL_ARENA_ARG. Only the struct_ops indirect trampoline
+ * converts: it dispatches to a single prog whose arena is known at
+ * generation time. Return 0 when there is nothing to convert.
  */
-bool bpf_tramp_collect_arena_args(struct bpf_tramp_nodes *tnodes, u32 flags,
-				  struct bpf_tramp_arena_args *aargs)
+u64 bpf_tramp_arena_base(const struct btf_func_model *m,
+			 struct bpf_tramp_nodes *tnodes, u32 flags)
 {
 	const struct bpf_prog *prog;
 	int i;
 
-	memset(aargs, 0, sizeof(*aargs));
-
 	if (!(flags & BPF_TRAMP_F_INDIRECT) ||
 	    tnodes[BPF_TRAMP_FENTRY].nr_nodes != 1)
-		return false;
+		return 0;
 
-	prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
-	for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
-		const struct bpf_ctx_arg_aux *info = &prog->aux->ctx_arg_info[i];
+	for (i = 0; i < m->nr_args; i++)
+		if (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG)
+			break;
+	if (i == m->nr_args)
+		return 0;
 
-		if (base_type(info->reg_type) != PTR_TO_ARENA)
-			continue;
-		aargs->slots |= BIT(info->offset / 8);
-		if (info->arena_nullable)
-			aargs->nullable_slots |= BIT(info->offset / 8);
-	}
-	if (!aargs->slots)
-		return false;
+	/* Verification rejects an arena argument without an arena. */
+	prog = tnodes[BPF_TRAMP_FENTRY].nodes[0]->link->prog;
 	if (WARN_ON_ONCE(!prog->aux->arena))
-		return false;
-	aargs->kern_vm_start = bpf_arena_get_kern_vm_start(prog->aux->arena);
-	return true;
+		return 0;
+
+	return bpf_arena_get_kern_vm_start(prog->aux->arena);
 }
 
 static void bpf_tramp_image_free(struct bpf_tramp_image *im)

  reply	other threads:[~2026-08-05  5:14 UTC|newest]

Thread overview: 37+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-03 12:51 [PATCH bpf-next v3 0/9] Add arena argument support to kfuncs and struct_ops Kumar Kartikeya Dwivedi
2026-08-03 12:51 ` [PATCH bpf-next v3 1/9] bpf: Support __arena and __arena_nullable kfunc argument suffixes Kumar Kartikeya Dwivedi
2026-08-03 13:19   ` sashiko-bot
2026-08-04 17:42   ` Amery Hung
2026-08-05 15:53     ` Kumar Kartikeya Dwivedi
2026-08-05 17:07       ` Amery Hung
2026-08-05 17:16         ` Kumar Kartikeya Dwivedi
2026-08-05 15:55     ` Kumar Kartikeya Dwivedi
2026-08-03 12:51 ` [PATCH bpf-next v3 2/9] bpf: Support __arena and __arena_nullable on struct_ops arguments Kumar Kartikeya Dwivedi
2026-08-03 14:19   ` sashiko-bot
2026-08-04 23:55   ` Eduard Zingerman
2026-08-05  5:14     ` Eduard Zingerman [this message]
2026-08-05 17:31       ` Amery Hung
2026-08-05 17:36         ` Kumar Kartikeya Dwivedi
2026-08-03 12:51 ` [PATCH bpf-next v3 3/9] bpf, x86: JIT __arena kfunc argument rebasing Kumar Kartikeya Dwivedi
2026-08-05  0:40   ` Eduard Zingerman
2026-08-03 12:51 ` [PATCH bpf-next v3 4/9] bpf, x86: Convert struct_ops arena arguments in the trampoline Kumar Kartikeya Dwivedi
2026-08-03 12:51 ` [PATCH bpf-next v3 5/9] selftests/bpf: Add kfunc __arena and __arena_nullable argument tests Kumar Kartikeya Dwivedi
2026-08-03 13:35   ` sashiko-bot
2026-08-04 20:01   ` Eduard Zingerman
2026-08-04 20:14     ` Kumar Kartikeya Dwivedi
2026-08-04 20:24       ` Eduard Zingerman
2026-08-04 20:26         ` Eduard Zingerman
2026-08-04 20:28           ` Kumar Kartikeya Dwivedi
2026-08-04 20:33             ` Eduard Zingerman
2026-08-04 20:36               ` Eduard Zingerman
2026-08-03 12:51 ` [PATCH bpf-next v3 6/9] selftests/bpf: Add JIT-sequence tests for __arena kfunc arguments Kumar Kartikeya Dwivedi
2026-08-03 13:35   ` sashiko-bot
2026-08-05  5:43   ` Eduard Zingerman
2026-08-03 12:51 ` [PATCH bpf-next v3 7/9] selftests/bpf: Add struct_ops __arena and __arena_nullable argument tests Kumar Kartikeya Dwivedi
2026-08-03 13:39   ` sashiko-bot
2026-08-05  6:52   ` Eduard Zingerman
2026-08-03 12:51 ` [PATCH bpf-next v3 8/9] bpf, x86: Fix stack-passed arguments for indirect trampolines Kumar Kartikeya Dwivedi
2026-08-03 13:42   ` sashiko-bot
2026-08-05  8:00   ` Eduard Zingerman
2026-08-03 12:51 ` [PATCH bpf-next v3 9/9] selftests/bpf: Test stack-passed struct_ops arena arguments Kumar Kartikeya Dwivedi
2026-08-05  8:00   ` Eduard Zingerman

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=0a7bc56a3753c9122ab421aa14d2daae08b08e30.camel@gmail.com \
    --to=eddyz87@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=emil@etsalapatis.com \
    --cc=kernel-team@meta.com \
    --cc=kkd@meta.com \
    --cc=memxor@gmail.com \
    --cc=tj@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