selftests/bpf: Test borrowed iterator result lifetimes

Exercise borrowed VMA results before and after iterator advancement
and destruction, including NULL next, trusted fields, and a FILE
dynptr that must be discarded before advancing. Keep positive controls
for using the current element and discarding the dynptr first.

Use a test-module iterator returning current to verify the explicit
independent-lifetime exception. Retain controls for RCU-protected
results. Also check the generic trusted-field rule outside iterators:
reading file->f_inode while holding an acquired file reference succeeds,
but using the saved inode pointer after bpf_put_file() is rejected.

The annotated programs check verifier loading and diagnostics; they do
not request runtime execution of the added programs.

Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
Link: https://patch.msgid.link/20261024100647.2579244-3-xyx2021@mail.ustc.edu.cn
This commit is contained in:
Xu Yunxiang 2026-10-24 18:06:47 +08:00 committed by Alexei Starovoitov
parent d09c2aa310
commit 2d7eaeb05a
No known key found for this signature in database
6 changed files with 440 additions and 0 deletions

View File

@ -8,6 +8,44 @@
char _license[] SEC("license") = "GPL";
struct bpf_iter_testmod_current {
u64 __opaque;
};
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)
@ -77,6 +407,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)

View File

@ -11,6 +11,23 @@
static char buf[64];
SEC("lsm.s/file_open")
__success
int BPF_PROG(inode_field_before_file_put)
{
struct file *file;
ino_t ino;
file = bpf_get_task_exe_file(bpf_get_current_task_btf());
if (!file)
return 0;
ino = file->f_inode->i_ino;
bpf_put_file(file);
__sink(ino);
return 0;
}
SEC("lsm.s/file_open")
__success
int BPF_PROG(get_task_exe_file_and_put_kfunc_from_current_sleepable)

View File

@ -12,6 +12,25 @@
static char buf[PATH_MAX];
SEC("lsm.s/file_open")
__failure __msg("invalid mem access 'scalar'")
int BPF_PROG(inode_field_after_file_put)
{
struct inode *inode;
struct file *file;
ino_t ino;
file = bpf_get_task_exe_file(bpf_get_current_task_btf());
if (!file)
return 0;
inode = file->f_inode;
bpf_put_file(file);
ino = inode->i_ino;
__sink(ino);
return 0;
}
SEC("lsm.s/file_open")
__failure __msg("Possibly NULL pointer passed to trusted R1")
int BPF_PROG(get_task_exe_file_kfunc_null)

View File

@ -281,6 +281,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)
{
}
@ -1016,6 +1037,10 @@ 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 | KF_ITER_RET_INDEPENDENT)
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)

View File

@ -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);

View File

@ -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;