BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v5 0/2] bpf: Track iterator-owned BTF pointer lifetimes
@ 2026-09-20 21:04 Xu Yunxiang
  2026-09-20 21:04 ` [PATCH bpf-next v5 1/2] " Xu Yunxiang
  2026-09-20 21:04 ` [PATCH bpf-next v5 2/2] selftests/bpf: Test iterator " Xu Yunxiang
  0 siblings, 2 replies; 7+ messages in thread
From: Xu Yunxiang @ 2026-09-20 21:04 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, memxor, ameryhung, yonghong.song,
	puranjay

Some non-RCU struct iterator results borrow the current element's lifetime,
while others return objects that remain valid independently of the iterator.
Make current-element lifetime tracking explicit with KF_ITER_LIFETIME on the
next kfunc, and opt in task_vma, css_task, kmem_cache and dmabuf after checking
their native ownership and release paths.

Track the selected results and their fully trusted fields through the
iterator reference. Check child-resource cleanup and invalidate the previous
result before either next outcome. Use the common dynptr constructor's
backing lifetime while preserving clone and explicit-release ownership.
Unflagged struct iterators and RCU results retain their existing rules.

Changes in v5:
  - Add KF_ITER_LIFETIME opt-in, as requested by Alexei, instead of applying
    the current-element rule to every non-RCU struct iterator result.
  - Validate the flag on a NEXT method returning a strict struct pointer,
    without KF_ACQUIRE, and opt in the four audited native iterators.
  - Add a module iterator returning current, with positive verifier tests
    for using its independent-lifetime result after next and destroy.
  - Rebase onto bpf-next b99f71407ce5; retain the existing lifetime tests.

v4:
https://lore.kernel.org/r/20260917051948.1588826-1-xyx2021@mail.ustc.edu.cn
Review:
https://lore.kernel.org/r/DLHO7NCJVWJV.1K8VWK8LFIKGD@gmail.com

Validation on this exact candidate with a matching bpf_testmod:
  - W=1 verifier, full kernel/modules, changed BPF objects and test_progs
    builds passed.
  - iters: 1/111 passed; 0 skipped.
  - dynptr: 2/132 passed; 0 skipped.
  - file_reader: 1/8 passed; 0 skipped.
  - kmem_cache_iter: 1/3 passed; 0 skipped.
  - dmabuf_iter: 1/4 passed; 0 skipped.

No selected test failed. The VM ran with panic_on_warn and panic_on_oops;
no kernel WARN, Oops or panic was found.

Annotated verifier tests check load outcomes and diagnostics. The full
unfiltered suite, sanitizer configurations and architecture matrix were
not run.

Changes in v4:
  - Rebase onto bpf-next as requested by Amery Hung.
  - Keep update_ref_obj() and reg_is_referenced() unchanged; introduce
    reg_lifetime_id() for owned and borrowed source lifetimes.
  - Use the common constructor path instead of a FILE-only special case.
  - Capture the source lifetime before marking a BTF field destination.
  - Move next-time invalidation into process_iter_arg().
  - Share the child-reference leak check with release_reference(), while
    removing only the initially released ID from acquired references.
  - Retain the existing lifetime selftests on the new base.

v3:
https://lore.kernel.org/r/20260911084254.3481508-1-xyx2021@mail.ustc.edu.cn
Earlier review:
https://lore.kernel.org/r/CAMB2axMX07j49sZRmGFm2s==FMgKFzvKPWxc8hDvU=b3Lp=VOg@mail.gmail.com

Xu Yunxiang (2):
  bpf: Track iterator-owned BTF pointer lifetimes
  selftests/bpf: Test iterator BTF pointer lifetimes

 Documentation/bpf/bpf_iterators.rst           |   9 +
 include/linux/btf.h                           |   1 +
 kernel/bpf/btf.c                              |  12 +-
 kernel/bpf/helpers.c                          |  10 +-
 kernel/bpf/verifier.c                         |  88 ++++-
 .../selftests/bpf/progs/iters_testmod.c       | 370 ++++++++++++++++++
 .../selftests/bpf/test_kmods/bpf_testmod.c    |  24 ++
 .../selftests/bpf/test_kmods/bpf_testmod.h    |   4 +
 .../bpf/test_kmods/bpf_testmod_kfunc.h        |   5 +
 9 files changed, 503 insertions(+), 20 deletions(-)


base-commit: b99f71407ce529ba01a9392f477522d2e76c6613
-- 
2.43.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v5 1/2] bpf: Track iterator-owned BTF pointer lifetimes
  2026-09-20 21:04 [PATCH bpf-next v5 0/2] bpf: Track iterator-owned BTF pointer lifetimes Xu Yunxiang
@ 2026-09-20 21:04 ` Xu Yunxiang
  2026-09-20 21:21   ` sashiko-bot
                     ` (2 more replies)
  2026-09-20 21:04 ` [PATCH bpf-next v5 2/2] selftests/bpf: Test iterator " Xu Yunxiang
  1 sibling, 3 replies; 7+ messages in thread
From: Xu Yunxiang @ 2026-09-20 21:04 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, memxor, ameryhung, yonghong.song,
	puranjay

Some non-RCU struct iterator results borrow their lifetime from the
iterator's current element. Other iterators may return objects with an
independent lifetime, so make current-element lifetime tracking opt-in
through KF_ITER_LIFETIME on the next kfunc. Validate that the flag is used
with a borrowed struct pointer result.

Opt in task_vma, css_task, kmem_cache and dmabuf, whose next and destroy
operations can release the previous element or its backing references.
Associate their PTR_TO_BTF_ID results with the iterator reference and
invalidate the previous result before modeling either next outcome.
Destroy invalidates the result through reference release. Unflagged
iterators and RCU-protected results retain their existing rules.

Preserve that lifetime when loading a fully trusted BTF field. Capture the
source lifetime before marking the destination, as the registers can
alias. Keep RCU, user and percpu field results under their existing rules.

A borrowed field can also back a dynptr. For example, a FILE dynptr owns
reader state but borrows its file. Record the backing register's lifetime
in the common dynptr constructor path, using the acquired ID for an owned
reference and parent_id for a borrowed pointer. Keep clone handling and
the ownership meaning of reg_is_referenced() unchanged.

Reuse the existing child-reference leak check before advancing an iterator
and propagate destroy errors. Outstanding child resources must be
explicitly released before their backing lifetime ends. Preserve the
existing rule that only the initial ID is removed from acquired references
when release_reference() invalidates descendants.

This issue was found during BPF verifier testing with an in-house runtime
semantic checker.

Fixes: 4cbee026db54 ("bpf: return VMA snapshot from task_vma iterator")
Suggested-by: Amery Hung <ameryhung@gmail.com>
Assisted-by: LLM
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
 Documentation/bpf/bpf_iterators.rst |  9 +++
 include/linux/btf.h                 |  1 +
 kernel/bpf/btf.c                    | 12 +++-
 kernel/bpf/helpers.c                | 10 ++--
 kernel/bpf/verifier.c               | 88 ++++++++++++++++++++++++-----
 5 files changed, 100 insertions(+), 20 deletions(-)

diff --git a/Documentation/bpf/bpf_iterators.rst b/Documentation/bpf/bpf_iterators.rst
index 189e3ec1c6c8e..814e63296bee1 100644
--- a/Documentation/bpf/bpf_iterators.rst
+++ b/Documentation/bpf/bpf_iterators.rst
@@ -86,6 +86,15 @@ Constructor/next/destructor implementation contract is as follows:
     resources and marks stack space used by `struct bpf_iter_<type>` as usable
     for something else. Destructor is marked with KF_ITER_DESTROY flag.
 
+An iterator whose non-RCU struct result borrows the lifetime of its current
+element must additionally mark its next method with ``KF_ITER_LIFETIME``.
+The verifier invalidates the previous result and its trusted derived fields
+on the next call, including when that call returns NULL, and on destruction
+of the iterator. Resources borrowing that lifetime must be released first.
+The flag requires ``KF_ITER_NEXT`` and a struct pointer return type, and
+cannot be combined with ``KF_ACQUIRE``. It does not change RCU lifetime rules.
+Iterators whose results have an independent lifetime should omit this flag.
+
 Any open-coded BPF iterator implementation has to implement at least these
 three methods. It is enforced that for any given type of iterator only
 applicable constructor/destructor/next are callable. I.e., verifier ensures
diff --git a/include/linux/btf.h b/include/linux/btf.h
index ddd0f4f32d24b..9877f1455a0ef 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -80,6 +80,7 @@
 #define KF_ARENA_ARG2   (1 << 15) /* kfunc takes an arena pointer as its second argument */
 #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_ITER_LIFETIME (1 << 18) /* next result borrows the current element's lifetime */
 
 /*
  * Tag marking a kernel function as a kfunc. This is meant to minimize the
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 314ecb0e593b0..6a287f808200b 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8886,6 +8886,10 @@ static int btf_check_iter_kfuncs(struct btf *btf, const char *func_name,
 	if (!flags || (flags & (flags - 1)))
 		return -EINVAL;
 
+	if ((func_flags & KF_ITER_LIFETIME) &&
+	    (!(flags & KF_ITER_NEXT) || (func_flags & KF_ACQUIRE)))
+		return -EINVAL;
+
 	/* any BPF iter kfunc should have `struct bpf_iter_<type> *` first arg */
 	nr_args = btf_type_vlen(func);
 	if (nr_args < 1)
@@ -8926,6 +8930,12 @@ static int btf_check_iter_kfuncs(struct btf *btf, const char *func_name,
 		t = btf_type_skip_modifiers(btf, func->type, NULL);
 		if (!t || !btf_type_is_ptr(t))
 			return -EINVAL;
+		/* Lifetime-bound results are borrowed struct pointers. */
+		if (func_flags & KF_ITER_LIFETIME) {
+			t = btf_type_skip_modifiers(btf, t->type, NULL);
+			if (!t || !__btf_type_is_struct(t))
+				return -EINVAL;
+		}
 	}
 
 	if (flags & KF_ITER_DESTROY) {
@@ -8992,7 +9002,7 @@ static int btf_check_kfunc_protos(struct btf *btf, u32 func_id, u32 func_flags)
 	if (!func || !btf_type_is_func_proto(func))
 		return -EINVAL;
 
-	if (func_flags & (KF_ITER_NEW | KF_ITER_NEXT | KF_ITER_DESTROY)) {
+	if (func_flags & (KF_ITER_NEW | KF_ITER_NEXT | KF_ITER_DESTROY | KF_ITER_LIFETIME)) {
 		err = btf_check_iter_kfuncs(btf, func_name, func, func_flags);
 		if (err)
 			return err;
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 82402d97ce67c..0606ce279893c 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -5021,11 +5021,11 @@ BTF_ID_FLAGS(func, bpf_iter_num_new, KF_ITER_NEW | KF_SPINLOCK_SAFE)
 BTF_ID_FLAGS(func, bpf_iter_num_next, KF_ITER_NEXT | KF_RET_NULL | KF_SPINLOCK_SAFE)
 BTF_ID_FLAGS(func, bpf_iter_num_destroy, KF_ITER_DESTROY | KF_SPINLOCK_SAFE)
 BTF_ID_FLAGS(func, bpf_iter_task_vma_new, KF_ITER_NEW | KF_RCU)
-BTF_ID_FLAGS(func, bpf_iter_task_vma_next, KF_ITER_NEXT | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_iter_task_vma_next, KF_ITER_NEXT | KF_RET_NULL | KF_ITER_LIFETIME)
 BTF_ID_FLAGS(func, bpf_iter_task_vma_destroy, KF_ITER_DESTROY)
 #ifdef CONFIG_CGROUPS
 BTF_ID_FLAGS(func, bpf_iter_css_task_new, KF_ITER_NEW)
-BTF_ID_FLAGS(func, bpf_iter_css_task_next, KF_ITER_NEXT | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_iter_css_task_next, KF_ITER_NEXT | KF_RET_NULL | KF_ITER_LIFETIME)
 BTF_ID_FLAGS(func, bpf_iter_css_task_destroy, KF_ITER_DESTROY)
 BTF_ID_FLAGS(func, bpf_iter_css_new, KF_ITER_NEW | KF_RCU_PROTECTED)
 BTF_ID_FLAGS(func, bpf_iter_css_next, KF_ITER_NEXT | KF_RET_NULL)
@@ -5058,7 +5058,8 @@ BTF_ID_FLAGS(func, bpf_copy_from_user_mm_str, KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_get_kmem_cache)
 BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE)
-BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next,
+	     KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE | KF_ITER_LIFETIME)
 BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_local_irq_save)
 BTF_ID_FLAGS(func, bpf_local_irq_restore)
@@ -5074,7 +5075,8 @@ BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE)
 #endif
 #ifdef CONFIG_DMA_SHARED_BUFFER
 BTF_ID_FLAGS(func, bpf_iter_dmabuf_new, KF_ITER_NEW | KF_SLEEPABLE)
-BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_iter_dmabuf_next,
+	     KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE | KF_ITER_LIFETIME)
 BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
 #endif
 BTF_ID_FLAGS(func, __bpf_trap)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 18aad4886f9cf..4cf2010dcbbdd 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -209,6 +209,9 @@ 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 check_reference_children_leak(struct bpf_verifier_env *env, int parent_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);
@@ -753,8 +756,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;
@@ -1024,7 +1027,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)
@@ -1034,8 +1037,11 @@ static int unmark_stack_slots_iter(struct bpf_verifier_env *env,
 		struct bpf_stack_state *slot = &state->stack[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);
 
@@ -1577,6 +1583,12 @@ 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;
@@ -6224,9 +6236,14 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
 	}
 
 	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;
@@ -7824,6 +7841,27 @@ static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,
 	return btf_param_match_suffix(meta->btf, arg, "__iter");
 }
 
+static int invalidate_iter_owned_btf_ptrs(struct bpf_verifier_env *env, u32 parent_id)
+{
+	struct bpf_func_state *unused;
+	struct bpf_reg_state *reg;
+	int err;
+
+	err = check_reference_children_leak(env, parent_id);
+	if (err)
+		return err;
+
+	/* Opted-in iterators can release their previous element on next. */
+	bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
+		if (base_type(reg->type) != PTR_TO_BTF_ID || reg->parent_id != parent_id)
+			continue;
+		bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_REF_RELEASE);
+		mark_reg_invalid(env, reg);
+	}));
+
+	return 0;
+}
+
 static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno, int insn_idx,
 			    struct bpf_call_arg_meta *meta)
 {
@@ -7921,6 +7959,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, &state->stack[spi].spilled_ptr);
 
+		if ((meta->kfunc_flags & KF_ITER_LIFETIME) &&
+		    !(state->stack[spi].spilled_ptr.type & MEM_RCU)) {
+			err = invalidate_iter_owned_btf_ptrs(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)
@@ -10106,6 +10151,23 @@ static int idstack_pop(struct bpf_idmap *idmap)
 	return idmap->map[--idmap->cnt].old;
 }
 
+static int check_reference_children_leak(struct bpf_verifier_env *env, int parent_id)
+{
+	struct bpf_verifier_state *state = env->cur_state;
+	int i;
+
+	for (i = 0; i < state->acquired_refs; i++) {
+		if (state->refs[i].type != REF_TYPE_PTR ||
+		    state->refs[i].parent_id != parent_id)
+			continue;
+		verbose(env, "Leaking reference id=%d alloc_insn=%d. Release it first.\n",
+			state->refs[i].id, state->refs[i].insn_idx);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 /* Release id and objects derived from it iteratively in a DFS manner */
 static int release_reference(struct bpf_verifier_env *env, int id)
 {
@@ -10115,7 +10177,7 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 	struct bpf_stack_state *stack;
 	struct bpf_func_state *state;
 	struct bpf_reg_state *reg;
-	int i, err;
+	int err;
 
 	idstack->cnt = 0;
 	err = idstack_push(idstack, id);
@@ -10132,15 +10194,9 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 		 * Child references are inaccessible after parent is released,
 		 * any child references that exist at this point are a leak.
 		 */
-		for (i = 0; i < vstate->acquired_refs; i++) {
-			if (vstate->refs[i].type != REF_TYPE_PTR)
-				continue;
-			if (vstate->refs[i].parent_id != id)
-				continue;
-			verbose(env, "Leaking reference id=%d alloc_insn=%d. Release it first.\n",
-				vstate->refs[i].id, vstate->refs[i].insn_idx);
-			return -EINVAL;
-		}
+		err = check_reference_children_leak(env, id);
+		if (err)
+			return err;
 
 		bpf_for_each_reg_in_vstate_mask(vstate, state, reg, stack, mask, ({
 			if (reg->id != id && reg->parent_id != id)
@@ -14485,6 +14541,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 ((meta.kfunc_flags & KF_ITER_LIFETIME) && !(type & MEM_RCU))
+				regs[BPF_REG_0].parent_id = meta.ref_obj.id;
 		}
 
 		if (is_kfunc_ret_null(&meta)) {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH bpf-next v5 2/2] selftests/bpf: Test iterator BTF pointer lifetimes
  2026-09-20 21:04 [PATCH bpf-next v5 0/2] bpf: Track iterator-owned BTF pointer lifetimes Xu Yunxiang
  2026-09-20 21:04 ` [PATCH bpf-next v5 1/2] " Xu Yunxiang
@ 2026-09-20 21:04 ` Xu Yunxiang
  1 sibling, 0 replies; 7+ messages in thread
From: Xu Yunxiang @ 2026-09-20 21:04 UTC (permalink / raw)
  To: bpf
  Cc: ast, daniel, andrii, eddyz87, memxor, ameryhung, yonghong.song,
	puranjay

Add verifier coverage for iterator-owned BTF pointer lifetimes.
Cover current struct results, iterator transitions,
trusted fields and FILE dynptr cleanup obligations. Keep current-result,
explicit-discard and independent RCU lifetime controls.

These annotations check verifier outcomes; they do not assert runtime
execution of the newly added programs.

Add an unflagged module iterator that returns current, with a lifetime
independent of iterator storage. Check that its result remains accepted
after next and destroy to cover the opt-in boundary.

Assisted-by: LLM
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
 .../selftests/bpf/progs/iters_testmod.c       | 370 ++++++++++++++++++
 .../selftests/bpf/test_kmods/bpf_testmod.c    |  24 ++
 .../selftests/bpf/test_kmods/bpf_testmod.h    |   4 +
 .../bpf/test_kmods/bpf_testmod_kfunc.h        |   5 +
 4 files changed, 403 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/iters_testmod.c b/tools/testing/selftests/bpf/progs/iters_testmod.c
index f65cc9766633e..213ba375872fc 100644
--- a/tools/testing/selftests/bpf/progs/iters_testmod.c
+++ b/tools/testing/selftests/bpf/progs/iters_testmod.c
@@ -8,6 +8,44 @@
 
 char _license[] SEC("license") = "GPL";
 
+struct bpf_iter_testmod_current {
+	u64 __opaque[1];
+};
+
+SEC("raw_tp/sys_enter")
+__success
+int iter_independent_lifetime_after_next(const void *ctx)
+{
+	struct bpf_iter_testmod_current it;
+	struct task_struct *task;
+
+	bpf_iter_testmod_current_new(&it);
+	task = bpf_iter_testmod_current_next(&it);
+	if (!task)
+		goto out;
+
+	bpf_iter_testmod_current_next(&it);
+	bpf_kfunc_trusted_task_test(task);
+out:
+	bpf_iter_testmod_current_destroy(&it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__success
+int iter_independent_lifetime_after_destroy(const void *ctx)
+{
+	struct bpf_iter_testmod_current it;
+	struct task_struct *task;
+
+	bpf_iter_testmod_current_new(&it);
+	task = bpf_iter_testmod_current_next(&it);
+	bpf_iter_testmod_current_destroy(&it);
+	if (task)
+		bpf_kfunc_trusted_task_test(task);
+	return 0;
+}
+
 SEC("raw_tp/sys_enter")
 __success
 int iter_next_trusted(const void *ctx)
@@ -28,6 +66,298 @@ int iter_next_trusted(const void *ctx)
 	return 0;
 }
 
+SEC("raw_tp/sys_enter")
+__failure __msg("invalid mem access 'scalar'")
+int iter_next_trusted_after_destroy(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!vma_ptr)
+		goto out;
+
+	bpf_iter_task_vma_destroy(&vma_it);
+	return vma_ptr->vm_start;
+out:
+	bpf_iter_task_vma_destroy(&vma_it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("invalid mem access 'scalar'")
+int iter_next_trusted_after_next(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr, *next_vma_ptr;
+	u64 vm_start;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!vma_ptr)
+		goto out;
+
+	next_vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!next_vma_ptr)
+		goto out;
+
+	vm_start = vma_ptr->vm_start;
+	bpf_iter_task_vma_destroy(&vma_it);
+	return vm_start;
+out:
+	bpf_iter_task_vma_destroy(&vma_it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("invalid mem access 'scalar'")
+int iter_next_trusted_after_next_null(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr, *next_vma_ptr;
+	u64 vm_start;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!vma_ptr)
+		goto out;
+
+	next_vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (next_vma_ptr)
+		goto out;
+
+	vm_start = vma_ptr->vm_start;
+	bpf_iter_task_vma_destroy(&vma_it);
+	return vm_start;
+out:
+	bpf_iter_task_vma_destroy(&vma_it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("invalid mem access 'scalar'")
+int iter_next_trusted_field_after_destroy(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr;
+	struct file *file_ptr;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!vma_ptr)
+		goto out;
+
+	file_ptr = vma_ptr->vm_file;
+	bpf_iter_task_vma_destroy(&vma_it);
+	if (file_ptr)
+		return file_ptr->f_mode;
+	return 0;
+out:
+	bpf_iter_task_vma_destroy(&vma_it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("invalid mem access 'scalar'")
+int iter_next_trusted_field_after_next(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr, *next_vma_ptr;
+	struct file *file_ptr;
+	u32 mode;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!vma_ptr)
+		goto out;
+
+	file_ptr = vma_ptr->vm_file;
+	if (!file_ptr)
+		goto out;
+
+	next_vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!next_vma_ptr)
+		goto out;
+
+	mode = file_ptr->f_mode;
+	bpf_iter_task_vma_destroy(&vma_it);
+	return mode;
+out:
+	bpf_iter_task_vma_destroy(&vma_it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("Leaking reference id=")
+int iter_next_file_dynptr_after_next(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr;
+	struct bpf_dynptr dynptr;
+	struct file *file_ptr;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!vma_ptr)
+		goto out;
+
+	file_ptr = vma_ptr->vm_file;
+	if (!file_ptr)
+		goto out;
+
+	bpf_dynptr_from_file(file_ptr, 0, &dynptr);
+	bpf_iter_task_vma_next(&vma_it);
+	bpf_dynptr_file_discard(&dynptr);
+out:
+	bpf_iter_task_vma_destroy(&vma_it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__failure __msg("Leaking reference id=")
+int iter_next_file_dynptr_after_destroy(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr;
+	struct bpf_dynptr dynptr;
+	struct file *file_ptr;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!vma_ptr)
+		goto out;
+
+	file_ptr = vma_ptr->vm_file;
+	if (!file_ptr)
+		goto out;
+
+	bpf_dynptr_from_file(file_ptr, 0, &dynptr);
+	bpf_iter_task_vma_destroy(&vma_it);
+	bpf_dynptr_file_discard(&dynptr);
+	return 0;
+out:
+	bpf_iter_task_vma_destroy(&vma_it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__success
+int iter_next_file_dynptr_discard_before_advance(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr;
+	struct bpf_dynptr dynptr;
+	struct file *file_ptr;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!vma_ptr)
+		goto out;
+
+	file_ptr = vma_ptr->vm_file;
+	if (!file_ptr)
+		goto out;
+
+	bpf_dynptr_from_file(file_ptr, 0, &dynptr);
+	bpf_dynptr_file_discard(&dynptr);
+	bpf_iter_task_vma_next(&vma_it);
+out:
+	bpf_iter_task_vma_destroy(&vma_it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__success
+int iter_next_trusted_current_after_next(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (!vma_ptr)
+		goto out;
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (vma_ptr)
+		bpf_kfunc_trusted_vma_test(vma_ptr);
+out:
+	bpf_iter_task_vma_destroy(&vma_it);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__success
+int iter_next_trusted_rcu_field_after_destroy(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr;
+	struct mm_struct *mm_ptr;
+	struct file *file_ptr = NULL;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (vma_ptr) {
+		mm_ptr = vma_ptr->vm_mm;
+		/* exe_file has RCU protection independent of the iterator. */
+		if (mm_ptr)
+			file_ptr = mm_ptr->exe_file;
+	}
+
+	bpf_iter_task_vma_destroy(&vma_it);
+	if (file_ptr)
+		return file_ptr->f_mode;
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__success
+int iter_next_trusted_rcu_field_after_next(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task_vma vma_it;
+	struct vm_area_struct *vma_ptr;
+	struct mm_struct *mm_ptr;
+	struct file *file_ptr = NULL;
+	u32 mode = 0;
+
+	bpf_iter_task_vma_new(&vma_it, cur_task, 0);
+
+	vma_ptr = bpf_iter_task_vma_next(&vma_it);
+	if (vma_ptr) {
+		mm_ptr = vma_ptr->vm_mm;
+		if (mm_ptr)
+			file_ptr = mm_ptr->exe_file;
+	}
+
+	bpf_iter_task_vma_next(&vma_it);
+	if (file_ptr)
+		mode = file_ptr->f_mode;
+	bpf_iter_task_vma_destroy(&vma_it);
+	return mode;
+}
+
 SEC("raw_tp/sys_enter")
 __failure __msg("Possibly NULL pointer passed to trusted R1")
 int iter_next_trusted_or_null(const void *ctx)
@@ -66,6 +396,46 @@ int iter_next_rcu(const void *ctx)
 	return 0;
 }
 
+SEC("raw_tp/sys_enter")
+__success
+int iter_next_rcu_after_destroy(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task task_it;
+	struct task_struct *task_ptr;
+
+	bpf_iter_task_new(&task_it, cur_task, 0);
+
+	task_ptr = bpf_iter_task_next(&task_it);
+	bpf_iter_task_destroy(&task_it);
+	if (!task_ptr)
+		return 0;
+
+	bpf_kfunc_rcu_task_test(task_ptr);
+	return 0;
+}
+
+SEC("raw_tp/sys_enter")
+__success
+int iter_next_rcu_after_next(const void *ctx)
+{
+	struct task_struct *cur_task = bpf_get_current_task_btf();
+	struct bpf_iter_task task_it;
+	struct task_struct *task_ptr;
+
+	bpf_iter_task_new(&task_it, cur_task, 0);
+
+	task_ptr = bpf_iter_task_next(&task_it);
+	if (!task_ptr)
+		goto out;
+
+	bpf_iter_task_next(&task_it);
+	bpf_kfunc_rcu_task_test(task_ptr);
+out:
+	bpf_iter_task_destroy(&task_it);
+	return 0;
+}
+
 SEC("raw_tp/sys_enter")
 __failure __msg("Possibly NULL pointer passed to trusted R1")
 int iter_next_rcu_or_null(const void *ctx)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index fd2c0cdc91b18..2ecd3b6fccff5 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -243,6 +243,27 @@ __bpf_kfunc void bpf_iter_testmod_seq_destroy(struct bpf_iter_testmod_seq *it)
 	it->cnt = 0;
 }
 
+__bpf_kfunc int bpf_iter_testmod_current_new(struct bpf_iter_testmod_current *it)
+{
+	it->remaining = 2;
+	return 0;
+}
+
+__bpf_kfunc struct task_struct *
+bpf_iter_testmod_current_next(struct bpf_iter_testmod_current *it)
+{
+	if (!it->remaining)
+		return NULL;
+	it->remaining--;
+	/* current remains alive independently of the iterator. */
+	return current;
+}
+
+__bpf_kfunc void bpf_iter_testmod_current_destroy(struct bpf_iter_testmod_current *it)
+{
+	it->remaining = 0;
+}
+
 __bpf_kfunc void bpf_kfunc_common_test(void)
 {
 }
@@ -892,6 +913,9 @@ BTF_ID_FLAGS(func, bpf_iter_testmod_seq_new, KF_ITER_NEW)
 BTF_ID_FLAGS(func, bpf_iter_testmod_seq_next, KF_ITER_NEXT | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_iter_testmod_seq_destroy, KF_ITER_DESTROY)
 BTF_ID_FLAGS(func, bpf_iter_testmod_seq_value)
+BTF_ID_FLAGS(func, bpf_iter_testmod_current_new, KF_ITER_NEW)
+BTF_ID_FLAGS(func, bpf_iter_testmod_current_next, KF_ITER_NEXT | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_iter_testmod_current_destroy, KF_ITER_DESTROY)
 BTF_ID_FLAGS(func, bpf_kfunc_common_test)
 BTF_ID_FLAGS(func, bpf_kfunc_arena_arg_test)
 BTF_ID_FLAGS(func, bpf_kfunc_arena_cap_test)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
index 2b12f81bf7487..94a228e807582 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
@@ -31,6 +31,10 @@ struct bpf_iter_testmod_seq {
 	int cnt;
 };
 
+struct bpf_iter_testmod_current {
+	u64 remaining;
+};
+
 struct bpf_testmod_ops {
 	int (*test_1)(void);
 	void (*test_2)(int a, int b);
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
index d3696d5254c9e..ef8bff27e4c31 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -33,6 +33,11 @@ struct prog_test_ref_kfunc {
 #endif
 
 struct bpf_iter_testmod_seq;
+struct bpf_iter_testmod_current;
+
+int bpf_iter_testmod_current_new(struct bpf_iter_testmod_current *it) __ksym;
+struct task_struct *bpf_iter_testmod_current_next(struct bpf_iter_testmod_current *it) __ksym;
+void bpf_iter_testmod_current_destroy(struct bpf_iter_testmod_current *it) __ksym;
 
 struct prog_test_pass1 {
 	int x0;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next v5 1/2] bpf: Track iterator-owned BTF pointer lifetimes
  2026-09-20 21:04 ` [PATCH bpf-next v5 1/2] " Xu Yunxiang
@ 2026-09-20 21:21   ` sashiko-bot
  2026-09-22 23:13   ` Amery Hung
  2026-09-22 23:31   ` Alexei Starovoitov
  2 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-09-20 21:21 UTC (permalink / raw)
  To: Xu Yunxiang; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] bpf: Incomplete invalidation of iter-owned dynptrs and derived memory slices
--

commit 1d039be28498ad8b8b6d1373478f0903a6cc5f06
Author: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>

bpf: Track iterator-owned BTF pointer lifetimes

This commit opts specific iterators into tracking the element lifetime,
associating the PTR_TO_BTF_ID results with the iterator reference. It
invalidates the previous result before advancing the iterator or destroying
it.

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -7824,6 +7841,27 @@ static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,
>  	return btf_param_match_suffix(meta->btf, arg, "__iter");
>  }
>  
> +static int invalidate_iter_owned_btf_ptrs(struct bpf_verifier_env *env, u32 parent_id)
> +{
> +	struct bpf_func_state *unused;
> +	struct bpf_reg_state *reg;
> +	int err;
> +
> +	err = check_reference_children_leak(env, parent_id);
> +	if (err)
> +		return err;
> +
> +	/* Opted-in iterators can release their previous element on next. */
> +	bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
> +		if (base_type(reg->type) != PTR_TO_BTF_ID || reg->parent_id != parent_id)
> +			continue;
> +		bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_REF_RELEASE);
> +		mark_reg_invalid(env, reg);
> +	}));

[Severity: Medium]
Does this loop completely invalidate all derived pointers?

In mark_stack_slots_dynptr(), the lifetime of a dynptr is explicitly tied
to the iter-owned backing object using the parent ID:

/* All non-clone constructors take their backing object in R1. */
parent_id = reg_lifetime_id(env, &cur_regs(env)[BPF_REG_1]);

However, the invalidation logic here restricts its checks to PTR_TO_BTF_ID.
It ignores STACK_DYNPTR slots because bpf_for_each_reg_in_vstate expands
to use a mask of 1 << STACK_SPILL, skipping STACK_DYNPTR entirely.

It also misses PTR_TO_MEM slices because of the strict PTR_TO_BTF_ID
type check.

If a BPF program creates a non-referenced dynptr from an iter-owned object,
or creates a PTR_TO_MEM slice from it, will these pointers remain usable
after the iterator advances and frees the underlying element, leading to a
use-after-free?

> +
> +	return 0;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260920210432.345758-1-xyx2021@mail.ustc.edu.cn?part=1

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next v5 1/2] bpf: Track iterator-owned BTF pointer lifetimes
  2026-09-20 21:04 ` [PATCH bpf-next v5 1/2] " Xu Yunxiang
  2026-09-20 21:21   ` sashiko-bot
@ 2026-09-22 23:13   ` Amery Hung
  2026-09-22 23:30     ` Amery Hung
  2026-09-22 23:31   ` Alexei Starovoitov
  2 siblings, 1 reply; 7+ messages in thread
From: Amery Hung @ 2026-09-22 23:13 UTC (permalink / raw)
  To: Xu Yunxiang, memxor
  Cc: bpf, ast, daniel, andrii, eddyz87, yonghong.song, puranjay

> +++ b/include/linux/btf.h
> @@ -80,6 +80,7 @@
>  #define KF_ARENA_ARG2   (1 << 15) /* kfunc takes an arena pointer as its second argument */
>  #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_ITER_LIFETIME (1 << 18) /* next result borrows the current element's lifetime */

I would hardcode it using the special kfunc list instead of
introducing KF_ITER_LIFETIME.

>
>  /*
>   * Tag marking a kernel function as a kfunc. This is meant to minimize the
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index 314ecb0e593b0..6a287f808200b 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c

[...]

> @@ -6224,9 +6236,14 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
>         }
>
>         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;
>         }
>

CC Kumar, who might be also working on it and have a fix locally
(mentioned in [0]).

[0] https://lore.kernel.org/bpf/DLGYPZAL708V.3GFLANAPH2L8O@gmail.com/#t

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next v5 1/2] bpf: Track iterator-owned BTF pointer lifetimes
  2026-09-22 23:13   ` Amery Hung
@ 2026-09-22 23:30     ` Amery Hung
  0 siblings, 0 replies; 7+ messages in thread
From: Amery Hung @ 2026-09-22 23:30 UTC (permalink / raw)
  To: Xu Yunxiang, memxor
  Cc: bpf, ast, daniel, andrii, eddyz87, yonghong.song, puranjay

On Tue, Sep 22, 2026 at 4:13 PM Amery Hung <ameryhung@gmail.com> wrote:
>
> > +++ b/include/linux/btf.h
> > @@ -80,6 +80,7 @@
> >  #define KF_ARENA_ARG2   (1 << 15) /* kfunc takes an arena pointer as its second argument */
> >  #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_ITER_LIFETIME (1 << 18) /* next result borrows the current element's lifetime */
>
> I would hardcode it using the special kfunc list instead of
> introducing KF_ITER_LIFETIME.
>

No strong preference for special kfunc list over KF_XXX.

Let's do opt-out because that is the safe default behavior.

> >
> >  /*
> >   * Tag marking a kernel function as a kfunc. This is meant to minimize the
> > diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> > index 314ecb0e593b0..6a287f808200b 100644
> > --- a/kernel/bpf/btf.c
> > +++ b/kernel/bpf/btf.c
>
> [...]
>
> > @@ -6224,9 +6236,14 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
> >         }
> >
> >         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;
> >         }
> >
>
> CC Kumar, who might be also working on it and have a fix locally
> (mentioned in [0]).
>
> [0] https://lore.kernel.org/bpf/DLGYPZAL708V.3GFLANAPH2L8O@gmail.com/#t

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH bpf-next v5 1/2] bpf: Track iterator-owned BTF pointer lifetimes
  2026-09-20 21:04 ` [PATCH bpf-next v5 1/2] " Xu Yunxiang
  2026-09-20 21:21   ` sashiko-bot
  2026-09-22 23:13   ` Amery Hung
@ 2026-09-22 23:31   ` Alexei Starovoitov
  2 siblings, 0 replies; 7+ messages in thread
From: Alexei Starovoitov @ 2026-09-22 23:31 UTC (permalink / raw)
  To: Xu Yunxiang, bpf
  Cc: daniel, andrii, eddyz87, memxor, ameryhung, yonghong.song,
	puranjay

On Mon, Sep 21, 2026 at 05:04 AM Xu Yunxiang <xyx2021@mail.ustc.edu.cn> wrote:
>  BTF_ID_FLAGS(func, bpf_iter_task_vma_new, KF_ITER_NEW | KF_RCU)
> -BTF_ID_FLAGS(func, bpf_iter_task_vma_next, KF_ITER_NEXT | KF_RET_NULL)
> +BTF_ID_FLAGS(func, bpf_iter_task_vma_next, KF_ITER_NEXT | KF_RET_NULL | KF_ITER_LIFETIME)
>  BTF_ID_FLAGS(func, bpf_iter_task_vma_destroy, KF_ITER_DESTROY)
>  #ifdef CONFIG_CGROUPS
>  BTF_ID_FLAGS(func, bpf_iter_css_task_new, KF_ITER_NEW)
> -BTF_ID_FLAGS(func, bpf_iter_css_task_next, KF_ITER_NEXT | KF_RET_NULL)
> +BTF_ID_FLAGS(func, bpf_iter_css_task_next, KF_ITER_NEXT | KF_RET_NULL | KF_ITER_LIFETIME)

[...]

> -BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
> +BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next,
> +	     KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE | KF_ITER_LIFETIME)

[...]

> -BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
> +BTF_ID_FLAGS(func, bpf_iter_dmabuf_next,
> +	     KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE | KF_ITER_LIFETIME)

So all four iterators that return a non-rcu struct pointer got the
flag and the only one without it is the testmod iterator added in
patch 2. With opt-in a new iterator that forgets the flag gets the
same UAF.
I feel it's better to do opt-out.

pw-bot: cr

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-09-22 23:31 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-20 21:04 [PATCH bpf-next v5 0/2] bpf: Track iterator-owned BTF pointer lifetimes Xu Yunxiang
2026-09-20 21:04 ` [PATCH bpf-next v5 1/2] " Xu Yunxiang
2026-09-20 21:21   ` sashiko-bot
2026-09-22 23:13   ` Amery Hung
2026-09-22 23:30     ` Amery Hung
2026-09-22 23:31   ` Alexei Starovoitov
2026-09-20 21:04 ` [PATCH bpf-next v5 2/2] selftests/bpf: Test iterator " Xu Yunxiang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox