* [PATCH bpf v3 0/2] bpf: Track iterator-owned BTF pointer lifetime
@ 2026-09-11 8:42 Xu Yunxiang
2026-09-11 8:42 ` [PATCH bpf v3 1/2] bpf: Invalidate iterator-owned BTF pointers on next and destroy Xu Yunxiang
2026-09-11 8:42 ` [PATCH bpf v3 2/2] selftests/bpf: Test iterator BTF pointer lifetimes Xu Yunxiang
0 siblings, 2 replies; 4+ messages in thread
From: Xu Yunxiang @ 2026-09-11 8:42 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Amery Hung,
Yonghong Song, Puranjay Mohan
bpf_iter_task_vma_next() returns a pointer to an iterator-owned VMA
snapshot. The next call resets that snapshot and drops the previous
snapshot's vm_file reference before filling it with another VMA. Destroying
the iterator frees the snapshot allocation. The verifier does not associate
the PTR_TO_BTF_ID result with either lifetime transition, so it permits old
direct and vm_file-derived pointers to remain usable.
Other non-RCU struct iterators also release or replace their previous
element when next advances. PTR_TO_MEM iterators already associate their
results with the iterator reference, but struct pointer returns take a
separate PTR_TO_BTF_ID path which omits this relationship.
Patch 1 associates non-RCU PTR_TO_BTF_ID next results with the iterator
reference. Before each next call creates a new result, it invalidates
older BTF pointers carrying that parent. Destroying the iterator uses the
existing reference-release path to invalidate the current result.
Pointer-valued BTF field loads preserve the parent only while the result
remains fully trusted and tied to its source; MEM_RCU, MEM_PERCPU, MEM_USER,
and untrusted fields retain their separate lifetime or access-domain rules.
A borrowed vm_file can also back a FILE dynptr, which does not take its own
file reference. Patch 1 carries the iterator lifetime anchor into that
dynptr and rejects next or destroy until the resource child is discarded.
Patch 2 adds task VMA verifier tests for destroy and for ACTIVE and DRAINED
next outcomes. It covers direct and vm_file-derived pointers, a FILE dynptr
across next and destroy, explicit dynptr discard, the current next result,
and independent MEM_RCU controls.
With the final 109-subtest payload, the baseline passes 102 expected
subtests while seven expected-failure programs load. V2 passes 104 while
five load. The final v3 passes all 109. The current-result, MEM_RCU, and
explicit-discard controls pass on every tested kernel.
The external triplet also behaves as expected on final v3: FRESH is
accepted and executes, STALE is rejected, and destroy-then-reacquire is
accepted and executes. Each probe invocation exits zero.
The issue is reachable by privileged BPF programs. This series is submitted
as a verifier correctness fix.
Please queue the verifier fix for stable after it reaches the BPF tree.
Tests:
- W=1 make O=<build> kernel/bpf/verifier.o
- make O=<build> -j12 bzImage modules
- strict clang-20 build of iters_testmod.bpf.o
- test_progs -t iters -v --watchdog-timeout 180 --workers=1
- baseline/v2/v3 verifier differential
- external live, stale, and destroy-then-reacquire load/test-run
differential on final v3
The full unfiltered BPF selftest suite was not run.
Changes in v3:
- Invalidate the previous non-RCU BTF result and its trusted descendants
before modeling every next outcome, as raised in review of v2.
- Carry a borrowed vm_file lifetime into FILE dynptr ancestry and reject
iterator transitions until the dynptr is discarded.
- Add separate ACTIVE and DRAINED next-call stale-pointer tests, with
complete iterator cleanup on otherwise valid paths.
- Add FILE dynptr next/destroy failures and an explicit-discard control.
- Add current-result and across-next MEM_RCU controls.
- Rebase from 15e2565f1c43 to e4a62833adff.
v2: https://lore.kernel.org/r/20260910044021.2279628-1-xyx2021@mail.ustc.edu.cn
v1: https://lore.kernel.org/r/20260909042907.1734153-1-xyx2021@mail.ustc.edu.cn
Xu Yunxiang (2):
bpf: Invalidate iterator-owned BTF pointers on next and destroy
selftests/bpf: Test iterator BTF pointer lifetimes
include/linux/bpf_verifier.h | 6 +-
kernel/bpf/verifier.c | 110 ++++--
.../selftests/bpf/progs/iters_testmod.c | 332 ++++++++++++++++++
3 files changed, 424 insertions(+), 24 deletions(-)
base-commit: e4a62833adff6ef0fe7c0b90393204fe3c26b5c5
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH bpf v3 1/2] bpf: Invalidate iterator-owned BTF pointers on next and destroy
2026-09-11 8:42 [PATCH bpf v3 0/2] bpf: Track iterator-owned BTF pointer lifetime Xu Yunxiang
@ 2026-09-11 8:42 ` Xu Yunxiang
2026-09-12 3:34 ` Alexei Starovoitov
2026-09-11 8:42 ` [PATCH bpf v3 2/2] selftests/bpf: Test iterator BTF pointer lifetimes Xu Yunxiang
1 sibling, 1 reply; 4+ messages in thread
From: Xu Yunxiang @ 2026-09-11 8:42 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Amery Hung,
Yonghong Song, Puranjay Mohan
Open-coded iterator next kfuncs that return non-struct pointers associate
the returned PTR_TO_MEM with the iterator's reference ID. Struct pointer
returns take a separate PTR_TO_BTF_ID path which omits this relationship.
This leaves such pointers verifier-valid after their lifetime ends. The
task_vma iterator resets its VMA snapshot and drops the previous vm_file
reference when next advances, then frees the snapshot on destroy. Other
non-RCU struct iterators also release or replace their previous element on
advance.
A trusted field derived from an iterator result can also back a
resource-tracked object. In particular, bpf_dynptr_from_file() does not
take its own file reference. Losing vm_file's iterator parent while
constructing a FILE dynptr therefore lets the dynptr outlive the snapshot's
file reference.
Associate non-RCU PTR_TO_BTF_ID results with the iterator reference. Before
modeling another next result, invalidate older BTF pointers carrying that
parent. Do this before constructing R0 and for both the ACTIVE and DRAINED
iterator branches. The existing iterator reference release invalidates the
current result on destroy.
Preserve the parent across BTF field loads when the result remains fully
trusted and its lifetime is tied to the parent. Do not propagate it to
MEM_RCU, MEM_PERCPU, MEM_USER, or untrusted results, whose lifetime or
access domain follows separate rules.
When bpf_dynptr_from_file() consumes a borrowed file carrying such a
parent, use that parent as the FILE dynptr's lifetime anchor. Reject
iterator next or destroy while a resource child remains live, requiring
the program to discard the dynptr before its backing file can be released.
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>
Link: https://lore.kernel.org/bpf/CAMB2axMV4rZ_Mb91OdzGH6T4Yym7nB+wz+EtxP_wUEniwA+EzA@mail.gmail.com
Assisted-by: LLM
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
include/linux/bpf_verifier.h | 6 +-
kernel/bpf/verifier.c | 110 ++++++++++++++++++++++++++++-------
2 files changed, 92 insertions(+), 24 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 36b65797877d0..ae30df629d5b3 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1518,9 +1518,9 @@ struct bpf_dynptr_desc {
/*
* The last seen rereferenced object; Updated by update_ref_obj() when a register refers to a
- * referenced object. Used when the helper or kfunc is casting a referenced object, returning
- * allocated memory derived from referenced object or creating a dynptr with a referenced
- * object as parent.
+ * referenced object or another verifier-tracked lifetime anchor. Used when the helper or kfunc
+ * is casting a referenced object, returning allocated memory derived from a referenced object
+ * or creating a dynptr whose backing object has a tracked lifetime.
*/
struct ref_obj_desc {
u32 id;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 72a3f5998dd27..eb013954182f2 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -238,13 +238,18 @@ static void bpf_map_key_store(struct bpf_insn_aux_data *aux, u64 state)
(poisoned ? BPF_MAP_KEY_POISON : 0ULL);
}
-static void update_ref_obj(struct ref_obj_desc *ref_obj, struct bpf_reg_state *reg)
+static void update_ref_obj_id(struct ref_obj_desc *ref_obj, u32 id, u32 parent_id)
{
- ref_obj->id = reg->id;
- ref_obj->parent_id = reg->parent_id;
+ ref_obj->id = id;
+ ref_obj->parent_id = parent_id;
ref_obj->cnt++;
}
+static void update_ref_obj(struct ref_obj_desc *ref_obj, struct bpf_reg_state *reg)
+{
+ update_ref_obj_id(ref_obj, reg->id, reg->parent_id);
+}
+
static int validate_ref_obj(struct bpf_verifier_env *env, struct ref_obj_desc *ref_obj)
{
if (ref_obj->cnt > 1) {
@@ -977,7 +982,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)
@@ -987,8 +992,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);
@@ -6140,9 +6148,16 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
}
if (atype == BPF_READ && value_regno >= 0) {
- ret = mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
+ enum bpf_reg_type reg_type = ret;
+ u32 parent_id = reg->parent_id;
+
+ ret = mark_btf_ld_reg(env, regs, value_regno, reg_type,
+ 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;
@@ -9456,6 +9471,24 @@ static int idstack_pop(struct bpf_idmap *idmap)
return idmap->map[--idmap->cnt].old;
}
+static int check_reference_children(struct bpf_verifier_env *env, int parent_id)
+{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ int i;
+
+ for (i = 0; i < vstate->acquired_refs; i++) {
+ if (vstate->refs[i].type != REF_TYPE_PTR)
+ continue;
+ if (vstate->refs[i].parent_id != parent_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;
+ }
+
+ 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)
{
@@ -9465,31 +9498,25 @@ 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);
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,
* 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(env, id);
+ if (err)
+ return err;
+
+ if (find_reference_state(vstate, id)) {
+ err = release_reference_nomark(env, id);
+ WARN_ON_ONCE(err);
}
bpf_for_each_reg_in_vstate_mask(vstate, state, reg, stack, mask, ({
@@ -9541,6 +9568,33 @@ static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
}));
}
+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;
+
+ if (WARN_ON_ONCE(!parent_id))
+ return -EFAULT;
+
+ err = check_reference_children(env, parent_id);
+ if (err)
+ return err;
+
+ /*
+ * Struct iterators can release the previous element on each next call.
+ * PTR_TO_MEM iterator results use storage that remains valid until destroy.
+ */
+ 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 void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)
{
struct bpf_stack_state *stack;
@@ -13100,6 +13154,10 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i, argno);
if (ret < 0)
return ret;
+ if (meta->btf == btf_vmlinux &&
+ meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_file] &&
+ i == 0 && !reg_is_referenced(env, reg) && reg->parent_id)
+ update_ref_obj_id(&meta->ref_obj, reg->parent_id, 0);
break;
}
@@ -13995,6 +14053,13 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
}
}
+ if (bpf_is_iter_next_kfunc(&meta) &&
+ !(get_iter_from_state(env->cur_state, &meta)->type & MEM_RCU)) {
+ err = invalidate_iter_owned_btf_ptrs(env, meta.ref_obj.id);
+ if (err)
+ return err;
+ }
+
bpf_diag_record_caller_saved(env, regs);
bpf_diag_mod_begin(env, ®s[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
@@ -14105,6 +14170,9 @@ 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 (bpf_is_iter_next_kfunc(&meta) && !(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] 4+ messages in thread
* [PATCH bpf v3 2/2] selftests/bpf: Test iterator BTF pointer lifetimes
2026-09-11 8:42 [PATCH bpf v3 0/2] bpf: Track iterator-owned BTF pointer lifetime Xu Yunxiang
2026-09-11 8:42 ` [PATCH bpf v3 1/2] bpf: Invalidate iterator-owned BTF pointers on next and destroy Xu Yunxiang
@ 2026-09-11 8:42 ` Xu Yunxiang
1 sibling, 0 replies; 4+ messages in thread
From: Xu Yunxiang @ 2026-09-11 8:42 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Amery Hung,
Yonghong Song, Puranjay Mohan
Add verifier coverage for struct pointers returned by open-coded iterator
next kfuncs. Verify that the pointer is trusted for kfunc arguments while
the iterator element is current, then rejected after either next or
destroy. Cover both non-NULL and NULL next results.
Exercise a trusted vm_file field as well as the direct VMA pointer. Verify
that a FILE dynptr derived from vm_file must be discarded before iterator
next or destroy, and that advancing after an explicit discard remains
accepted. Keep a MEM_RCU field reachable through the snapshot's vm_mm field
valid across iterator transitions because its lifetime is independent of
the iterator.
Also cover the existing RCU iterator behavior to make sure it retains its
current lifetime and trust semantics.
Assisted-by: LLM
Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
---
.../selftests/bpf/progs/iters_testmod.c | 332 ++++++++++++++++++
1 file changed, 332 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/iters_testmod.c b/tools/testing/selftests/bpf/progs/iters_testmod.c
index 76012dbbdb413..a55e5cf29474a 100644
--- a/tools/testing/selftests/bpf/progs/iters_testmod.c
+++ b/tools/testing/selftests/bpf/progs/iters_testmod.c
@@ -28,6 +28,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 +358,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)
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH bpf v3 1/2] bpf: Invalidate iterator-owned BTF pointers on next and destroy
2026-09-11 8:42 ` [PATCH bpf v3 1/2] bpf: Invalidate iterator-owned BTF pointers on next and destroy Xu Yunxiang
@ 2026-09-12 3:34 ` Alexei Starovoitov
0 siblings, 0 replies; 4+ messages in thread
From: Alexei Starovoitov @ 2026-09-12 3:34 UTC (permalink / raw)
To: Xu Yunxiang, bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Amery Hung,
Yonghong Song, Puranjay Mohan
On Fri Sep 11, 2026 at 1:42 AM PDT, Xu Yunxiang wrote:
> Open-coded iterator next kfuncs that return non-struct pointers associate
> the returned PTR_TO_MEM with the iterator's reference ID. Struct pointer
> returns take a separate PTR_TO_BTF_ID path which omits this relationship.
>
> This leaves such pointers verifier-valid after their lifetime ends. The
> task_vma iterator resets its VMA snapshot and drops the previous vm_file
> reference when next advances, then frees the snapshot on destroy. Other
> non-RCU struct iterators also release or replace their previous element on
> advance.
>
> A trusted field derived from an iterator result can also back a
> resource-tracked object. In particular, bpf_dynptr_from_file() does not
> take its own file reference. Losing vm_file's iterator parent while
> constructing a FILE dynptr therefore lets the dynptr outlive the snapshot's
> file reference.
>
> Associate non-RCU PTR_TO_BTF_ID results with the iterator reference. Before
> modeling another next result, invalidate older BTF pointers carrying that
> parent. Do this before constructing R0 and for both the ACTIVE and DRAINED
> iterator branches. The existing iterator reference release invalidates the
> current result on destroy.
>
> Preserve the parent across BTF field loads when the result remains fully
> trusted and its lifetime is tied to the parent. Do not propagate it to
> MEM_RCU, MEM_PERCPU, MEM_USER, or untrusted results, whose lifetime or
> access domain follows separate rules.
>
> When bpf_dynptr_from_file() consumes a borrowed file carrying such a
> parent, use that parent as the FILE dynptr's lifetime anchor. Reject
> iterator next or destroy while a resource child remains live, requiring
> the program to discard the dynptr before its backing file can be released.
>
> 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>
Amery,
is this really what you meant?
pls review.
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-12 3:34 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-11 8:42 [PATCH bpf v3 0/2] bpf: Track iterator-owned BTF pointer lifetime Xu Yunxiang
2026-09-11 8:42 ` [PATCH bpf v3 1/2] bpf: Invalidate iterator-owned BTF pointers on next and destroy Xu Yunxiang
2026-09-12 3:34 ` Alexei Starovoitov
2026-09-11 8:42 ` [PATCH bpf v3 2/2] selftests/bpf: Test iterator BTF pointer lifetimes Xu Yunxiang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox