AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/24 02:04 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "array_map_set_for_each_callback_args",
    "mark_frame_scoped_arg",
    "acquire_frame_reference",
    "release_frame_reference",
    "setup_func_entry",
    "prepare_func_exit",
    "check_reference_leak",
    "refsafe",
    "set_user_ringbuf_callback_state"
  ],
  "KMSANReasoning": "The patch introduces verifier tracking for frame-scoped callback arguments (such as bpf_for_each_array_elem keys and user ringbuf dynptrs) by introducing REF_TYPE_FRAME reference states that are automatically invalidated when the callback frame exits.\n\nKASAN vs KMSAN applicability:\n- Memory Safety \u0026 Initialization: All newly added and modified fields (`frameno` in `struct bpf_reference_state`, `frame_scoped_args` in `struct bpf_func_state`) are allocated using zeroing mechanisms (`kzalloc_obj`, and `realloc_array` which explicitly memsets newly grown entries to zero). State copying via `copy_func_state` and `copy_reference_state` properly covers the new fields from already initialized memory. No uninitialized memory reads or conditional branches on uninitialized fields exist in the verifier.\n- Leakage to User Space: No kernel structures or uninitialized paddings are copied to user space. Verifier diagnostics and log messages format explicit string literals and initialized integers into the user-provided log buffer.\n- Failure Modes: The vulnerability class addressed by this patch is lifetime violation in verified BPF programs (preventing a BPF program from accessing pointers to callback stack frames after the callback returns). Such an escape at runtime results in use-after-free or stack-out-of-scope accesses, which are detected by KASAN.\n\nBecause the changes do not introduce or expose uninitialized memory reads or info-leaks, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch modifies the core BPF verifier and array map implementations to track frame-scoped callback arguments (introducing REF_TYPE_FRAME and frame reference tracking for bpf_for_each_map_elem and bpf_user_ringbuf_drain callbacks). It enforces reference lifetimes across callback entry/exit, updates verifier state pruning comparison (refsafe), and introduces runtime verifier bug assertions (verifier_bug_if, verifier_bug). These changes are directly reachable from userspace via the bpf(BPF_PROG_LOAD) syscall and warrant fuzzing to identify state tracking regressions, escapes, or assertion triggers.",
  "WorthFuzzing": true
}

1/1 2026/09/24 02:04 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 7dd9702816d25af1e106ce10b7bb4365e5a49703\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Sep 24 02:04:19 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/bpf.h b/include/linux/bpf.h\nindex fd22db8bc6c50..d849e4873417e 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -3118,6 +3118,7 @@ int bpf_iter_map_fill_link_info(const struct bpf_iter_aux_info *aux,\n int map_set_for_each_callback_args(struct bpf_verifier_env *env,\n \t\t\t\t   struct bpf_func_state *caller,\n \t\t\t\t   struct bpf_func_state *callee);\n+void mark_frame_scoped_arg(struct bpf_func_state *callee, u32 regno);\n \n int bpf_percpu_hash_copy(struct bpf_map *map, void *key, void *value, u64 flags);\n int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value, u64 flags);\ndiff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex 92f528c456052..51c310ea4e828 100644\n--- a/include/linux/bpf_verifier.h\n+++ b/include/linux/bpf_verifier.h\n@@ -307,15 +307,13 @@ struct bpf_stack_state {\n };\n \n struct bpf_reference_state {\n-\t/* Each reference object has a type. Ensure REF_TYPE_PTR is zero to\n-\t * default to pointer reference on zero initialization of a state.\n-\t */\n \tenum ref_state_type {\n \t\tREF_TYPE_PTR\t\t= (1 \u003c\u003c 1),\n \t\tREF_TYPE_IRQ\t\t= (1 \u003c\u003c 2),\n \t\tREF_TYPE_LOCK\t\t= (1 \u003c\u003c 3),\n \t\tREF_TYPE_RES_LOCK \t= (1 \u003c\u003c 4),\n \t\tREF_TYPE_RES_LOCK_IRQ\t= (1 \u003c\u003c 5),\n+\t\tREF_TYPE_FRAME\t\t= (1 \u003c\u003c 6),\n \t\tREF_TYPE_LOCK_MASK\t= REF_TYPE_LOCK | REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ,\n \t} type;\n \t/* Track each reference created with a unique id, even if the same\n@@ -333,6 +331,8 @@ struct bpf_reference_state {\n \t\t * it matches on unlock.\n \t\t */\n \t\tvoid *ptr;\n+\t\t/* For REF_TYPE_FRAME */\n+\t\tu32 frameno;\n \t};\n };\n \n@@ -387,6 +387,11 @@ struct bpf_func_state {\n \tu32 callback_depth;\n \t/* Instructions processed in this frame and callees on the current path. */\n \tu32 insns_subtotal;\n+\t/*\n+\t * Set for arguments valid until the frame is popped.\n+\t * Consumed by setup_func_entry().\n+\t */\n+\tu16 frame_scoped_args;\n \n \t/* The following fields should be last. See copy_func_state() */\n \t/* The state of the stack. Each element of the array describes BPF_REG_SIZE\ndiff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c\nindex 0ce26b538075e..44bd229873cad 100644\n--- a/kernel/bpf/arraymap.c\n+++ b/kernel/bpf/arraymap.c\n@@ -855,6 +855,20 @@ static u64 array_map_mem_usage(const struct bpf_map *map)\n \treturn usage;\n }\n \n+static int array_map_set_for_each_callback_args(struct bpf_verifier_env *env,\n+\t\t\t\t\t\tstruct bpf_func_state *caller,\n+\t\t\t\t\t\tstruct bpf_func_state *callee)\n+{\n+\tint err;\n+\n+\terr = map_set_for_each_callback_args(env, caller, callee);\n+\tif (err)\n+\t\treturn err;\n+\n+\tmark_frame_scoped_arg(callee, BPF_REG_2);\n+\treturn 0;\n+}\n+\n BTF_ID_LIST_SINGLE(array_map_btf_ids, struct, bpf_array)\n const struct bpf_map_ops array_map_ops = {\n \t.map_meta_equal = array_map_meta_equal,\n@@ -875,7 +889,7 @@ const struct bpf_map_ops array_map_ops = {\n \t.map_check_btf = array_map_check_btf,\n \t.map_lookup_batch = generic_map_lookup_batch,\n \t.map_update_batch = generic_map_update_batch,\n-\t.map_set_for_each_callback_args = map_set_for_each_callback_args,\n+\t.map_set_for_each_callback_args = array_map_set_for_each_callback_args,\n \t.map_for_each_callback = bpf_for_each_array_elem,\n \t.map_mem_usage = array_map_mem_usage,\n \t.map_btf_id = \u0026array_map_btf_ids[0],\n@@ -900,7 +914,7 @@ const struct bpf_map_ops percpu_array_map_ops = {\n \t.map_check_btf = array_map_check_btf,\n \t.map_lookup_batch = generic_map_lookup_batch,\n \t.map_update_batch = generic_map_update_batch,\n-\t.map_set_for_each_callback_args = map_set_for_each_callback_args,\n+\t.map_set_for_each_callback_args = array_map_set_for_each_callback_args,\n \t.map_for_each_callback = bpf_for_each_array_elem,\n \t.map_mem_usage = array_map_mem_usage,\n \t.map_btf_id = \u0026array_map_btf_ids[0],\ndiff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c\nindex 5ecfa86ed49f4..0610c3f6b3342 100644\n--- a/kernel/bpf/diagnostics.c\n+++ b/kernel/bpf/diagnostics.c\n@@ -2233,6 +2233,9 @@ static void diag_print_mod(struct bpf_verifier_env *env, const struct bpf_diag_h\n \t\t\t\t\t\t\t\t   \"resource release invalidated \"\n \t\t\t\t\t\t\t\t   \"this value\";\n \t\tbreak;\n+\tcase BPF_DIAG_MOD_FRAME_RELEASE:\n+\t\treason = \"the callback that owned this value returned\";\n+\t\tbreak;\n \tcase BPF_DIAG_MOD_PKT_DATA_CHANGE:\n \t\treason = \"packet data may have moved\";\n \t\tbreak;\ndiff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h\nindex a4102fb049ece..b5cab4d79c1a2 100644\n--- a/kernel/bpf/diagnostics.h\n+++ b/kernel/bpf/diagnostics.h\n@@ -22,6 +22,7 @@ enum bpf_diag_mod_reason {\n \tBPF_DIAG_MOD_SPILL,\n \tBPF_DIAG_MOD_VAR_WRITE,\n \tBPF_DIAG_MOD_REF_RELEASE,\n+\tBPF_DIAG_MOD_FRAME_RELEASE,\n \tBPF_DIAG_MOD_PKT_DATA_CHANGE,\n \tBPF_DIAG_MOD_NON_OWN_REF,\n \tBPF_DIAG_MOD_CALLER_SAVED,\ndiff --git a/kernel/bpf/states.c b/kernel/bpf/states.c\nindex 66fb11b6c6a76..bf7efe5bcdcae 100644\n--- a/kernel/bpf/states.c\n+++ b/kernel/bpf/states.c\n@@ -898,6 +898,10 @@ static bool refsafe(struct bpf_verifier_state *old, struct bpf_verifier_state *c\n \t\t\tbreak;\n \t\tcase REF_TYPE_IRQ:\n \t\t\tbreak;\n+\t\tcase REF_TYPE_FRAME:\n+\t\t\tif (old-\u003erefs[i].frameno != cur-\u003erefs[i].frameno)\n+\t\t\t\treturn false;\n+\t\t\tbreak;\n \t\tcase REF_TYPE_LOCK:\n \t\tcase REF_TYPE_RES_LOCK:\n \t\tcase REF_TYPE_RES_LOCK_IRQ:\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex d62c0f74cff5e..ba0c8c27b45f4 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -707,11 +707,15 @@ static void mark_dynptr_stack_regs(struct bpf_verifier_env *env,\n \t__mark_dynptr_reg(sreg2, type, false, id, parent_id);\n }\n \n-static void mark_dynptr_cb_reg(struct bpf_verifier_env *env,\n-\t\t\t       struct bpf_reg_state *reg,\n+/*\n+ * A callback dynptr argument is valid only until the frame is popped, so\n+ * setup_func_entry() assigns its id along with the frame reference.\n+ */\n+static void mark_dynptr_cb_reg(struct bpf_func_state *callee, u32 regno,\n \t\t\t       enum bpf_dynptr_type type)\n {\n-\t__mark_dynptr_reg(reg, type, true, ++env-\u003eid_gen, 0);\n+\t__mark_dynptr_reg(\u0026callee-\u003eregs[regno], type, true, 0, 0);\n+\tmark_frame_scoped_arg(callee, regno);\n }\n \n static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,\n@@ -1474,14 +1478,13 @@ static int grow_stack_arg_slots(struct bpf_verifier_env *env,\n \treturn 0;\n }\n \n-/* Acquire a pointer id from the env and update the state-\u003erefs to include\n- * this new pointer reference.\n- * On success, returns a valid pointer id to associate with the register\n- * On failure, returns a negative errno.\n+/* Append an entry to @state-\u003erefs and record the instruction that created it.\n+ * The caller fills in the type and the id.\n+ * On success, returns the new entry. On failure, returns NULL.\n  */\n-static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_env *env, int insn_idx)\n+static struct bpf_reference_state *__acquire_reference_state(struct bpf_verifier_state *state,\n+\t\t\t\t\t\t\t     int insn_idx)\n {\n-\tstruct bpf_verifier_state *state = env-\u003ecur_state;\n \tint new_ofs = state-\u003eacquired_refs;\n \tint err;\n \n@@ -1493,6 +1496,12 @@ static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_e\n \treturn \u0026state-\u003erefs[new_ofs];\n }\n \n+static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_env *env,\n+\t\t\t\t\t\t\t   int insn_idx)\n+{\n+\treturn __acquire_reference_state(env-\u003ecur_state, insn_idx);\n+}\n+\n static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id)\n {\n \tstruct bpf_reference_state *s;\n@@ -1507,6 +1516,31 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par\n \treturn s-\u003eid;\n }\n \n+/* Acquire a reference owned by frame @frameno of @state */\n+static int acquire_frame_reference(struct bpf_verifier_env *env, struct bpf_verifier_state *state,\n+\t\t\t\t   int insn_idx, u32 frameno)\n+{\n+\tstruct bpf_reference_state *s;\n+\n+\ts = __acquire_reference_state(state, insn_idx);\n+\tif (!s)\n+\t\treturn -ENOMEM;\n+\ts-\u003etype = REF_TYPE_FRAME;\n+\ts-\u003eid = ++env-\u003eid_gen;\n+\ts-\u003eframeno = frameno;\n+\treturn s-\u003eid;\n+}\n+\n+/*\n+ * Declare that @regno in @callee holds a value that stops being valid once the\n+ * frame is popped. setup_func_entry() turns each declaration into a frame-owned\n+ * reference.\n+ */\n+void mark_frame_scoped_arg(struct bpf_func_state *callee, u32 regno)\n+{\n+\tcallee-\u003eframe_scoped_args |= BIT(regno);\n+}\n+\n static int acquire_lock_state(struct bpf_verifier_env *env, int insn_idx, enum ref_state_type type,\n \t\t\t      int id, void *ptr)\n {\n@@ -10135,7 +10169,8 @@ static int idstack_pop(struct bpf_idmap *idmap)\n }\n \n /* Release id and objects derived from it iteratively in a DFS manner */\n-static int release_reference(struct bpf_verifier_env *env, int id)\n+static int __release_reference(struct bpf_verifier_env *env, int id,\n+\t\t\t       enum bpf_diag_mod_reason reason)\n {\n \tu32 mask = (1 \u003c\u003c STACK_SPILL) | (1 \u003c\u003c STACK_DYNPTR);\n \tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\n@@ -10175,7 +10210,7 @@ static int release_reference(struct bpf_verifier_env *env, int id)\n \t\t\t\tcontinue;\n \n \t\t\t/* Free objects derived from the current object */\n-\t\t\tif (reg-\u003eparent_id == id) {\n+\t\t\tif (reg-\u003eparent_id == id \u0026\u0026 reg-\u003eid != id) {\n \t\t\t\terr = idstack_push(idstack, reg-\u003eid);\n \t\t\t\tif (err)\n \t\t\t\t\treturn err;\n@@ -10190,14 +10225,12 @@ static int release_reference(struct bpf_verifier_env *env, int id)\n \n \t\t\t\tif (reg-\u003edynptr.first_slot)\n \t\t\t\t\tdyn_stack--;\n-\t\t\t\tbpf_diag_record_scrub(env, \u0026dyn_stack[0].spilled_ptr,\n-\t\t\t\t\t\t      BPF_DIAG_MOD_REF_RELEASE);\n-\t\t\t\tbpf_diag_record_scrub(env, \u0026dyn_stack[1].spilled_ptr,\n-\t\t\t\t\t\t      BPF_DIAG_MOD_REF_RELEASE);\n+\t\t\t\tbpf_diag_record_scrub(env, \u0026dyn_stack[0].spilled_ptr, reason);\n+\t\t\t\tbpf_diag_record_scrub(env, \u0026dyn_stack[1].spilled_ptr, reason);\n \t\t\t\tinvalidate_dynptr(env, dyn_stack);\n \t\t\t\tcontinue;\n \t\t\t}\n-\t\t\tbpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_REF_RELEASE);\n+\t\t\tbpf_diag_record_scrub(env, reg, reason);\n \t\t\tif (!stack || stack-\u003eslot_type[BPF_REG_SIZE - 1] == STACK_SPILL)\n \t\t\t\tmark_reg_invalid(env, reg);\n \t\t}));\n@@ -10206,6 +10239,39 @@ static int release_reference(struct bpf_verifier_env *env, int id)\n \treturn 0;\n }\n \n+static int release_reference(struct bpf_verifier_env *env, int id)\n+{\n+\treturn __release_reference(env, id, BPF_DIAG_MOD_REF_RELEASE);\n+}\n+\n+/* Find the first reference owned by frame @frameno, or 0 if it owns none. */\n+static u32 frame_reference_id(struct bpf_verifier_state *state, u32 frameno)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i \u003c state-\u003eacquired_refs; i++)\n+\t\tif (state-\u003erefs[i].type == REF_TYPE_FRAME \u0026\u0026\n+\t\t    state-\u003erefs[i].frameno == frameno)\n+\t\t\treturn state-\u003erefs[i].id;\n+\n+\treturn 0;\n+}\n+\n+static int release_frame_reference(struct bpf_verifier_env *env, int id)\n+{\n+\tstruct bpf_verifier_state *state = env-\u003ecur_state;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c state-\u003eacquired_refs; i++) {\n+\t\tif (state-\u003erefs[i].type != REF_TYPE_FRAME || state-\u003erefs[i].id != id)\n+\t\t\tcontinue;\n+\t\trelease_reference_state(state, i);\n+\t\tbreak;\n+\t}\n+\n+\treturn __release_reference(env, id, BPF_DIAG_MOD_FRAME_RELEASE);\n+}\n+\n static void invalidate_non_owning_refs(struct bpf_verifier_env *env)\n {\n \tstruct bpf_func_state *unused;\n@@ -10301,7 +10367,8 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls\n \t\t\t    struct bpf_verifier_state *state)\n {\n \tstruct bpf_func_state *caller, *callee;\n-\tint err;\n+\tu16 scoped_args;\n+\tint err, regno;\n \n \tif (state-\u003ecurframe + 1 \u003e= MAX_CALL_FRAMES) {\n \t\tverbose(env, \"the call stack of %d frames is too deep\\n\",\n@@ -10333,6 +10400,30 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls\n \tif (err)\n \t\tgoto err_out;\n \n+\tscoped_args = callee-\u003eframe_scoped_args;\n+\tcallee-\u003eframe_scoped_args = 0;\n+\tfor (regno = 0; regno \u003c MAX_BPF_REG; regno++) {\n+\t\tint id;\n+\n+\t\tif (!(scoped_args \u0026 BIT(regno)))\n+\t\t\tcontinue;\n+\n+\t\tid = acquire_frame_reference(env, state, callsite, callee-\u003eframeno);\n+\t\tif (id \u003c 0) {\n+\t\t\terr = id;\n+\t\t\tgoto err_out;\n+\t\t}\n+\t\t/*\n+\t\t * The value is its own lifetime anchor: there is no associated\n+\t\t * object to borrow from, only the frame. parent_id = id here\n+\t\t * covers both possible derived references:\n+\t\t *  - through the id (e.g. dynptr slice)\n+\t\t *  - through parent_id (e.g. dynptr clone)\n+\t\t */\n+\t\tcallee-\u003eregs[regno].id = id;\n+\t\tcallee-\u003eregs[regno].parent_id = id;\n+\t}\n+\n \t/* only increment it after check_reg_arg() finished */\n \tstate-\u003ecurframe++;\n \n@@ -10560,6 +10651,10 @@ static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *ins\n \t\tif (err)\n \t\t\treturn err;\n \n+\t\tif (verifier_bug_if(callee-\u003eframe_scoped_args, env,\n+\t\t\t\t    \"frame-scoped argument declared for async callback\"))\n+\t\t\treturn -EFAULT;\n+\n \t\treturn 0;\n \t}\n \n@@ -10875,7 +10970,7 @@ static int set_user_ringbuf_callback_state(struct bpf_verifier_env *env,\n \t * callback_fn(const struct bpf_dynptr_t* dynptr, void *callback_ctx);\n \t */\n \tbpf_mark_reg_not_init(env, \u0026callee-\u003eregs[BPF_REG_0]);\n-\tmark_dynptr_cb_reg(env, \u0026callee-\u003eregs[BPF_REG_1], BPF_DYNPTR_TYPE_LOCAL);\n+\tmark_dynptr_cb_reg(callee, BPF_REG_1, BPF_DYNPTR_TYPE_LOCAL);\n \tcallee-\u003eregs[BPF_REG_2] = caller-\u003eregs[BPF_REG_3];\n \n \t/* unused */\n@@ -11033,7 +11128,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)\n \tstruct bpf_func_state *caller, *callee;\n \tstruct bpf_reg_state *r0;\n \tbool in_callback_fn;\n-\tu32 i, nregs;\n+\tu32 i, nregs, id;\n \tint err;\n \n \tcallee = state-\u003eframe[state-\u003ecurframe];\n@@ -11106,6 +11201,17 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)\n \t\tverbose(env, \"to caller at %d:\\n\", *insn_idx);\n \t\tprint_verifier_state(env, state, caller-\u003eframeno, true);\n \t}\n+\n+\t/*\n+\t * Values the caller only guaranteed for the duration of the call stop\n+\t * being valid here.\n+\t */\n+\twhile ((id = frame_reference_id(state, callee-\u003eframeno))) {\n+\t\terr = release_frame_reference(env, id);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n \taccount_processed_insns(env, callee, caller);\n \t/* clear everything in the callee. In case of exceptional exits using\n \t * bpf_throw, this will be done by copy_verifier_state for extra frames. */\n@@ -11283,6 +11389,11 @@ static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exi\n \t\treturn 0;\n \n \tfor (i = 0; i \u003c state-\u003eacquired_refs; i++) {\n+\t\tif (!exception_exit \u0026\u0026 state-\u003erefs[i].type == REF_TYPE_FRAME) {\n+\t\t\tverifier_bug(env, \"frame %u reference id=%d alive at program exit\",\n+\t\t\t\t     state-\u003erefs[i].frameno, state-\u003erefs[i].id);\n+\t\t\treturn -EFAULT;\n+\t\t}\n \t\tif (state-\u003erefs[i].type != REF_TYPE_PTR)\n \t\t\tcontinue;\n \t\t/* Allow struct_ops programs to return a referenced kptr back to\ndiff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c\nindex c32c6dab49bce..645f065c21f53 100644\n--- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c\n+++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c\n@@ -12,8 +12,8 @@ struct {\n \tconst char *err_msg;\n } cb_refs_tests[] = {\n \t{ \"underflow_prog\", \"R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_\" },\n-\t{ \"leak_prog\", \"Unreleased reference id=4 alloc_insn=3\" }, /* alloc_insn=3{2,3} */\n-\t{ \"nested_cb\", \"Unreleased reference id=4 alloc_insn=2\" }, /* alloc_insn=2{4,5} */\n+\t{ \"leak_prog\", \"Unreleased reference id=5 alloc_insn=3\" }, /* alloc_insn=3{2,3} */\n+\t{ \"nested_cb\", \"Unreleased reference id=5 alloc_insn=2\" }, /* alloc_insn=2{4,5} */\n \t{ \"non_cb_transfer_ref\", \"Unreleased reference id=4 alloc_insn=1\" }, /* alloc_insn=1{1,2} */\n };\n \ndiff --git a/tools/testing/selftests/bpf/progs/exceptions_fail.c b/tools/testing/selftests/bpf/progs/exceptions_fail.c\nindex 22503cf62e9f3..86a0667ba3484 100644\n--- a/tools/testing/selftests/bpf/progs/exceptions_fail.c\n+++ b/tools/testing/selftests/bpf/progs/exceptions_fail.c\n@@ -31,6 +31,11 @@ struct {\n \t__type(value, struct hmap_elem);\n } hmap SEC(\".maps\");\n \n+struct {\n+\t__uint(type, BPF_MAP_TYPE_USER_RINGBUF);\n+\t__uint(max_entries, 4096);\n+} user_ringbuf SEC(\".maps\");\n+\n private(A) struct bpf_spin_lock lock;\n private(A) struct bpf_rb_root rbtree __contains(foo, node);\n \n@@ -110,6 +115,21 @@ static int timer_cb(void *map, int *key, struct bpf_timer *timer)\n \treturn 0;\n }\n \n+static long drain_cb(struct bpf_dynptr *dynptr, void *context)\n+{\n+\tbpf_throw(0);\n+\treturn 0;\n+}\n+\n+SEC(\"?tc\")\n+__failure\n+__msg(\"bpf_throw kfunc (insn {{[0-9]+}}) cannot be called from callback subprog {{[0-9]+}}\")\n+int reject_user_ringbuf_callback_throw(struct __sk_buff *ctx)\n+{\n+\tbpf_user_ringbuf_drain(\u0026user_ringbuf, drain_cb, NULL, 0);\n+\treturn 0;\n+}\n+\n SEC(\"?tc\")\n __failure __msg(\"cannot be called from callback subprog\")\n int reject_async_callback_throw(struct __sk_buff *ctx)\ndiff --git a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c\nindex c0d0422b8030b..a8d3acfa2bd25 100644\n--- a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c\n+++ b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c\n@@ -1,9 +1,11 @@\n // SPDX-License-Identifier: GPL-2.0\n /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */\n \n+#include \u003cstdbool.h\u003e\n #include \u003clinux/bpf.h\u003e\n #include \u003cbpf/bpf_helpers.h\u003e\n #include \"bpf_misc.h\"\n+#include \"bpf_kfuncs.h\"\n \n char _license[] SEC(\"license\") = \"GPL\";\n \n@@ -243,3 +245,144 @@ int user_ringbuf_callback_const_ptr_to_dynptr_reg_off(void *ctx)\n \t\t\t       callback_adjust_bpf_dynptr_reg_off, NULL, 0);\n \treturn 0;\n }\n+\n+/* The sample goes back to the producer as soon as the callback returns. */\n+struct dynptr_ctx {\n+\tstruct bpf_dynptr *saved;\n+};\n+\n+static long callback_park_dynptr(struct bpf_dynptr *dynptr, void *context)\n+{\n+\tstruct dynptr_ctx *c = context;\n+\n+\tc-\u003esaved = dynptr;\n+\treturn 0;\n+}\n+\n+SEC(\"?raw_tp\")\n+__failure __msg(\"the callback that owned this value returned\")\n+int user_ringbuf_callback_park_dynptr(void *ctx)\n+{\n+\tstruct dynptr_ctx c = {};\n+\tchar buf[8] = {};\n+\n+\tbpf_user_ringbuf_drain(\u0026user_ringbuf, callback_park_dynptr, \u0026c, 0);\n+\tif (c.saved)\n+\t\tbpf_dynptr_read(buf, sizeof(buf), c.saved, 0, 0);\n+\treturn buf[0];\n+}\n+\n+struct slice_ctx {\n+\tchar *p;\n+};\n+\n+static long callback_park_data_slice(struct bpf_dynptr *dynptr, void *context)\n+{\n+\tstruct slice_ctx *c = context;\n+\n+\tc-\u003ep = bpf_dynptr_data(dynptr, 0, 8);\n+\treturn 0;\n+}\n+\n+SEC(\"?raw_tp\")\n+__failure __msg(\"the callback that owned this value returned\")\n+int user_ringbuf_callback_park_data_slice(void *ctx)\n+{\n+\tstruct slice_ctx c = {};\n+\n+\tbpf_user_ringbuf_drain(\u0026user_ringbuf, callback_park_data_slice, \u0026c, 0);\n+\tif (c.p)\n+\t\treturn c.p[0];\n+\treturn 0;\n+}\n+\n+static long callback_park_kfunc_slice(struct bpf_dynptr *dynptr, void *context)\n+{\n+\tstruct slice_ctx *c = context;\n+\n+\tc-\u003ep = bpf_dynptr_slice(dynptr, 0, NULL, 8);\n+\treturn 0;\n+}\n+\n+SEC(\"?raw_tp\")\n+__failure __msg(\"the callback that owned this value returned\")\n+int user_ringbuf_callback_park_kfunc_slice(void *ctx)\n+{\n+\tstruct slice_ctx c = {};\n+\n+\tbpf_user_ringbuf_drain(\u0026user_ringbuf, callback_park_kfunc_slice, \u0026c, 0);\n+\tif (c.p)\n+\t\treturn c.p[0];\n+\treturn 0;\n+}\n+\n+struct clone_ctx {\n+\tstruct bpf_dynptr clone;\n+\t__u64 armed;\n+};\n+\n+static long callback_park_clone(struct bpf_dynptr *dynptr, void *context)\n+{\n+\tstruct clone_ctx *c = context;\n+\n+\tbpf_dynptr_clone(dynptr, \u0026c-\u003eclone);\n+\tc-\u003earmed = 1;\n+\treturn 0;\n+}\n+\n+SEC(\"?raw_tp\")\n+__failure __msg(\"Expected an initialized dynptr as R3\")\n+int user_ringbuf_callback_park_clone(void *ctx)\n+{\n+\tstruct clone_ctx c = {};\n+\tchar buf[8] = {};\n+\n+\tbpf_user_ringbuf_drain(\u0026user_ringbuf, callback_park_clone, \u0026c, 0);\n+\tif (c.armed)\n+\t\tbpf_dynptr_read(buf, sizeof(buf), \u0026c.clone, 0, 0);\n+\treturn buf[0];\n+}\n+\n+SEC(\"?raw_tp\")\n+__failure __msg(\"Expected an initialized dynptr as R1\")\n+int user_ringbuf_callback_park_clone_then_slice(void *ctx)\n+{\n+\tstruct clone_ctx c = {};\n+\tchar *p;\n+\n+\tbpf_user_ringbuf_drain(\u0026user_ringbuf, callback_park_clone, \u0026c, 0);\n+\tif (c.armed) {\n+\t\tp = bpf_dynptr_data(\u0026c.clone, 0, 8);\n+\t\tif (p)\n+\t\t\treturn p[0];\n+\t}\n+\treturn 0;\n+}\n+\n+static long callback_park_inner(struct bpf_dynptr *dynptr, void *context)\n+{\n+\tstruct dynptr_ctx *c = context;\n+\n+\tc-\u003esaved = dynptr;\n+\treturn 0;\n+}\n+\n+/* An inner drain's dynptr must not escape into the outer callback either. */\n+static long callback_park_outer(struct bpf_dynptr *dynptr, void *context)\n+{\n+\tstruct dynptr_ctx inner = {};\n+\tchar buf[8] = {};\n+\n+\tbpf_user_ringbuf_drain(\u0026user_ringbuf, callback_park_inner, \u0026inner, 0);\n+\tif (inner.saved)\n+\t\tbpf_dynptr_read(buf, sizeof(buf), inner.saved, 0, 0);\n+\treturn buf[0] ? 1 : 0;\n+}\n+\n+SEC(\"?raw_tp\")\n+__failure __msg(\"the callback that owned this value returned\")\n+int user_ringbuf_callback_nested_park_inner(void *ctx)\n+{\n+\tbpf_user_ringbuf_drain(\u0026user_ringbuf, callback_park_outer, NULL, 0);\n+\treturn 0;\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c\nindex 1fbcc52283061..2e0c56888953b 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c\n@@ -9,6 +9,20 @@ struct {\n \t__type(value, __u64);\n } map SEC(\".maps\");\n \n+struct {\n+\t__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);\n+\t__uint(max_entries, 8);\n+\t__type(key, __u32);\n+\t__type(value, __u64);\n+} percpu_map SEC(\".maps\");\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_HASH);\n+\t__uint(max_entries, 8);\n+\t__type(key, __u32);\n+\t__type(value, __u64);\n+} hash_map SEC(\".maps\");\n+\n struct {\n \t__uint(type, BPF_MAP_TYPE_USER_RINGBUF);\n \t__uint(max_entries, 8);\n@@ -800,4 +814,81 @@ __naked void check_add_const_regsafe_off(void)\n \t: __clobber_common);\n }\n \n+struct key_ctx {\n+\t__u32 *key;\n+};\n+\n+static long park_key_cb(struct bpf_map *map, __u32 *key, __u64 *value,\n+\t\t\tvoid *context)\n+{\n+\tstruct key_ctx *c = context;\n+\n+\tc-\u003ekey = key;\n+\treturn 0;\n+}\n+\n+/* bpf_for_each_array_elem() passes a key from its own stack frame. */\n+SEC(\"?raw_tp\")\n+__failure __msg(\"invalid mem access 'scalar'\")\n+int array_park_map_key(void *ctx)\n+{\n+\tstruct key_ctx c = {};\n+\n+\tbpf_for_each_map_elem(\u0026map, park_key_cb, \u0026c, 0);\n+\tif (c.key)\n+\t\treturn *c.key;\n+\treturn 0;\n+}\n+\n+SEC(\"?raw_tp\")\n+__failure __msg(\"invalid mem access 'scalar'\")\n+int percpu_array_park_map_key(void *ctx)\n+{\n+\tstruct key_ctx c = {};\n+\n+\tbpf_for_each_map_elem(\u0026percpu_map, park_key_cb, \u0026c, 0);\n+\tif (c.key)\n+\t\treturn *c.key;\n+\treturn 0;\n+}\n+\n+/* A hash key points into the element, which outlives the callback. */\n+SEC(\"?raw_tp\")\n+__success\n+int hash_park_map_key(void *ctx)\n+{\n+\tstruct key_ctx c = {};\n+\n+\tbpf_for_each_map_elem(\u0026hash_map, park_key_cb, \u0026c, 0);\n+\tif (c.key)\n+\t\treturn *c.key;\n+\treturn 0;\n+}\n+\n+struct value_ctx {\n+\t__u64 *value;\n+};\n+\n+static long park_value_cb(struct bpf_map *map, __u32 *key, __u64 *value,\n+\t\t\t  void *context)\n+{\n+\tstruct value_ctx *c = context;\n+\n+\tc-\u003evalue = value;\n+\treturn 0;\n+}\n+\n+/* Only the key is frame-scoped; the element lives until map teardown. */\n+SEC(\"?raw_tp\")\n+__success\n+int array_park_map_value(void *ctx)\n+{\n+\tstruct value_ctx c = {};\n+\n+\tbpf_for_each_map_elem(\u0026map, park_value_cb, \u0026c, 0);\n+\tif (c.value)\n+\t\treturn *c.value;\n+\treturn 0;\n+}\n+\n char _license[] SEC(\"license\") = \"GPL\";\n"
}

2/1 2026/09/24 02:04 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "array_map_set_for_each_callback_args",
    "mark_frame_scoped_arg",
    "acquire_frame_reference",
    "release_frame_reference",
    "setup_func_entry",
    "prepare_func_exit",
    "check_reference_leak",
    "refsafe",
    "set_user_ringbuf_callback_state"
  ],
  "Reasoning": "The patch modifies the core BPF verifier and array map implementations to track frame-scoped callback arguments (introducing REF_TYPE_FRAME and frame reference tracking for bpf_for_each_map_elem and bpf_user_ringbuf_drain callbacks). It enforces reference lifetimes across callback entry/exit, updates verifier state pruning comparison (refsafe), and introduces runtime verifier bug assertions (verifier_bug_if, verifier_bug). These changes are directly reachable from userspace via the bpf(BPF_PROG_LOAD) syscall and warrant fuzzing to identify state tracking regressions, escapes, or assertion triggers.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 7dd9702816d25af1e106ce10b7bb4365e5a49703
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Sep 24 02:04:19 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index fd22db8bc6c50..d849e4873417e 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3118,6 +3118,7 @@ int bpf_iter_map_fill_link_info(const struct bpf_iter_aux_info *aux,
 int map_set_for_each_callback_args(struct bpf_verifier_env *env,
 				   struct bpf_func_state *caller,
 				   struct bpf_func_state *callee);
+void mark_frame_scoped_arg(struct bpf_func_state *callee, u32 regno);
 
 int bpf_percpu_hash_copy(struct bpf_map *map, void *key, void *value, u64 flags);
 int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value, u64 flags);
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 92f528c456052..51c310ea4e828 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -307,15 +307,13 @@ struct bpf_stack_state {
 };
 
 struct bpf_reference_state {
-	/* Each reference object has a type. Ensure REF_TYPE_PTR is zero to
-	 * default to pointer reference on zero initialization of a state.
-	 */
 	enum ref_state_type {
 		REF_TYPE_PTR		= (1 << 1),
 		REF_TYPE_IRQ		= (1 << 2),
 		REF_TYPE_LOCK		= (1 << 3),
 		REF_TYPE_RES_LOCK 	= (1 << 4),
 		REF_TYPE_RES_LOCK_IRQ	= (1 << 5),
+		REF_TYPE_FRAME		= (1 << 6),
 		REF_TYPE_LOCK_MASK	= REF_TYPE_LOCK | REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ,
 	} type;
 	/* Track each reference created with a unique id, even if the same
@@ -333,6 +331,8 @@ struct bpf_reference_state {
 		 * it matches on unlock.
 		 */
 		void *ptr;
+		/* For REF_TYPE_FRAME */
+		u32 frameno;
 	};
 };
 
@@ -387,6 +387,11 @@ struct bpf_func_state {
 	u32 callback_depth;
 	/* Instructions processed in this frame and callees on the current path. */
 	u32 insns_subtotal;
+	/*
+	 * Set for arguments valid until the frame is popped.
+	 * Consumed by setup_func_entry().
+	 */
+	u16 frame_scoped_args;
 
 	/* The following fields should be last. See copy_func_state() */
 	/* The state of the stack. Each element of the array describes BPF_REG_SIZE
diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index 0ce26b538075e..44bd229873cad 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -855,6 +855,20 @@ static u64 array_map_mem_usage(const struct bpf_map *map)
 	return usage;
 }
 
+static int array_map_set_for_each_callback_args(struct bpf_verifier_env *env,
+						struct bpf_func_state *caller,
+						struct bpf_func_state *callee)
+{
+	int err;
+
+	err = map_set_for_each_callback_args(env, caller, callee);
+	if (err)
+		return err;
+
+	mark_frame_scoped_arg(callee, BPF_REG_2);
+	return 0;
+}
+
 BTF_ID_LIST_SINGLE(array_map_btf_ids, struct, bpf_array)
 const struct bpf_map_ops array_map_ops = {
 	.map_meta_equal = array_map_meta_equal,
@@ -875,7 +889,7 @@ const struct bpf_map_ops array_map_ops = {
 	.map_check_btf = array_map_check_btf,
 	.map_lookup_batch = generic_map_lookup_batch,
 	.map_update_batch = generic_map_update_batch,
-	.map_set_for_each_callback_args = map_set_for_each_callback_args,
+	.map_set_for_each_callback_args = array_map_set_for_each_callback_args,
 	.map_for_each_callback = bpf_for_each_array_elem,
 	.map_mem_usage = array_map_mem_usage,
 	.map_btf_id = &array_map_btf_ids[0],
@@ -900,7 +914,7 @@ const struct bpf_map_ops percpu_array_map_ops = {
 	.map_check_btf = array_map_check_btf,
 	.map_lookup_batch = generic_map_lookup_batch,
 	.map_update_batch = generic_map_update_batch,
-	.map_set_for_each_callback_args = map_set_for_each_callback_args,
+	.map_set_for_each_callback_args = array_map_set_for_each_callback_args,
 	.map_for_each_callback = bpf_for_each_array_elem,
 	.map_mem_usage = array_map_mem_usage,
 	.map_btf_id = &array_map_btf_ids[0],
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 5ecfa86ed49f4..0610c3f6b3342 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -2233,6 +2233,9 @@ static void diag_print_mod(struct bpf_verifier_env *env, const struct bpf_diag_h
 								   "resource release invalidated "
 								   "this value";
 		break;
+	case BPF_DIAG_MOD_FRAME_RELEASE:
+		reason = "the callback that owned this value returned";
+		break;
 	case BPF_DIAG_MOD_PKT_DATA_CHANGE:
 		reason = "packet data may have moved";
 		break;
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index a4102fb049ece..b5cab4d79c1a2 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -22,6 +22,7 @@ enum bpf_diag_mod_reason {
 	BPF_DIAG_MOD_SPILL,
 	BPF_DIAG_MOD_VAR_WRITE,
 	BPF_DIAG_MOD_REF_RELEASE,
+	BPF_DIAG_MOD_FRAME_RELEASE,
 	BPF_DIAG_MOD_PKT_DATA_CHANGE,
 	BPF_DIAG_MOD_NON_OWN_REF,
 	BPF_DIAG_MOD_CALLER_SAVED,
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 66fb11b6c6a76..bf7efe5bcdcae 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -898,6 +898,10 @@ static bool refsafe(struct bpf_verifier_state *old, struct bpf_verifier_state *c
 			break;
 		case REF_TYPE_IRQ:
 			break;
+		case REF_TYPE_FRAME:
+			if (old->refs[i].frameno != cur->refs[i].frameno)
+				return false;
+			break;
 		case REF_TYPE_LOCK:
 		case REF_TYPE_RES_LOCK:
 		case REF_TYPE_RES_LOCK_IRQ:
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index d62c0f74cff5e..ba0c8c27b45f4 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -707,11 +707,15 @@ static void mark_dynptr_stack_regs(struct bpf_verifier_env *env,
 	__mark_dynptr_reg(sreg2, type, false, id, parent_id);
 }
 
-static void mark_dynptr_cb_reg(struct bpf_verifier_env *env,
-			       struct bpf_reg_state *reg,
+/*
+ * A callback dynptr argument is valid only until the frame is popped, so
+ * setup_func_entry() assigns its id along with the frame reference.
+ */
+static void mark_dynptr_cb_reg(struct bpf_func_state *callee, u32 regno,
 			       enum bpf_dynptr_type type)
 {
-	__mark_dynptr_reg(reg, type, true, ++env->id_gen, 0);
+	__mark_dynptr_reg(&callee->regs[regno], type, true, 0, 0);
+	mark_frame_scoped_arg(callee, regno);
 }
 
 static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
@@ -1474,14 +1478,13 @@ static int grow_stack_arg_slots(struct bpf_verifier_env *env,
 	return 0;
 }
 
-/* Acquire a pointer id from the env and update the state->refs to include
- * this new pointer reference.
- * On success, returns a valid pointer id to associate with the register
- * On failure, returns a negative errno.
+/* Append an entry to @state->refs and record the instruction that created it.
+ * The caller fills in the type and the id.
+ * On success, returns the new entry. On failure, returns NULL.
  */
-static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_env *env, int insn_idx)
+static struct bpf_reference_state *__acquire_reference_state(struct bpf_verifier_state *state,
+							     int insn_idx)
 {
-	struct bpf_verifier_state *state = env->cur_state;
 	int new_ofs = state->acquired_refs;
 	int err;
 
@@ -1493,6 +1496,12 @@ static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_e
 	return &state->refs[new_ofs];
 }
 
+static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_env *env,
+							   int insn_idx)
+{
+	return __acquire_reference_state(env->cur_state, insn_idx);
+}
+
 static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id)
 {
 	struct bpf_reference_state *s;
@@ -1507,6 +1516,31 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par
 	return s->id;
 }
 
+/* Acquire a reference owned by frame @frameno of @state */
+static int acquire_frame_reference(struct bpf_verifier_env *env, struct bpf_verifier_state *state,
+				   int insn_idx, u32 frameno)
+{
+	struct bpf_reference_state *s;
+
+	s = __acquire_reference_state(state, insn_idx);
+	if (!s)
+		return -ENOMEM;
+	s->type = REF_TYPE_FRAME;
+	s->id = ++env->id_gen;
+	s->frameno = frameno;
+	return s->id;
+}
+
+/*
+ * Declare that @regno in @callee holds a value that stops being valid once the
+ * frame is popped. setup_func_entry() turns each declaration into a frame-owned
+ * reference.
+ */
+void mark_frame_scoped_arg(struct bpf_func_state *callee, u32 regno)
+{
+	callee->frame_scoped_args |= BIT(regno);
+}
+
 static int acquire_lock_state(struct bpf_verifier_env *env, int insn_idx, enum ref_state_type type,
 			      int id, void *ptr)
 {
@@ -10135,7 +10169,8 @@ static int idstack_pop(struct bpf_idmap *idmap)
 }
 
 /* Release id and objects derived from it iteratively in a DFS manner */
-static int release_reference(struct bpf_verifier_env *env, int id)
+static int __release_reference(struct bpf_verifier_env *env, int id,
+			       enum bpf_diag_mod_reason reason)
 {
 	u32 mask = (1 << STACK_SPILL) | (1 << STACK_DYNPTR);
 	struct bpf_verifier_state *vstate = env->cur_state;
@@ -10175,7 +10210,7 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 				continue;
 
 			/* Free objects derived from the current object */
-			if (reg->parent_id == id) {
+			if (reg->parent_id == id && reg->id != id) {
 				err = idstack_push(idstack, reg->id);
 				if (err)
 					return err;
@@ -10190,14 +10225,12 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 
 				if (reg->dynptr.first_slot)
 					dyn_stack--;
-				bpf_diag_record_scrub(env, &dyn_stack[0].spilled_ptr,
-						      BPF_DIAG_MOD_REF_RELEASE);
-				bpf_diag_record_scrub(env, &dyn_stack[1].spilled_ptr,
-						      BPF_DIAG_MOD_REF_RELEASE);
+				bpf_diag_record_scrub(env, &dyn_stack[0].spilled_ptr, reason);
+				bpf_diag_record_scrub(env, &dyn_stack[1].spilled_ptr, reason);
 				invalidate_dynptr(env, dyn_stack);
 				continue;
 			}
-			bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_REF_RELEASE);
+			bpf_diag_record_scrub(env, reg, reason);
 			if (!stack || stack->slot_type[BPF_REG_SIZE - 1] == STACK_SPILL)
 				mark_reg_invalid(env, reg);
 		}));
@@ -10206,6 +10239,39 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 	return 0;
 }
 
+static int release_reference(struct bpf_verifier_env *env, int id)
+{
+	return __release_reference(env, id, BPF_DIAG_MOD_REF_RELEASE);
+}
+
+/* Find the first reference owned by frame @frameno, or 0 if it owns none. */
+static u32 frame_reference_id(struct bpf_verifier_state *state, u32 frameno)
+{
+	int i;
+
+	for (i = 0; i < state->acquired_refs; i++)
+		if (state->refs[i].type == REF_TYPE_FRAME &&
+		    state->refs[i].frameno == frameno)
+			return state->refs[i].id;
+
+	return 0;
+}
+
+static int release_frame_reference(struct bpf_verifier_env *env, int id)
+{
+	struct bpf_verifier_state *state = env->cur_state;
+	int i;
+
+	for (i = 0; i < state->acquired_refs; i++) {
+		if (state->refs[i].type != REF_TYPE_FRAME || state->refs[i].id != id)
+			continue;
+		release_reference_state(state, i);
+		break;
+	}
+
+	return __release_reference(env, id, BPF_DIAG_MOD_FRAME_RELEASE);
+}
+
 static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
 {
 	struct bpf_func_state *unused;
@@ -10301,7 +10367,8 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls
 			    struct bpf_verifier_state *state)
 {
 	struct bpf_func_state *caller, *callee;
-	int err;
+	u16 scoped_args;
+	int err, regno;
 
 	if (state->curframe + 1 >= MAX_CALL_FRAMES) {
 		verbose(env, "the call stack of %d frames is too deep\n",
@@ -10333,6 +10400,30 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls
 	if (err)
 		goto err_out;
 
+	scoped_args = callee->frame_scoped_args;
+	callee->frame_scoped_args = 0;
+	for (regno = 0; regno < MAX_BPF_REG; regno++) {
+		int id;
+
+		if (!(scoped_args & BIT(regno)))
+			continue;
+
+		id = acquire_frame_reference(env, state, callsite, callee->frameno);
+		if (id < 0) {
+			err = id;
+			goto err_out;
+		}
+		/*
+		 * The value is its own lifetime anchor: there is no associated
+		 * object to borrow from, only the frame. parent_id = id here
+		 * covers both possible derived references:
+		 *  - through the id (e.g. dynptr slice)
+		 *  - through parent_id (e.g. dynptr clone)
+		 */
+		callee->regs[regno].id = id;
+		callee->regs[regno].parent_id = id;
+	}
+
 	/* only increment it after check_reg_arg() finished */
 	state->curframe++;
 
@@ -10560,6 +10651,10 @@ static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *ins
 		if (err)
 			return err;
 
+		if (verifier_bug_if(callee->frame_scoped_args, env,
+				    "frame-scoped argument declared for async callback"))
+			return -EFAULT;
+
 		return 0;
 	}
 
@@ -10875,7 +10970,7 @@ static int set_user_ringbuf_callback_state(struct bpf_verifier_env *env,
 	 * callback_fn(const struct bpf_dynptr_t* dynptr, void *callback_ctx);
 	 */
 	bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_0]);
-	mark_dynptr_cb_reg(env, &callee->regs[BPF_REG_1], BPF_DYNPTR_TYPE_LOCAL);
+	mark_dynptr_cb_reg(callee, BPF_REG_1, BPF_DYNPTR_TYPE_LOCAL);
 	callee->regs[BPF_REG_2] = caller->regs[BPF_REG_3];
 
 	/* unused */
@@ -11033,7 +11128,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
 	struct bpf_func_state *caller, *callee;
 	struct bpf_reg_state *r0;
 	bool in_callback_fn;
-	u32 i, nregs;
+	u32 i, nregs, id;
 	int err;
 
 	callee = state->frame[state->curframe];
@@ -11106,6 +11201,17 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
 		verbose(env, "to caller at %d:\n", *insn_idx);
 		print_verifier_state(env, state, caller->frameno, true);
 	}
+
+	/*
+	 * Values the caller only guaranteed for the duration of the call stop
+	 * being valid here.
+	 */
+	while ((id = frame_reference_id(state, callee->frameno))) {
+		err = release_frame_reference(env, id);
+		if (err)
+			return err;
+	}
+
 	account_processed_insns(env, callee, caller);
 	/* clear everything in the callee. In case of exceptional exits using
 	 * bpf_throw, this will be done by copy_verifier_state for extra frames. */
@@ -11283,6 +11389,11 @@ static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exi
 		return 0;
 
 	for (i = 0; i < state->acquired_refs; i++) {
+		if (!exception_exit && state->refs[i].type == REF_TYPE_FRAME) {
+			verifier_bug(env, "frame %u reference id=%d alive at program exit",
+				     state->refs[i].frameno, state->refs[i].id);
+			return -EFAULT;
+		}
 		if (state->refs[i].type != REF_TYPE_PTR)
 			continue;
 		/* Allow struct_ops programs to return a referenced kptr back to
diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
index c32c6dab49bce..645f065c21f53 100644
--- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c
+++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
@@ -12,8 +12,8 @@ struct {
 	const char *err_msg;
 } cb_refs_tests[] = {
 	{ "underflow_prog", "R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_" },
-	{ "leak_prog", "Unreleased reference id=4 alloc_insn=3" }, /* alloc_insn=3{2,3} */
-	{ "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */
+	{ "leak_prog", "Unreleased reference id=5 alloc_insn=3" }, /* alloc_insn=3{2,3} */
+	{ "nested_cb", "Unreleased reference id=5 alloc_insn=2" }, /* alloc_insn=2{4,5} */
 	{ "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */
 };
 
diff --git a/tools/testing/selftests/bpf/progs/exceptions_fail.c b/tools/testing/selftests/bpf/progs/exceptions_fail.c
index 22503cf62e9f3..86a0667ba3484 100644
--- a/tools/testing/selftests/bpf/progs/exceptions_fail.c
+++ b/tools/testing/selftests/bpf/progs/exceptions_fail.c
@@ -31,6 +31,11 @@ struct {
 	__type(value, struct hmap_elem);
 } hmap SEC(".maps");
 
+struct {
+	__uint(type, BPF_MAP_TYPE_USER_RINGBUF);
+	__uint(max_entries, 4096);
+} user_ringbuf SEC(".maps");
+
 private(A) struct bpf_spin_lock lock;
 private(A) struct bpf_rb_root rbtree __contains(foo, node);
 
@@ -110,6 +115,21 @@ static int timer_cb(void *map, int *key, struct bpf_timer *timer)
 	return 0;
 }
 
+static long drain_cb(struct bpf_dynptr *dynptr, void *context)
+{
+	bpf_throw(0);
+	return 0;
+}
+
+SEC("?tc")
+__failure
+__msg("bpf_throw kfunc (insn {{[0-9]+}}) cannot be called from callback subprog {{[0-9]+}}")
+int reject_user_ringbuf_callback_throw(struct __sk_buff *ctx)
+{
+	bpf_user_ringbuf_drain(&user_ringbuf, drain_cb, NULL, 0);
+	return 0;
+}
+
 SEC("?tc")
 __failure __msg("cannot be called from callback subprog")
 int reject_async_callback_throw(struct __sk_buff *ctx)
diff --git a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
index c0d0422b8030b..a8d3acfa2bd25 100644
--- a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
+++ b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
@@ -1,9 +1,11 @@
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
 
+#include <stdbool.h>
 #include <linux/bpf.h>
 #include <bpf/bpf_helpers.h>
 #include "bpf_misc.h"
+#include "bpf_kfuncs.h"
 
 char _license[] SEC("license") = "GPL";
 
@@ -243,3 +245,144 @@ int user_ringbuf_callback_const_ptr_to_dynptr_reg_off(void *ctx)
 			       callback_adjust_bpf_dynptr_reg_off, NULL, 0);
 	return 0;
 }
+
+/* The sample goes back to the producer as soon as the callback returns. */
+struct dynptr_ctx {
+	struct bpf_dynptr *saved;
+};
+
+static long callback_park_dynptr(struct bpf_dynptr *dynptr, void *context)
+{
+	struct dynptr_ctx *c = context;
+
+	c->saved = dynptr;
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("the callback that owned this value returned")
+int user_ringbuf_callback_park_dynptr(void *ctx)
+{
+	struct dynptr_ctx c = {};
+	char buf[8] = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_dynptr, &c, 0);
+	if (c.saved)
+		bpf_dynptr_read(buf, sizeof(buf), c.saved, 0, 0);
+	return buf[0];
+}
+
+struct slice_ctx {
+	char *p;
+};
+
+static long callback_park_data_slice(struct bpf_dynptr *dynptr, void *context)
+{
+	struct slice_ctx *c = context;
+
+	c->p = bpf_dynptr_data(dynptr, 0, 8);
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("the callback that owned this value returned")
+int user_ringbuf_callback_park_data_slice(void *ctx)
+{
+	struct slice_ctx c = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_data_slice, &c, 0);
+	if (c.p)
+		return c.p[0];
+	return 0;
+}
+
+static long callback_park_kfunc_slice(struct bpf_dynptr *dynptr, void *context)
+{
+	struct slice_ctx *c = context;
+
+	c->p = bpf_dynptr_slice(dynptr, 0, NULL, 8);
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("the callback that owned this value returned")
+int user_ringbuf_callback_park_kfunc_slice(void *ctx)
+{
+	struct slice_ctx c = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_kfunc_slice, &c, 0);
+	if (c.p)
+		return c.p[0];
+	return 0;
+}
+
+struct clone_ctx {
+	struct bpf_dynptr clone;
+	__u64 armed;
+};
+
+static long callback_park_clone(struct bpf_dynptr *dynptr, void *context)
+{
+	struct clone_ctx *c = context;
+
+	bpf_dynptr_clone(dynptr, &c->clone);
+	c->armed = 1;
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("Expected an initialized dynptr as R3")
+int user_ringbuf_callback_park_clone(void *ctx)
+{
+	struct clone_ctx c = {};
+	char buf[8] = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_clone, &c, 0);
+	if (c.armed)
+		bpf_dynptr_read(buf, sizeof(buf), &c.clone, 0, 0);
+	return buf[0];
+}
+
+SEC("?raw_tp")
+__failure __msg("Expected an initialized dynptr as R1")
+int user_ringbuf_callback_park_clone_then_slice(void *ctx)
+{
+	struct clone_ctx c = {};
+	char *p;
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_clone, &c, 0);
+	if (c.armed) {
+		p = bpf_dynptr_data(&c.clone, 0, 8);
+		if (p)
+			return p[0];
+	}
+	return 0;
+}
+
+static long callback_park_inner(struct bpf_dynptr *dynptr, void *context)
+{
+	struct dynptr_ctx *c = context;
+
+	c->saved = dynptr;
+	return 0;
+}
+
+/* An inner drain's dynptr must not escape into the outer callback either. */
+static long callback_park_outer(struct bpf_dynptr *dynptr, void *context)
+{
+	struct dynptr_ctx inner = {};
+	char buf[8] = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_inner, &inner, 0);
+	if (inner.saved)
+		bpf_dynptr_read(buf, sizeof(buf), inner.saved, 0, 0);
+	return buf[0] ? 1 : 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("the callback that owned this value returned")
+int user_ringbuf_callback_nested_park_inner(void *ctx)
+{
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_outer, NULL, 0);
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c
index 1fbcc52283061..2e0c56888953b 100644
--- a/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c
+++ b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c
@@ -9,6 +9,20 @@ struct {
 	__type(value, __u64);
 } map SEC(".maps");
 
+struct {
+	__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+	__uint(max_entries, 8);
+	__type(key, __u32);
+	__type(value, __u64);
+} percpu_map SEC(".maps");
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 8);
+	__type(key, __u32);
+	__type(value, __u64);
+} hash_map SEC(".maps");
+
 struct {
 	__uint(type, BPF_MAP_TYPE_USER_RINGBUF);
 	__uint(max_entries, 8);
@@ -800,4 +814,81 @@ __naked void check_add_const_regsafe_off(void)
 	: __clobber_common);
 }
 
+struct key_ctx {
+	__u32 *key;
+};
+
+static long park_key_cb(struct bpf_map *map, __u32 *key, __u64 *value,
+			void *context)
+{
+	struct key_ctx *c = context;
+
+	c->key = key;
+	return 0;
+}
+
+/* bpf_for_each_array_elem() passes a key from its own stack frame. */
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int array_park_map_key(void *ctx)
+{
+	struct key_ctx c = {};
+
+	bpf_for_each_map_elem(&map, park_key_cb, &c, 0);
+	if (c.key)
+		return *c.key;
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int percpu_array_park_map_key(void *ctx)
+{
+	struct key_ctx c = {};
+
+	bpf_for_each_map_elem(&percpu_map, park_key_cb, &c, 0);
+	if (c.key)
+		return *c.key;
+	return 0;
+}
+
+/* A hash key points into the element, which outlives the callback. */
+SEC("?raw_tp")
+__success
+int hash_park_map_key(void *ctx)
+{
+	struct key_ctx c = {};
+
+	bpf_for_each_map_elem(&hash_map, park_key_cb, &c, 0);
+	if (c.key)
+		return *c.key;
+	return 0;
+}
+
+struct value_ctx {
+	__u64 *value;
+};
+
+static long park_value_cb(struct bpf_map *map, __u32 *key, __u64 *value,
+			  void *context)
+{
+	struct value_ctx *c = context;
+
+	c->value = value;
+	return 0;
+}
+
+/* Only the key is frame-scoped; the element lives until map teardown. */
+SEC("?raw_tp")
+__success
+int array_park_map_value(void *ctx)
+{
+	struct value_ctx c = {};
+
+	bpf_for_each_map_elem(&map, park_value_cb, &c, 0);
+	if (c.value)
+		return *c.value;
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";

3/2 2026/09/24 02:04 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 12222
							output: 30
							thoughts: 583
						

Thoughts:
**My Assessment of the Patch Series for Syzkaller Fuzzing**

Okay, so I've got this patch series in front of me, and my immediate concern is whether it warrants fuzzing with syzkaller. Let's break this down. First, this looks like a substantial modification to the BPF verifier. The files touched, including `include/linux/bpf.h`, `include/linux/bpf_verifier.h`, `kernel/bpf/arraymap.c`, `kernel/bpf/diagnostics.c`, `kernel/bpf/diagnostics.h`, `kernel/bpf/states.c`, `kernel/bpf/verifier.c`, and the self-tests under `tools/testing/selftests/bpf/...`, are screaming "potential for exploitation."

Specifically, it looks like this patch is introducing a new `REF_TYPE_FRAME` and related functions, like `mark_frame_scoped_arg()`, `acquire_frame_reference()`, `release_frame_reference()`, and `frame_reference_id()`. The BPF verifier is being modified in several key areas. We see `array_map_set_for_each_callback_args` marking `BPF_REG_2` as a frame-scoped argument. We also see modifications in the verifier itself: `mark_dynptr_cb_reg`, `setup_func_entry`, `prepare_func_exit`, `check_reference_leak`, and the use of `refsafe` in states.c, all deal with this frame reference mechanism.

I'm interpreting this as the verifier is gaining new ways to track and manage frame references, which is usually a code smell that it might be an area of attack. Remember the positive criteria from the documentation? It explicitly states that "Any logic modifications in... BPF" justifies fuzzing. This patch definitely ticks that box.

The BPF verifier is reachable through standard syscalls, specifically `bpf(BPF_PROG_LOAD, ...)`, and this makes it a very tempting target for syzkaller.

Therefore, my initial impression is that this patch series is *definitely* worth fuzzing. The changes to the BPF verifier, the new frame reference management, and the potential for memory corruption or reference leaks make it a prime candidate. Now, I need to look into what config flags are needed to enable this functionality, which involves checking the Kconfig or using grep for relevant configs. It's time to dig into the details to configure the fuzzing environment correctly.



4/2 2026/09/24 02:04 tool grepper
0m Args:
{
  "Expression": "BPF_SYSCALL",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/Kconfig=29=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-30-\nkernel/bpf/Kconfig:31:config BPF_SYSCALL\nkernel/bpf/Kconfig-32-\tbool \"Enable bpf() system call\"\n--\nkernel/bpf/Kconfig=62=config BPF_JIT_ALWAYS_ON\nkernel/bpf/Kconfig-63-\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\nkernel/bpf/Kconfig:64:\tdepends on BPF_SYSCALL \u0026\u0026 HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\nkernel/bpf/Kconfig-65-\thelp\n--\nkernel/bpf/Kconfig=77=config BPF_UNPRIV_DEFAULT_OFF\n--\nkernel/bpf/Kconfig-79-\tdefault y\nkernel/bpf/Kconfig:80:\tdepends on BPF_SYSCALL\nkernel/bpf/Kconfig-81-\thelp\n--\nkernel/bpf/Kconfig=96=config BPF_LSM\n--\nkernel/bpf/Kconfig-98-\tdepends on BPF_EVENTS\nkernel/bpf/Kconfig:99:\tdepends on BPF_SYSCALL\nkernel/bpf/Kconfig-100-\tdepends on SECURITY\n--\nkernel/bpf/Makefile=7=CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)\nkernel/bpf/Makefile-8-\nkernel/bpf/Makefile:9:obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o cnum.o log.o token.o liveness.o const_fold.o diagnostics.o\nkernel/bpf/Makefile:10:obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o\nkernel/bpf/Makefile:11:obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o\nkernel/bpf/Makefile:12:obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o\nkernel/bpf/Makefile:13:obj-$(CONFIG_BPF_SYSCALL) += bpf_local_storage.o bpf_task_storage.o\nkernel/bpf/Makefile:14:obj-$(CONFIG_BPF_SYSCALL) += fixups.o cfg.o states.o backtrack.o check_btf.o\nkernel/bpf/Makefile-15-obj-${CONFIG_BPF_LSM}\t  += bpf_inode_storage.o\nkernel/bpf/Makefile:16:obj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o\nkernel/bpf/Makefile-17-obj-$(CONFIG_BPF_JIT) += trampoline.o\nkernel/bpf/Makefile:18:obj-$(CONFIG_BPF_SYSCALL) += btf.o memalloc.o rqspinlock.o stream.o\nkernel/bpf/Makefile-19-ifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)\nkernel/bpf/Makefile:20:obj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o\nkernel/bpf/Makefile-21-endif\n--\nkernel/bpf/Makefile=23=ifeq ($(CONFIG_NET),y)\nkernel/bpf/Makefile:24:obj-$(CONFIG_BPF_SYSCALL) += devmap.o\nkernel/bpf/Makefile:25:obj-$(CONFIG_BPF_SYSCALL) += cpumap.o\nkernel/bpf/Makefile:26:obj-$(CONFIG_BPF_SYSCALL) += offload.o\nkernel/bpf/Makefile:27:obj-$(CONFIG_BPF_SYSCALL) += net_namespace.o\nkernel/bpf/Makefile:28:obj-$(CONFIG_BPF_SYSCALL) += tcx.o\nkernel/bpf/Makefile-29-endif\nkernel/bpf/Makefile=30=ifeq ($(CONFIG_KEYS),y)\nkernel/bpf/Makefile:31:obj-$(CONFIG_BPF_SYSCALL) += keys.o\nkernel/bpf/Makefile-32-endif\nkernel/bpf/Makefile=33=ifeq ($(CONFIG_PERF_EVENTS),y)\nkernel/bpf/Makefile:34:obj-$(CONFIG_BPF_SYSCALL) += stackmap.o\nkernel/bpf/Makefile-35-endif\nkernel/bpf/Makefile=36=ifeq ($(CONFIG_CGROUPS),y)\nkernel/bpf/Makefile:37:obj-$(CONFIG_BPF_SYSCALL) += cgroup_iter.o bpf_cgrp_storage.o\nkernel/bpf/Makefile-38-endif\n--\nkernel/bpf/Makefile=40=ifeq ($(CONFIG_INET),y)\nkernel/bpf/Makefile:41:obj-$(CONFIG_BPF_SYSCALL) += reuseport_array.o\nkernel/bpf/Makefile-42-endif\n--\nkernel/bpf/Makefile=46=ifeq ($(CONFIG_BPF_JIT),y)\nkernel/bpf/Makefile:47:obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o\nkernel/bpf/Makefile:48:obj-$(CONFIG_BPF_SYSCALL) += cpumask.o\nkernel/bpf/Makefile-49-# bpf_lsm_proto.o must precede bpf_lsm.o. The current pahole logic\n--\nkernel/bpf/Makefile=61=ifneq ($(CONFIG_CRYPTO),)\nkernel/bpf/Makefile:62:obj-$(CONFIG_BPF_SYSCALL) += crypto.o\nkernel/bpf/Makefile-63-endif\nkernel/bpf/Makefile=64=obj-$(CONFIG_BPF_PRELOAD) += preload/\nkernel/bpf/Makefile-65-\nkernel/bpf/Makefile:66:obj-$(CONFIG_BPF_SYSCALL) += relo_core.o\nkernel/bpf/Makefile:67:obj-$(CONFIG_BPF_SYSCALL) += btf_iter.o\nkernel/bpf/Makefile:68:obj-$(CONFIG_BPF_SYSCALL) += btf_relocate.o\nkernel/bpf/Makefile:69:obj-$(CONFIG_BPF_SYSCALL) += kmem_cache_iter.o\nkernel/bpf/Makefile-70-ifeq ($(CONFIG_DMA_SHARED_BUFFER),y)\nkernel/bpf/Makefile:71:obj-$(CONFIG_BPF_SYSCALL) += dmabuf_iter.o\nkernel/bpf/Makefile-72-endif\n--\nkernel/bpf/core.c=100=struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flags)\n--\nkernel/bpf/core.c-143-\nkernel/bpf/core.c:144:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-145-\tbpf_prog_stream_init(fp);\n--\nkernel/bpf/core.c=2468=EVAL4(PROG_NAME_LIST, 416, 448, 480, 512)\n--\nkernel/bpf/core.c-2471-\nkernel/bpf/core.c:2472:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-2473-int bpf_patch_call_args(struct bpf_insn *insn, u32 stack_depth)\n--\nkernel/bpf/core.c=3041=void __bpf_free_used_btfs(struct btf_mod_pair *used_btfs, u32 len)\nkernel/bpf/core.c-3042-{\nkernel/bpf/core.c:3043:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3044-\tstruct btf_mod_pair *btf_mod;\n--\nkernel/bpf/core.c=3062=static void bpf_prog_free_deferred(struct work_struct *work)\n--\nkernel/bpf/core.c-3067-\taux = container_of(work, struct bpf_prog_aux, work);\nkernel/bpf/core.c:3068:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3069-\tbpf_free_kfunc_btf_tab(aux-\u003ekfunc_btf_tab);\n--\nkernel/bpf/core.c=3488=static noinline void bpf_prog_report_may_goto_violation(void)\nkernel/bpf/core.c-3489-{\nkernel/bpf/core.c:3490:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3491-\tstruct bpf_stream_stage ss;\n--\nkernel/bpf/core.c=3528=__weak u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)\n--\nkernel/bpf/core.c-3532-\nkernel/bpf/core.c:3533:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3534-__weak bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write,\n--\nkernel/bpf/core.c=3559=EXPORT_TRACEPOINT_SYMBOL_GPL(xdp_bulk_tx);\nkernel/bpf/core.c-3560-\nkernel/bpf/core.c:3561:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3562-\n--\nkernel/bpf/preload/Kconfig=2=menuconfig BPF_PRELOAD\n--\nkernel/bpf/preload/Kconfig-4-\tdepends on BPF\nkernel/bpf/preload/Kconfig:5:\tdepends on BPF_SYSCALL\nkernel/bpf/preload/Kconfig-6-\t# The dependency on !COMPILE_TEST prevents it from being enabled\n--\nkernel/bpf/trampoline.c=1514=int __weak arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,\n--\nkernel/bpf/trampoline.c-1521-    defined(CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS) \u0026\u0026 \\\nkernel/bpf/trampoline.c:1522:    defined(CONFIG_BPF_SYSCALL)\nkernel/bpf/trampoline.c-1523-\n--\nkernel/bpf/trampoline.c=1769=void bpf_trampoline_multi_detach(struct bpf_prog *prog,\n--\nkernel/bpf/trampoline.c-1806-\t  CONFIG_HAVE_SINGLE_FTRACE_DIRECT_OPS \u0026\u0026\nkernel/bpf/trampoline.c:1807:\t  CONFIG_BPF_SYSCALL */\nkernel/bpf/trampoline.c-1808-\n"
}

5/2 2026/09/24 02:04 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16257
							output: 34
							thoughts: 84
						

Thoughts:
Okay, here's my understanding, formatted as requested:

**Digging into the BPF Configuration Landscape**

Right, so `CONFIG_BPF_SYSCALL` is the key to all this. It's the flag that actually triggers the compilation of critical BPF components, namely `verifier.c`, `arraymap.c`, `states.c`, and `diagnostics.c`. I need to make sure I'm not missing anything; are there any other config options that also come into play?

Okay, the more fundamental question is: what is the dependency chain here? It seems `CONFIG_BPF_SYSCALL` is the main enabler, but I need to confirm if it ultimately hinges on `CONFIG_BPF` somehow. Best to dig into `kernel/bpf/Kconfig` to clear that up. I need to understand the fundamental building blocks before I can analyze the more complex configurations that depend on them.



6/2 2026/09/24 02:04 tool read-file
0m Args:
{
  "File": "kernel/bpf/Kconfig",
  "FirstLine": 30,
  "LineCount": 40
}

Results:
{
  "Contents": "  30:\t\n  31:\tconfig BPF_SYSCALL\n  32:\t\tbool \"Enable bpf() system call\"\n  33:\t\tselect BPF\n  34:\t\tselect IRQ_WORK\n  35:\t\tselect NEED_TASKS_RCU\n  36:\t\tselect TASKS_TRACE_RCU\n  37:\t\tselect BINARY_PRINTF\n  38:\t\tselect NET_SOCK_MSG if NET\n  39:\t\tselect NET_XGRESS if NET\n  40:\t\tselect PAGE_POOL if NET\n  41:\t\tdefault n\n  42:\t\thelp\n  43:\t\t  Enable the bpf() system call that allows to manipulate BPF programs\n  44:\t\t  and maps via file descriptors.\n  45:\t\n  46:\tconfig BPF_JIT\n  47:\t\tbool \"Enable BPF Just In Time compiler\"\n  48:\t\tdepends on BPF\n  49:\t\tdepends on HAVE_CBPF_JIT || HAVE_EBPF_JIT\n  50:\t\tselect EXECMEM\n  51:\t\thelp\n  52:\t\t  BPF programs are normally handled by a BPF interpreter. This option\n  53:\t\t  allows the kernel to generate native code when a program is loaded\n  54:\t\t  into the kernel. This will significantly speed-up processing of BPF\n  55:\t\t  programs.\n  56:\t\n  57:\t\t  Note, an admin should enable this feature changing:\n  58:\t\t  /proc/sys/net/core/bpf_jit_enable\n  59:\t\t  /proc/sys/net/core/bpf_jit_harden   (optional)\n  60:\t\t  /proc/sys/net/core/bpf_jit_kallsyms (optional)\n  61:\t\n  62:\tconfig BPF_JIT_ALWAYS_ON\n  63:\t\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\n  64:\t\tdepends on BPF_SYSCALL \u0026\u0026 HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\n  65:\t\thelp\n  66:\t\t  Enables BPF JIT and removes BPF interpreter to avoid speculative\n  67:\t\t  execution of BPF instructions by the interpreter.\n  68:\t\n  69:\t\t  When CONFIG_BPF_JIT_ALWAYS_ON is enabled, /proc/sys/net/core/bpf_jit_enable\n"
}

7/2 2026/09/24 02:04 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 17184
							output: 35
							thoughts: 1096
						

Thoughts:
**Analyzing BPF Symbol Modifications**

Okay, let's break down these BPF symbol modifications. First, I need to get a clear picture of what's been changed. The diff includes several functions, and I want to understand their scope and how they're used, especially concerning static vs. non-static functions and their potential for coverage analysis.

1.  `mark_frame_scoped_arg`: This one's exported, which is good. Non-static, defined in `kernel/bpf/verifier.c` and declared in `include/linux/bpf.h`.

2.  `array_map_set_for_each_callback_args`: This is in `kernel/bpf/arraymap.c` and is used via `array_map_ops` and `percpu_array_map_ops`.  That's straightforward – part of the array map implementation.

3.  `__acquire_reference_state`, `acquire_reference_state`, `acquire_frame_reference`:  These are all verifier functions, which deals with reference management in frame references.

4.  `__release_reference`, `release_frame_reference`, `frame_reference_id`:  These are also reference tracking functions, the inverse of the acquirers.

5.  `setup_func_entry`: This is a static function in `verifier.c`.  I need to remember that static functions *do* have symbols in the kernel, though.

6.  `prepare_func_exit`: Another static function in `verifier.c`.

7.  `check_reference_leak`: Yet another static function in `verifier.c`.

8.  `push_callback_call`: Also static in `verifier.c`.

9.  `refsafe`: Found in `kernel/bpf/states.c`.

10. `mark_dynptr_cb_reg`: Found in `kernel/bpf/verifier.c`.

Now, the critical question: how will these changes affect coverage analysis?  The instructions mentioned static *inline* functions being problematic for symbol-based coverage tools. I'm focusing on the static functions here because the static inline functions are compiled into their call sites, and don't get their own symbol. The instructions emphasize that the compiler-inlined static functions lack distinct symbol addresses and cannot be directly targeted by symbol coverage filters. However, none of the functions in this patch are static inline in headers, which is good.  `mark_frame_scoped_arg` is a regular function, and the static functions have symbols.

So, I need to check how the functions within `verifier.c` call any functions involved in the array map callbacks.  That'll help me assess if these changes in `arraymap.c` are reachable through those static functions. This is important for determining which parts of the code are exercised. I want to trace the call paths to know if static functions in the `verifier.c` get called by the ones in `arraymap.c`.



8/2 2026/09/24 02:04 tool grepper
0m Args:
{
  "Expression": "map_set_for_each_callback_args",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/arraymap.c=836=static u64 array_map_mem_usage(const struct bpf_map *map)\n--\nkernel/bpf/arraymap.c-857-\nkernel/bpf/arraymap.c:858:static int array_map_set_for_each_callback_args(struct bpf_verifier_env *env,\nkernel/bpf/arraymap.c-859-\t\t\t\t\t\tstruct bpf_func_state *caller,\n--\nkernel/bpf/arraymap.c-863-\nkernel/bpf/arraymap.c:864:\terr = map_set_for_each_callback_args(env, caller, callee);\nkernel/bpf/arraymap.c-865-\tif (err)\n--\nkernel/bpf/arraymap.c=873=const struct bpf_map_ops array_map_ops = {\n--\nkernel/bpf/arraymap.c-891-\t.map_update_batch = generic_map_update_batch,\nkernel/bpf/arraymap.c:892:\t.map_set_for_each_callback_args = array_map_set_for_each_callback_args,\nkernel/bpf/arraymap.c-893-\t.map_for_each_callback = bpf_for_each_array_elem,\n--\nkernel/bpf/arraymap.c=900=const struct bpf_map_ops percpu_array_map_ops = {\n--\nkernel/bpf/arraymap.c-916-\t.map_update_batch = generic_map_update_batch,\nkernel/bpf/arraymap.c:917:\t.map_set_for_each_callback_args = array_map_set_for_each_callback_args,\nkernel/bpf/arraymap.c-918-\t.map_for_each_callback = bpf_for_each_array_elem,\n--\nkernel/bpf/hashtab.c=2361=const struct bpf_map_ops htab_map_ops = {\n--\nkernel/bpf/hashtab.c-2373-\t.map_seq_show_elem = htab_map_seq_show_elem,\nkernel/bpf/hashtab.c:2374:\t.map_set_for_each_callback_args = map_set_for_each_callback_args,\nkernel/bpf/hashtab.c-2375-\t.map_for_each_callback = bpf_for_each_hash_elem,\n--\nkernel/bpf/hashtab.c=2383=const struct bpf_map_ops htab_lru_map_ops = {\n--\nkernel/bpf/hashtab.c-2396-\t.map_seq_show_elem = htab_map_seq_show_elem,\nkernel/bpf/hashtab.c:2397:\t.map_set_for_each_callback_args = map_set_for_each_callback_args,\nkernel/bpf/hashtab.c-2398-\t.map_for_each_callback = bpf_for_each_hash_elem,\n--\nkernel/bpf/hashtab.c=2564=const struct bpf_map_ops htab_percpu_map_ops = {\n--\nkernel/bpf/hashtab.c-2576-\t.map_seq_show_elem = htab_percpu_map_seq_show_elem,\nkernel/bpf/hashtab.c:2577:\t.map_set_for_each_callback_args = map_set_for_each_callback_args,\nkernel/bpf/hashtab.c-2578-\t.map_for_each_callback = bpf_for_each_hash_elem,\n--\nkernel/bpf/hashtab.c=2586=const struct bpf_map_ops htab_lru_percpu_map_ops = {\n--\nkernel/bpf/hashtab.c-2597-\t.map_seq_show_elem = htab_percpu_map_seq_show_elem,\nkernel/bpf/hashtab.c:2598:\t.map_set_for_each_callback_args = map_set_for_each_callback_args,\nkernel/bpf/hashtab.c-2599-\t.map_for_each_callback = bpf_for_each_hash_elem,\n--\nkernel/bpf/hashtab.c=3508=const struct bpf_map_ops rhtab_map_ops = {\n--\nkernel/bpf/hashtab.c-3521-\t.map_seq_show_elem = rhtab_map_seq_show_elem,\nkernel/bpf/hashtab.c:3522:\t.map_set_for_each_callback_args = map_set_for_each_callback_args,\nkernel/bpf/hashtab.c-3523-\t.map_for_each_callback = bpf_each_rhash_elem,\n--\nkernel/bpf/verifier.c=10682=static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-10809-\nkernel/bpf/verifier.c:10810:int map_set_for_each_callback_args(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-10811-\t\t\t\t   struct bpf_func_state *caller,\n--\nkernel/bpf/verifier.c=10853=static int set_map_elem_callback_state(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-10863-\tmap = insn_aux-\u003emap_ptr_state.map_ptr;\nkernel/bpf/verifier.c:10864:\tif (!map-\u003eops-\u003emap_set_for_each_callback_args ||\nkernel/bpf/verifier.c-10865-\t    !map-\u003eops-\u003emap_for_each_callback) {\n--\nkernel/bpf/verifier.c-10869-\nkernel/bpf/verifier.c:10870:\terr = map-\u003eops-\u003emap_set_for_each_callback_args(env, caller, callee);\nkernel/bpf/verifier.c-10871-\tif (err)\n"
}

9/2 2026/09/24 02:04 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20593
							output: 29
							thoughts: 1069
						

Thoughts:
**Analyzing BPF Frame Reference Handling for Verifier Optimization**

Okay, let's break this down from a BPF verifier perspective. My initial thought process revolves around how frame references are managed, particularly within the callback mechanisms of BPF maps and user ring buffers. I've noted that `set_map_elem_callback_state` and `set_user_ringbuf_callback_state` are key entry points, eventually triggering verifier callbacks, specifically through functions like `array_map_set_for_each_callback_args` and `mark_dynptr_cb_reg`.

The lifecycle of these references seems to be centered on `setup_func_entry` (processing scoped arguments via `acquire_frame_reference`) and `prepare_func_exit` (using `frame_reference_id` and `release_frame_reference`), with a final `check_reference_leak` at verifier completion, and state comparisons with `REF_TYPE_FRAME` inside `refsafe`.

My next step is to assess the inlining potential of these functions. I've identified that `mark_frame_scoped_arg` *cannot* be inlined due to cross-unit linkage (exported to `include/linux/bpf.h`), making it a concrete, non-inlined symbol in `verifier.o`.  `array_map_set_for_each_callback_args` will be a symbol as it's address is taken. Functions like `setup_func_entry`, `prepare_func_exit`, `check_reference_leak`, `release_frame_reference`, and `refsafe` are all static and potentially sizable, which might impact the verification time.  A large function is less likely to be inlined by the compiler.

Given this, I have to consider the right target for fuzzing. While `bpf` is ubiquitous, I need to focus on *specific feature logic* or entry points. Therefore, the following are good targets: `mark_frame_scoped_arg`, `array_map_set_for_each_callback_args`, `acquire_frame_reference`, `release_frame_reference`, `setup_func_entry`, `prepare_func_exit`, `check_reference_leak`, and `refsafe`.

Finally, it seems `diag_print_mod` handles `BPF_DIAG_MOD_FRAME_RELEASE`, so I should inspect it, to understand its role. This all provides useful clues to build and focus fuzzing efforts.



10/2 2026/09/24 02:04 tool grepper
0m Args:
{
  "Expression": "diag_print_mod",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/diagnostics.c=2196=static const char *diag_mod_target_desc(struct bpf_verifier_env *env,\n--\nkernel/bpf/diagnostics.c-2210-\nkernel/bpf/diagnostics.c:2211:static void diag_print_mod(struct bpf_verifier_env *env, const struct bpf_diag_history_event *event)\nkernel/bpf/diagnostics.c-2212-{\n--\nkernel/bpf/diagnostics.c=2307=static void diag_print_history(struct bpf_verifier_env *env,\n--\nkernel/bpf/diagnostics.c-2372-\t\tcase BPF_DIAG_HISTORY_MOD:\nkernel/bpf/diagnostics.c:2373:\t\t\tdiag_print_mod(env, event);\nkernel/bpf/diagnostics.c-2374-\t\t\tbreak;\n"
}

11/2 2026/09/24 02:04 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22467
							output: 28
							thoughts: 663
						

Thoughts:
**BPF Verifier Fix Deep Dive**

Okay, let's dissect this BPF verifier change. My initial thought is to dive into `kernel/bpf/verifier.c` and specifically look at how `__release_reference` was modified. Right away, I see the core fix: that added `&& reg->id != id` condition within `__release_reference`. It's a classic scenario, previously if `parent_id == id` — something that `setup_func_entry` now sets up – we'd be in a world of hurt. Without that new check, we'd have been pushing `reg->id` onto the DFS idstack repeatedly, inviting infinite loops or a catastrophic stack overflow. That's a textbook reference management and state-tracking issue.

Now, let's see how `release_frame_reference` utilizes `__release_reference` and see if the fix carries through the rest of the code. Looks like `prepare_func_exit` is calling `release_frame_reference` within a `while` loop, and the addition of `verifier_bug_if` within `push_callback_call` confirms this. I'm also examining the `check_reference_leak` function as well. I'm seeing that reference leaks have been taken into account, which is a great sign. The verifier’s core logic seems tightly coupled with tracking references, managing memory lifetime, and incorporating assertions with `verifier_bug_if` and `verifier_bug`.

This entire change is right in the heart of the BPF verifier, directly accessible from userspace through `sys_bpf` (`BPF_PROG_LOAD`). From a fuzzing perspective, this change is a goldmine. BPF verifier changes are always high-value targets, and this looks prime for fuzzing.

Finally, I need to check the relevant kernel configs. `CONFIG_BPF_SYSCALL` is the obvious one, but I'm curious about dependencies related to `user_ringbuf` or `arraymap`. I'll verify how `BPF_MAP_TYPE_USER_RINGBUF` and `BPF_MAP_TYPE_ARRAY` are compiled to make sure they're not causing any conflicts. It's important to understand the bigger picture and potential interactions with other BPF features.



12/2 2026/09/24 02:04 tool grepper
0m Args:
{
  "Expression": "user_ringbuf",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/helpers.c=2106=bpf_base_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)\n--\nkernel/bpf/helpers.c-2190-\t\treturn \u0026bpf_loop_proto;\nkernel/bpf/helpers.c:2191:\tcase BPF_FUNC_user_ringbuf_drain:\nkernel/bpf/helpers.c:2192:\t\treturn \u0026bpf_user_ringbuf_drain_proto;\nkernel/bpf/helpers.c-2193-\tcase BPF_FUNC_ringbuf_reserve_dynptr:\n--\nkernel/bpf/liveness.c=1464=static int find_callback_subprog(struct bpf_verifier_env *env,\n--\nkernel/bpf/liveness.c-1494-\t\tbreak;\nkernel/bpf/liveness.c:1495:\tcase BPF_FUNC_user_ringbuf_drain:\nkernel/bpf/liveness.c:1496:\t\t/* user_ringbuf_drain(map, cb, ctx, flags): cb=R2, R3-\u003ecb R2 */\nkernel/bpf/liveness.c-1497-\t\tcb_reg = BPF_REG_2;\n--\nkernel/bpf/ringbuf.c=28=struct bpf_ringbuf {\n--\nkernel/bpf/ringbuf.c-41-\t *\nkernel/bpf/ringbuf.c:42:\t *    __bpf_user_ringbuf_peek() // lock acquired\nkernel/bpf/ringbuf.c-43-\t * -\u003e program callback_fn()\nkernel/bpf/ringbuf.c:44:\t * -\u003e __bpf_user_ringbuf_sample_release() // lock released\nkernel/bpf/ringbuf.c-45-\t *\n--\nkernel/bpf/ringbuf.c-48-\t * access to the ring buffer by kernel consumers, and return -EBUSY from\nkernel/bpf/ringbuf.c:49:\t * __bpf_user_ringbuf_peek() if the busy bit is held by another task.\nkernel/bpf/ringbuf.c-50-\t */\n--\nkernel/bpf/ringbuf.c=378=const struct bpf_map_ops ringbuf_map_ops = {\n--\nkernel/bpf/ringbuf.c-391-\nkernel/bpf/ringbuf.c:392:BTF_ID_LIST_SINGLE(user_ringbuf_map_btf_ids, struct, bpf_ringbuf_map)\nkernel/bpf/ringbuf.c:393:const struct bpf_map_ops user_ringbuf_map_ops = {\nkernel/bpf/ringbuf.c-394-\t.map_meta_equal = bpf_map_meta_equal,\n--\nkernel/bpf/ringbuf.c-403-\t.map_mem_usage = ringbuf_map_mem_usage,\nkernel/bpf/ringbuf.c:404:\t.map_btf_id = \u0026user_ringbuf_map_btf_ids[0],\nkernel/bpf/ringbuf.c-405-};\n--\nkernel/bpf/ringbuf.c=741=const struct bpf_func_proto bpf_ringbuf_discard_dynptr_proto = {\n--\nkernel/bpf/ringbuf.c-747-\nkernel/bpf/ringbuf.c:748:static int __bpf_user_ringbuf_peek(struct bpf_ringbuf *rb, void **sample, u32 *size)\nkernel/bpf/ringbuf.c-749-{\n--\nkernel/bpf/ringbuf.c-758-\nkernel/bpf/ringbuf.c:759:\t/* Synchronizes with smp_store_release() in __bpf_user_ringbuf_sample_release() */\nkernel/bpf/ringbuf.c-760-\tcons_pos = smp_load_acquire(\u0026rb-\u003econsumer_pos);\n--\nkernel/bpf/ringbuf.c-802-\nkernel/bpf/ringbuf.c:803:static void __bpf_user_ringbuf_sample_release(struct bpf_ringbuf *rb, size_t size, u64 flags)\nkernel/bpf/ringbuf.c-804-{\n--\nkernel/bpf/ringbuf.c-809-\t * prevents another task from writing to consumer_pos after it was read\nkernel/bpf/ringbuf.c:810:\t * by this task with smp_load_acquire() in __bpf_user_ringbuf_peek().\nkernel/bpf/ringbuf.c-811-\t */\n--\nkernel/bpf/ringbuf.c-816-\nkernel/bpf/ringbuf.c:817:BPF_CALL_4(bpf_user_ringbuf_drain, struct bpf_map *, map,\nkernel/bpf/ringbuf.c-818-\t   void *, callback_fn, void *, callback_ctx, u64, flags)\n--\nkernel/bpf/ringbuf.c-840-\nkernel/bpf/ringbuf.c:841:\t\terr = __bpf_user_ringbuf_peek(rb, \u0026sample, \u0026size);\nkernel/bpf/ringbuf.c-842-\t\tif (err) {\n--\nkernel/bpf/ringbuf.c-855-\t\tret = callback((uintptr_t)\u0026dynptr, (uintptr_t)callback_ctx, 0, 0, 0);\nkernel/bpf/ringbuf.c:856:\t\t__bpf_user_ringbuf_sample_release(rb, size, flags);\nkernel/bpf/ringbuf.c-857-\t}\n--\nkernel/bpf/ringbuf.c-872-\nkernel/bpf/ringbuf.c:873:const struct bpf_func_proto bpf_user_ringbuf_drain_proto = {\nkernel/bpf/ringbuf.c:874:\t.func\t\t= bpf_user_ringbuf_drain,\nkernel/bpf/ringbuf.c-875-\t.ret_type\t= RET_INTEGER,\n--\nkernel/bpf/verifier.c=543=static bool is_sync_callback_calling_function(enum bpf_func_id func_id)\n--\nkernel/bpf/verifier.c-547-\t       func_id == BPF_FUNC_loop ||\nkernel/bpf/verifier.c:548:\t       func_id == BPF_FUNC_user_ringbuf_drain;\nkernel/bpf/verifier.c-549-}\n--\nkernel/bpf/verifier.c=9690=static int check_map_func_compatibility(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-9719-\tcase BPF_MAP_TYPE_USER_RINGBUF:\nkernel/bpf/verifier.c:9720:\t\tif (func_id != BPF_FUNC_user_ringbuf_drain)\nkernel/bpf/verifier.c-9721-\t\t\tgoto error;\n--\nkernel/bpf/verifier.c-9850-\t\tbreak;\nkernel/bpf/verifier.c:9851:\tcase BPF_FUNC_user_ringbuf_drain:\nkernel/bpf/verifier.c-9852-\t\tif (map-\u003emap_type != BPF_MAP_TYPE_USER_RINGBUF)\n--\nkernel/bpf/verifier.c=10935=static int set_find_vma_callback_state(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-10962-\nkernel/bpf/verifier.c:10963:static int set_user_ringbuf_callback_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-10964-\t\t\t\t\t   struct bpf_func_state *caller,\n--\nkernel/bpf/verifier.c-10967-{\nkernel/bpf/verifier.c:10968:\t/* bpf_user_ringbuf_drain(struct bpf_map *map, void *callback_fn, void\nkernel/bpf/verifier.c-10969-\t *\t\t\t  callback_ctx, u64 flags);\n--\nkernel/bpf/verifier.c=11632=static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-11880-\t}\nkernel/bpf/verifier.c:11881:\tcase BPF_FUNC_user_ringbuf_drain:\nkernel/bpf/verifier.c-11882-\t\terr = push_callback_call(env, insn, insn_idx, meta.subprogno,\nkernel/bpf/verifier.c:11883:\t\t\t\t\t set_user_ringbuf_callback_state);\nkernel/bpf/verifier.c-11884-\t\tbreak;\n"
}

13/2 2026/09/24 02:04 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25945
							output: 253
							thoughts: 805
						

Thoughts:
**My Analysis of BPF Verifier Symbol Selection**

Okay, so I'm focusing on these BPF verifier changes. It's all built under `CONFIG_BPF_SYSCALL`, meaning that `BPF_SYSCALL` is *the* config flag. Got it. Now, what's crucial here are the symbols that are modified or directly relevant to the core logic.

I've identified these symbols in `kernel/bpf/verifier.c`: `mark_frame_scoped_arg`, `acquire_frame_reference`, `release_frame_reference`, `setup_func_entry`, `prepare_func_exit`, `check_reference_leak`, and `set_user_ringbuf_callback_state`.  Then, in `kernel/bpf/arraymap.c`, I have `array_map_set_for_each_callback_args` and in `kernel/bpf/states.c`, `refsafe`.

I double-checked to see if `mark_dynptr_cb_reg` was a separate symbol, but it appears small and might be inlined. However, `set_user_ringbuf_callback_state` *is* passed as a function pointer to `push_callback_call`, so it definitely has a symbol address. The same is true for `array_map_set_for_each_callback_args` which is in `array_map_ops`, and `mark_frame_scoped_arg` is a global function. The remaining symbols are static, meaning they are internal to their respective files.

The key is that these functions aren't ubiquitous. They are specialized to the BPF verifier and are involved in verifying program calls, callbacks, and state transitions, specifically focusing on reference tracking.

I went through a final reasoning check to solidify this: The core subsystem is the BPF verifier and arraymap, with a modification of reference tracking using  `REF_TYPE_FRAME` and frame-scoped arguments to prevent data leaks. This introduces new verifier invariants and bug checks. It's accessible from userspace via the `bpf()` syscall and `BPF_PROG_LOAD`. I've determined it is high-value for fuzzing.

My EnableConfigs: `["BPF_SYSCALL"]`.

I'm satisfied with the thoroughness of this analysis. Time to call `set-results`.



14/2 2026/09/24 02:05 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "array_map_set_for_each_callback_args",
    "mark_frame_scoped_arg",
    "acquire_frame_reference",
    "release_frame_reference",
    "setup_func_entry",
    "prepare_func_exit",
    "check_reference_leak",
    "refsafe",
    "set_user_ringbuf_callback_state"
  ],
  "Reasoning": "The patch modifies the core BPF verifier and array map implementations to track frame-scoped callback arguments (introducing REF_TYPE_FRAME and frame reference tracking for bpf_for_each_map_elem and bpf_user_ringbuf_drain callbacks). It enforces reference lifetimes across callback entry/exit, updates verifier state pruning comparison (refsafe), and introduces runtime verifier bug assertions (verifier_bug_if, verifier_bug). These changes are directly reachable from userspace via the bpf(BPF_PROG_LOAD) syscall and warrant fuzzing to identify state tracking regressions, escapes, or assertion triggers.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "array_map_set_for_each_callback_args",
    "mark_frame_scoped_arg",
    "acquire_frame_reference",
    "release_frame_reference",
    "setup_func_entry",
    "prepare_func_exit",
    "check_reference_leak",
    "refsafe",
    "set_user_ringbuf_callback_state"
  ],
  "Reasoning": "The patch modifies the core BPF verifier and array map implementations to track frame-scoped callback arguments (introducing REF_TYPE_FRAME and frame reference tracking for bpf_for_each_map_elem and bpf_user_ringbuf_drain callbacks). It enforces reference lifetimes across callback entry/exit, updates verifier state pruning comparison (refsafe), and introduces runtime verifier bug assertions (verifier_bug_if, verifier_bug). These changes are directly reachable from userspace via the bpf(BPF_PROG_LOAD) syscall and warrant fuzzing to identify state tracking regressions, escapes, or assertion triggers.",
  "WorthFuzzing": true
}

15/1 2026/09/24 02:05 action If
1m Args:
{
  "WorthFuzzing": true
}

Results:
null

16/2 2026/09/24 02:05 agent kmsan-evaluator
1m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch introduces verifier tracking for frame-scoped callback arguments (such as bpf_for_each_array_elem keys and user ringbuf dynptrs) by introducing REF_TYPE_FRAME reference states that are automatically invalidated when the callback frame exits.\n\nKASAN vs KMSAN applicability:\n- Memory Safety \u0026 Initialization: All newly added and modified fields (`frameno` in `struct bpf_reference_state`, `frame_scoped_args` in `struct bpf_func_state`) are allocated using zeroing mechanisms (`kzalloc_obj`, and `realloc_array` which explicitly memsets newly grown entries to zero). State copying via `copy_func_state` and `copy_reference_state` properly covers the new fields from already initialized memory. No uninitialized memory reads or conditional branches on uninitialized fields exist in the verifier.\n- Leakage to User Space: No kernel structures or uninitialized paddings are copied to user space. Verifier diagnostics and log messages format explicit string literals and initialized integers into the user-provided log buffer.\n- Failure Modes: The vulnerability class addressed by this patch is lifetime violation in verified BPF programs (preventing a BPF program from accessing pointers to callback stack frames after the callback returns). Such an escape at runtime results in use-after-free or stack-out-of-scope accesses, which are detected by KASAN.\n\nBecause the changes do not introduce or expose uninitialized memory reads or info-leaks, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 7dd9702816d25af1e106ce10b7bb4365e5a49703
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Sep 24 02:04:19 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index fd22db8bc6c50..d849e4873417e 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3118,6 +3118,7 @@ int bpf_iter_map_fill_link_info(const struct bpf_iter_aux_info *aux,
 int map_set_for_each_callback_args(struct bpf_verifier_env *env,
 				   struct bpf_func_state *caller,
 				   struct bpf_func_state *callee);
+void mark_frame_scoped_arg(struct bpf_func_state *callee, u32 regno);
 
 int bpf_percpu_hash_copy(struct bpf_map *map, void *key, void *value, u64 flags);
 int bpf_percpu_array_copy(struct bpf_map *map, void *key, void *value, u64 flags);
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 92f528c456052..51c310ea4e828 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -307,15 +307,13 @@ struct bpf_stack_state {
 };
 
 struct bpf_reference_state {
-	/* Each reference object has a type. Ensure REF_TYPE_PTR is zero to
-	 * default to pointer reference on zero initialization of a state.
-	 */
 	enum ref_state_type {
 		REF_TYPE_PTR		= (1 << 1),
 		REF_TYPE_IRQ		= (1 << 2),
 		REF_TYPE_LOCK		= (1 << 3),
 		REF_TYPE_RES_LOCK 	= (1 << 4),
 		REF_TYPE_RES_LOCK_IRQ	= (1 << 5),
+		REF_TYPE_FRAME		= (1 << 6),
 		REF_TYPE_LOCK_MASK	= REF_TYPE_LOCK | REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ,
 	} type;
 	/* Track each reference created with a unique id, even if the same
@@ -333,6 +331,8 @@ struct bpf_reference_state {
 		 * it matches on unlock.
 		 */
 		void *ptr;
+		/* For REF_TYPE_FRAME */
+		u32 frameno;
 	};
 };
 
@@ -387,6 +387,11 @@ struct bpf_func_state {
 	u32 callback_depth;
 	/* Instructions processed in this frame and callees on the current path. */
 	u32 insns_subtotal;
+	/*
+	 * Set for arguments valid until the frame is popped.
+	 * Consumed by setup_func_entry().
+	 */
+	u16 frame_scoped_args;
 
 	/* The following fields should be last. See copy_func_state() */
 	/* The state of the stack. Each element of the array describes BPF_REG_SIZE
diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index 0ce26b538075e..44bd229873cad 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -855,6 +855,20 @@ static u64 array_map_mem_usage(const struct bpf_map *map)
 	return usage;
 }
 
+static int array_map_set_for_each_callback_args(struct bpf_verifier_env *env,
+						struct bpf_func_state *caller,
+						struct bpf_func_state *callee)
+{
+	int err;
+
+	err = map_set_for_each_callback_args(env, caller, callee);
+	if (err)
+		return err;
+
+	mark_frame_scoped_arg(callee, BPF_REG_2);
+	return 0;
+}
+
 BTF_ID_LIST_SINGLE(array_map_btf_ids, struct, bpf_array)
 const struct bpf_map_ops array_map_ops = {
 	.map_meta_equal = array_map_meta_equal,
@@ -875,7 +889,7 @@ const struct bpf_map_ops array_map_ops = {
 	.map_check_btf = array_map_check_btf,
 	.map_lookup_batch = generic_map_lookup_batch,
 	.map_update_batch = generic_map_update_batch,
-	.map_set_for_each_callback_args = map_set_for_each_callback_args,
+	.map_set_for_each_callback_args = array_map_set_for_each_callback_args,
 	.map_for_each_callback = bpf_for_each_array_elem,
 	.map_mem_usage = array_map_mem_usage,
 	.map_btf_id = &array_map_btf_ids[0],
@@ -900,7 +914,7 @@ const struct bpf_map_ops percpu_array_map_ops = {
 	.map_check_btf = array_map_check_btf,
 	.map_lookup_batch = generic_map_lookup_batch,
 	.map_update_batch = generic_map_update_batch,
-	.map_set_for_each_callback_args = map_set_for_each_callback_args,
+	.map_set_for_each_callback_args = array_map_set_for_each_callback_args,
 	.map_for_each_callback = bpf_for_each_array_elem,
 	.map_mem_usage = array_map_mem_usage,
 	.map_btf_id = &array_map_btf_ids[0],
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 5ecfa86ed49f4..0610c3f6b3342 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -2233,6 +2233,9 @@ static void diag_print_mod(struct bpf_verifier_env *env, const struct bpf_diag_h
 								   "resource release invalidated "
 								   "this value";
 		break;
+	case BPF_DIAG_MOD_FRAME_RELEASE:
+		reason = "the callback that owned this value returned";
+		break;
 	case BPF_DIAG_MOD_PKT_DATA_CHANGE:
 		reason = "packet data may have moved";
 		break;
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index a4102fb049ece..b5cab4d79c1a2 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -22,6 +22,7 @@ enum bpf_diag_mod_reason {
 	BPF_DIAG_MOD_SPILL,
 	BPF_DIAG_MOD_VAR_WRITE,
 	BPF_DIAG_MOD_REF_RELEASE,
+	BPF_DIAG_MOD_FRAME_RELEASE,
 	BPF_DIAG_MOD_PKT_DATA_CHANGE,
 	BPF_DIAG_MOD_NON_OWN_REF,
 	BPF_DIAG_MOD_CALLER_SAVED,
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 66fb11b6c6a76..bf7efe5bcdcae 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -898,6 +898,10 @@ static bool refsafe(struct bpf_verifier_state *old, struct bpf_verifier_state *c
 			break;
 		case REF_TYPE_IRQ:
 			break;
+		case REF_TYPE_FRAME:
+			if (old->refs[i].frameno != cur->refs[i].frameno)
+				return false;
+			break;
 		case REF_TYPE_LOCK:
 		case REF_TYPE_RES_LOCK:
 		case REF_TYPE_RES_LOCK_IRQ:
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index d62c0f74cff5e..ba0c8c27b45f4 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -707,11 +707,15 @@ static void mark_dynptr_stack_regs(struct bpf_verifier_env *env,
 	__mark_dynptr_reg(sreg2, type, false, id, parent_id);
 }
 
-static void mark_dynptr_cb_reg(struct bpf_verifier_env *env,
-			       struct bpf_reg_state *reg,
+/*
+ * A callback dynptr argument is valid only until the frame is popped, so
+ * setup_func_entry() assigns its id along with the frame reference.
+ */
+static void mark_dynptr_cb_reg(struct bpf_func_state *callee, u32 regno,
 			       enum bpf_dynptr_type type)
 {
-	__mark_dynptr_reg(reg, type, true, ++env->id_gen, 0);
+	__mark_dynptr_reg(&callee->regs[regno], type, true, 0, 0);
+	mark_frame_scoped_arg(callee, regno);
 }
 
 static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
@@ -1474,14 +1478,13 @@ static int grow_stack_arg_slots(struct bpf_verifier_env *env,
 	return 0;
 }
 
-/* Acquire a pointer id from the env and update the state->refs to include
- * this new pointer reference.
- * On success, returns a valid pointer id to associate with the register
- * On failure, returns a negative errno.
+/* Append an entry to @state->refs and record the instruction that created it.
+ * The caller fills in the type and the id.
+ * On success, returns the new entry. On failure, returns NULL.
  */
-static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_env *env, int insn_idx)
+static struct bpf_reference_state *__acquire_reference_state(struct bpf_verifier_state *state,
+							     int insn_idx)
 {
-	struct bpf_verifier_state *state = env->cur_state;
 	int new_ofs = state->acquired_refs;
 	int err;
 
@@ -1493,6 +1496,12 @@ static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_e
 	return &state->refs[new_ofs];
 }
 
+static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_env *env,
+							   int insn_idx)
+{
+	return __acquire_reference_state(env->cur_state, insn_idx);
+}
+
 static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id)
 {
 	struct bpf_reference_state *s;
@@ -1507,6 +1516,31 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par
 	return s->id;
 }
 
+/* Acquire a reference owned by frame @frameno of @state */
+static int acquire_frame_reference(struct bpf_verifier_env *env, struct bpf_verifier_state *state,
+				   int insn_idx, u32 frameno)
+{
+	struct bpf_reference_state *s;
+
+	s = __acquire_reference_state(state, insn_idx);
+	if (!s)
+		return -ENOMEM;
+	s->type = REF_TYPE_FRAME;
+	s->id = ++env->id_gen;
+	s->frameno = frameno;
+	return s->id;
+}
+
+/*
+ * Declare that @regno in @callee holds a value that stops being valid once the
+ * frame is popped. setup_func_entry() turns each declaration into a frame-owned
+ * reference.
+ */
+void mark_frame_scoped_arg(struct bpf_func_state *callee, u32 regno)
+{
+	callee->frame_scoped_args |= BIT(regno);
+}
+
 static int acquire_lock_state(struct bpf_verifier_env *env, int insn_idx, enum ref_state_type type,
 			      int id, void *ptr)
 {
@@ -10135,7 +10169,8 @@ static int idstack_pop(struct bpf_idmap *idmap)
 }
 
 /* Release id and objects derived from it iteratively in a DFS manner */
-static int release_reference(struct bpf_verifier_env *env, int id)
+static int __release_reference(struct bpf_verifier_env *env, int id,
+			       enum bpf_diag_mod_reason reason)
 {
 	u32 mask = (1 << STACK_SPILL) | (1 << STACK_DYNPTR);
 	struct bpf_verifier_state *vstate = env->cur_state;
@@ -10175,7 +10210,7 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 				continue;
 
 			/* Free objects derived from the current object */
-			if (reg->parent_id == id) {
+			if (reg->parent_id == id && reg->id != id) {
 				err = idstack_push(idstack, reg->id);
 				if (err)
 					return err;
@@ -10190,14 +10225,12 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 
 				if (reg->dynptr.first_slot)
 					dyn_stack--;
-				bpf_diag_record_scrub(env, &dyn_stack[0].spilled_ptr,
-						      BPF_DIAG_MOD_REF_RELEASE);
-				bpf_diag_record_scrub(env, &dyn_stack[1].spilled_ptr,
-						      BPF_DIAG_MOD_REF_RELEASE);
+				bpf_diag_record_scrub(env, &dyn_stack[0].spilled_ptr, reason);
+				bpf_diag_record_scrub(env, &dyn_stack[1].spilled_ptr, reason);
 				invalidate_dynptr(env, dyn_stack);
 				continue;
 			}
-			bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_REF_RELEASE);
+			bpf_diag_record_scrub(env, reg, reason);
 			if (!stack || stack->slot_type[BPF_REG_SIZE - 1] == STACK_SPILL)
 				mark_reg_invalid(env, reg);
 		}));
@@ -10206,6 +10239,39 @@ static int release_reference(struct bpf_verifier_env *env, int id)
 	return 0;
 }
 
+static int release_reference(struct bpf_verifier_env *env, int id)
+{
+	return __release_reference(env, id, BPF_DIAG_MOD_REF_RELEASE);
+}
+
+/* Find the first reference owned by frame @frameno, or 0 if it owns none. */
+static u32 frame_reference_id(struct bpf_verifier_state *state, u32 frameno)
+{
+	int i;
+
+	for (i = 0; i < state->acquired_refs; i++)
+		if (state->refs[i].type == REF_TYPE_FRAME &&
+		    state->refs[i].frameno == frameno)
+			return state->refs[i].id;
+
+	return 0;
+}
+
+static int release_frame_reference(struct bpf_verifier_env *env, int id)
+{
+	struct bpf_verifier_state *state = env->cur_state;
+	int i;
+
+	for (i = 0; i < state->acquired_refs; i++) {
+		if (state->refs[i].type != REF_TYPE_FRAME || state->refs[i].id != id)
+			continue;
+		release_reference_state(state, i);
+		break;
+	}
+
+	return __release_reference(env, id, BPF_DIAG_MOD_FRAME_RELEASE);
+}
+
 static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
 {
 	struct bpf_func_state *unused;
@@ -10301,7 +10367,8 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls
 			    struct bpf_verifier_state *state)
 {
 	struct bpf_func_state *caller, *callee;
-	int err;
+	u16 scoped_args;
+	int err, regno;
 
 	if (state->curframe + 1 >= MAX_CALL_FRAMES) {
 		verbose(env, "the call stack of %d frames is too deep\n",
@@ -10333,6 +10400,30 @@ static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int calls
 	if (err)
 		goto err_out;
 
+	scoped_args = callee->frame_scoped_args;
+	callee->frame_scoped_args = 0;
+	for (regno = 0; regno < MAX_BPF_REG; regno++) {
+		int id;
+
+		if (!(scoped_args & BIT(regno)))
+			continue;
+
+		id = acquire_frame_reference(env, state, callsite, callee->frameno);
+		if (id < 0) {
+			err = id;
+			goto err_out;
+		}
+		/*
+		 * The value is its own lifetime anchor: there is no associated
+		 * object to borrow from, only the frame. parent_id = id here
+		 * covers both possible derived references:
+		 *  - through the id (e.g. dynptr slice)
+		 *  - through parent_id (e.g. dynptr clone)
+		 */
+		callee->regs[regno].id = id;
+		callee->regs[regno].parent_id = id;
+	}
+
 	/* only increment it after check_reg_arg() finished */
 	state->curframe++;
 
@@ -10560,6 +10651,10 @@ static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *ins
 		if (err)
 			return err;
 
+		if (verifier_bug_if(callee->frame_scoped_args, env,
+				    "frame-scoped argument declared for async callback"))
+			return -EFAULT;
+
 		return 0;
 	}
 
@@ -10875,7 +10970,7 @@ static int set_user_ringbuf_callback_state(struct bpf_verifier_env *env,
 	 * callback_fn(const struct bpf_dynptr_t* dynptr, void *callback_ctx);
 	 */
 	bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_0]);
-	mark_dynptr_cb_reg(env, &callee->regs[BPF_REG_1], BPF_DYNPTR_TYPE_LOCAL);
+	mark_dynptr_cb_reg(callee, BPF_REG_1, BPF_DYNPTR_TYPE_LOCAL);
 	callee->regs[BPF_REG_2] = caller->regs[BPF_REG_3];
 
 	/* unused */
@@ -11033,7 +11128,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
 	struct bpf_func_state *caller, *callee;
 	struct bpf_reg_state *r0;
 	bool in_callback_fn;
-	u32 i, nregs;
+	u32 i, nregs, id;
 	int err;
 
 	callee = state->frame[state->curframe];
@@ -11106,6 +11201,17 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
 		verbose(env, "to caller at %d:\n", *insn_idx);
 		print_verifier_state(env, state, caller->frameno, true);
 	}
+
+	/*
+	 * Values the caller only guaranteed for the duration of the call stop
+	 * being valid here.
+	 */
+	while ((id = frame_reference_id(state, callee->frameno))) {
+		err = release_frame_reference(env, id);
+		if (err)
+			return err;
+	}
+
 	account_processed_insns(env, callee, caller);
 	/* clear everything in the callee. In case of exceptional exits using
 	 * bpf_throw, this will be done by copy_verifier_state for extra frames. */
@@ -11283,6 +11389,11 @@ static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exi
 		return 0;
 
 	for (i = 0; i < state->acquired_refs; i++) {
+		if (!exception_exit && state->refs[i].type == REF_TYPE_FRAME) {
+			verifier_bug(env, "frame %u reference id=%d alive at program exit",
+				     state->refs[i].frameno, state->refs[i].id);
+			return -EFAULT;
+		}
 		if (state->refs[i].type != REF_TYPE_PTR)
 			continue;
 		/* Allow struct_ops programs to return a referenced kptr back to
diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
index c32c6dab49bce..645f065c21f53 100644
--- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c
+++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
@@ -12,8 +12,8 @@ struct {
 	const char *err_msg;
 } cb_refs_tests[] = {
 	{ "underflow_prog", "R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_" },
-	{ "leak_prog", "Unreleased reference id=4 alloc_insn=3" }, /* alloc_insn=3{2,3} */
-	{ "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */
+	{ "leak_prog", "Unreleased reference id=5 alloc_insn=3" }, /* alloc_insn=3{2,3} */
+	{ "nested_cb", "Unreleased reference id=5 alloc_insn=2" }, /* alloc_insn=2{4,5} */
 	{ "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */
 };
 
diff --git a/tools/testing/selftests/bpf/progs/exceptions_fail.c b/tools/testing/selftests/bpf/progs/exceptions_fail.c
index 22503cf62e9f3..86a0667ba3484 100644
--- a/tools/testing/selftests/bpf/progs/exceptions_fail.c
+++ b/tools/testing/selftests/bpf/progs/exceptions_fail.c
@@ -31,6 +31,11 @@ struct {
 	__type(value, struct hmap_elem);
 } hmap SEC(".maps");
 
+struct {
+	__uint(type, BPF_MAP_TYPE_USER_RINGBUF);
+	__uint(max_entries, 4096);
+} user_ringbuf SEC(".maps");
+
 private(A) struct bpf_spin_lock lock;
 private(A) struct bpf_rb_root rbtree __contains(foo, node);
 
@@ -110,6 +115,21 @@ static int timer_cb(void *map, int *key, struct bpf_timer *timer)
 	return 0;
 }
 
+static long drain_cb(struct bpf_dynptr *dynptr, void *context)
+{
+	bpf_throw(0);
+	return 0;
+}
+
+SEC("?tc")
+__failure
+__msg("bpf_throw kfunc (insn {{[0-9]+}}) cannot be called from callback subprog {{[0-9]+}}")
+int reject_user_ringbuf_callback_throw(struct __sk_buff *ctx)
+{
+	bpf_user_ringbuf_drain(&user_ringbuf, drain_cb, NULL, 0);
+	return 0;
+}
+
 SEC("?tc")
 __failure __msg("cannot be called from callback subprog")
 int reject_async_callback_throw(struct __sk_buff *ctx)
diff --git a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
index c0d0422b8030b..a8d3acfa2bd25 100644
--- a/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
+++ b/tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
@@ -1,9 +1,11 @@
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
 
+#include <stdbool.h>
 #include <linux/bpf.h>
 #include <bpf/bpf_helpers.h>
 #include "bpf_misc.h"
+#include "bpf_kfuncs.h"
 
 char _license[] SEC("license") = "GPL";
 
@@ -243,3 +245,144 @@ int user_ringbuf_callback_const_ptr_to_dynptr_reg_off(void *ctx)
 			       callback_adjust_bpf_dynptr_reg_off, NULL, 0);
 	return 0;
 }
+
+/* The sample goes back to the producer as soon as the callback returns. */
+struct dynptr_ctx {
+	struct bpf_dynptr *saved;
+};
+
+static long callback_park_dynptr(struct bpf_dynptr *dynptr, void *context)
+{
+	struct dynptr_ctx *c = context;
+
+	c->saved = dynptr;
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("the callback that owned this value returned")
+int user_ringbuf_callback_park_dynptr(void *ctx)
+{
+	struct dynptr_ctx c = {};
+	char buf[8] = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_dynptr, &c, 0);
+	if (c.saved)
+		bpf_dynptr_read(buf, sizeof(buf), c.saved, 0, 0);
+	return buf[0];
+}
+
+struct slice_ctx {
+	char *p;
+};
+
+static long callback_park_data_slice(struct bpf_dynptr *dynptr, void *context)
+{
+	struct slice_ctx *c = context;
+
+	c->p = bpf_dynptr_data(dynptr, 0, 8);
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("the callback that owned this value returned")
+int user_ringbuf_callback_park_data_slice(void *ctx)
+{
+	struct slice_ctx c = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_data_slice, &c, 0);
+	if (c.p)
+		return c.p[0];
+	return 0;
+}
+
+static long callback_park_kfunc_slice(struct bpf_dynptr *dynptr, void *context)
+{
+	struct slice_ctx *c = context;
+
+	c->p = bpf_dynptr_slice(dynptr, 0, NULL, 8);
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("the callback that owned this value returned")
+int user_ringbuf_callback_park_kfunc_slice(void *ctx)
+{
+	struct slice_ctx c = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_kfunc_slice, &c, 0);
+	if (c.p)
+		return c.p[0];
+	return 0;
+}
+
+struct clone_ctx {
+	struct bpf_dynptr clone;
+	__u64 armed;
+};
+
+static long callback_park_clone(struct bpf_dynptr *dynptr, void *context)
+{
+	struct clone_ctx *c = context;
+
+	bpf_dynptr_clone(dynptr, &c->clone);
+	c->armed = 1;
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("Expected an initialized dynptr as R3")
+int user_ringbuf_callback_park_clone(void *ctx)
+{
+	struct clone_ctx c = {};
+	char buf[8] = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_clone, &c, 0);
+	if (c.armed)
+		bpf_dynptr_read(buf, sizeof(buf), &c.clone, 0, 0);
+	return buf[0];
+}
+
+SEC("?raw_tp")
+__failure __msg("Expected an initialized dynptr as R1")
+int user_ringbuf_callback_park_clone_then_slice(void *ctx)
+{
+	struct clone_ctx c = {};
+	char *p;
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_clone, &c, 0);
+	if (c.armed) {
+		p = bpf_dynptr_data(&c.clone, 0, 8);
+		if (p)
+			return p[0];
+	}
+	return 0;
+}
+
+static long callback_park_inner(struct bpf_dynptr *dynptr, void *context)
+{
+	struct dynptr_ctx *c = context;
+
+	c->saved = dynptr;
+	return 0;
+}
+
+/* An inner drain's dynptr must not escape into the outer callback either. */
+static long callback_park_outer(struct bpf_dynptr *dynptr, void *context)
+{
+	struct dynptr_ctx inner = {};
+	char buf[8] = {};
+
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_inner, &inner, 0);
+	if (inner.saved)
+		bpf_dynptr_read(buf, sizeof(buf), inner.saved, 0, 0);
+	return buf[0] ? 1 : 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("the callback that owned this value returned")
+int user_ringbuf_callback_nested_park_inner(void *ctx)
+{
+	bpf_user_ringbuf_drain(&user_ringbuf, callback_park_outer, NULL, 0);
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c
index 1fbcc52283061..2e0c56888953b 100644
--- a/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c
+++ b/tools/testing/selftests/bpf/progs/verifier_iterating_callbacks.c
@@ -9,6 +9,20 @@ struct {
 	__type(value, __u64);
 } map SEC(".maps");
 
+struct {
+	__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+	__uint(max_entries, 8);
+	__type(key, __u32);
+	__type(value, __u64);
+} percpu_map SEC(".maps");
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 8);
+	__type(key, __u32);
+	__type(value, __u64);
+} hash_map SEC(".maps");
+
 struct {
 	__uint(type, BPF_MAP_TYPE_USER_RINGBUF);
 	__uint(max_entries, 8);
@@ -800,4 +814,81 @@ __naked void check_add_const_regsafe_off(void)
 	: __clobber_common);
 }
 
+struct key_ctx {
+	__u32 *key;
+};
+
+static long park_key_cb(struct bpf_map *map, __u32 *key, __u64 *value,
+			void *context)
+{
+	struct key_ctx *c = context;
+
+	c->key = key;
+	return 0;
+}
+
+/* bpf_for_each_array_elem() passes a key from its own stack frame. */
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int array_park_map_key(void *ctx)
+{
+	struct key_ctx c = {};
+
+	bpf_for_each_map_elem(&map, park_key_cb, &c, 0);
+	if (c.key)
+		return *c.key;
+	return 0;
+}
+
+SEC("?raw_tp")
+__failure __msg("invalid mem access 'scalar'")
+int percpu_array_park_map_key(void *ctx)
+{
+	struct key_ctx c = {};
+
+	bpf_for_each_map_elem(&percpu_map, park_key_cb, &c, 0);
+	if (c.key)
+		return *c.key;
+	return 0;
+}
+
+/* A hash key points into the element, which outlives the callback. */
+SEC("?raw_tp")
+__success
+int hash_park_map_key(void *ctx)
+{
+	struct key_ctx c = {};
+
+	bpf_for_each_map_elem(&hash_map, park_key_cb, &c, 0);
+	if (c.key)
+		return *c.key;
+	return 0;
+}
+
+struct value_ctx {
+	__u64 *value;
+};
+
+static long park_value_cb(struct bpf_map *map, __u32 *key, __u64 *value,
+			  void *context)
+{
+	struct value_ctx *c = context;
+
+	c->value = value;
+	return 0;
+}
+
+/* Only the key is frame-scoped; the element lives until map teardown. */
+SEC("?raw_tp")
+__success
+int array_park_map_value(void *ctx)
+{
+	struct value_ctx c = {};
+
+	bpf_for_each_map_elem(&map, park_value_cb, &c, 0);
+	if (c.value)
+		return *c.value;
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";

17/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 11434
							output: 33
							thoughts: 1651
						

Thoughts:
**BPF Verifier Frame-Scoped Argument Tracking and KMSAN Considerations**

Okay, so this patch is adding the ability for the BPF verifier to track "frame-scoped" arguments within BPF programs. Essentially, it's making sure that arguments passed to callbacks within a frame's context are handled safely in terms of lifetime and potential memory issues.

From a functional perspective, the patch introduces `REF_TYPE_FRAME` in `bpf_reference_state`.  This involves modifications in several key areas. The verifier now includes a `mark_frame_scoped_arg` function, used to flag registers as frame-scoped, and functions like `acquire_frame_reference` and `release_frame_reference` to manage the lifecycle of these references. We also see updates to the `setup_func_entry`, `prepare_func_exit` and `check_reference_leak` functions within the verifier to handle frame-scoped arguments correctly during function calls and program exits, respectively. The introduction of `frame_scoped_args` within `bpf_func_state` seems critical for tracking which registers are frame-scoped within each function. The inclusion of `BPF_DIAG_MOD_FRAME_RELEASE` indicates the addition of diagnostics around frame releases. This also involved the modification of `__release_reference`, adding new release reasons and new behavior.

Now, considering the KMSAN (KernelMemorySanitizer) aspect, and since my expertise is in this area, let's carefully evaluate if `NeedsKMSAN=true`. This is where it gets interesting.  The KMSAN rules are quite specific.  We *only* need KMSAN if the changes potentially expose or introduce risks of uninitialized memory *that aren't* caught by standard bug detectors like KASAN or LOCKDEP.

The key question is the allocation and initialization of `state->refs`.  I need to dig into the `__acquire_reference_state` function. The code mentions `__acquire_reference_state`, which is responsible for allocating the `struct bpf_reference_state`. I need to ensure that this struct is fully initialized, specifically the memory associated with `state->refs`, before being used. If it's not, and if `acquire_frame_reference` uses this uninitialized memory, we have a potential KMSAN issue. The same could be said for modifications to `struct bpf_reference_state`.
My analysis hinges on ensuring the correct initialization of the `struct bpf_reference_state` and related data structures. If the patch introduces any new data structures or code paths where uninitialized memory could be read, then `NeedsKMSAN=true`. Otherwise, given the patch's focus on tracking lifetimes and references, a cursory look suggests we might be able to get away with a KASAN check.



18/3 2026/09/24 02:05 tool grepper
0m Args:
{
  "Expression": "acquire_reference_state",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=1464=static int grow_stack_arg_slots(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-1484- */\nkernel/bpf/verifier.c:1485:static struct bpf_reference_state *__acquire_reference_state(struct bpf_verifier_state *state,\nkernel/bpf/verifier.c-1486-\t\t\t\t\t\t\t     int insn_idx)\n--\nkernel/bpf/verifier.c-1498-\nkernel/bpf/verifier.c:1499:static struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-1500-\t\t\t\t\t\t\t   int insn_idx)\nkernel/bpf/verifier.c-1501-{\nkernel/bpf/verifier.c:1502:\treturn __acquire_reference_state(env-\u003ecur_state, insn_idx);\nkernel/bpf/verifier.c-1503-}\n--\nkernel/bpf/verifier.c=1505=static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id)\n--\nkernel/bpf/verifier.c-1508-\nkernel/bpf/verifier.c:1509:\ts = acquire_reference_state(env, insn_idx);\nkernel/bpf/verifier.c-1510-\tif (!s)\n--\nkernel/bpf/verifier.c=1520=static int acquire_frame_reference(struct bpf_verifier_env *env, struct bpf_verifier_state *state,\n--\nkernel/bpf/verifier.c-1524-\nkernel/bpf/verifier.c:1525:\ts = __acquire_reference_state(state, insn_idx);\nkernel/bpf/verifier.c-1526-\tif (!s)\n--\nkernel/bpf/verifier.c=1544=static int acquire_lock_state(struct bpf_verifier_env *env, int insn_idx, enum ref_state_type type,\n--\nkernel/bpf/verifier.c-1549-\nkernel/bpf/verifier.c:1550:\ts = acquire_reference_state(env, insn_idx);\nkernel/bpf/verifier.c-1551-\tif (!s)\n--\nkernel/bpf/verifier.c=1565=static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx)\n--\nkernel/bpf/verifier.c-1569-\nkernel/bpf/verifier.c:1570:\ts = acquire_reference_state(env, insn_idx);\nkernel/bpf/verifier.c-1571-\tif (!s)\n"
}

19/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 14441
							output: 37
							thoughts: 26
						

Thoughts:
Okay, let's dive into this. Here's my take on those lines from `kernel/bpf/verifier.c`:

**Analyzing BPF Verifier Code (Lines 1470-1535)**

Alright, so I'm focusing in on lines 1470-1535 of `kernel/bpf/verifier.c`. This section likely deals with a specific aspect of the BPF verifier's logic, and my experience tells me it's probably related to some nuanced check or optimization within the program's safety validation. I need to understand what this code snippet is aiming to achieve; and why it matters in the grand scheme of BPF program execution.

My first thought is to quickly identify the function or block that these lines are part of. Is this an instruction-level check, an overall program state analysis, or something else entirely? Given the verifier's purpose, it's most likely related to data dependencies, register usage, or potential memory access violations.

I'm mentally preparing to look for key keywords or conditional statements that reveal the function of this chunk. I'll be particularly interested in any `if` statements that perform safety checks, or any loops that might be tracking register states or data flows. I'm expecting to see calls to other verifier functions or data structures that are essential to the verification process. Are they checking for register overflows or data races? Memory boundaries are always important.

Based on context, I'm anticipating this section will play a critical role in preventing malicious or errant BPF programs from compromising the kernel. Is it dealing with arithmetic operations? Memory access? Function calls? I need to understand the constraints and rules it's enforcing.

The goal here is to grasp the core logic and its significance for the BPF verifier's overall function. Understanding the intent of these lines will allow me to assess their effectiveness and identify any potential areas for improvement. Let's see what's actually in there!



20/3 2026/09/24 02:05 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 1470,
  "LineCount": 50
}

Results:
{
  "Contents": "1470:\t\t\treturn 0;\n1471:\t\n1472:\t\tstate-\u003estack_arg_regs = realloc_array(state-\u003estack_arg_regs, old_n, cnt,\n1473:\t\t\t\t\t\t      sizeof(struct bpf_reg_state));\n1474:\t\tif (!state-\u003estack_arg_regs)\n1475:\t\t\treturn -ENOMEM;\n1476:\t\n1477:\t\tstate-\u003eout_stack_arg_cnt = cnt;\n1478:\t\treturn 0;\n1479:\t}\n1480:\t\n1481:\t/* Append an entry to @state-\u003erefs and record the instruction that created it.\n1482:\t * The caller fills in the type and the id.\n1483:\t * On success, returns the new entry. On failure, returns NULL.\n1484:\t */\n1485:\tstatic struct bpf_reference_state *__acquire_reference_state(struct bpf_verifier_state *state,\n1486:\t\t\t\t\t\t\t\t     int insn_idx)\n1487:\t{\n1488:\t\tint new_ofs = state-\u003eacquired_refs;\n1489:\t\tint err;\n1490:\t\n1491:\t\terr = resize_reference_state(state, state-\u003eacquired_refs + 1);\n1492:\t\tif (err)\n1493:\t\t\treturn NULL;\n1494:\t\tstate-\u003erefs[new_ofs].insn_idx = insn_idx;\n1495:\t\n1496:\t\treturn \u0026state-\u003erefs[new_ofs];\n1497:\t}\n1498:\t\n1499:\tstatic struct bpf_reference_state *acquire_reference_state(struct bpf_verifier_env *env,\n1500:\t\t\t\t\t\t\t\t   int insn_idx)\n1501:\t{\n1502:\t\treturn __acquire_reference_state(env-\u003ecur_state, insn_idx);\n1503:\t}\n1504:\t\n1505:\tstatic int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id)\n1506:\t{\n1507:\t\tstruct bpf_reference_state *s;\n1508:\t\n1509:\t\ts = acquire_reference_state(env, insn_idx);\n1510:\t\tif (!s)\n1511:\t\t\treturn -ENOMEM;\n1512:\t\ts-\u003etype = REF_TYPE_PTR;\n1513:\t\ts-\u003eid = ++env-\u003eid_gen;\n1514:\t\ts-\u003eparent_id = parent_id;\n1515:\t\tbpf_diag_record_ref_acquire(env, insn_idx, s-\u003eid);\n1516:\t\treturn s-\u003eid;\n1517:\t}\n1518:\t\n1519:\t/* Acquire a reference owned by frame @frameno of @state */\n"
}

21/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 15697
							output: 30
							thoughts: 12
						

Thoughts:
Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:

**Deep Dive into `resize_reference_state`**

Alright, let's get down to brass tacks. I need to take a look at the `resize_reference_state` function. I'm assuming it handles the logic for… well, resizing the reference state. I need to refresh my memory on the critical nuances of its implementation. I'm particularly interested in how it manages memory allocation, data copying, and potential edge cases. Does it use any optimizations for different data types or input sizes? Are there any opportunities for parallelization? I need to analyze its time complexity and space complexity, to get an understanding of the impact it has on the overall performance of the algorithm/system. What data structures are involved and how are they modified? I also want to make sure I understand the error handling: what exceptions are caught, and is there a proper mechanism to report unexpected behaviour, in case of failures? I'll probably start by quickly scanning the function signature, then step through the most relevant parts with the debugger. I want to see how this function plays with the other related code in the system, to gain a thorough understanding of the logic. I need to be sure this function is rock-solid and behaves as expected in all conditions, especially under heavy load. I want to be certain about the invariants it preserves and whether it introduces any potential race conditions. It will be helpful to look at the unit tests around it.



22/3 2026/09/24 02:05 tool grepper
0m Args:
{
  "Expression": "resize_reference_state",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=1401=static int copy_stack_state(struct bpf_func_state *dst, const struct bpf_func_state *src)\n--\nkernel/bpf/verifier.c-1425-\nkernel/bpf/verifier.c:1426:static int resize_reference_state(struct bpf_verifier_state *state, size_t n)\nkernel/bpf/verifier.c-1427-{\n--\nkernel/bpf/verifier.c=1485=static struct bpf_reference_state *__acquire_reference_state(struct bpf_verifier_state *state,\n--\nkernel/bpf/verifier.c-1490-\nkernel/bpf/verifier.c:1491:\terr = resize_reference_state(state, state-\u003eacquired_refs + 1);\nkernel/bpf/verifier.c-1492-\tif (err)\n"
}

23/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16301
							output: 37
							thoughts: 11
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**Analyzing the Call to `resize_reference_state`**

Alright, the code tells me to focus on `resize_reference_state`. Immediately, my mental gears start turning. I need to understand what this function is doing, which implies that I need to be aware of the state, likely some form of data structure or memory block, that it's acting upon. "Resize" is the keyword here - I'm thinking about memory allocation, deallocation, possibly re-allocation, and the implications of data movement. Is this a simple change in size, or does this potentially involve copying data, invalidating pointers, or requiring a more complex re-arrangement?

I'm instantly considering the context of the call. Where is this function being invoked? Is it within a loop, triggered by external events, or part of a larger initialization sequence? Knowing the calling code will be critical to understanding the performance impact and potential side effects of this resize operation. Am I dealing with a specific data type? Is there metadata associated with this reference state that I'll need to consider? Any information about how the reference state is used is crucial.

I need to quickly assess potential performance bottlenecks. If the data is large, this could be an expensive operation. Is there any existing pre-allocation strategy in place, or any opportunities for optimizations? What are the implications for thread-safety if this state is accessed concurrently? Finally, I need to evaluate the possibility of errors: can the resize fail? What happens if memory allocation fails? Are there sufficient error handling mechanisms in place? It's a quick but crucial analysis.



24/3 2026/09/24 02:05 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 1425,
  "LineCount": 40
}

Results:
{
  "Contents": "1425:\t\n1426:\tstatic int resize_reference_state(struct bpf_verifier_state *state, size_t n)\n1427:\t{\n1428:\t\tstate-\u003erefs = realloc_array(state-\u003erefs, state-\u003eacquired_refs, n,\n1429:\t\t\t\t\t    sizeof(struct bpf_reference_state));\n1430:\t\tif (!state-\u003erefs)\n1431:\t\t\treturn -ENOMEM;\n1432:\t\n1433:\t\tstate-\u003eacquired_refs = n;\n1434:\t\treturn 0;\n1435:\t}\n1436:\t\n1437:\t/* Possibly update state-\u003eallocated_stack to be at least size bytes. Also\n1438:\t * possibly update the function's high-water mark in its bpf_subprog_info.\n1439:\t */\n1440:\tstatic int grow_stack_state(struct bpf_verifier_env *env, struct bpf_func_state *state, int size)\n1441:\t{\n1442:\t\tsize_t old_n = state-\u003eallocated_stack / BPF_REG_SIZE, n;\n1443:\t\n1444:\t\t/* The stack size is always a multiple of BPF_REG_SIZE. */\n1445:\t\tsize = round_up(size, BPF_REG_SIZE);\n1446:\t\tn = size / BPF_REG_SIZE;\n1447:\t\n1448:\t\tif (old_n \u003e= n)\n1449:\t\t\treturn 0;\n1450:\t\n1451:\t\tstate-\u003estack = realloc_array(state-\u003estack, old_n, n, sizeof(struct bpf_stack_state));\n1452:\t\tif (!state-\u003estack)\n1453:\t\t\treturn -ENOMEM;\n1454:\t\n1455:\t\tstate-\u003eallocated_stack = size;\n1456:\t\n1457:\t\t/* update known max for given subprogram */\n1458:\t\tif (env-\u003esubprog_info[state-\u003esubprogno].stack_depth \u003c size)\n1459:\t\t\tenv-\u003esubprog_info[state-\u003esubprogno].stack_depth = size;\n1460:\t\n1461:\t\treturn 0;\n1462:\t}\n1463:\t\n1464:\tstatic int grow_stack_arg_slots(struct bpf_verifier_env *env,\n"
}

25/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 17349
							output: 29
							thoughts: 13
						

Thoughts:
Okay, let's dive into this `realloc_array` function, shall we? Here's my take on it:

**Deconstructing `realloc_array`**

Alright, I need to get a handle on this `realloc_array` function. From what I know, it's probably designed to combine the memory allocation functionality of `realloc` with an enhanced security or overflow protection mechanism. The name alone hints at it. I'm guessing it aims to prevent integer overflows during the calculation of the total size for the reallocation, specifically when dealing with arrays where the number of elements is dynamically determined. So, I need to understand if it's handling potential issues that regular `realloc` might miss. Is it doing a multiplication check before calling `realloc`? Does it wrap around? Does it introduce any significant overhead? Does it handle the special case of allocating a zero-sized array gracefully? And most importantly, what are the use-cases where I should reach for this specific method, instead of relying on the more 'traditional' approaches? Time to delve into the details!



26/3 2026/09/24 02:05 tool grepper
0m Args:
{
  "Expression": "realloc_array",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/core.c=830=int bpf_jit_add_poke_descriptor(struct bpf_prog *prog,\n--\nkernel/bpf/core.c-852-\nkernel/bpf/core.c:853:\ttab = krealloc_array(tab, size, sizeof(*poke), GFP_KERNEL);\nkernel/bpf/core.c-854-\tif (!tab)\n--\nkernel/bpf/verifier.c=1332=static void *copy_array(void *dst, const void *src, size_t n, size_t size, gfp_t flags)\n--\nkernel/bpf/verifier.c-1358- *\nkernel/bpf/verifier.c:1359: * Contrary to krealloc_array, does not free arr if new_n is zero.\nkernel/bpf/verifier.c-1360- */\nkernel/bpf/verifier.c:1361:static void *realloc_array(void *arr, size_t old_n, size_t new_n, size_t size)\nkernel/bpf/verifier.c-1362-{\n--\nkernel/bpf/verifier.c=1426=static int resize_reference_state(struct bpf_verifier_state *state, size_t n)\nkernel/bpf/verifier.c-1427-{\nkernel/bpf/verifier.c:1428:\tstate-\u003erefs = realloc_array(state-\u003erefs, state-\u003eacquired_refs, n,\nkernel/bpf/verifier.c-1429-\t\t\t\t    sizeof(struct bpf_reference_state));\n--\nkernel/bpf/verifier.c=1440=static int grow_stack_state(struct bpf_verifier_env *env, struct bpf_func_state *state, int size)\n--\nkernel/bpf/verifier.c-1450-\nkernel/bpf/verifier.c:1451:\tstate-\u003estack = realloc_array(state-\u003estack, old_n, n, sizeof(struct bpf_stack_state));\nkernel/bpf/verifier.c-1452-\tif (!state-\u003estack)\n--\nkernel/bpf/verifier.c=1464=static int grow_stack_arg_slots(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-1471-\nkernel/bpf/verifier.c:1472:\tstate-\u003estack_arg_regs = realloc_array(state-\u003estack_arg_regs, old_n, cnt,\nkernel/bpf/verifier.c-1473-\t\t\t\t\t      sizeof(struct bpf_reg_state));\n"
}

27/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18288
							output: 37
							thoughts: 18
						

Thoughts:
Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:

**Diving into `realloc_array`**

Alright, let's zero in on `realloc_array`. Specifically, I want to jump over to line 1358 and take a closer look. I'm already pretty familiar with the general principles of memory management and reallocation, but I want to refresh my memory on the specific implementation details here. I'm especially interested in how this particular function handles potential overflows when calculating the new size, and any edge cases it might be designed to address. I'm anticipating this will involve some clever bit manipulation or checks, and I want to see how robust this particular implementation is. Time to dig in and see what's what.



28/3 2026/09/24 02:05 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 1358,
  "LineCount": 40
}

Results:
{
  "Contents": "1358:\t *\n1359:\t * Contrary to krealloc_array, does not free arr if new_n is zero.\n1360:\t */\n1361:\tstatic void *realloc_array(void *arr, size_t old_n, size_t new_n, size_t size)\n1362:\t{\n1363:\t\tsize_t alloc_size;\n1364:\t\tvoid *new_arr;\n1365:\t\n1366:\t\tif (!new_n || old_n == new_n)\n1367:\t\t\tgoto out;\n1368:\t\n1369:\t\talloc_size = kmalloc_size_roundup(size_mul(new_n, size));\n1370:\t\tnew_arr = krealloc(arr, alloc_size, GFP_KERNEL_ACCOUNT);\n1371:\t\tif (!new_arr) {\n1372:\t\t\tkfree(arr);\n1373:\t\t\treturn NULL;\n1374:\t\t}\n1375:\t\tarr = new_arr;\n1376:\t\n1377:\t\tif (new_n \u003e old_n)\n1378:\t\t\tmemset(arr + old_n * size, 0, (new_n - old_n) * size);\n1379:\t\n1380:\tout:\n1381:\t\treturn arr ? arr : ZERO_SIZE_PTR;\n1382:\t}\n1383:\t\n1384:\tstatic int copy_reference_state(struct bpf_verifier_state *dst, const struct bpf_verifier_state *src)\n1385:\t{\n1386:\t\tdst-\u003erefs = copy_array(dst-\u003erefs, src-\u003erefs, src-\u003eacquired_refs,\n1387:\t\t\t\t       sizeof(struct bpf_reference_state), GFP_KERNEL_ACCOUNT);\n1388:\t\tif (!dst-\u003erefs)\n1389:\t\t\treturn -ENOMEM;\n1390:\t\n1391:\t\tdst-\u003eacquired_refs = src-\u003eacquired_refs;\n1392:\t\tdst-\u003eactive_locks = src-\u003eactive_locks;\n1393:\t\tdst-\u003eactive_preempt_locks = src-\u003eactive_preempt_locks;\n1394:\t\tdst-\u003eactive_rcu_locks = src-\u003eactive_rcu_locks;\n1395:\t\tdst-\u003eactive_irq_id = src-\u003eactive_irq_id;\n1396:\t\tdst-\u003eactive_lock_id = src-\u003eactive_lock_id;\n1397:\t\tdst-\u003eactive_lock_ptr = src-\u003eactive_lock_ptr;\n"
}

29/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19193
							output: 30
							thoughts: 276
						

Thoughts:
**Examining the BPF Verifier's Memory Management**

Okay, so `realloc_array` *does* zero out new allocations with `memset`. Good, that's what I'd expect for safety. And, `copy_reference_state` just copies the size of `struct bpf_reference_state`. Nothing unexpected there. Now, I need to make sure that `frame_scoped_args` is handled correctly during state copies. Let me check `copy_func_state`.

Hold on... I need to double-check something first. In `include/linux/bpf_verifier.h`, I see `struct bpf_func_state` contains: `u32 callback_depth`, `u32 insns_subtotal`, and crucially, `u16 frame_scoped_args`. And then there is a comment: `/* The following fields should be last. See copy_func_state() */`. Aha! That's a good hint. Now, let's go back and examine `copy_func_state` with this in mind!



30/3 2026/09/24 02:05 tool grepper
0m Args:
{
  "Expression": "copy_func_state",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=1705=void bpf_free_verifier_state(struct bpf_verifier_state *state,\n--\nkernel/bpf/verifier.c-1722- */\nkernel/bpf/verifier.c:1723:static int copy_func_state(struct bpf_func_state *dst,\nkernel/bpf/verifier.c-1724-\t\t\t   const struct bpf_func_state *src)\n--\nkernel/bpf/verifier.c=1732=int bpf_copy_verifier_state(struct bpf_verifier_state *dst_state,\n--\nkernel/bpf/verifier.c-1773-\t\t}\nkernel/bpf/verifier.c:1774:\t\terr = copy_func_state(dst, src-\u003eframe[i]);\nkernel/bpf/verifier.c-1775-\t\tif (err)\n"
}

31/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19967
							output: 37
							thoughts: 22
						

Thoughts:
Okay, let's dive into this. Here's my take on understanding this code:

**Analyzing `copy_func_state` in `kernel/bpf/verifier.c`**

Alright, so the task at hand is to understand the workings of `copy_func_state` within the BPF verifier. From my perspective, this function is critical. It's essentially responsible for capturing and propagating the state of a BPF function call. We need to be able to replicate and modify that state effectively for successful code execution. It sounds like this copy function is critical to accurately represent the states.

I'm assuming this function handles aspects like register values, memory states, potential stack modifications, and perhaps even some metadata related to the function's execution context. How meticulously it handles these things has implications for ensuring the safety and correctness of the BPF programs that it verifies. The function must manage the copy process across various control-flow constructs, such as loops, conditional branches, and function calls.

My goal in looking at this specific function is to determine how well it manages the complexity of copying and modifying the state during function calls. I'm keen to evaluate its capabilities in ensuring that the verifier does not miss corner cases where the state is mis-managed, leading to potential exploits or crashes. I want to see if it makes reasonable assumptions, or if it covers all the necessary possibilities to maintain state integrity.



32/3 2026/09/24 02:05 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 1718,
  "LineCount": 25
}

Results:
{
  "Contents": "1718:\t}\n1719:\t\n1720:\t/* copy verifier state from src to dst growing dst stack space\n1721:\t * when necessary to accommodate larger src stack\n1722:\t */\n1723:\tstatic int copy_func_state(struct bpf_func_state *dst,\n1724:\t\t\t\t   const struct bpf_func_state *src)\n1725:\t{\n1726:\t\tmemcpy(dst, src, offsetof(struct bpf_func_state, stack));\n1727:\t\t/* Instruction accounting is path-local, not part of verifier state. */\n1728:\t\tdst-\u003einsns_subtotal = 0;\n1729:\t\treturn copy_stack_state(dst, src);\n1730:\t}\n1731:\t\n1732:\tint bpf_copy_verifier_state(struct bpf_verifier_state *dst_state,\n1733:\t\t\t\t   const struct bpf_verifier_state *src)\n1734:\t{\n1735:\t\tstruct bpf_func_state *dst;\n1736:\t\tint i, err;\n1737:\t\n1738:\t\tdst_state-\u003ejmp_history = copy_array(dst_state-\u003ejmp_history, src-\u003ejmp_history,\n1739:\t\t\t\t\t\t  src-\u003ejmp_history_cnt, sizeof(*dst_state-\u003ejmp_history),\n1740:\t\t\t\t\t\t  GFP_KERNEL_ACCOUNT);\n1741:\t\tif (!dst_state-\u003ejmp_history)\n1742:\t\t\treturn -ENOMEM;\n"
}

33/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20739
							output: 34
							thoughts: 214
						

Thoughts:
Here's my analysis of that code snippet:

**Memory Copy and Structure Layout**

Okay, so I see this `memcpy` call: `memcpy(dst, src, offsetof(struct bpf_func_state, stack));`. That's copying data from `src` to `dst`, right up to the `stack` member of the `bpf_func_state` struct. And look at the struct definition itself. The crucial thing is the placement of `frame_scoped_args` *before* the `stack` member. This means the `memcpy` correctly includes `frame_scoped_args` in the data that gets copied. The code seems to be relying on this specific struct layout for a reason, and the `offsetof` is ensuring the right amount of memory is copied.

But... hang on a minute. I need to be *absolutely* sure. Is this `bpf_func_state` struct zero-initialized during its allocation? If it's *not* zeroed, then any uninitialized data *before* the stack could lead to incorrect results. I'd better dig deeper into the allocation and initialization process for this struct to be certain. I need to see how `bpf_func_state` is created and filled with initial values. Zero-initialization is my working assumption for now, but I need to confirm it. I'll need to check the surrounding code for how this structure is allocated.



34/3 2026/09/24 02:05 tool grepper
0m Args:
{
  "Expression": "kzalloc.*bpf_func_state",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=19968=static int do_check_common(struct bpf_verifier_env *env, int subprog)\n--\nkernel/bpf/verifier.c-19987-\tstate-\u003ein_sleepable = env-\u003eprog-\u003esleepable;\nkernel/bpf/verifier.c:19988:\tstate-\u003eframe[0] = kzalloc_obj(struct bpf_func_state, GFP_KERNEL_ACCOUNT);\nkernel/bpf/verifier.c-19989-\tif (!state-\u003eframe[0]) {\n"
}

35/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21442
							output: 34
							thoughts: 24
						

Thoughts:
Okay, here's my summarized thought process, assuming I'm an expert in the field:

**Digging Deeper into State Allocation**

Alright, so I need to understand the lifecycle of `state->frame[` or `struct bpf_func_state`. It's pretty clear where I *am* looking at the code, but I need a more comprehensive view. The question is, where *else* are these structures allocated? I need to track down all the allocation sites. This isn't just about understanding the current usage, I need to understand the full picture, all allocation locations, and the impact this has on the system. I'm going to start with a broad search to ensure I don't miss anything that may be indirect or subtle. I'll need to use some clever search terms to cover all bases to make sure I don't miss any allocations.



36/3 2026/09/24 02:05 tool grepper
0m Args:
{
  "Expression": "bpf_func_state",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 561 lines.\nUse more precise expression if possible.\n\nkernel/bpf/verifier.c=589=bool bpf_is_may_goto_insn(struct bpf_insn *insn)\n--\nkernel/bpf/verifier.c-593-\nkernel/bpf/verifier.c:594:static bool is_spi_bounds_valid(struct bpf_func_state *state, int spi, int nr_slots)\nkernel/bpf/verifier.c-595-{\n--\nkernel/bpf/verifier.c=699=static void mark_dynptr_stack_regs(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-713- */\nkernel/bpf/verifier.c:714:static void mark_dynptr_cb_reg(struct bpf_func_state *callee, u32 regno,\nkernel/bpf/verifier.c-715-\t\t\t       enum bpf_dynptr_type type)\n--\nkernel/bpf/verifier.c=721=static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:722:\t\t\t\t        struct bpf_func_state *state, int spi);\nkernel/bpf/verifier.c-723-\nkernel/bpf/verifier.c=724=static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-727-{\nkernel/bpf/verifier.c:728:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-729-\tint spi, i, err, parent_id = 0;\n--\nkernel/bpf/verifier.c=806=static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg)\nkernel/bpf/verifier.c-807-{\nkernel/bpf/verifier.c:808:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-809-\tint spi;\n--\nkernel/bpf/verifier.c=837=static int dynptr_ref_cnt(struct bpf_verifier_env *env, int v_parent_id)\n--\nkernel/bpf/verifier.c-839-\tstruct bpf_stack_state *stack;\nkernel/bpf/verifier.c:840:\tstruct bpf_func_state *state;\nkernel/bpf/verifier.c-841-\tstruct bpf_reg_state *reg;\n--\nkernel/bpf/verifier.c=856=static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:857:\t\t\t\t        struct bpf_func_state *state, int spi)\nkernel/bpf/verifier.c-858-{\n--\nkernel/bpf/verifier.c=926=static bool is_dynptr_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_state *reg)\nkernel/bpf/verifier.c-927-{\nkernel/bpf/verifier.c:928:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-929-\tint i, spi;\n--\nkernel/bpf/verifier.c=955=static enum bpf_dynptr_type dynptr_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg)\nkernel/bpf/verifier.c-956-{\nkernel/bpf/verifier.c:957:\tstruct bpf_func_state *state;\nkernel/bpf/verifier.c-958-\tint spi;\n--\nkernel/bpf/verifier.c=990=static int mark_stack_slots_iter(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-994-{\nkernel/bpf/verifier.c:995:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-996-\tint spi, i, j, id;\n--\nkernel/bpf/verifier.c=1033=static int unmark_stack_slots_iter(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-1035-{\nkernel/bpf/verifier.c:1036:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-1037-\tint spi, i, j;\n--\nkernel/bpf/verifier.c=1061=static bool is_iter_reg_valid_uninit(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-1063-{\nkernel/bpf/verifier.c:1064:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-1065-\tint spi, i, j;\n--\nkernel/bpf/verifier.c=1088=static int is_iter_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-1090-{\nkernel/bpf/verifier.c:1091:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-1092-\tint spi, i, j;\n--\nkernel/bpf/verifier.c=1123=static int mark_stack_slot_irq_flag(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-1127-{\nkernel/bpf/verifier.c:1128:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-1129-\tstruct bpf_stack_state *slot;\n--\nkernel/bpf/verifier.c=1156=static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-1158-{\nkernel/bpf/verifier.c:1159:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-1160-\tstruct bpf_stack_state *slot;\n--\nkernel/bpf/verifier.c=1221=static bool is_irq_flag_reg_valid_uninit(struct bpf_verifier_env *env, struct bpf_reg_state *reg)\nkernel/bpf/verifier.c-1222-{\nkernel/bpf/verifier.c:1223:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-1224-\tstruct bpf_stack_state *slot;\n--\nkernel/bpf/verifier.c=1245=static int is_irq_flag_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_state *reg)\nkernel/bpf/verifier.c-1246-{\nkernel/bpf/verifier.c:1247:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-1248-\tstruct bpf_stack_state *slot;\n--\nkernel/bpf/verifier.c=1384=static int copy_reference_state(struct bpf_verifier_state *dst, const struct bpf_verifier_state *src)\n--\nkernel/bpf/verifier.c-1400-\nkernel/bpf/verifier.c:1401:static int copy_stack_state(struct bpf_func_state *dst, const struct bpf_func_state *src)\nkernel/bpf/verifier.c-1402-{\n--\nkernel/bpf/verifier.c=1426=static int resize_reference_state(struct bpf_verifier_state *state, size_t n)\n--\nkernel/bpf/verifier.c-1439- */\nkernel/bpf/verifier.c:1440:static int grow_stack_state(struct bpf_verifier_env *env, struct bpf_func_state *state, int size)\nkernel/bpf/verifier.c-1441-{\n--\nkernel/bpf/verifier.c=1464=static int grow_stack_arg_slots(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:1465:\t\t\t\tstruct bpf_func_state *state, int cnt)\nkernel/bpf/verifier.c-1466-{\n--\nkernel/bpf/verifier.c=1520=static int acquire_frame_reference(struct bpf_verifier_env *env, struct bpf_verifier_state *state,\n--\nkernel/bpf/verifier.c-1538- */\nkernel/bpf/verifier.c:1539:void mark_frame_scoped_arg(struct bpf_func_state *callee, u32 regno)\nkernel/bpf/verifier.c-1540-{\n--\nkernel/bpf/verifier.c=1672=static struct bpf_reference_state *find_lock_state(struct bpf_verifier_state *state, enum ref_state_type type,\n--\nkernel/bpf/verifier.c-1688-\nkernel/bpf/verifier.c:1689:static void free_func_state(struct bpf_func_state *state)\nkernel/bpf/verifier.c-1690-{\n--\nkernel/bpf/verifier.c=1705=void bpf_free_verifier_state(struct bpf_verifier_state *state,\n--\nkernel/bpf/verifier.c-1722- */\nkernel/bpf/verifier.c:1723:static int copy_func_state(struct bpf_func_state *dst,\nkernel/bpf/verifier.c:1724:\t\t\t   const struct bpf_func_state *src)\nkernel/bpf/verifier.c-1725-{\nkernel/bpf/verifier.c:1726:\tmemcpy(dst, src, offsetof(struct bpf_func_state, stack));\nkernel/bpf/verifier.c-1727-\t/* Instruction accounting is path-local, not part of verifier state. */\n--\nkernel/bpf/verifier.c=1732=int bpf_copy_verifier_state(struct bpf_verifier_state *dst_state,\n--\nkernel/bpf/verifier.c-1734-{\nkernel/bpf/verifier.c:1735:\tstruct bpf_func_state *dst;\nkernel/bpf/verifier.c-1736-\tint i, err;\n--\nkernel/bpf/verifier.c=1786=struct list_head *bpf_explored_state(struct bpf_verifier_env *env, int idx)\n--\nkernel/bpf/verifier.c-1788-\tstruct bpf_verifier_state *cur = env-\u003ecur_state;\nkernel/bpf/verifier.c:1789:\tstruct bpf_func_state *state = cur-\u003eframe[cur-\u003ecurframe];\nkernel/bpf/verifier.c-1790-\n--\nkernel/bpf/verifier.c=2358=static void init_reg_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:2359:\t\t\t   struct bpf_func_state *state)\nkernel/bpf/verifier.c-2360-{\n--\nkernel/bpf/verifier.c=2383=static void init_func_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:2384:\t\t\t    struct bpf_func_state *state,\nkernel/bpf/verifier.c-2385-\t\t\t    int callsite, int frameno, int subprogno)\n--\nkernel/bpf/verifier.c=2397=static struct bpf_verifier_state *push_async_cb(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-2401-\tstruct bpf_verifier_stack_elem *elem;\nkernel/bpf/verifier.c:2402:\tstruct bpf_func_state *frame;\nkernel/bpf/verifier.c-2403-\n--\nkernel/bpf/verifier.c=3315=static int check_reg_arg(struct bpf_verifier_env *env, u32 regno,\n--\nkernel/bpf/verifier.c-3318-\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\nkernel/bpf/verifier.c:3319:\tstruct bpf_func_state *state = vstate-\u003eframe[vstate-\u003ecurframe];\nkernel/bpf/verifier.c-3320-\n--\nkernel/bpf/verifier.c=3550=static void save_register_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:3551:\t\t\t\tstruct bpf_func_state *state,\nkernel/bpf/verifier.c-3552-\t\t\t\tint spi, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c=3581=static void check_fastcall_stack_contract(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:3582:\t\t\t\t\t  struct bpf_func_state *state, int insn_idx, int off)\nkernel/bpf/verifier.c-3583-{\n--\nkernel/bpf/verifier.c-3604-\nkernel/bpf/verifier.c:3605:static void scrub_special_slot(struct bpf_func_state *state, int spi)\nkernel/bpf/verifier.c-3606-{\n--\nkernel/bpf/verifier.c=3620=static int check_stack_write_fixed_off(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-3621-\t\t\t\t       /* stack frame we're writing to */\nkernel/bpf/verifier.c:3622:\t\t\t\t       struct bpf_func_state *state,\nkernel/bpf/verifier.c-3623-\t\t\t\t       int off, int size, int value_regno,\n--\nkernel/bpf/verifier.c-3625-{\nkernel/bpf/verifier.c:3626:\tstruct bpf_func_state *cur; /* state of the current function */\nkernel/bpf/verifier.c-3627-\tint i, slot = -off - 1, spi = slot / BPF_REG_SIZE, err;\n--\nkernel/bpf/verifier.c=3763=static int check_stack_write_var_off(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-3764-\t\t\t\t     /* func where register points to */\nkernel/bpf/verifier.c:3765:\t\t\t\t     struct bpf_func_state *state,\nkernel/bpf/verifier.c-3766-\t\t\t\t     struct bpf_reg_state *ptr_reg, int off, int size,\n--\nkernel/bpf/verifier.c-3768-{\nkernel/bpf/verifier.c:3769:\tstruct bpf_func_state *cur; /* state of the current function */\nkernel/bpf/verifier.c-3770-\tint min_off, max_off;\n--\nkernel/bpf/verifier.c=3895=static int mark_reg_stack_read(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-3896-\t\t\t       /* func where src register points to */\nkernel/bpf/verifier.c:3897:\t\t\t       struct bpf_func_state *ptr_state,\nkernel/bpf/verifier.c-3898-\t\t\t       int min_off, int max_off, int dst_regno)\n--\nkernel/bpf/verifier.c-3900-\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\nkernel/bpf/verifier.c:3901:\tstruct bpf_func_state *state = vstate-\u003eframe[vstate-\u003ecurframe];\nkernel/bpf/verifier.c-3902-\tu64 zero_spill_mask = 0;\n--\nkernel/bpf/verifier.c=3965=static int check_stack_read_fixed_off(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-3966-\t\t\t\t      /* func where src register points to */\nkernel/bpf/verifier.c:3967:\t\t\t\t      struct bpf_func_state *reg_state,\nkernel/bpf/verifier.c-3968-\t\t\t\t      int off, int size, int dst_regno)\n--\nkernel/bpf/verifier.c-3970-\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\nkernel/bpf/verifier.c:3971:\tstruct bpf_func_state *state = vstate-\u003eframe[vstate-\u003ecurframe];\nkernel/bpf/verifier.c-3972-\tint i, slot = -off - 1, spi = slot / BPF_REG_SIZE;\n--\nkernel/bpf/verifier.c=4150=static int check_stack_read_var_off(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-4152-{\nkernel/bpf/verifier.c:4153:\tstruct bpf_func_state *ptr_state = bpf_func(env, reg);\nkernel/bpf/verifier.c-4154-\tint err;\n--\nkernel/bpf/verifier.c=4183=static int check_stack_read(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-4186-{\nkernel/bpf/verifier.c:4187:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-4188-\tint err;\n--\nkernel/bpf/verifier.c=4245=static int check_stack_write(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-4248-{\nkernel/bpf/verifier.c:4249:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-4250-\tint err;\n--\nkernel/bpf/verifier.c-4270- */\nkernel/bpf/verifier.c:4271:static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_state *state,\nkernel/bpf/verifier.c-4272-\t\t\t\t int off, struct bpf_reg_state *value_reg)\n--\nkernel/bpf/verifier.c-4313- */\nkernel/bpf/verifier.c:4314:static int check_stack_arg_read(struct bpf_verifier_env *env, struct bpf_func_state *state,\nkernel/bpf/verifier.c-4315-\t\t\t\tint off, int dst_regno)\n--\nkernel/bpf/verifier.c-4319-\tint spi = off / BPF_REG_SIZE - 1;\nkernel/bpf/verifier.c:4320:\tstruct bpf_func_state *caller, *cur;\nkernel/bpf/verifier.c-4321-\tstruct bpf_reg_state *arg;\n--\nkernel/bpf/verifier.c=4344=static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx)\nkernel/bpf/verifier.c-4345-{\nkernel/bpf/verifier.c:4346:\tstruct bpf_func_state *caller = cur_func(env);\nkernel/bpf/verifier.c-4347-\tint spi = arg_idx - MAX_BPF_FUNC_REG_ARGS;\n--\nkernel/bpf/verifier.c=4353=static int mark_arg_precision(struct bpf_verifier_env *env, argno_t argno)\n--\nkernel/bpf/verifier.c-4361-\nkernel/bpf/verifier.c:4362:static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller,\nkernel/bpf/verifier.c-4363-\t\t\t\t     int nargs, const char *callee_name, const struct btf *btf,\n--\nkernel/bpf/verifier.c-4386-\nkernel/bpf/verifier.c:4387:static struct bpf_reg_state *get_func_arg_reg(struct bpf_func_state *caller,\nkernel/bpf/verifier.c-4388-\t\t\t\t\t      struct bpf_reg_state *regs, int arg)\n--\nkernel/bpf/verifier.c=6346=static int check_stack_slot_within_bounds(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-6347-                                          s64 off,\nkernel/bpf/verifier.c:6348:                                          struct bpf_func_state *state,\nkernel/bpf/verifier.c-6349-                                          enum bpf_access_type t)\n--\nkernel/bpf/verifier.c=6368=static int check_stack_access_within_bounds(\n--\nkernel/bpf/verifier.c-6372-{\nkernel/bpf/verifier.c:6373:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-6374-\ts64 min_off, max_off;\n--\nkernel/bpf/verifier.c=6761=static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-6765-\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\nkernel/bpf/verifier.c:6766:\tstruct bpf_func_state *state = vstate-\u003eframe[vstate-\u003ecurframe];\nkernel/bpf/verifier.c-6767-\tstruct bpf_reg_state *regs = cur_regs(env);\n--\nkernel/bpf/verifier.c=6808=static int check_store_reg(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-6811-\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\nkernel/bpf/verifier.c:6812:\tstruct bpf_func_state *state = vstate-\u003eframe[vstate-\u003ecurframe];\nkernel/bpf/verifier.c-6813-\tstruct bpf_reg_state *regs = cur_regs(env);\n--\nkernel/bpf/verifier.c=7012=static int check_stack_range_initialized(\n--\nkernel/bpf/verifier.c-7016-{\nkernel/bpf/verifier.c:7017:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-7018-\tint err, min_off, max_off, i, j, slot, spi;\n--\nkernel/bpf/verifier.c=7735=static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-7825-\t\tif (reg-\u003etype != CONST_PTR_TO_DYNPTR) {\nkernel/bpf/verifier.c:7826:\t\t\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-7827-\n--\nkernel/bpf/verifier.c=7872=static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno, int insn_idx,\n--\nkernel/bpf/verifier.c-7874-{\nkernel/bpf/verifier.c:7875:\tstruct bpf_func_state *state = bpf_func(env, reg);\nkernel/bpf/verifier.c-7876-\tconst struct btf_type *t;\n--\nkernel/bpf/verifier.c=8028=static int widen_imprecise_scalars(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-8031-{\nkernel/bpf/verifier.c:8032:\tstruct bpf_func_state *fold, *fcur;\nkernel/bpf/verifier.c-8033-\tint i, fr, num_slots;\n--\nkernel/bpf/verifier.c=8146=static int process_iter_next_call(struct bpf_verifier_env *env, int insn_idx,\n--\nkernel/bpf/verifier.c-8149-\tstruct bpf_verifier_state *cur_st = env-\u003ecur_state, *queued_st, *prev_st;\nkernel/bpf/verifier.c:8150:\tstruct bpf_func_state *cur_fr = cur_st-\u003eframe[cur_st-\u003ecurframe], *queued_fr;\nkernel/bpf/verifier.c-8151-\tstruct bpf_reg_state *cur_iter, *queued_iter;\n--\nkernel/bpf/verifier.c=8844=static int get_constant_map_key(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-8848-{\nkernel/bpf/verifier.c:8849:\tstruct bpf_func_state *state = bpf_func(env, key);\nkernel/bpf/verifier.c-8850-\tstruct bpf_reg_state *reg;\n--\nkernel/bpf/verifier.c=8957=static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 prev_slot,\n--\nkernel/bpf/verifier.c-8962-\tconst struct bpf_func_proto *fn = meta-\u003efn;\nkernel/bpf/verifier.c:8963:\tstruct bpf_func_state *caller = cur_func(env);\nkernel/bpf/verifier.c-8964-\tstruct bpf_reg_state *regs = cur_regs(env);\n--\nkernel/bpf/verifier.c=9531=static u32 kfunc_proto_slots(const struct btf *btf, const struct btf_type *func_proto)\n--\nkernel/bpf/verifier.c-9542-/* The argument slot @slot holds an eightbyte of a by-value argument. */\nkernel/bpf/verifier.c:9543:static int check_arg_extra_slot(struct bpf_verifier_env *env, struct bpf_func_state *caller,\nkernel/bpf/verifier.c-9544-\t\t\t\tu32 slot, struct bpf_call_arg_meta *meta)\n--\nkernel/bpf/verifier.c=9560=static int check_func_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-9563-\tu32 slot = 0, prev_slot = 0, proto_slots, nslots;\nkernel/bpf/verifier.c:9564:\tstruct bpf_func_state *caller = cur_func(env);\nkernel/bpf/verifier.c-9565-\tconst struct btf_param *args = NULL;\n--\nkernel/bpf/verifier.c=9620=static int mark_raw_stack(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-9622-{\nkernel/bpf/verifier.c:9623:\tstruct bpf_func_state *caller = cur_func(env);\nkernel/bpf/verifier.c-9624-\tu32 slot;\n--\nkernel/bpf/verifier.c=10081=static void clear_all_pkt_pointers(struct bpf_verifier_env *env)\nkernel/bpf/verifier.c-10082-{\nkernel/bpf/verifier.c:10083:\tstruct bpf_func_state *state;\nkernel/bpf/verifier.c-10084-\tstruct bpf_reg_state *reg;\n--\nkernel/bpf/verifier.c=10099=static void mark_pkt_end(struct bpf_verifier_state *vstate, int regn, bool range_open)\nkernel/bpf/verifier.c-10100-{\nkernel/bpf/verifier.c:10101:\tstruct bpf_func_state *state = vstate-\u003eframe[vstate-\u003ecurframe];\nkernel/bpf/verifier.c-10102-\tstruct bpf_reg_state *reg = \u0026state-\u003eregs[regn];\n--\nkernel/bpf/verifier.c=10172=static int __release_reference(struct bpf_verifier_env *env, int id,\n--\nkernel/bpf/verifier.c-10178-\tstruct bpf_stack_state *stack;\nkernel/bpf/verifier.c:10179:\tstruct bpf_func_state *state;\nkernel/bpf/verifier.c-10180-\tstruct bpf_reg_state *reg;\n--\nkernel/bpf/verifier.c=10275=static void invalidate_non_owning_refs(struct bpf_verifier_env *env)\nkernel/bpf/verifier.c-10276-{\nkernel/bpf/verifier.c:10277:\tstruct bpf_func_state *unused;\nkernel/bpf/verifier.c-10278-\tstruct bpf_reg_state *reg;\n--\nkernel/bpf/verifier.c=10288=static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-10290-\tstruct bpf_stack_state *stack;\nkernel/bpf/verifier.c:10291:\tstruct bpf_func_state *state;\nkernel/bpf/verifier.c-10292-\tstruct bpf_reg_state *reg;\n--\nkernel/bpf/verifier.c=10305=static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 id)\nkernel/bpf/verifier.c-10306-{\nkernel/bpf/verifier.c:10307:\tstruct bpf_func_state *state;\nkernel/bpf/verifier.c-10308-\tstruct bpf_reg_state *reg;\n--\nkernel/bpf/verifier.c=10345=static void invalidate_outgoing_stack_args(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10346:\t\t\t\t\t   struct bpf_func_state *state)\nkernel/bpf/verifier.c-10347-{\n--\nkernel/bpf/verifier.c=10356=typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10357:\t\t\t\t   struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10358:\t\t\t\t   struct bpf_func_state *callee,\nkernel/bpf/verifier.c-10359-\t\t\t\t   int insn_idx);\n--\nkernel/bpf/verifier.c=10361=static int set_callee_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10362:\t\t\t    struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10363:\t\t\t    struct bpf_func_state *callee, int insn_idx);\nkernel/bpf/verifier.c-10364-\nkernel/bpf/verifier.c=10365=static int setup_func_entry(struct bpf_verifier_env *env, int subprog, int callsite,\n--\nkernel/bpf/verifier.c-10368-{\nkernel/bpf/verifier.c:10369:\tstruct bpf_func_state *caller, *callee;\nkernel/bpf/verifier.c-10370-\tu16 scoped_args;\n--\nkernel/bpf/verifier.c=10438=static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n--\nkernel/bpf/verifier.c-10442-\tstruct bpf_subprog_info *sub = subprog_info(env, subprog);\nkernel/bpf/verifier.c:10443:\tstruct bpf_func_state *caller = cur_func(env);\nkernel/bpf/verifier.c-10444-\tstruct bpf_verifier_log *log = \u0026env-\u003elog;\n--\nkernel/bpf/verifier.c=10606=static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-10610-\tstruct bpf_verifier_state *state = env-\u003ecur_state, *callback_state;\nkernel/bpf/verifier.c:10611:\tstruct bpf_func_state *caller, *callee;\nkernel/bpf/verifier.c-10612-\tint err;\n--\nkernel/bpf/verifier.c=10682=static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-10687-\tu16 callee_incoming, stack_arg_cnt;\nkernel/bpf/verifier.c:10688:\tstruct bpf_func_state *caller;\nkernel/bpf/verifier.c-10689-\tint err, subprog, target_insn;\n--\nkernel/bpf/verifier.c=10810=int map_set_for_each_callback_args(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10811:\t\t\t\t   struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10812:\t\t\t\t   struct bpf_func_state *callee)\nkernel/bpf/verifier.c-10813-{\n--\nkernel/bpf/verifier.c=10839=static int set_callee_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10840:\t\t\t    struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10841:\t\t\t    struct bpf_func_state *callee, int insn_idx)\nkernel/bpf/verifier.c-10842-{\n--\nkernel/bpf/verifier.c=10853=static int set_map_elem_callback_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10854:\t\t\t\t       struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10855:\t\t\t\t       struct bpf_func_state *callee,\nkernel/bpf/verifier.c-10856-\t\t\t\t       int insn_idx)\n--\nkernel/bpf/verifier.c=10879=static int set_loop_callback_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10880:\t\t\t\t   struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10881:\t\t\t\t   struct bpf_func_state *callee,\nkernel/bpf/verifier.c-10882-\t\t\t\t   int insn_idx)\n--\nkernel/bpf/verifier.c=10901=static int set_timer_callback_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10902:\t\t\t\t    struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10903:\t\t\t\t    struct bpf_func_state *callee,\nkernel/bpf/verifier.c-10904-\t\t\t\t    int insn_idx)\n--\nkernel/bpf/verifier.c=10935=static int set_find_vma_callback_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10936:\t\t\t\t       struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10937:\t\t\t\t       struct bpf_func_state *callee,\nkernel/bpf/verifier.c-10938-\t\t\t\t       int insn_idx)\n--\nkernel/bpf/verifier.c=10963=static int set_user_ringbuf_callback_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10964:\t\t\t\t\t   struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10965:\t\t\t\t\t   struct bpf_func_state *callee,\nkernel/bpf/verifier.c-10966-\t\t\t\t\t   int insn_idx)\n--\nkernel/bpf/verifier.c=10986=static int set_rbtree_add_callback_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:10987:\t\t\t\t\t struct bpf_func_state *caller,\nkernel/bpf/verifier.c:10988:\t\t\t\t\t struct bpf_func_state *callee,\nkernel/bpf/verifier.c-10989-\t\t\t\t\t int insn_idx)\n--\nkernel/bpf/verifier.c=11019=static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:11020:\t\t\t\t\t\t struct bpf_func_state *caller,\nkernel/bpf/verifier.c:11021:\t\t\t\t\t\t struct bpf_func_state *callee,\nkernel/bpf/verifier.c-11022-\t\t\t\t\t\t int insn_idx)\n--\nkernel/bpf/verifier.c=11055=static void account_processed_insn(struct bpf_verifier_env *env)\nkernel/bpf/verifier.c-11056-{\nkernel/bpf/verifier.c:11057:\tstruct bpf_func_state *frame = cur_func(env);\nkernel/bpf/verifier.c-11058-\n--\nkernel/bpf/verifier.c=11064=static void account_processed_insns(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c:11065:\t\t\t\t    struct bpf_func_state *callee,\nkernel/bpf/verifier.c:11066:\t\t\t\t    struct bpf_func_state *caller)\nkernel/bpf/verifier.c-11067-{\n--\nkernel/bpf/verifier.c=11096=static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-11099-\tstruct bpf_insn *insn = env-\u003eprog-\u003einsnsi;\nkernel/bpf/verifier.c:11100:\tstruct bpf_func_state *callee;\nkernel/bpf/verifier.c-11101-\tint kfunc_btf_id;\n--\nkernel/bpf/verifier.c=11125=static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)\n--\nkernel/bpf/verifier.c-11127-\tstruct bpf_verifier_state *state = env-\u003ecur_state, *prev_st;\nkernel/bpf/verifier.c:11128:\tstruct bpf_func_state *caller, *callee;\nkernel/bpf/verifier.c-11129-\tstruct bpf_reg_state *r0;\n--\nkernel/bpf/verifier.c=13405=static void ref_convert_owning_non_owning(struct bpf_verifier_env *env, u32 id)\nkernel/bpf/verifier.c-13406-{\nkernel/bpf/verifier.c:13407:\tstruct bpf_func_state *unused;\nkernel/bpf/verifier.c-13408-\tstruct bpf_reg_state *reg;\n--\nkernel/bpf/verifier.c=14224=static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-14667-\tif (proto_slots \u003e MAX_BPF_FUNC_REG_ARGS) {\nkernel/bpf/verifier.c:14668:\t\tstruct bpf_func_state *caller = cur_func(env);\nkernel/bpf/verifier.c-14669-\t\tstruct bpf_subprog_info *caller_info = \u0026env-\u003esubprog_info[caller-\u003esubprogno];\n--\nkernel/bpf/verifier.c=15067=static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-15071-\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\nkernel/bpf/verifier.c:15072:\tstruct bpf_func_state *state = vstate-\u003eframe[vstate-\u003ecurframe];\nkernel/bpf/verifier.c-15073-\tstruct bpf_reg_state *regs = state-\u003eregs, *dst_reg;\n--\nkernel/bpf/verifier.c=16226=static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-16229-\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\nkernel/bpf/verifier.c:16230:\tstruct bpf_func_state *state = vstate-\u003eframe[vstate-\u003ecurframe];\nkernel/bpf/verifier.c-16231-\tstruct bpf_reg_state *regs = state-\u003eregs, *dst_reg, *src_reg;\n--\nkernel/bpf/verifier.c=16582=static void find_good_pkt_pointers(struct bpf_verifier_state *vstate,\n--\nkernel/bpf/verifier.c-16586-{\nkernel/bpf/verifier.c:16587:\tstruct bpf_func_state *state;\n\n"
}

37/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32792
							output: 38
							thoughts: 41
						

Thoughts:
Okay, let's dive into this `push_callback_call` function and how it handles the allocation and initialization of `callee`. I need to understand this meticulously. I'm going to look at the code block around line 10606. That should give me a solid view of the pertinent logic. I'm expecting to see the initialization of `callee` and the mechanics of the allocation. Let's see how they handle that. I want to pay attention to any subtle details, any unusual memory management patterns, any potential pitfalls or optimizations. I have to make sure that the code is sound and consider how this might interact with the overall system.



38/3 2026/09/24 02:05 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 10606,
  "LineCount": 55
}

Results:
{
  "Contents": "10606:\tstatic int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n10607:\t\t\t\t      int insn_idx, int subprog,\n10608:\t\t\t\t      set_callee_state_fn set_callee_state_cb)\n10609:\t{\n10610:\t\tstruct bpf_verifier_state *state = env-\u003ecur_state, *callback_state;\n10611:\t\tstruct bpf_func_state *caller, *callee;\n10612:\t\tint err;\n10613:\t\n10614:\t\tcaller = state-\u003eframe[state-\u003ecurframe];\n10615:\t\terr = btf_check_subprog_call(env, subprog, caller-\u003eregs);\n10616:\t\tif (err == -EFAULT)\n10617:\t\t\treturn err;\n10618:\t\n10619:\t\t/* set_callee_state is used for direct subprog calls, but we are\n10620:\t\t * interested in validating only BPF helpers that can call subprogs as\n10621:\t\t * callbacks\n10622:\t\t */\n10623:\t\tenv-\u003esubprog_info[subprog].is_cb = true;\n10624:\t\tif (bpf_pseudo_kfunc_call(insn) \u0026\u0026\n10625:\t\t    !is_callback_calling_kfunc(insn-\u003eimm)) {\n10626:\t\t\tverifier_bug(env, \"kfunc %s#%d not marked as callback-calling\",\n10627:\t\t\t\t     func_id_name(insn-\u003eimm), insn-\u003eimm);\n10628:\t\t\treturn -EFAULT;\n10629:\t\t} else if (!bpf_pseudo_kfunc_call(insn) \u0026\u0026\n10630:\t\t\t   !is_callback_calling_function(insn-\u003eimm)) { /* helper */\n10631:\t\t\tverifier_bug(env, \"helper %s#%d not marked as callback-calling\",\n10632:\t\t\t\t     func_id_name(insn-\u003eimm), insn-\u003eimm);\n10633:\t\t\treturn -EFAULT;\n10634:\t\t}\n10635:\t\n10636:\t\tif (bpf_is_async_callback_calling_insn(insn)) {\n10637:\t\t\tstruct bpf_verifier_state *async_cb;\n10638:\t\n10639:\t\t\t/* there is no real recursion here. timer and workqueue callbacks are async */\n10640:\t\t\tenv-\u003esubprog_info[subprog].is_async_cb = true;\n10641:\t\t\tasync_cb = push_async_cb(env, env-\u003esubprog_info[subprog].start,\n10642:\t\t\t\t\t\t insn_idx, subprog,\n10643:\t\t\t\t\t\t is_async_cb_sleepable(env, insn));\n10644:\t\t\tif (IS_ERR(async_cb))\n10645:\t\t\t\treturn PTR_ERR(async_cb);\n10646:\t\t\tcallee = async_cb-\u003eframe[0];\n10647:\t\t\tcallee-\u003easync_entry_cnt = caller-\u003easync_entry_cnt + 1;\n10648:\t\n10649:\t\t\t/* Convert bpf_timer_set_callback() args into timer callback args */\n10650:\t\t\terr = set_callee_state_cb(env, caller, callee, insn_idx);\n10651:\t\t\tif (err)\n10652:\t\t\t\treturn err;\n10653:\t\n10654:\t\t\tif (verifier_bug_if(callee-\u003eframe_scoped_args, env,\n10655:\t\t\t\t\t    \"frame-scoped argument declared for async callback\"))\n10656:\t\t\t\treturn -EFAULT;\n10657:\t\n10658:\t\t\treturn 0;\n10659:\t\t}\n10660:\t\n"
}

39/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 34060
							output: 38
							thoughts: 22
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing `push_callback_call` After Line 10660**

Right, let's dive into this. I'm focusing on `push_callback_call` and specifically what's going on *after* line 10660. The immediate question in my mind is: what specific control flow paths or data manipulations are active at that point? Is it a crucial juncture for error handling, or resource cleanup, or is this the moment where a specific state is committed/signaled? Line numbers aren't typically arbitrary; so there is clearly something significant in play. I need to understand the function's internal state post-10660.

Am I looking at a critical section's unlock, a specific object's release, or the sending of a notification after the event happens?

The context is essential to understand this point - is it related to threading, IPC, or event-driven design? I need to think about the broader system interaction that the function is a part of. What are the possible race conditions or data inconsistencies that might become apparent *after* this specific line? The code around line 10660 is my key area of interest. Any subtle interaction with other components of the system post-10660 is the key here.



40/3 2026/09/24 02:05 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 10660,
  "LineCount": 40
}

Results:
{
  "Contents": "10660:\t\n10661:\t\t/* for callback functions enqueue entry to callback and\n10662:\t\t * proceed with next instruction within current frame.\n10663:\t\t */\n10664:\t\tcallback_state = push_stack(env, env-\u003esubprog_info[subprog].start, insn_idx, false);\n10665:\t\tif (IS_ERR(callback_state))\n10666:\t\t\treturn PTR_ERR(callback_state);\n10667:\t\n10668:\t\terr = setup_func_entry(env, subprog, insn_idx, set_callee_state_cb,\n10669:\t\t\t\t       callback_state);\n10670:\t\tif (err)\n10671:\t\t\treturn err;\n10672:\t\n10673:\t\tcallback_state-\u003ecallback_unroll_depth++;\n10674:\t\tcallback_state-\u003eframe[callback_state-\u003ecurframe - 1]-\u003ecallback_depth++;\n10675:\t\tcaller-\u003ecallback_depth = 0;\n10676:\t\treturn 0;\n10677:\t}\n10678:\t\n10679:\tstatic int process_bpf_exit_full(struct bpf_verifier_env *env,\n10680:\t\t\t\t\t bool *do_print_state, bool exception_exit);\n10681:\t\n10682:\tstatic int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n10683:\t\t\t\t   int *insn_idx)\n10684:\t{\n10685:\t\tstruct bpf_verifier_state *state = env-\u003ecur_state;\n10686:\t\tstruct bpf_subprog_info *caller_info;\n10687:\t\tu16 callee_incoming, stack_arg_cnt;\n10688:\t\tstruct bpf_func_state *caller;\n10689:\t\tint err, subprog, target_insn;\n10690:\t\tu32 i, nregs;\n10691:\t\n10692:\t\ttarget_insn = *insn_idx + insn-\u003eimm + 1;\n10693:\t\tsubprog = bpf_find_subprog(env, target_insn);\n10694:\t\tif (verifier_bug_if(subprog \u003c 0, env, \"target of func call at insn %d is not a program\",\n10695:\t\t\t\t    target_insn))\n10696:\t\t\treturn -EFAULT;\n10697:\t\n10698:\t\tcaller = state-\u003eframe[state-\u003ecurframe];\n10699:\t\terr = btf_check_subprog_call(env, subprog, caller-\u003eregs);\n"
}

41/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35149
							output: 38
							thoughts: 20
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm deep in code analysis and looking for something specific:

**Locating a Function Entry Point**

Alright, my brain just went straight to the code. Specifically, I'm thinking about tracking down the `setup_func_entry` function, and the context suggests it's somewhere around line 10365. That's a pretty precise line number, so I'm likely debugging or tracing some execution flow, or maybe I'm reviewing code changes. This is probably a function that's critical to understanding the way some other function starts up.



42/3 2026/09/24 02:05 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 10365,
  "LineCount": 55
}

Results:
{
  "Contents": "10365:\tstatic int setup_func_entry(struct bpf_verifier_env *env, int subprog, int callsite,\n10366:\t\t\t\t    set_callee_state_fn set_callee_state_cb,\n10367:\t\t\t\t    struct bpf_verifier_state *state)\n10368:\t{\n10369:\t\tstruct bpf_func_state *caller, *callee;\n10370:\t\tu16 scoped_args;\n10371:\t\tint err, regno;\n10372:\t\n10373:\t\tif (state-\u003ecurframe + 1 \u003e= MAX_CALL_FRAMES) {\n10374:\t\t\tverbose(env, \"the call stack of %d frames is too deep\\n\",\n10375:\t\t\t\tstate-\u003ecurframe + 2);\n10376:\t\t\treturn -E2BIG;\n10377:\t\t}\n10378:\t\n10379:\t\tif (state-\u003eframe[state-\u003ecurframe + 1]) {\n10380:\t\t\tverifier_bug(env, \"Frame %d already allocated\", state-\u003ecurframe + 1);\n10381:\t\t\treturn -EFAULT;\n10382:\t\t}\n10383:\t\n10384:\t\tcaller = state-\u003eframe[state-\u003ecurframe];\n10385:\t\tcallee = kzalloc_obj(*callee, GFP_KERNEL_ACCOUNT);\n10386:\t\tif (!callee)\n10387:\t\t\treturn -ENOMEM;\n10388:\t\tstate-\u003eframe[state-\u003ecurframe + 1] = callee;\n10389:\t\n10390:\t\t/* callee cannot access r0, r6 - r9 for reading and has to write\n10391:\t\t * into its own stack before reading from it.\n10392:\t\t * callee can read/write into caller's stack\n10393:\t\t */\n10394:\t\tinit_func_state(env, callee,\n10395:\t\t\t\t/* remember the callsite, it will be used by bpf_exit */\n10396:\t\t\t\tcallsite,\n10397:\t\t\t\tstate-\u003ecurframe + 1 /* frameno within this callchain */,\n10398:\t\t\t\tsubprog /* subprog number within this prog */);\n10399:\t\terr = set_callee_state_cb(env, caller, callee, callsite);\n10400:\t\tif (err)\n10401:\t\t\tgoto err_out;\n10402:\t\n10403:\t\tscoped_args = callee-\u003eframe_scoped_args;\n10404:\t\tcallee-\u003eframe_scoped_args = 0;\n10405:\t\tfor (regno = 0; regno \u003c MAX_BPF_REG; regno++) {\n10406:\t\t\tint id;\n10407:\t\n10408:\t\t\tif (!(scoped_args \u0026 BIT(regno)))\n10409:\t\t\t\tcontinue;\n10410:\t\n10411:\t\t\tid = acquire_frame_reference(env, state, callsite, callee-\u003eframeno);\n10412:\t\t\tif (id \u003c 0) {\n10413:\t\t\t\terr = id;\n10414:\t\t\t\tgoto err_out;\n10415:\t\t\t}\n10416:\t\t\t/*\n10417:\t\t\t * The value is its own lifetime anchor: there is no associated\n10418:\t\t\t * object to borrow from, only the frame. parent_id = id here\n10419:\t\t\t * covers both possible derived references:\n"
}

43/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 36289
							output: 33
							thoughts: 544
						

Thoughts:
**Analyzing Frame Reference Release in the BPF Verifier**

Okay, let's break down this frame reference handling in the BPF verifier. The `callee` structure, which is allocated using `kzalloc_obj`, starts with `frame_scoped_args` initialized to zero. Then, `set_callee_state_cb` populates this structure, potentially through calls like `array_map_set_for_each_callback_args` or `set_user_ringbuf_callback_state`, using `mark_frame_scoped_arg` to flag the relevant arguments. The crucial step follows in `setup_func_entry`, where `callee->frame_scoped_args` is inspected and cleared, and frame references are subsequently created.

Now, the focus shifts to the `__release_reference` function. The addition of the `reg->id != id` condition in the loop immediately catches my eye. This is clearly a critical safeguard. For frame references, the `parent_id` *does* equal the `id`, so without this check, pushing `reg->id` onto the stack would lead to infinite recursion. It's a classic example of how a subtle oversight can create a devastating bug.

Moving on, `release_frame_reference` is a wrapper around `__release_reference`. It iterates through the acquired references, specifically looking for those of `REF_TYPE_FRAME` with the matching `id`. It then calls `release_reference_state` to handle the actual release of the reference and, ultimately, calls `__release_reference` with a specific diagnostic mode, `BPF_DIAG_MOD_FRAME_RELEASE`. This is where I want to dig deeper; the real work must be done in `release_reference_state`. I need to understand what it actually *does* to release a frame reference.



44/3 2026/09/24 02:05 tool grepper
0m Args:
{
  "Expression": "release_reference_state",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=1565=static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx)\n--\nkernel/bpf/verifier.c-1581-\nkernel/bpf/verifier.c:1582:static void release_reference_state(struct bpf_verifier_state *state, int idx)\nkernel/bpf/verifier.c-1583-{\n--\nkernel/bpf/verifier.c=1620=static int release_lock_state(struct bpf_verifier_env *env, int type, int id, void *ptr)\n--\nkernel/bpf/verifier.c-1629-\t\t    state-\u003erefs[i].ptr == ptr) {\nkernel/bpf/verifier.c:1630:\t\t\trelease_reference_state(state, i);\nkernel/bpf/verifier.c-1631-\t\t\tstate-\u003eactive_locks--;\n--\nkernel/bpf/verifier.c=1647=static int release_irq_state(struct bpf_verifier_env *env, int id)\n--\nkernel/bpf/verifier.c-1659-\t\tif (state-\u003erefs[i].id == id) {\nkernel/bpf/verifier.c:1660:\t\t\trelease_reference_state(state, i);\nkernel/bpf/verifier.c-1661-\t\t\tstate-\u003eactive_irq_id = prev_id;\n--\nkernel/bpf/verifier.c=10120=static int __release_reference_nomark(struct bpf_verifier_state *state, int id)\n--\nkernel/bpf/verifier.c-10127-\t\tif (state-\u003erefs[i].id == id) {\nkernel/bpf/verifier.c:10128:\t\t\trelease_reference_state(state, i);\nkernel/bpf/verifier.c-10129-\t\t\treturn 0;\n--\nkernel/bpf/verifier.c=10260=static int release_frame_reference(struct bpf_verifier_env *env, int id)\n--\nkernel/bpf/verifier.c-10267-\t\t\tcontinue;\nkernel/bpf/verifier.c:10268:\t\trelease_reference_state(state, i);\nkernel/bpf/verifier.c-10269-\t\tbreak;\n"
}

45/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37854
							output: 37
							thoughts: 19
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**Analyzing `release_reference_state`**

Alright, I see a specific line number mentioned, around line 1582, and the function `release_reference_state` is flagged. My immediate thought is, "Okay, let's drill down into that section of code." Given the naming convention, I'm expecting this function to be responsible for deallocating or resetting some critical resource related to a "reference state." This is a crucial area to examine, especially if we're dealing with complex data structures, concurrent operations, or resource management. I want to understand: what kind of "reference" is being managed? Is it a shared pointer, a handle, a lock, or something more specialized? What are the implications of releasing this state at this specific point in the code? Is it tied to a lifecycle, a transaction, or a specific event? The context around line 1582 will be key. I'll need to look for any potential race conditions, use-after-free scenarios, or memory leaks that could arise. I'll be particularly interested in how this function interacts with other parts of the system and whether any cleanup tasks are properly handled. Time to dig in and see what's what.



46/3 2026/09/24 02:05 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 1580,
  "LineCount": 25
}

Results:
{
  "Contents": "1580:\t}\n1581:\t\n1582:\tstatic void release_reference_state(struct bpf_verifier_state *state, int idx)\n1583:\t{\n1584:\t\tint last_idx;\n1585:\t\tsize_t rem;\n1586:\t\n1587:\t\t/* IRQ state requires the relative ordering of elements remaining the\n1588:\t\t * same, since it relies on the refs array to behave as a stack, so that\n1589:\t\t * it can detect out-of-order IRQ restore. Hence use memmove to shift\n1590:\t\t * the array instead of swapping the final element into the deleted idx.\n1591:\t\t */\n1592:\t\tlast_idx = state-\u003eacquired_refs - 1;\n1593:\t\trem = state-\u003eacquired_refs - idx - 1;\n1594:\t\tif (last_idx \u0026\u0026 idx != last_idx)\n1595:\t\t\tmemmove(\u0026state-\u003erefs[idx], \u0026state-\u003erefs[idx + 1], sizeof(*state-\u003erefs) * rem);\n1596:\t\tmemset(\u0026state-\u003erefs[last_idx], 0, sizeof(*state-\u003erefs));\n1597:\t\tstate-\u003eacquired_refs--;\n1598:\t\treturn;\n1599:\t}\n1600:\t\n1601:\tstatic bool find_reference_state(struct bpf_verifier_state *state, int id)\n1602:\t{\n1603:\t\tint i;\n1604:\t\n"
}

47/3 2026/09/24 02:05 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38597
							output: 36
							thoughts: 207
						

Thoughts:
Here's my analysis of the provided code snippet:

**Analyzing BPF Verifier State Handling**

Okay, so I'm looking at the `release_reference_state` function. My understanding is that it's responsible for managing memory related to removed elements, specifically utilizing `memmove` and `memset` to zero out the freed space. That's good practice for security, but I need to understand what else is happening.

Now I'm diving into `kernel/bpf/states.c` to examine the `refsafe` function. Specifically, I'm focusing around line 898 to understand how the verifier ensures that references are safe. It looks like this function is crucial for comparing states, checking the integrity of references. The code seems to be iterating through different reference types. I see a new case has been added, `REF_TYPE_FRAME`. If the `frameno` field differs between the `old` and `cur` states for a `REF_TYPE_FRAME`, then `refsafe` returns false, meaning they're not safe. Seems like a simple, but important check for frame validity. The other reference types look relatively straightforward.



48/3 2026/09/24 02:06 tool read-file
0m Args:
{
  "File": "kernel/bpf/states.c",
  "FirstLine": 880,
  "LineCount": 40
}

Results:
{
  "Contents": " 880:\t\tif (old-\u003eactive_rcu_locks != cur-\u003eactive_rcu_locks)\n 881:\t\t\treturn false;\n 882:\t\n 883:\t\tif (!check_ids(old-\u003eactive_irq_id, cur-\u003eactive_irq_id, idmap))\n 884:\t\t\treturn false;\n 885:\t\n 886:\t\tif (!check_ids(old-\u003eactive_lock_id, cur-\u003eactive_lock_id, idmap) ||\n 887:\t\t    old-\u003eactive_lock_ptr != cur-\u003eactive_lock_ptr)\n 888:\t\t\treturn false;\n 889:\t\n 890:\t\tfor (i = 0; i \u003c old-\u003eacquired_refs; i++) {\n 891:\t\t\tif (!check_ids(old-\u003erefs[i].id, cur-\u003erefs[i].id, idmap) ||\n 892:\t\t\t    old-\u003erefs[i].type != cur-\u003erefs[i].type)\n 893:\t\t\t\treturn false;\n 894:\t\t\tswitch (old-\u003erefs[i].type) {\n 895:\t\t\tcase REF_TYPE_PTR:\n 896:\t\t\t\tif (!check_ids(old-\u003erefs[i].parent_id, cur-\u003erefs[i].parent_id, idmap))\n 897:\t\t\t\t\treturn false;\n 898:\t\t\t\tbreak;\n 899:\t\t\tcase REF_TYPE_IRQ:\n 900:\t\t\t\tbreak;\n 901:\t\t\tcase REF_TYPE_FRAME:\n 902:\t\t\t\tif (old-\u003erefs[i].frameno != cur-\u003erefs[i].frameno)\n 903:\t\t\t\t\treturn false;\n 904:\t\t\t\tbreak;\n 905:\t\t\tcase REF_TYPE_LOCK:\n 906:\t\t\tcase REF_TYPE_RES_LOCK:\n 907:\t\t\tcase REF_TYPE_RES_LOCK_IRQ:\n 908:\t\t\t\tif (old-\u003erefs[i].ptr != cur-\u003erefs[i].ptr)\n 909:\t\t\t\t\treturn false;\n 910:\t\t\t\tbreak;\n 911:\t\t\tdefault:\n 912:\t\t\t\tWARN_ONCE(1, \"Unhandled enum type for reference state: %d\\n\", old-\u003erefs[i].type);\n 913:\t\t\t\treturn false;\n 914:\t\t\t}\n 915:\t\t}\n 916:\t\n 917:\t\treturn true;\n 918:\t}\n 919:\t\n"
}

49/3 2026/09/24 02:06 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 39726
							output: 349
							thoughts: 3364
						

Thoughts:
**KMSAN Reasoning for BPF Verifier Patch**

Okay, so I'm diving into this BPF verifier patch. My primary focus right now is figuring out if it introduces any uninitialized memory risks that would warrant a KMSAN fuzzing session. The code modifies the `bpf_reference_state` and `bpf_func_state` structs to improve tracking of frame-scoped callback arguments, which is key. This specifically addresses situations where a BPF program might try to access data (like an array key or ringbuf dynptr) after the function that owns that data returns and the stack frame is gone.

Initially, I looked closely at the `bpf_reference_state` union and the addition of `frameno`. I wanted to ensure that when `REF_TYPE_FRAME` is used, the `frameno` field is correctly initialized and that no other parts of the union are read uninitialized. The code does zero the memory with `realloc_array` and sets the necessary fields. I checked the code paths in `refsafe` and `frame_reference_id`, and these only read `frameno` when the type is `REF_TYPE_FRAME`, which means that there are no uninitialized reads here.

Next, I examined `bpf_func_state`, particularly the `frame_scoped_args` addition. I wanted to verify that this is correctly initialized. Turns out the `frame_scoped_args` is zeroed during allocation with `kzalloc`, and that it is copied by `memcpy` which will copy the valid initialized data from the `src`. It also appears that the value is only used in a check, before being set to zero so it's a good place to look for that check.

A quick look at the patch commit also reminded me of the key issue: if the verifier doesn't catch the problem, the actual runtime consequence is a use-after-free, a dangling stack pointer. KASAN should be able to catch this (because accessing freed memory is bad, and should be detected). In other words, this patch is a verifier soundness fix. It's about preventing the kernel from even *allowing* BPF programs that could cause runtime issues, not addressing a runtime issue itself.

I also looked at the verifier diagnostics/log to see if any user-space exposure of this information could be a problem and I do not see that the verifier exports any of this information. The verifier's log is just text formatted via `snprintf`, and this information does not seem to get exported to userspace.

Ultimately, I needed to make sure that no new code paths are introduced that read uninitialized memory from any struct fields or memory. Based on my analysis, all newly introduced structures are zero-initialized, and no information is leaked to userspace. In summary, this patch is concerned with *preventing* potential memory safety violations (use-after-free), but doesn't *introduce* uninitialized memory exposure. Therefore, a KMSAN session is not necessary, as KASAN and standard verifier tests should be sufficient to validate the changes.



50/3 2026/09/24 02:06 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch introduces verifier tracking for frame-scoped callback arguments (such as bpf_for_each_array_elem keys and user ringbuf dynptrs) by introducing REF_TYPE_FRAME reference states that are automatically invalidated when the callback frame exits.\n\nKASAN vs KMSAN applicability:\n- Memory Safety \u0026 Initialization: All newly added and modified fields (`frameno` in `struct bpf_reference_state`, `frame_scoped_args` in `struct bpf_func_state`) are allocated using zeroing mechanisms (`kzalloc_obj`, and `realloc_array` which explicitly memsets newly grown entries to zero). State copying via `copy_func_state` and `copy_reference_state` properly covers the new fields from already initialized memory. No uninitialized memory reads or conditional branches on uninitialized fields exist in the verifier.\n- Leakage to User Space: No kernel structures or uninitialized paddings are copied to user space. Verifier diagnostics and log messages format explicit string literals and initialized integers into the user-provided log buffer.\n- Failure Modes: The vulnerability class addressed by this patch is lifetime violation in verified BPF programs (preventing a BPF program from accessing pointers to callback stack frames after the callback returns). Such an escape at runtime results in use-after-free or stack-out-of-scope accesses, which are detected by KASAN.\n\nBecause the changes do not introduce or expose uninitialized memory reads or info-leaks, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch introduces verifier tracking for frame-scoped callback arguments (such as bpf_for_each_array_elem keys and user ringbuf dynptrs) by introducing REF_TYPE_FRAME reference states that are automatically invalidated when the callback frame exits.\n\nKASAN vs KMSAN applicability:\n- Memory Safety \u0026 Initialization: All newly added and modified fields (`frameno` in `struct bpf_reference_state`, `frame_scoped_args` in `struct bpf_func_state`) are allocated using zeroing mechanisms (`kzalloc_obj`, and `realloc_array` which explicitly memsets newly grown entries to zero). State copying via `copy_func_state` and `copy_reference_state` properly covers the new fields from already initialized memory. No uninitialized memory reads or conditional branches on uninitialized fields exist in the verifier.\n- Leakage to User Space: No kernel structures or uninitialized paddings are copied to user space. Verifier diagnostics and log messages format explicit string literals and initialized integers into the user-provided log buffer.\n- Failure Modes: The vulnerability class addressed by this patch is lifetime violation in verified BPF programs (preventing a BPF program from accessing pointers to callback stack frames after the callback returns). Such an escape at runtime results in use-after-free or stack-out-of-scope accesses, which are detected by KASAN.\n\nBecause the changes do not introduce or expose uninitialized memory reads or info-leaks, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)