mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
parent
d09c2aa310
commit
2d7eaeb05a
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user