mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge branch 'bpf-track-borrowed-iterator-result-lifetimes'
Xu Yunxiang says:
====================
bpf: Track borrowed iterator result lifetimes
Make non-RCU, non-acquired struct iterator results borrow the current
element lifetime by default. KF_ITER_RET_INDEPENDENT marks the explicit
exception for results whose lifetime is independent of the iterator.
Trusted field loads and dynptr constructors propagate the source lifetime
so derived pointers stay in the invalidation graph. Next preserves the
iterator itself and invalidates old dependents before both return
outcomes; destroy propagates outstanding-child errors.
This revision addresses Alexei's implementation review. Share the pointer
invalidation traversal through invalidate_reference(), with root reference
release performed separately by release_reference(). Iterator next calls
invalidation directly. The existing result classification already keeps
MEM_RCU results out of the iterator dependency graph, so the duplicate
RCU checks at next are unnecessary. The traversal excludes STACK_ITER,
so it also needs no special guard for the iterator's own stack identity.
Patch 1 retains the generic trusted-field rule and its file/inode lifetime
explanation. Patch 2 retains the iterator lifetime tests and the VFS
before-release and after-release controls unchanged.
Justin has coordinated the overlapping work and dropped the iterator
and acquired-parent trusted-field fixes from his v3 series. This series
keeps those fixes; his series covers RCU-parent field classification and
the __derives_ret kfunc argument annotation, including bpf_real_data_inode().
The two series are based independently on bpf-next. They overlap at
reg_lifetime_id() and the struct-return parent_id assignment and will need
those sites reconciled when combined.
Changes in v8:
- Remove the duplicate RCU checks before iterator-next invalidation.
- Separate pointer invalidation from root reference release, removing
release_root and its unreachable root-alias guard. Keep the existing
inline acquired-child leak check in the shared traversal.
- Retain the test code and carry Amery's Reviewed-by tags from v7.
- Rebase onto bpf-next 4169c9c905 without changing either patch diff.
- Remove the obsolete Assisted-by trailer for the current submission
tooling; retain the AI-assistance disclosure in this cover.
- Document the scope coordination with Justin and add him to Cc.
Changes in v7:
- Explain the generic trusted-field lifetime rule and the file/inode
example in patch 1's commit log, as requested by Alexei.
- Add a positive and negative VFS test for that example in patch 2.
- Rebase onto bpf-next b116ac6087. The implementation's added and
removed lines were unchanged from posted v6; the field-load hunk's
context includes the upstream __nullable handling.
v7:
https://lore.kernel.org/r/20261021210754.3270023-1-xyx2021@mail.ustc.edu.cn
Alexei's v7 review:
https://lore.kernel.org/r/DMB212RCKZ56.LTNAJWB2D39T@gmail.com
v6:
https://lore.kernel.org/r/20260927120406.1041915-1-xyx2021@mail.ustc.edu.cn
Alexei's v6 review:
https://lore.kernel.org/r/DMA2SZ5PRG4N.3I51MHSTWUUTS@gmail.com
Justin's scope-coordination reply:
https://lore.kernel.org/r/att73Z9XpompG3k1@zenbox
Justin's v3:
https://lore.kernel.org/r/20261023212041.884996-1-utilityemal77@gmail.com
Kumar's related work:
https://lore.kernel.org/r/DLGYPZAL708V.3GFLANAPH2L8O@gmail.com
These lifetime paths were investigated through AI-assisted source
inspection and annotated verifier programs. The regressions check loading
and diagnostics. The independent-lifetime control uses a small bpf_testmod
iterator; the VFS control uses existing kfuncs. No delay or fault injection
is used to force runtime reclamation.
Validation on this candidate with a matching bpf_testmod:
- W=1 verifier, full kernel and modules builds passed.
- Changed BPF objects and required fixture compiled strictly.
- iters: 1/122 passed; 0 skipped.
- dynptr: 2/137 passed; 0 skipped.
- file_reader: 1/10 passed; 0 skipped.
- verifier_vfs_accept: 1/7 passed; 0 skipped.
- verifier_vfs_reject: 1/13 passed; 0 skipped.
- kmem_cache_iter: 1/3 passed; 0 skipped.
- dmabuf_iter: 1/4 passed; 0 skipped.
- verifier_ref_tracking: 1/57 passed; 0 skipped.
- task_kfunc: 1/47 passed; 0 skipped.
The before-release VFS program loaded on both the unmodified
baseline and this candidate. The after-release program loaded on the
baseline and was rejected by this candidate with the expected scalar
access diagnostic. These annotated programs are load-only.
The VMs used panic_on_warn and panic_on_oops. No WARN_ON report,
Oops or panic was found. Unprivileged BPF was enabled only in the
isolated guests.
The complete test_progs build used BPF_STRICT_BUILD=1. The unfiltered
full suite, sanitizer and architecture matrices were not run. The
fresh baseline comparison covers the VFS example retained from v7.
Please queue the verifier fix for stable after it reaches the BPF tree.
====================
Link: https://patch.msgid.link/20261024100647.2579244-1-xyx2021@mail.ustc.edu.cn
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
00d2cdf163
|
|
@ -86,6 +86,16 @@ 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.
|
||||
|
||||
The verifier treats a non-RCU, non-acquired struct result as borrowed from
|
||||
the iterator's current element. It invalidates the previous result and its
|
||||
tracked derived pointers on the next call, including when it returns NULL,
|
||||
and on destruction of the iterator. Resources borrowing that lifetime must
|
||||
be released first. A next method whose struct result has an independent
|
||||
lifetime must use ``KF_ITER_RET_INDEPENDENT``. This flag requires
|
||||
``KF_ITER_NEXT`` and a struct pointer return type, and cannot be combined
|
||||
with ``KF_ACQUIRE``. RCU-protected and acquired results retain their own
|
||||
lifetime rules.
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
#define KF_IMPLICIT_ARGS (1 << 16) /* kfunc has implicit arguments supplied by the verifier */
|
||||
#define KF_SPINLOCK_SAFE (1 << 17) /* kfunc is allowed inside bpf_spin_lock-ed region */
|
||||
#define KF_PERFMON (1 << 18) /* kfunc requires CAP_PERFMON */
|
||||
#define KF_ITER_RET_INDEPENDENT (1 << 19) /* next result outlives the current element */
|
||||
|
||||
/*
|
||||
* Tag marking a kernel function as a kfunc. This is meant to minimize the
|
||||
|
|
|
|||
|
|
@ -9720,6 +9720,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_RET_INDEPENDENT) &&
|
||||
(!(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)
|
||||
|
|
@ -9760,6 +9764,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;
|
||||
/* The opt-out applies only to non-acquired struct results. */
|
||||
if (func_flags & KF_ITER_RET_INDEPENDENT) {
|
||||
t = btf_type_skip_modifiers(btf, t->type, NULL);
|
||||
if (!t || !__btf_type_is_struct(t))
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & KF_ITER_DESTROY) {
|
||||
|
|
@ -9826,7 +9836,8 @@ 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_RET_INDEPENDENT)) {
|
||||
err = btf_check_iter_kfuncs(btf, func_name, func, func_flags);
|
||||
if (err)
|
||||
return err;
|
||||
|
|
|
|||
|
|
@ -211,6 +211,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 invalidate_reference(struct bpf_verifier_env *env, int id);
|
||||
static u32 reg_lifetime_id(struct bpf_verifier_env *env,
|
||||
const struct bpf_reg_state *reg);
|
||||
static void invalidate_non_owning_refs(struct bpf_verifier_env *env);
|
||||
static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env);
|
||||
static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env);
|
||||
|
|
@ -793,8 +796,8 @@ static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
/* Track parent's id if the parent is a referenced object */
|
||||
parent_id = ref_obj->id;
|
||||
/* All non-clone constructors take their backing object in R1. */
|
||||
parent_id = reg_lifetime_id(env, &cur_regs(env)[BPF_REG_1]);
|
||||
|
||||
if (dynptr_type_referenced(type)) {
|
||||
int id;
|
||||
|
|
@ -1074,7 +1077,7 @@ static int unmark_stack_slots_iter(struct bpf_verifier_env *env,
|
|||
struct bpf_reg_state *reg, int nr_slots)
|
||||
{
|
||||
struct bpf_func_state *state = bpf_func(env, reg);
|
||||
int spi, i, j;
|
||||
int spi, i, j, err;
|
||||
|
||||
spi = iter_get_spi(env, reg, nr_slots);
|
||||
if (spi < 0)
|
||||
|
|
@ -1084,8 +1087,11 @@ static int unmark_stack_slots_iter(struct bpf_verifier_env *env,
|
|||
struct bpf_stack_state *slot = bpf_stack_slot(state, spi - i);
|
||||
struct bpf_reg_state *st = &slot->spilled_ptr;
|
||||
|
||||
if (i == 0)
|
||||
WARN_ON_ONCE(release_reference(env, st->id));
|
||||
if (i == 0) {
|
||||
err = release_reference(env, st->id);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
bpf_mark_reg_not_init(env, st);
|
||||
|
||||
|
|
@ -1627,6 +1633,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;
|
||||
|
|
@ -6848,9 +6860,14 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
|
|||
flag &= ~PTR_MAYBE_NULL;
|
||||
|
||||
if (atype == BPF_READ && value_regno >= 0) {
|
||||
u32 parent_id = reg_lifetime_id(env, reg);
|
||||
|
||||
ret = mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if ((regs[value_regno].type & PTR_TRUSTED) &&
|
||||
!(regs[value_regno].type & (MEM_RCU | MEM_PERCPU | MEM_USER)))
|
||||
regs[value_regno].parent_id = parent_id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -8636,6 +8653,21 @@ static bool is_iter_destroy_kfunc(struct bpf_call_arg_meta *meta)
|
|||
return meta->kfunc_flags & KF_ITER_DESTROY;
|
||||
}
|
||||
|
||||
static bool iter_next_borrows_struct(struct bpf_call_arg_meta *meta)
|
||||
{
|
||||
const struct btf_type *t;
|
||||
|
||||
if (!(meta->kfunc_flags & KF_ITER_NEXT) ||
|
||||
(meta->kfunc_flags & (KF_ITER_RET_INDEPENDENT | KF_ACQUIRE)))
|
||||
return false;
|
||||
|
||||
t = btf_type_skip_modifiers(meta->btf, meta->func_proto->type, NULL);
|
||||
if (!t || !btf_type_is_ptr(t))
|
||||
return false;
|
||||
t = btf_type_skip_modifiers(meta->btf, t->type, NULL);
|
||||
return t && __btf_type_is_struct(t);
|
||||
}
|
||||
|
||||
static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,
|
||||
const struct btf_param *arg)
|
||||
{
|
||||
|
|
@ -8746,6 +8778,13 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
|
|||
meta->iter.frameno = reg->frameno;
|
||||
update_ref_obj(&meta->ref_obj, &bpf_stack_slot(state, spi)->spilled_ptr);
|
||||
|
||||
/* The iterator stays. What was borrowed from the previous element goes. */
|
||||
if (iter_next_borrows_struct(meta)) {
|
||||
err = invalidate_reference(env, meta->ref_obj.id);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (is_iter_destroy_kfunc(meta)) {
|
||||
err = unmark_stack_slots_iter(env, reg, nr_slots);
|
||||
if (err)
|
||||
|
|
@ -11073,8 +11112,8 @@ static int idstack_pop(struct bpf_idmap *idmap)
|
|||
return idmap->map[--idmap->cnt].old;
|
||||
}
|
||||
|
||||
/* Release id and objects derived from it iteratively in a DFS manner */
|
||||
static int release_reference(struct bpf_verifier_env *env, int id)
|
||||
/* Invalidate id and objects derived from it iteratively in a DFS manner */
|
||||
static int invalidate_reference(struct bpf_verifier_env *env, int id)
|
||||
{
|
||||
u32 mask = (1 << STACK_SPILL) | (1 << STACK_DYNPTR);
|
||||
struct bpf_verifier_state *vstate = env->cur_state;
|
||||
|
|
@ -11089,11 +11128,6 @@ static int release_reference(struct bpf_verifier_env *env, int id)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
if (find_reference_state(vstate, id)) {
|
||||
err = release_reference_nomark(env, id);
|
||||
WARN_ON_ONCE(err);
|
||||
}
|
||||
|
||||
while ((id = idstack_pop(idstack))) {
|
||||
/*
|
||||
* Child references are inaccessible after parent is released,
|
||||
|
|
@ -11145,6 +11179,14 @@ static int release_reference(struct bpf_verifier_env *env, int id)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int release_reference(struct bpf_verifier_env *env, int id)
|
||||
{
|
||||
if (find_reference_state(env->cur_state, id))
|
||||
WARN_ON_ONCE(release_reference_nomark(env, id));
|
||||
|
||||
return invalidate_reference(env, id);
|
||||
}
|
||||
|
||||
static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
|
||||
{
|
||||
struct bpf_func_state *unused;
|
||||
|
|
@ -15822,6 +15864,8 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
|||
regs[BPF_REG_0].btf = desc_btf;
|
||||
regs[BPF_REG_0].type = type;
|
||||
regs[BPF_REG_0].btf_id = ptr_type_id;
|
||||
if (iter_next_borrows_struct(&meta) && !(type & MEM_RCU))
|
||||
regs[BPF_REG_0].parent_id = meta.ref_obj.id;
|
||||
}
|
||||
|
||||
if (is_kfunc_ret_null(&meta)) {
|
||||
|
|
|
|||
|
|
@ -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