bpf: Track verifier reference diagnostic events

Add reference acquire and release events to diagnostic history so Resource
Lifetime Safety reports can show the lifetime of a specific reference id along
the path.

Record acquisitions after the verifier assigns the reference id. Record
releases only after release_reference_nomark() succeeds, including the
kptr_xchg RCU conversion path and owning-to-non-owning conversion path that
consume an owning reference.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://patch.msgid.link/20260815064612.378577-7-memxor@gmail.com
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
This commit is contained in:
Kumar Kartikeya Dwivedi
2026-08-15 08:46:01 +02:00
committed by Eduard Zingerman
parent af4ea6e20f
commit 9ecd70304e
3 changed files with 55 additions and 7 deletions

View File

@@ -102,6 +102,8 @@ struct bpf_diag_reg_snapshot {
enum bpf_diag_history_kind {
BPF_DIAG_HISTORY_BRANCH,
BPF_DIAG_HISTORY_MOD,
BPF_DIAG_HISTORY_REF_ACQUIRE,
BPF_DIAG_HISTORY_REF_RELEASE,
};
struct bpf_diag_history_event {
@@ -119,6 +121,9 @@ struct bpf_diag_history_event {
u8 reason;
bool origin_valid;
} mod;
struct {
u32 ref_id;
} ref;
};
};
@@ -1021,3 +1026,26 @@ void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,
diag_stack_range_target(state->diag_frame_id, state->frameno, min_off, max_off),
reason, NULL, NULL, NULL);
}
static void diag_record_ref(struct bpf_verifier_env *env, u32 insn_idx, u8 kind, u32 ref_id)
{
struct bpf_diag_history_event event = {
.insn_idx = insn_idx,
.kind = kind,
.ref = {
.ref_id = ref_id,
},
};
diag_append_history(env, &event);
}
void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)
{
diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_ACQUIRE, ref_id);
}
void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)
{
diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_RELEASE, ref_id);
}

View File

@@ -43,5 +43,7 @@ void bpf_diag_record_scrub(struct bpf_verifier_env *env, const struct bpf_reg_st
void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,
const struct bpf_func_state *state, s16 min_off, s16 max_off,
enum bpf_diag_mod_reason reason);
void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);
void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);
#endif /* __BPF_DIAGNOSTICS_H */

View File

@@ -205,7 +205,8 @@ struct bpf_verifier_stack_elem {
#define BPF_PRIV_STACK_MIN_SIZE 64
static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id);
static int release_reference_nomark(struct bpf_verifier_state *state, int id);
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 void invalidate_non_owning_refs(struct bpf_verifier_env *env);
static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env);
@@ -1418,6 +1419,7 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par
s->type = REF_TYPE_PTR;
s->id = ++env->id_gen;
s->parent_id = parent_id;
bpf_diag_record_ref_acquire(env, insn_idx, s->id);
return s->id;
}
@@ -9017,7 +9019,7 @@ static void mark_pkt_end(struct bpf_verifier_state *vstate, int regn, bool range
reg->range = AT_PKT_END;
}
static int release_reference_nomark(struct bpf_verifier_state *state, int id)
static int __release_reference_nomark(struct bpf_verifier_state *state, int id)
{
int i;
@@ -9032,6 +9034,16 @@ static int release_reference_nomark(struct bpf_verifier_state *state, int id)
return -EINVAL;
}
static int release_reference_nomark(struct bpf_verifier_env *env, int id)
{
int err;
err = __release_reference_nomark(env->cur_state, id);
if (!err)
bpf_diag_record_ref_release(env, env->insn_idx, id);
return err;
}
static int idstack_push(struct bpf_idmap *idmap, u32 id)
{
int i;
@@ -9074,8 +9086,10 @@ static int release_reference(struct bpf_verifier_env *env, int id)
if (err)
return err;
if (find_reference_state(vstate, id))
WARN_ON_ONCE(release_reference_nomark(vstate, id));
if (find_reference_state(vstate, id)) {
err = release_reference_nomark(env, id);
WARN_ON_ONCE(err);
}
while ((id = idstack_pop(idstack))) {
/*
@@ -9164,7 +9178,9 @@ static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 id)
struct bpf_reg_state *reg;
int err;
err = release_reference_nomark(env->cur_state, id);
err = release_reference_nomark(env, id);
if (err)
return err;
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
if (reg->id != id)
@@ -11757,8 +11773,10 @@ static void ref_convert_owning_non_owning(struct bpf_verifier_env *env, u32 id)
{
struct bpf_func_state *unused;
struct bpf_reg_state *reg;
int err;
WARN_ON_ONCE(release_reference_nomark(env->cur_state, id));
err = release_reference_nomark(env, id);
WARN_ON_ONCE(err);
bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
if (reg->id == id) {
@@ -15890,7 +15908,7 @@ static void mark_ptr_or_null_regs(struct bpf_verifier_state *vstate, u32 regno,
* No one could have freed the reference state before
* doing the NULL check.
*/
WARN_ON_ONCE(release_reference_nomark(vstate, id));
WARN_ON_ONCE(__release_reference_nomark(vstate, id));
bpf_for_each_reg_in_vstate(vstate, state, reg, ({
mark_ptr_or_null_reg(state, reg, id, is_null);