From: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>
To: "Xu Yunxiang" <xyx2021@mail.ustc.edu.cn>, <bpf@vger.kernel.org>
Cc: <daniel@iogearbox.net>, <andrii@kernel.org>, <eddyz87@gmail.com>,
<memxor@gmail.com>, <ameryhung@gmail.com>,
<yonghong.song@linux.dev>, <puranjay@kernel.org>
Subject: Re: [PATCH bpf-next v7 1/2] bpf: Track borrowed iterator results by default
Date: Thu, 22 Oct 2026 03:42:01 +0000 [thread overview]
Message-ID: <DMB212RCKZ56.LTNAJWB2D39T@gmail.com> (raw)
In-Reply-To: <20261021210754.3270023-2-xyx2021@mail.ustc.edu.cn>
On Wed, Oct 21, 2026 at 02:07 PM Xu Yunxiang <xyx2021@mail.ustc.edu.cn> wrote:
> + if (iter_next_borrows_struct(meta) &&
> + !is_kfunc_rcu_protected(meta) &&
> + !(bpf_stack_slot(state, spi)->spilled_ptr.type & MEM_RCU)) {
> + err = invalidate_reference_dependents(env, meta->ref_obj.id);
check_kfunc_call() doesn't set parent_id when the result is MEM_RCU,
so nothing is tied to such iterator and there is nothing to invalidate.
No need to repeat the RCU checks here.
> +static int __release_reference(struct bpf_verifier_env *env, int id, bool release_root)
[...]
> + if (!release_root && id == root_id && reg->parent_id != id)
> + continue;
Drop this check. It's dead code.
The id of the iterator is only in its first STACK_ITER slot and the mask
doesn't include STACK_ITER, so no reg has the id of the iterator.
Then release_root only decides whether release_reference_nomark()
is called. No need for the bool.
I think it can be implemented a bit simpler:
diff --git a/include/linux/btf.h b/include/linux/btf.h
index b66e20a..a18254b 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -82,6 +82,7 @@
#define KF_IMPLICIT_ARGS (1 << 16) /* kfunc has implicit arguments supplied by the verifier */
#define KF_SPINLOCK_SAFE (1 << 17) /* kfunc is allowed inside bpf_spin_lock-ed region */
#define KF_PERFMON (1 << 18) /* kfunc requires CAP_PERFMON */
+#define KF_ITER_RET_INDEPENDENT (1 << 19) /* next result outlives the current element */
/*
* Tag marking a kernel function as a kfunc. This is meant to minimize the
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 5601d20..516f6b8 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -211,6 +211,8 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par
static int __release_reference_nomark(struct bpf_verifier_state *state, int id);
static int release_reference_nomark(struct bpf_verifier_env *env, int id);
static int release_reference(struct bpf_verifier_env *env, int id);
+static int invalidate_reference(struct bpf_verifier_env *env, int id);
+static u32 reg_lifetime_id(struct bpf_verifier_env *env, const struct bpf_reg_state *reg);
static void invalidate_non_owning_refs(struct bpf_verifier_env *env);
static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env);
static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env);
@@ -793,8 +795,8 @@ static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_
if (err)
return err;
- /* Track parent's id if the parent is a referenced object */
- parent_id = ref_obj->id;
+ /* All non-clone constructors take their backing object in R1. */
+ parent_id = reg_lifetime_id(env, &cur_regs(env)[BPF_REG_1]);
if (dynptr_type_referenced(type)) {
int id;
@@ -1074,7 +1076,7 @@ static int unmark_stack_slots_iter(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, int nr_slots)
{
struct bpf_func_state *state = bpf_func(env, reg);
- int spi, i, j;
+ int spi, i, j, err;
spi = iter_get_spi(env, reg, nr_slots);
if (spi < 0)
@@ -1084,8 +1086,11 @@ static int unmark_stack_slots_iter(struct bpf_verifier_env *env,
struct bpf_stack_state *slot = bpf_stack_slot(state, spi - i);
struct bpf_reg_state *st = &slot->spilled_ptr;
- if (i == 0)
- WARN_ON_ONCE(release_reference(env, st->id));
+ if (i == 0) {
+ err = release_reference(env, st->id);
+ if (err)
+ return err;
+ }
bpf_mark_reg_not_init(env, st);
@@ -1627,6 +1632,11 @@ static bool reg_is_referenced(struct bpf_verifier_env *env, const struct bpf_reg
return find_reference_state(env->cur_state, reg->id);
}
+static u32 reg_lifetime_id(struct bpf_verifier_env *env, const struct bpf_reg_state *reg)
+{
+ return reg_is_referenced(env, reg) ? reg->id : reg->parent_id;
+}
+
static int release_lock_state(struct bpf_verifier_env *env, int type, int id, void *ptr)
{
struct bpf_verifier_state *state = env->cur_state;
@@ -6848,9 +6858,14 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
flag &= ~PTR_MAYBE_NULL;
if (atype == BPF_READ && value_regno >= 0) {
+ u32 parent_id = reg_lifetime_id(env, reg);
+
ret = mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
if (ret < 0)
return ret;
+ if ((regs[value_regno].type & PTR_TRUSTED) &&
+ !(regs[value_regno].type & (MEM_RCU | MEM_PERCPU | MEM_USER)))
+ regs[value_regno].parent_id = parent_id;
}
return 0;
@@ -8636,6 +8651,21 @@ static bool is_iter_destroy_kfunc(struct bpf_call_arg_meta *meta)
return meta->kfunc_flags & KF_ITER_DESTROY;
}
+static bool iter_next_borrows_struct(struct bpf_call_arg_meta *meta)
+{
+ const struct btf_type *t;
+
+ if (!(meta->kfunc_flags & KF_ITER_NEXT) ||
+ (meta->kfunc_flags & (KF_ITER_RET_INDEPENDENT | KF_ACQUIRE)))
+ return false;
+
+ t = btf_type_skip_modifiers(meta->btf, meta->func_proto->type, NULL);
+ if (!t || !btf_type_is_ptr(t))
+ return false;
+ t = btf_type_skip_modifiers(meta->btf, t->type, NULL);
+ return t && __btf_type_is_struct(t);
+}
+
static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,
const struct btf_param *arg)
{
@@ -8746,6 +8776,13 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
meta->iter.frameno = reg->frameno;
update_ref_obj(&meta->ref_obj, &bpf_stack_slot(state, spi)->spilled_ptr);
+ /* The iterator stays. What was borrowed from the previous element goes. */
+ if (iter_next_borrows_struct(meta)) {
+ err = invalidate_reference(env, meta->ref_obj.id);
+ if (err)
+ return err;
+ }
+
if (is_iter_destroy_kfunc(meta)) {
err = unmark_stack_slots_iter(env, reg, nr_slots);
if (err)
@@ -11073,8 +11110,8 @@ static int idstack_pop(struct bpf_idmap *idmap)
return idmap->map[--idmap->cnt].old;
}
-/* Release id and objects derived from it iteratively in a DFS manner */
-static int release_reference(struct bpf_verifier_env *env, int id)
+/* Invalidate id and objects derived from it iteratively in a DFS manner */
+static int invalidate_reference(struct bpf_verifier_env *env, int id)
{
u32 mask = (1 << STACK_SPILL) | (1 << STACK_DYNPTR);
struct bpf_verifier_state *vstate = env->cur_state;
@@ -11089,11 +11126,6 @@ static int release_reference(struct bpf_verifier_env *env, int id)
if (err)
return err;
- if (find_reference_state(vstate, id)) {
- err = release_reference_nomark(env, id);
- WARN_ON_ONCE(err);
- }
-
while ((id = idstack_pop(idstack))) {
/*
* Child references are inaccessible after parent is released,
@@ -11145,6 +11177,14 @@ static int release_reference(struct bpf_verifier_env *env, int id)
return 0;
}
+static int release_reference(struct bpf_verifier_env *env, int id)
+{
+ if (find_reference_state(env->cur_state, id))
+ WARN_ON_ONCE(release_reference_nomark(env, id));
+
+ return invalidate_reference(env, id);
+}
+
static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
{
struct bpf_func_state *unused;
@@ -15822,6 +15862,8 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
regs[BPF_REG_0].btf = desc_btf;
regs[BPF_REG_0].type = type;
regs[BPF_REG_0].btf_id = ptr_type_id;
+ if (iter_next_borrows_struct(&meta) && !(type & MEM_RCU))
+ regs[BPF_REG_0].parent_id = meta.ref_obj.id;
}
if (is_kfunc_ret_null(&meta)) {
pw-bot: cr
next prev parent reply other threads:[~2026-10-22 3:42 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-21 21:07 [PATCH bpf-next v7 0/2] bpf: Track borrowed iterator result lifetimes Xu Yunxiang
2026-10-21 21:07 ` [PATCH bpf-next v7 1/2] bpf: Track borrowed iterator results by default Xu Yunxiang
2026-10-21 21:29 ` Amery Hung
2026-10-22 3:42 ` Alexei Starovoitov [this message]
2026-10-21 21:07 ` [PATCH bpf-next v7 2/2] selftests/bpf: Test borrowed iterator result lifetimes Xu Yunxiang
2026-10-21 21:31 ` Amery Hung
2026-10-23 15:43 ` [PATCH bpf-next v7 0/2] bpf: Track " Justin Suess
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=DMB212RCKZ56.LTNAJWB2D39T@gmail.com \
--to=alexei.starovoitov@gmail.com \
--cc=ameryhung@gmail.com \
--cc=andrii@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=memxor@gmail.com \
--cc=puranjay@kernel.org \
--cc=xyx2021@mail.ustc.edu.cn \
--cc=yonghong.song@linux.dev \
/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.