| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/06 11:55 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_check",
"regsafe",
"check_pkt_ptr_ids",
"bpf_patch_insn_data",
"verifier_remove_insns",
"refine_map_lookup_value",
"map_set_for_each_callback_args",
"set_timer_callback_state",
"set_task_work_schedule_callback_state",
"push_insn"
],
"KMSANReasoning": "The patch makes internal correctness and safety improvements to the BPF verifier and instruction rewriting passes:\n1. Adds a subprogram boundary fallthrough check in cfg.c to ensure CO-RE relocations cannot violate subprogram boundaries.\n2. Introduces fatal signal checks and preemption points (cond_resched) in fixups.c and verifier.c during instruction patching.\n3. Enhances state equivalence pruning in states.c for packet pointers by tracking intra-class pointer displacement (pkt_ptr_delta / pkt_ptr_delta_set in struct bpf_id_pair).\n4. Corrects spinlock identity tracking for inner map lookups and callback argument states in verifier.c.\n\nNone of these changes allocate or modify kernel data buffers copied to user space, nor do they introduce reads of uninitialized stack, heap, or page memory. All newly added struct fields (such as pkt_ptr_delta_set in struct bpf_id_pair) are explicitly initialized before access. Any potential memory safety or logic regressions (e.g. out-of-bounds access or verifier state bugs) fall within the scope of standard KASAN/verifier test suites. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces functional changes to the BPF verifier subsystem across several files:\n1. kernel/bpf/states.c: Adds check_pkt_ptr_ids() and modifies regsafe() to preserve displacement within packet pointer ID classes during state equivalence pruning.\n2. kernel/bpf/verifier.c: Adds map_value_has_static_identity(), refines spinlock map UID tracking in refine_map_lookup_value(), generates unique register IDs for spinlock-containing map values in callback helpers (map_set_for_each_callback_args, set_timer_callback_state, set_task_work_schedule_callback_state), and adds fatal signal abort checks in bpf_check().\n3. kernel/bpf/cfg.c: Adds fallthrough validation across subprogram boundaries in push_insn().\n4. kernel/bpf/fixups.c: Adds fatal signal checks (bpf_rewrite_must_abort) to bpf_patch_insn_data() and verifier_remove_insns().\n\nThese modifications affect reachable code in the core BPF verifier reachable via sys_bpf(BPF_PROG_LOAD) and warrant fuzzing for state pruning correctness, verifier edge cases, and lock safety.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/06 11:55 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit e4d2667ab5d8809179d28d9351054834ad1eb9ce\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Sep 6 11:55:48 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex 36b65797877d0..5cf92ce185204 100644\n--- a/include/linux/bpf_verifier.h\n+++ b/include/linux/bpf_verifier.h\n@@ -854,6 +854,8 @@ struct backtrack_state {\n struct bpf_id_pair {\n \tu32 old;\n \tu32 cur;\n+\ts32 pkt_ptr_delta;\n+\tbool pkt_ptr_delta_set;\n };\n \n struct bpf_idmap {\ndiff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c\nindex 842c7d1eabccc..e3c904328ede0 100644\n--- a/kernel/bpf/cfg.c\n+++ b/kernel/bpf/cfg.c\n@@ -102,6 +102,7 @@ enum {\n */\n static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)\n {\n+\tstruct bpf_subprog_info *subprog;\n \tint *insn_stack = env-\u003ecfg.insn_stack;\n \tint *insn_state = env-\u003ecfg.insn_state;\n \n@@ -121,6 +122,26 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)\n \t\treturn -EINVAL;\n \t}\n \n+\t/*\n+\t * check_subprogs() prevents control flow from falling through a\n+\t * subprogram boundary, but runs before CO-RE relocations can rewrite an\n+\t * instruction. Reestablish the invariant on the final instruction stream\n+\t * before constructing the CFG used by later per-subprogram passes.\n+\t */\n+\tif (e == FALLTHROUGH) {\n+\t\tsubprog = bpf_find_containing_subprog(env, t);\n+\t\tif (w \u003c subprog-\u003estart || w \u003e= (subprog + 1)-\u003estart) {\n+\t\t\tverbose_linfo(env, t, \"%d: \", t);\n+\t\t\tverbose(env, \"fall-through out of subprog from insn %d to %d\\n\", t, w);\n+\t\t\tbpf_diag_program_structure(\n+\t\t\t\tenv, t, \"fall-through leaves subprogram\",\n+\t\t\t\t\"Keep fall-through control flow inside the current subprogram.\",\n+\t\t\t\t\"Instruction %d falls through to instruction %d outside its subprogram.\",\n+\t\t\t\tt, w);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n \tif (e == BRANCH) {\n \t\t/* mark branch target for state pruning */\n \t\tmark_prune_point(env, w);\ndiff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c\nindex 52d3cec336727..9401fffcedfd6 100644\n--- a/kernel/bpf/fixups.c\n+++ b/kernel/bpf/fixups.c\n@@ -8,6 +8,7 @@\n #include \u003clinux/bsearch.h\u003e\n #include \u003clinux/sort.h\u003e\n #include \u003clinux/perf_event.h\u003e\n+#include \u003clinux/sched/signal.h\u003e\n #include \u003cnet/xdp.h\u003e\n #include \"disasm.h\"\n \n@@ -306,12 +307,28 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len)\n \t}\n }\n \n+/*\n+ * Some post-verification instruction rewriting passes require an\n+ * O(prog-\u003elen) operation per instruction. Keep their shared primitives\n+ * killable and preemptible.\n+ */\n+static bool bpf_rewrite_must_abort(void)\n+{\n+\tif (fatal_signal_pending(current))\n+\t\treturn true;\n+\tcond_resched();\n+\treturn false;\n+}\n+\n struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,\n \t\t\t\t const struct bpf_insn *patch, u32 len)\n {\n \tstruct bpf_prog *new_prog;\n \tstruct bpf_insn_aux_data *new_data = NULL;\n \n+\tif (bpf_rewrite_must_abort())\n+\t\treturn NULL;\n+\n \tif (len \u003e 1) {\n \t\tnew_data = vrealloc(env-\u003einsn_aux_data,\n \t\t\t\t array_size(env-\u003eprog-\u003elen + len - 1,\n@@ -523,6 +540,9 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt)\n \tunsigned int orig_prog_len = env-\u003eprog-\u003elen;\n \tint err;\n \n+\tif (bpf_rewrite_must_abort())\n+\t\treturn -EINTR;\n+\n \tif (bpf_prog_is_offloaded(env-\u003eprog-\u003eaux))\n \t\tbpf_prog_offload_remove_insns(env, off, cnt);\n \n@@ -2666,4 +2686,3 @@ int bpf_remove_fastcall_spills_fills(struct bpf_verifier_env *env)\n \n \treturn 0;\n }\n-\ndiff --git a/kernel/bpf/states.c b/kernel/bpf/states.c\nindex 66fb11b6c6a76..2f8f3fe164b01 100644\n--- a/kernel/bpf/states.c\n+++ b/kernel/bpf/states.c\n@@ -339,6 +339,7 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)\n \tif (idmap-\u003ecnt \u003c BPF_ID_MAP_SIZE) {\n \t\tmap[idmap-\u003ecnt].old = old_id;\n \t\tmap[idmap-\u003ecnt].cur = cur_id;\n+\t\tmap[idmap-\u003ecnt].pkt_ptr_delta_set = false;\n \t\tidmap-\u003ecnt++;\n \t\treturn true;\n \t}\n@@ -352,6 +353,43 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)\n \treturn false;\n }\n \n+static bool check_pkt_ptr_ids(const struct bpf_reg_state *old,\n+\t\t\t const struct bpf_reg_state *cur,\n+\t\t\t struct bpf_idmap *idmap)\n+{\n+\tstruct bpf_id_pair *map = idmap-\u003emap;\n+\ts64 delta;\n+\tunsigned int i;\n+\n+\tif (!check_ids(old-\u003eid, cur-\u003eid, idmap))\n+\t\treturn false;\n+\tif (!old-\u003eid)\n+\t\treturn true;\n+\n+\t/*\n+\t * Packet range is shared by all pointers with the same ID. Preserve\n+\t * their relative displacement, while allowing the whole class to move.\n+\t * Packet pointer offsets are bounded by BPF_MAX_VAR_OFF, so the delta\n+\t * between two valid offsets fits in s32.\n+\t */\n+\tdelta = (s64)(cur-\u003er64.base - old-\u003er64.base);\n+\tif (delta \u003c S32_MIN || delta \u003e S32_MAX)\n+\t\treturn false;\n+\n+\tfor (i = 0; i \u003c idmap-\u003ecnt; i++) {\n+\t\tif (map[i].old != old-\u003eid)\n+\t\t\tcontinue;\n+\t\tif (!map[i].pkt_ptr_delta_set) {\n+\t\t\tmap[i].pkt_ptr_delta = delta;\n+\t\t\tmap[i].pkt_ptr_delta_set = true;\n+\t\t\treturn true;\n+\t\t}\n+\t\treturn map[i].pkt_ptr_delta == delta;\n+\t}\n+\n+\treturn false;\n+}\n+\n /*\n * Compare scalar register IDs for state equivalence.\n *\n@@ -632,8 +670,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,\n \t\t} else if (rold-\u003erange \u003e rcur-\u003erange) {\n \t\t\treturn false;\n \t\t}\n-\t\t/* id relations must be preserved */\n-\t\tif (!check_ids(rold-\u003eid, rcur-\u003eid, idmap))\n+\t\t/* id relations and intra-class displacement must be preserved */\n+\t\tif (!check_pkt_ptr_ids(rold, rcur, idmap))\n \t\t\treturn false;\n \t\t/* new val must satisfy old val knowledge */\n \t\treturn range_within(rold, rcur) \u0026\u0026\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex 1c3039f3fc323..ad14a7fbed72f 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -19,6 +19,7 @@\n #include \u003clinux/bsearch.h\u003e\n #include \u003clinux/sort.h\u003e\n #include \u003clinux/perf_event.h\u003e\n+#include \u003clinux/sched/signal.h\u003e\n #include \u003clinux/ctype.h\u003e\n #include \u003clinux/error-injection.h\u003e\n #include \u003clinux/bpf_lsm.h\u003e\n@@ -447,6 +448,13 @@ static bool reg_may_point_to_spin_lock(const struct bpf_reg_state *reg)\n \treturn btf_record_has_field(reg_btf_record(reg), BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK);\n }\n \n+static bool map_value_has_static_identity(const struct bpf_reg_state *reg)\n+{\n+\tconst struct bpf_map *map = reg-\u003emap_ptr;\n+\n+\treturn !reg-\u003emap_uid \u0026\u0026 map-\u003emap_type == BPF_MAP_TYPE_ARRAY \u0026\u0026 map-\u003emax_entries == 1;\n+}\n+\n static bool type_is_rdonly_mem(u32 type)\n {\n \treturn type \u0026 MEM_RDONLY;\n@@ -1925,11 +1933,18 @@ static void refine_map_lookup_value(struct bpf_reg_state *reg)\n \tif (map-\u003einner_map_meta) {\n \t\treg-\u003etype = CONST_PTR_TO_MAP | maybe_null;\n \t\treg-\u003emap_ptr = map-\u003einner_map_meta;\n-\t\t/* transfer reg's id which is unique for every map_lookup_elem\n-\t\t * as UID of the inner map.\n+\t\t/*\n+\t\t * Concrete inner maps share the verifier-visible inner_map_meta.\n+\t\t * Preserve the lookup identity only for embedded objects whose\n+\t\t * verification needs to distinguish concrete map instances. Doing\n+\t\t * this for every inner map makes state pruning too conservative.\n+\t\t *\n+\t\t * Each map lookup has a unique register ID, so use it as the UID of\n+\t\t * the inner map.\n \t\t */\n \t\tif (btf_record_has_field(map-\u003einner_map_meta-\u003erecord,\n-\t\t\t\t\t BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK))\n+\t\t\t\t\t BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK |\n+\t\t\t\t\t BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK))\n \t\t\treg-\u003emap_uid = reg-\u003eid;\n \t} else if (map-\u003emap_type == BPF_MAP_TYPE_XSKMAP) {\n \t\treg-\u003etype = PTR_TO_XDP_SOCK | maybe_null;\n@@ -10035,6 +10050,9 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env,\n \t__mark_reg_known_zero(\u0026callee-\u003eregs[BPF_REG_3]);\n \tcallee-\u003eregs[BPF_REG_3].map_ptr = caller-\u003eregs[BPF_REG_1].map_ptr;\n \tcallee-\u003eregs[BPF_REG_3].map_uid = caller-\u003eregs[BPF_REG_1].map_uid;\n+\tif (reg_may_point_to_spin_lock(\u0026callee-\u003eregs[BPF_REG_3]) \u0026\u0026\n+\t !map_value_has_static_identity(\u0026callee-\u003eregs[BPF_REG_3]))\n+\t\tcallee-\u003eregs[BPF_REG_3].id = ++env-\u003eid_gen;\n \n \t/* pointer to stack or null */\n \tcallee-\u003eregs[BPF_REG_4] = caller-\u003eregs[BPF_REG_3];\n@@ -10131,6 +10149,9 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,\n \t__mark_reg_known_zero(\u0026callee-\u003eregs[BPF_REG_3]);\n \tcallee-\u003eregs[BPF_REG_3].map_ptr = map_ptr;\n \tcallee-\u003eregs[BPF_REG_3].map_uid = map_uid;\n+\tif (reg_may_point_to_spin_lock(\u0026callee-\u003eregs[BPF_REG_3]) \u0026\u0026\n+\t !map_value_has_static_identity(\u0026callee-\u003eregs[BPF_REG_3]))\n+\t\tcallee-\u003eregs[BPF_REG_3].id = ++env-\u003eid_gen;\n \n \t/* unused */\n \tbpf_mark_reg_not_init(env, \u0026callee-\u003eregs[BPF_REG_4]);\n@@ -10249,6 +10270,9 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,\n \t__mark_reg_known_zero(\u0026callee-\u003eregs[BPF_REG_3]);\n \tcallee-\u003eregs[BPF_REG_3].map_ptr = map_ptr;\n \tcallee-\u003eregs[BPF_REG_3].map_uid = map_uid;\n+\tif (reg_may_point_to_spin_lock(\u0026callee-\u003eregs[BPF_REG_3]) \u0026\u0026\n+\t !map_value_has_static_identity(\u0026callee-\u003eregs[BPF_REG_3]))\n+\t\tcallee-\u003eregs[BPF_REG_3].id = ++env-\u003eid_gen;\n \n \t/* unused */\n \tbpf_mark_reg_not_init(env, \u0026callee-\u003eregs[BPF_REG_4]);\n@@ -21367,6 +21391,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \n \tif (ret == 0)\n \t\tret = bpf_fixup_call_args(env);\n+\t/*\n+\t * JIT constant blinding treats instruction patching failures as a\n+\t * request to fall back to the interpreter. Do not let such fallback\n+\t * consume a fatal-signal cancellation from bpf_patch_insn_data().\n+\t */\n+\tif (ret == 0 \u0026\u0026 fatal_signal_pending(current))\n+\t\tret = -EINTR;\n \n \tenv-\u003everification_time = ktime_get_ns() - start_time;\n \tprint_verification_stats(env);\n@@ -21425,6 +21456,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \t\tenv-\u003eprog-\u003eexpected_attach_type = 0;\n \n \tenv-\u003eprog = __bpf_prog_select_runtime(env, env-\u003eprog, \u0026ret);\n+\tif (ret == 0 \u0026\u0026 fatal_signal_pending(current))\n+\t\tret = -EINTR;\n \n err_release_maps:\n \tif (ret)\ndiff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c\nindex a18d3680fb169..c350fbb958455 100644\n--- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c\n+++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c\n@@ -14,6 +14,117 @@\n \n static char log[16 * 1024];\n \n+static int load_core_relo_subprog(int btf_fd, int main_id, int sub_id,\n+\t\t\t\t int enum_id, int access_str_off, bool relocate)\n+{\n+\tstruct bpf_insn insns[] = {\n+\t\tBPF_CALL_REL(2),\n+\t\tBPF_MOV64_IMM(BPF_REG_0, 0),\n+\t\tBPF_EXIT_INSN(),\n+\t\tBPF_MOV64_IMM(BPF_REG_0, 0),\n+\t\tBPF_EXIT_INSN(),\n+\t};\n+\tstruct bpf_func_info funcs[] = {\n+\t\t{ .insn_off = 0, .type_id = main_id },\n+\t\t{ .insn_off = 3, .type_id = sub_id },\n+\t};\n+\tstruct bpf_core_relo relo = {\n+\t\t.insn_off = 2 * sizeof(struct bpf_insn),\n+\t\t.type_id = enum_id,\n+\t\t.access_str_off = access_str_off,\n+\t\t.kind = BPF_CORE_ENUMVAL_VALUE,\n+\t};\n+\tunion bpf_attr attr = {\n+\t\t.prog_type = BPF_PROG_TYPE_SOCKET_FILTER,\n+\t\t.insn_cnt = ARRAY_SIZE(insns),\n+\t\t.insns = (__u64)insns,\n+\t\t.license = (__u64)\"GPL\",\n+\t\t.log_buf = (__u64)log,\n+\t\t.log_size = sizeof(log),\n+\t\t.log_level = 1,\n+\t\t.prog_btf_fd = btf_fd,\n+\t\t.func_info_rec_size = sizeof(struct bpf_func_info),\n+\t\t.func_info = (__u64)funcs,\n+\t\t.func_info_cnt = ARRAY_SIZE(funcs),\n+\t};\n+\n+\tif (relocate) {\n+\t\tattr.core_relo_cnt = 1;\n+\t\tattr.core_relos = (__u64)\u0026relo;\n+\t\tattr.core_relo_rec_size = sizeof(relo);\n+\t}\n+\tmemset(log, 0, sizeof(log));\n+\treturn sys_bpf_prog_load(\u0026attr, sizeof(attr), 1);\n+}\n+\n+static void test_poisoned_subprog_terminator(void)\n+{\n+\tconst void *raw_btf;\n+\tstruct btf *btf = NULL;\n+\t__u32 raw_btf_size;\n+\tint access_str_off, btf_fd = -1, enum_id;\n+\tint int_id, main_id, prog_fd = -1, proto_id, sub_id;\n+\n+\tbtf = btf__new_empty();\n+\tif (!ASSERT_OK_PTR(btf, \"btf_new_empty\"))\n+\t\treturn;\n+\tint_id = btf__add_int(btf, \"int\", 4, BTF_INT_SIGNED);\n+\tif (!ASSERT_GT(int_id, 0, \"add_int\"))\n+\t\tgoto cleanup;\n+\tproto_id = btf__add_func_proto(btf, int_id);\n+\tif (!ASSERT_GT(proto_id, 0, \"add_func_proto\"))\n+\t\tgoto cleanup;\n+\tmain_id = btf__add_func(btf, \"main_fn\", BTF_FUNC_STATIC, proto_id);\n+\tif (!ASSERT_GT(main_id, 0, \"add_main_func\"))\n+\t\tgoto cleanup;\n+\tsub_id = btf__add_func(btf, \"sub_fn\", BTF_FUNC_STATIC, proto_id);\n+\tif (!ASSERT_GT(sub_id, 0, \"add_sub_func\"))\n+\t\tgoto cleanup;\n+\tenum_id = btf__add_enum(btf, \"core_relo_subprog_poison_missing\", 4);\n+\tif (!ASSERT_GT(enum_id, 0, \"add_enum\") ||\n+\t !ASSERT_OK(btf__add_enum_value(btf, \"value\", 0), \"add_enum_value\"))\n+\t\tgoto cleanup;\n+\taccess_str_off = btf__add_str(btf, \"0\");\n+\tif (!ASSERT_GT(access_str_off, 0, \"add_access_str\"))\n+\t\tgoto cleanup;\n+\n+\traw_btf = btf__raw_data(btf, \u0026raw_btf_size);\n+\tif (!ASSERT_OK_PTR(raw_btf, \"raw_btf\"))\n+\t\tgoto cleanup;\n+\tbtf_fd = bpf_btf_load(raw_btf, raw_btf_size, NULL);\n+\tif (!ASSERT_GE(btf_fd, 0, \"btf_load\"))\n+\t\tgoto cleanup;\n+\n+\t/* The same two-subprogram program is valid before the relocation. */\n+\tprog_fd = load_core_relo_subprog(btf_fd, main_id, sub_id, enum_id,\n+\t\t\t\t\t access_str_off, false);\n+\tif (!ASSERT_GE(prog_fd, 0, \"control_load\"))\n+\t\tgoto cleanup;\n+\tclose(prog_fd);\n+\tprog_fd = -1;\n+\n+\t/*\n+\t * Poison the first subprogram's terminal exit. The verifier must reject\n+\t * the resulting control flow across the subprogram boundary in the CFG.\n+\t */\n+\tprog_fd = load_core_relo_subprog(btf_fd, main_id, sub_id, enum_id,\n+\t\t\t\t\t access_str_off, true);\n+\tif (!ASSERT_LT(prog_fd, 0, \"poisoned_load\"))\n+\t\tgoto cleanup;\n+\tASSERT_HAS_SUBSTR(log, \"fall-through out of subprog from insn 2 to 3\",\n+\t\t\t \"poisoned_load_log\");\n+\n+cleanup:\n+\tif (env.verbosity \u003e VERBOSE_NORMAL \u0026\u0026 log[0]) {\n+\t\tprintf(\"-------- program load log start --------\\n\");\n+\t\tprintf(\"%s\", log);\n+\t\tprintf(\"-------- program load log end ----------\\n\");\n+\t}\n+\tclose(prog_fd);\n+\tclose(btf_fd);\n+\tbtf__free(btf);\n+}\n+\n /* Check that verifier rejects BPF program containing relocation\n * pointing to non-existent BTF type.\n */\n@@ -120,6 +231,8 @@ static void test_bad_local_id(void)\n \n void test_core_reloc_raw(void)\n {\n+\tif (test__start_subtest(\"poisoned_subprog_terminator\"))\n+\t\ttest_poisoned_subprog_terminator();\n \tif (test__start_subtest(\"bad_local_id\"))\n \t\ttest_bad_local_id();\n }\ndiff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c\nindex 64ac49ad67e63..5c572dd725e75 100644\n--- a/tools/testing/selftests/bpf/prog_tests/verifier.c\n+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c\n@@ -25,6 +25,7 @@\n #include \"verifier_btf_ctx_access.skel.h\"\n #include \"verifier_btf_unreliable_prog.skel.h\"\n #include \"verifier_call_large_imm.skel.h\"\n+#include \"verifier_callback_lock.skel.h\"\n #include \"verifier_cfg.skel.h\"\n #include \"verifier_cgroup_inv_retcode.skel.h\"\n #include \"verifier_cgroup_skb.skel.h\"\n@@ -188,6 +189,7 @@ void test_verifier_bswap(void) { RUN(verifier_bswap); }\n void test_verifier_btf_ctx_access(void) { RUN(verifier_btf_ctx_access); }\n void test_verifier_btf_unreliable_prog(void) { RUN(verifier_btf_unreliable_prog); }\n void test_verifier_call_large_imm(void) { RUN(verifier_call_large_imm); }\n+void test_verifier_callback_lock(void) { RUN(verifier_callback_lock); }\n void test_verifier_cfg(void) { RUN(verifier_cfg); }\n void test_verifier_cgroup_inv_retcode(void) { RUN(verifier_cgroup_inv_retcode); }\n void test_verifier_cgroup_skb(void) { RUN(verifier_cgroup_skb); }\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_callback_lock.c b/tools/testing/selftests/bpf/progs/verifier_callback_lock.c\nnew file mode 100644\nindex 0000000000000..d23e13908cebf\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/verifier_callback_lock.c\n@@ -0,0 +1,121 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003clinux/bpf.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"bpf_misc.h\"\n+\n+struct bpf_map;\n+\n+struct lock_value {\n+\tstruct bpf_spin_lock lock;\n+};\n+\n+struct inner_lock_map {\n+\t__uint(type, BPF_MAP_TYPE_ARRAY);\n+\t__uint(max_entries, 1);\n+\t__type(key, int);\n+\t__type(value, struct lock_value);\n+} inner_lock_map_a SEC(\".maps\"), inner_lock_map_b SEC(\".maps\");\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);\n+\t__uint(max_entries, 2);\n+\t__type(key, int);\n+\t__array(values, struct inner_lock_map);\n+} lock_map_of_maps SEC(\".maps\") = {\n+\t.values = {\n+\t\t[0] = \u0026inner_lock_map_a,\n+\t\t[1] = \u0026inner_lock_map_b,\n+\t},\n+};\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARRAY);\n+\t__uint(max_entries, 2);\n+\t__type(key, int);\n+\t__type(value, struct lock_value);\n+} lock_map SEC(\".maps\");\n+\n+struct callback_ctx {\n+\tstruct lock_value *value;\n+};\n+\n+static long lock_different_value(struct bpf_map *map, int *key,\n+\t\t\t\t struct lock_value *value, struct callback_ctx *ctx)\n+{\n+\tbpf_spin_lock(\u0026value-\u003elock);\n+\tbpf_spin_unlock(\u0026ctx-\u003evalue-\u003elock);\n+\treturn 0;\n+}\n+\n+static long nest_lock_different_value(struct bpf_map *map, int *key,\n+\t\t\t\t struct lock_value *value, void *data)\n+{\n+\tstruct callback_ctx ctx = { .value = value };\n+\n+\tbpf_for_each_map_elem(\u0026lock_map, lock_different_value, \u0026ctx, 0);\n+\treturn 0;\n+}\n+\n+SEC(\"?tc\")\n+__description(\"callback map value has a distinct lock identity\")\n+__failure __msg(\"bpf_spin_unlock of different lock\")\n+int callback_value_lock_identity(void *ctx)\n+{\n+\tbpf_for_each_map_elem(\u0026lock_map, nest_lock_different_value, NULL, 0);\n+\treturn 0;\n+}\n+\n+static long nest_lock_different_inner_value(struct bpf_map *map, int *key,\n+\t\t\t\t\t struct lock_value *value, void *data)\n+{\n+\tstruct callback_ctx ctx = { .value = value };\n+\tint inner_key = 1;\n+\tvoid *inner_map;\n+\n+\tinner_map = bpf_map_lookup_elem(\u0026lock_map_of_maps, \u0026inner_key);\n+\tif (!inner_map)\n+\t\treturn 0;\n+\tbpf_for_each_map_elem(inner_map, lock_different_value, \u0026ctx, 0);\n+\treturn 0;\n+}\n+\n+SEC(\"?tc\")\n+__description(\"distinct one-element inner maps have distinct lock identities\")\n+__failure __msg(\"bpf_spin_unlock of different lock\")\n+int callback_inner_map_value_lock_identity(void *ctx)\n+{\n+\tint inner_key = 0;\n+\tvoid *inner_map;\n+\n+\tinner_map = bpf_map_lookup_elem(\u0026lock_map_of_maps, \u0026inner_key);\n+\tif (!inner_map)\n+\t\treturn 0;\n+\tbpf_for_each_map_elem(inner_map, nest_lock_different_inner_value, NULL, 0);\n+\treturn 0;\n+}\n+\n+static long lock_same_value(struct bpf_map *map, int *key,\n+\t\t\t struct lock_value *value, void *data)\n+{\n+\tbpf_spin_lock(\u0026value-\u003elock);\n+\tbpf_spin_unlock(\u0026value-\u003elock);\n+\treturn 0;\n+}\n+\n+static long nest_lock_same_value(struct bpf_map *map, int *key,\n+\t\t\t\t struct lock_value *value, void *data)\n+{\n+\tbpf_for_each_map_elem(\u0026lock_map, lock_same_value, NULL, 0);\n+\treturn 0;\n+}\n+\n+SEC(\"?tc\")\n+__description(\"nested callback can lock its own map value\")\n+__success\n+int callback_value_lock_identity_same(void *ctx)\n+{\n+\tbpf_for_each_map_elem(\u0026lock_map, nest_lock_same_value, NULL, 0);\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c\nindex 0b86d95a41335..692a8468f3d74 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c\n@@ -5,6 +5,50 @@\n #include \u003cbpf/bpf_helpers.h\u003e\n #include \"bpf_misc.h\"\n \n+SEC(\"xdp\")\n+__description(\"XDP pkt regsafe preserves packet pointer class displacement\")\n+__failure __msg(\"R2 min value is outside of the allowed memory range\")\n+__flag(BPF_F_ANY_ALIGNMENT)\n+__naked void pkt_regsafe_class_displacement(void)\n+{\n+\tasm volatile (\"\\\n+\tr8 = *(u32 *)(r1 + %[xdp_md_data_end]);\\\n+\tr9 = *(u32 *)(r1 + %[xdp_md_data]);\\\n+\tr6 = r9;\\\n+\tr6 += 8;\\\n+\tif r6 \u003e r8 goto l_exit_%=;\\\n+\tr0 = *(u64 *)(r9 + 0);\\\n+\tr4 = r0;\\\n+\tr4 \u0026= 15;\\\n+\tr7 = r0;\\\n+\tr7 \u003e\u003e= 63;\\\n+\tif r7 != 0 goto l_path_b_%=;\\\n+\tr2 = r9;\\\n+\tr2 += r4;\\\n+\tr3 = r2;\\\n+\tr3 += 4;\\\n+\tgoto l_join_%=;\\\n+l_path_b_%=: \\\n+\tr4 \u0026= 3;\\\n+\tr4 += 8;\\\n+\tr2 = r9;\\\n+\tr2 += r4;\\\n+\tr3 = r2;\\\n+\tr3 -= 4;\\\n+l_join_%=: \\\n+\tr5 = r3;\\\n+\tr5 += 4;\\\n+\tif r5 \u003e r8 goto l_exit_%=;\\\n+\tr0 = *(u64 *)(r2 + 0);\\\n+l_exit_%=: \\\n+\tr0 = 0;\\\n+\texit;\\\n+\"\t:\n+\t: __imm_const(xdp_md_data, offsetof(struct xdp_md, data)),\n+\t __imm_const(xdp_md_data_end, offsetof(struct xdp_md, data_end))\n+\t: __clobber_all);\n+}\n+\n SEC(\"xdp\")\n __description(\"XDP pkt read, pkt_end mangling, bad access 1\")\n __failure __msg(\"R3 pointer arithmetic on pkt_end\")\n"
} |
| 2/1 | 2026/09/06 11:55 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_check",
"regsafe",
"check_pkt_ptr_ids",
"bpf_patch_insn_data",
"verifier_remove_insns",
"refine_map_lookup_value",
"map_set_for_each_callback_args",
"set_timer_callback_state",
"set_task_work_schedule_callback_state",
"push_insn"
],
"Reasoning": "The patch introduces functional changes to the BPF verifier subsystem across several files:\n1. kernel/bpf/states.c: Adds check_pkt_ptr_ids() and modifies regsafe() to preserve displacement within packet pointer ID classes during state equivalence pruning.\n2. kernel/bpf/verifier.c: Adds map_value_has_static_identity(), refines spinlock map UID tracking in refine_map_lookup_value(), generates unique register IDs for spinlock-containing map values in callback helpers (map_set_for_each_callback_args, set_timer_callback_state, set_task_work_schedule_callback_state), and adds fatal signal abort checks in bpf_check().\n3. kernel/bpf/cfg.c: Adds fallthrough validation across subprogram boundaries in push_insn().\n4. kernel/bpf/fixups.c: Adds fatal signal checks (bpf_rewrite_must_abort) to bpf_patch_insn_data() and verifier_remove_insns().\n\nThese modifications affect reachable code in the core BPF verifier reachable via sys_bpf(BPF_PROG_LOAD) and warrant fuzzing for state pruning correctness, verifier edge cases, and lock safety.",
"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 e4d2667ab5d8809179d28d9351054834ad1eb9ce
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Sep 6 11:55:48 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 36b65797877d0..5cf92ce185204 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -854,6 +854,8 @@ struct backtrack_state {
struct bpf_id_pair {
u32 old;
u32 cur;
+ s32 pkt_ptr_delta;
+ bool pkt_ptr_delta_set;
};
struct bpf_idmap {
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 842c7d1eabccc..e3c904328ede0 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -102,6 +102,7 @@ enum {
*/
static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
{
+ struct bpf_subprog_info *subprog;
int *insn_stack = env->cfg.insn_stack;
int *insn_state = env->cfg.insn_state;
@@ -121,6 +122,26 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
return -EINVAL;
}
+ /*
+ * check_subprogs() prevents control flow from falling through a
+ * subprogram boundary, but runs before CO-RE relocations can rewrite an
+ * instruction. Reestablish the invariant on the final instruction stream
+ * before constructing the CFG used by later per-subprogram passes.
+ */
+ if (e == FALLTHROUGH) {
+ subprog = bpf_find_containing_subprog(env, t);
+ if (w < subprog->start || w >= (subprog + 1)->start) {
+ verbose_linfo(env, t, "%d: ", t);
+ verbose(env, "fall-through out of subprog from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "fall-through leaves subprogram",
+ "Keep fall-through control flow inside the current subprogram.",
+ "Instruction %d falls through to instruction %d outside its subprogram.",
+ t, w);
+ return -EINVAL;
+ }
+ }
+
if (e == BRANCH) {
/* mark branch target for state pruning */
mark_prune_point(env, w);
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 52d3cec336727..9401fffcedfd6 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -8,6 +8,7 @@
#include <linux/bsearch.h>
#include <linux/sort.h>
#include <linux/perf_event.h>
+#include <linux/sched/signal.h>
#include <net/xdp.h>
#include "disasm.h"
@@ -306,12 +307,28 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len)
}
}
+/*
+ * Some post-verification instruction rewriting passes require an
+ * O(prog->len) operation per instruction. Keep their shared primitives
+ * killable and preemptible.
+ */
+static bool bpf_rewrite_must_abort(void)
+{
+ if (fatal_signal_pending(current))
+ return true;
+ cond_resched();
+ return false;
+}
+
struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
const struct bpf_insn *patch, u32 len)
{
struct bpf_prog *new_prog;
struct bpf_insn_aux_data *new_data = NULL;
+ if (bpf_rewrite_must_abort())
+ return NULL;
+
if (len > 1) {
new_data = vrealloc(env->insn_aux_data,
array_size(env->prog->len + len - 1,
@@ -523,6 +540,9 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt)
unsigned int orig_prog_len = env->prog->len;
int err;
+ if (bpf_rewrite_must_abort())
+ return -EINTR;
+
if (bpf_prog_is_offloaded(env->prog->aux))
bpf_prog_offload_remove_insns(env, off, cnt);
@@ -2666,4 +2686,3 @@ int bpf_remove_fastcall_spills_fills(struct bpf_verifier_env *env)
return 0;
}
-
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 66fb11b6c6a76..2f8f3fe164b01 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -339,6 +339,7 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)
if (idmap->cnt < BPF_ID_MAP_SIZE) {
map[idmap->cnt].old = old_id;
map[idmap->cnt].cur = cur_id;
+ map[idmap->cnt].pkt_ptr_delta_set = false;
idmap->cnt++;
return true;
}
@@ -352,6 +353,43 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)
return false;
}
+static bool check_pkt_ptr_ids(const struct bpf_reg_state *old,
+ const struct bpf_reg_state *cur,
+ struct bpf_idmap *idmap)
+{
+ struct bpf_id_pair *map = idmap->map;
+ s64 delta;
+ unsigned int i;
+
+ if (!check_ids(old->id, cur->id, idmap))
+ return false;
+ if (!old->id)
+ return true;
+
+ /*
+ * Packet range is shared by all pointers with the same ID. Preserve
+ * their relative displacement, while allowing the whole class to move.
+ * Packet pointer offsets are bounded by BPF_MAX_VAR_OFF, so the delta
+ * between two valid offsets fits in s32.
+ */
+ delta = (s64)(cur->r64.base - old->r64.base);
+ if (delta < S32_MIN || delta > S32_MAX)
+ return false;
+
+ for (i = 0; i < idmap->cnt; i++) {
+ if (map[i].old != old->id)
+ continue;
+ if (!map[i].pkt_ptr_delta_set) {
+ map[i].pkt_ptr_delta = delta;
+ map[i].pkt_ptr_delta_set = true;
+ return true;
+ }
+ return map[i].pkt_ptr_delta == delta;
+ }
+
+ return false;
+}
+
/*
* Compare scalar register IDs for state equivalence.
*
@@ -632,8 +670,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
} else if (rold->range > rcur->range) {
return false;
}
- /* id relations must be preserved */
- if (!check_ids(rold->id, rcur->id, idmap))
+ /* id relations and intra-class displacement must be preserved */
+ if (!check_pkt_ptr_ids(rold, rcur, idmap))
return false;
/* new val must satisfy old val knowledge */
return range_within(rold, rcur) &&
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 1c3039f3fc323..ad14a7fbed72f 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -19,6 +19,7 @@
#include <linux/bsearch.h>
#include <linux/sort.h>
#include <linux/perf_event.h>
+#include <linux/sched/signal.h>
#include <linux/ctype.h>
#include <linux/error-injection.h>
#include <linux/bpf_lsm.h>
@@ -447,6 +448,13 @@ static bool reg_may_point_to_spin_lock(const struct bpf_reg_state *reg)
return btf_record_has_field(reg_btf_record(reg), BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK);
}
+static bool map_value_has_static_identity(const struct bpf_reg_state *reg)
+{
+ const struct bpf_map *map = reg->map_ptr;
+
+ return !reg->map_uid && map->map_type == BPF_MAP_TYPE_ARRAY && map->max_entries == 1;
+}
+
static bool type_is_rdonly_mem(u32 type)
{
return type & MEM_RDONLY;
@@ -1925,11 +1933,18 @@ static void refine_map_lookup_value(struct bpf_reg_state *reg)
if (map->inner_map_meta) {
reg->type = CONST_PTR_TO_MAP | maybe_null;
reg->map_ptr = map->inner_map_meta;
- /* transfer reg's id which is unique for every map_lookup_elem
- * as UID of the inner map.
+ /*
+ * Concrete inner maps share the verifier-visible inner_map_meta.
+ * Preserve the lookup identity only for embedded objects whose
+ * verification needs to distinguish concrete map instances. Doing
+ * this for every inner map makes state pruning too conservative.
+ *
+ * Each map lookup has a unique register ID, so use it as the UID of
+ * the inner map.
*/
if (btf_record_has_field(map->inner_map_meta->record,
- BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK))
+ BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK |
+ BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK))
reg->map_uid = reg->id;
} else if (map->map_type == BPF_MAP_TYPE_XSKMAP) {
reg->type = PTR_TO_XDP_SOCK | maybe_null;
@@ -10035,6 +10050,9 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env,
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr;
callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid;
+ if (reg_may_point_to_spin_lock(&callee->regs[BPF_REG_3]) &&
+ !map_value_has_static_identity(&callee->regs[BPF_REG_3]))
+ callee->regs[BPF_REG_3].id = ++env->id_gen;
/* pointer to stack or null */
callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3];
@@ -10131,6 +10149,9 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = map_ptr;
callee->regs[BPF_REG_3].map_uid = map_uid;
+ if (reg_may_point_to_spin_lock(&callee->regs[BPF_REG_3]) &&
+ !map_value_has_static_identity(&callee->regs[BPF_REG_3]))
+ callee->regs[BPF_REG_3].id = ++env->id_gen;
/* unused */
bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
@@ -10249,6 +10270,9 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = map_ptr;
callee->regs[BPF_REG_3].map_uid = map_uid;
+ if (reg_may_point_to_spin_lock(&callee->regs[BPF_REG_3]) &&
+ !map_value_has_static_identity(&callee->regs[BPF_REG_3]))
+ callee->regs[BPF_REG_3].id = ++env->id_gen;
/* unused */
bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
@@ -21367,6 +21391,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret == 0)
ret = bpf_fixup_call_args(env);
+ /*
+ * JIT constant blinding treats instruction patching failures as a
+ * request to fall back to the interpreter. Do not let such fallback
+ * consume a fatal-signal cancellation from bpf_patch_insn_data().
+ */
+ if (ret == 0 && fatal_signal_pending(current))
+ ret = -EINTR;
env->verification_time = ktime_get_ns() - start_time;
print_verification_stats(env);
@@ -21425,6 +21456,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
env->prog->expected_attach_type = 0;
env->prog = __bpf_prog_select_runtime(env, env->prog, &ret);
+ if (ret == 0 && fatal_signal_pending(current))
+ ret = -EINTR;
err_release_maps:
if (ret)
diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
index a18d3680fb169..c350fbb958455 100644
--- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
+++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
@@ -14,6 +14,117 @@
static char log[16 * 1024];
+static int load_core_relo_subprog(int btf_fd, int main_id, int sub_id,
+ int enum_id, int access_str_off, bool relocate)
+{
+ struct bpf_insn insns[] = {
+ BPF_CALL_REL(2),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ struct bpf_func_info funcs[] = {
+ { .insn_off = 0, .type_id = main_id },
+ { .insn_off = 3, .type_id = sub_id },
+ };
+ struct bpf_core_relo relo = {
+ .insn_off = 2 * sizeof(struct bpf_insn),
+ .type_id = enum_id,
+ .access_str_off = access_str_off,
+ .kind = BPF_CORE_ENUMVAL_VALUE,
+ };
+ union bpf_attr attr = {
+ .prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
+ .insn_cnt = ARRAY_SIZE(insns),
+ .insns = (__u64)insns,
+ .license = (__u64)"GPL",
+ .log_buf = (__u64)log,
+ .log_size = sizeof(log),
+ .log_level = 1,
+ .prog_btf_fd = btf_fd,
+ .func_info_rec_size = sizeof(struct bpf_func_info),
+ .func_info = (__u64)funcs,
+ .func_info_cnt = ARRAY_SIZE(funcs),
+ };
+
+ if (relocate) {
+ attr.core_relo_cnt = 1;
+ attr.core_relos = (__u64)&relo;
+ attr.core_relo_rec_size = sizeof(relo);
+ }
+ memset(log, 0, sizeof(log));
+ return sys_bpf_prog_load(&attr, sizeof(attr), 1);
+}
+
+static void test_poisoned_subprog_terminator(void)
+{
+ const void *raw_btf;
+ struct btf *btf = NULL;
+ __u32 raw_btf_size;
+ int access_str_off, btf_fd = -1, enum_id;
+ int int_id, main_id, prog_fd = -1, proto_id, sub_id;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
+ return;
+ int_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+ if (!ASSERT_GT(int_id, 0, "add_int"))
+ goto cleanup;
+ proto_id = btf__add_func_proto(btf, int_id);
+ if (!ASSERT_GT(proto_id, 0, "add_func_proto"))
+ goto cleanup;
+ main_id = btf__add_func(btf, "main_fn", BTF_FUNC_STATIC, proto_id);
+ if (!ASSERT_GT(main_id, 0, "add_main_func"))
+ goto cleanup;
+ sub_id = btf__add_func(btf, "sub_fn", BTF_FUNC_STATIC, proto_id);
+ if (!ASSERT_GT(sub_id, 0, "add_sub_func"))
+ goto cleanup;
+ enum_id = btf__add_enum(btf, "core_relo_subprog_poison_missing", 4);
+ if (!ASSERT_GT(enum_id, 0, "add_enum") ||
+ !ASSERT_OK(btf__add_enum_value(btf, "value", 0), "add_enum_value"))
+ goto cleanup;
+ access_str_off = btf__add_str(btf, "0");
+ if (!ASSERT_GT(access_str_off, 0, "add_access_str"))
+ goto cleanup;
+
+ raw_btf = btf__raw_data(btf, &raw_btf_size);
+ if (!ASSERT_OK_PTR(raw_btf, "raw_btf"))
+ goto cleanup;
+ btf_fd = bpf_btf_load(raw_btf, raw_btf_size, NULL);
+ if (!ASSERT_GE(btf_fd, 0, "btf_load"))
+ goto cleanup;
+
+ /* The same two-subprogram program is valid before the relocation. */
+ prog_fd = load_core_relo_subprog(btf_fd, main_id, sub_id, enum_id,
+ access_str_off, false);
+ if (!ASSERT_GE(prog_fd, 0, "control_load"))
+ goto cleanup;
+ close(prog_fd);
+ prog_fd = -1;
+
+ /*
+ * Poison the first subprogram's terminal exit. The verifier must reject
+ * the resulting control flow across the subprogram boundary in the CFG.
+ */
+ prog_fd = load_core_relo_subprog(btf_fd, main_id, sub_id, enum_id,
+ access_str_off, true);
+ if (!ASSERT_LT(prog_fd, 0, "poisoned_load"))
+ goto cleanup;
+ ASSERT_HAS_SUBSTR(log, "fall-through out of subprog from insn 2 to 3",
+ "poisoned_load_log");
+
+cleanup:
+ if (env.verbosity > VERBOSE_NORMAL && log[0]) {
+ printf("-------- program load log start --------\n");
+ printf("%s", log);
+ printf("-------- program load log end ----------\n");
+ }
+ close(prog_fd);
+ close(btf_fd);
+ btf__free(btf);
+}
+
/* Check that verifier rejects BPF program containing relocation
* pointing to non-existent BTF type.
*/
@@ -120,6 +231,8 @@ static void test_bad_local_id(void)
void test_core_reloc_raw(void)
{
+ if (test__start_subtest("poisoned_subprog_terminator"))
+ test_poisoned_subprog_terminator();
if (test__start_subtest("bad_local_id"))
test_bad_local_id();
}
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 64ac49ad67e63..5c572dd725e75 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -25,6 +25,7 @@
#include "verifier_btf_ctx_access.skel.h"
#include "verifier_btf_unreliable_prog.skel.h"
#include "verifier_call_large_imm.skel.h"
+#include "verifier_callback_lock.skel.h"
#include "verifier_cfg.skel.h"
#include "verifier_cgroup_inv_retcode.skel.h"
#include "verifier_cgroup_skb.skel.h"
@@ -188,6 +189,7 @@ void test_verifier_bswap(void) { RUN(verifier_bswap); }
void test_verifier_btf_ctx_access(void) { RUN(verifier_btf_ctx_access); }
void test_verifier_btf_unreliable_prog(void) { RUN(verifier_btf_unreliable_prog); }
void test_verifier_call_large_imm(void) { RUN(verifier_call_large_imm); }
+void test_verifier_callback_lock(void) { RUN(verifier_callback_lock); }
void test_verifier_cfg(void) { RUN(verifier_cfg); }
void test_verifier_cgroup_inv_retcode(void) { RUN(verifier_cgroup_inv_retcode); }
void test_verifier_cgroup_skb(void) { RUN(verifier_cgroup_skb); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_callback_lock.c b/tools/testing/selftests/bpf/progs/verifier_callback_lock.c
new file mode 100644
index 0000000000000..d23e13908cebf
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_callback_lock.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+struct bpf_map;
+
+struct lock_value {
+ struct bpf_spin_lock lock;
+};
+
+struct inner_lock_map {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct lock_value);
+} inner_lock_map_a SEC(".maps"), inner_lock_map_b SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
+ __uint(max_entries, 2);
+ __type(key, int);
+ __array(values, struct inner_lock_map);
+} lock_map_of_maps SEC(".maps") = {
+ .values = {
+ [0] = &inner_lock_map_a,
+ [1] = &inner_lock_map_b,
+ },
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 2);
+ __type(key, int);
+ __type(value, struct lock_value);
+} lock_map SEC(".maps");
+
+struct callback_ctx {
+ struct lock_value *value;
+};
+
+static long lock_different_value(struct bpf_map *map, int *key,
+ struct lock_value *value, struct callback_ctx *ctx)
+{
+ bpf_spin_lock(&value->lock);
+ bpf_spin_unlock(&ctx->value->lock);
+ return 0;
+}
+
+static long nest_lock_different_value(struct bpf_map *map, int *key,
+ struct lock_value *value, void *data)
+{
+ struct callback_ctx ctx = { .value = value };
+
+ bpf_for_each_map_elem(&lock_map, lock_different_value, &ctx, 0);
+ return 0;
+}
+
+SEC("?tc")
+__description("callback map value has a distinct lock identity")
+__failure __msg("bpf_spin_unlock of different lock")
+int callback_value_lock_identity(void *ctx)
+{
+ bpf_for_each_map_elem(&lock_map, nest_lock_different_value, NULL, 0);
+ return 0;
+}
+
+static long nest_lock_different_inner_value(struct bpf_map *map, int *key,
+ struct lock_value *value, void *data)
+{
+ struct callback_ctx ctx = { .value = value };
+ int inner_key = 1;
+ void *inner_map;
+
+ inner_map = bpf_map_lookup_elem(&lock_map_of_maps, &inner_key);
+ if (!inner_map)
+ return 0;
+ bpf_for_each_map_elem(inner_map, lock_different_value, &ctx, 0);
+ return 0;
+}
+
+SEC("?tc")
+__description("distinct one-element inner maps have distinct lock identities")
+__failure __msg("bpf_spin_unlock of different lock")
+int callback_inner_map_value_lock_identity(void *ctx)
+{
+ int inner_key = 0;
+ void *inner_map;
+
+ inner_map = bpf_map_lookup_elem(&lock_map_of_maps, &inner_key);
+ if (!inner_map)
+ return 0;
+ bpf_for_each_map_elem(inner_map, nest_lock_different_inner_value, NULL, 0);
+ return 0;
+}
+
+static long lock_same_value(struct bpf_map *map, int *key,
+ struct lock_value *value, void *data)
+{
+ bpf_spin_lock(&value->lock);
+ bpf_spin_unlock(&value->lock);
+ return 0;
+}
+
+static long nest_lock_same_value(struct bpf_map *map, int *key,
+ struct lock_value *value, void *data)
+{
+ bpf_for_each_map_elem(&lock_map, lock_same_value, NULL, 0);
+ return 0;
+}
+
+SEC("?tc")
+__description("nested callback can lock its own map value")
+__success
+int callback_value_lock_identity_same(void *ctx)
+{
+ bpf_for_each_map_elem(&lock_map, nest_lock_same_value, NULL, 0);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
index 0b86d95a41335..692a8468f3d74 100644
--- a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
@@ -5,6 +5,50 @@
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
+SEC("xdp")
+__description("XDP pkt regsafe preserves packet pointer class displacement")
+__failure __msg("R2 min value is outside of the allowed memory range")
+__flag(BPF_F_ANY_ALIGNMENT)
+__naked void pkt_regsafe_class_displacement(void)
+{
+ asm volatile ("\
+ r8 = *(u32 *)(r1 + %[xdp_md_data_end]);\
+ r9 = *(u32 *)(r1 + %[xdp_md_data]);\
+ r6 = r9;\
+ r6 += 8;\
+ if r6 > r8 goto l_exit_%=;\
+ r0 = *(u64 *)(r9 + 0);\
+ r4 = r0;\
+ r4 &= 15;\
+ r7 = r0;\
+ r7 >>= 63;\
+ if r7 != 0 goto l_path_b_%=;\
+ r2 = r9;\
+ r2 += r4;\
+ r3 = r2;\
+ r3 += 4;\
+ goto l_join_%=;\
+l_path_b_%=: \
+ r4 &= 3;\
+ r4 += 8;\
+ r2 = r9;\
+ r2 += r4;\
+ r3 = r2;\
+ r3 -= 4;\
+l_join_%=: \
+ r5 = r3;\
+ r5 += 4;\
+ if r5 > r8 goto l_exit_%=;\
+ r0 = *(u64 *)(r2 + 0);\
+l_exit_%=: \
+ r0 = 0;\
+ exit;\
+" :
+ : __imm_const(xdp_md_data, offsetof(struct xdp_md, data)),
+ __imm_const(xdp_md_data_end, offsetof(struct xdp_md, data_end))
+ : __clobber_all);
+}
+
SEC("xdp")
__description("XDP pkt read, pkt_end mangling, bad access 1")
__failure __msg("R3 pointer arithmetic on pkt_end")
|
| 3/2 | 2026/09/06 11:55 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 11213 output: 30 thoughts: 824 Thoughts: **BPF Verifier Patch Analysis & Fuzzing Recommendation** Okay, so the user wants me to evaluate this patch series and determine if it's a good candidate for Syzkaller fuzzing. Let's see... I've reviewed the diff, and here's what I'm thinking: First, this is *definitely* worth a deeper dive. The changes touch the heart of the BPF verifier. It's not just a minor tweak; this hits core logic (`kernel/bpf/verifier.c`, `kernel/bpf/cfg.c`, `kernel/bpf/fixups.c`, `kernel/bpf/states.c`). That's a huge red flag... in the best possible way! The BPF verifier is a massive attack surface because it's the security boundary for the BPF subsystem, which is reachable via the `bpf()` syscall and the `BPF_PROG_LOAD` command. Any weakness here could lead to devastating consequences. Specifically, I see changes to state pruning (`regsafe`, `check_pkt_ptr_ids`), which handles tracking register states; this is super important because it's a core component of the verifier’s ability to prevent out-of-bounds accesses and other memory errors. Then there's lock tracking across callbacks (`reg_may_point_to_spin_lock`), which is critical for preventing concurrency issues and protecting against race conditions. There are also new checks for CFG subprogram boundaries, which helps prevent control-flow errors and potential bypasses of the verifier, and checks for fatal signal handling during instruction patching. All of these are prime targets for fuzzing. These are the kinds of changes that frequently uncover critical vulnerabilities. Based on my understanding of the Linux kernel and its attack surface, this patch series directly impacts Section 3: Core Subsystems, specifically BPF, its verifier, state pruning, CFG checks, lock tracking, and signal abort handling. This is precisely the kind of area where fuzzing with a tool like Syzkaller is most effective. My recommendation is an unequivocal YES! We need to get Syzkaller pointed at this *immediately*. Before we do that though, I'll quickly check the Kconfig requirements to make sure there are no prerequisites for enabling the BPF subsystem or specific BPF features. We don't want to waste cycles by fuzzing code that isn't built or enabled. I'll use codesearch/grep or read-file to check the `Kconfig` files under `kernel/bpf/` for any relevant config options. Then, with the Kconfig requirements clear, let's start fuzzing! |
| 4/2 | 2026/09/06 11:55 | tool | grepper |
0mArgs:{
"Expression": "BPF_SYSCALL",
"PathPrefix": "kernel/bpf"
}Results: {
"Output": "kernel/bpf/Kconfig=25=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-26-\nkernel/bpf/Kconfig:27:config BPF_SYSCALL\nkernel/bpf/Kconfig-28-\tbool \"Enable bpf() system call\"\n--\nkernel/bpf/Kconfig=58=config BPF_JIT_ALWAYS_ON\nkernel/bpf/Kconfig-59-\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\nkernel/bpf/Kconfig:60:\tdepends on BPF_SYSCALL \u0026\u0026 HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\nkernel/bpf/Kconfig-61-\thelp\n--\nkernel/bpf/Kconfig=73=config BPF_UNPRIV_DEFAULT_OFF\n--\nkernel/bpf/Kconfig-75-\tdefault y\nkernel/bpf/Kconfig:76:\tdepends on BPF_SYSCALL\nkernel/bpf/Kconfig-77-\thelp\n--\nkernel/bpf/Kconfig=92=config BPF_LSM\n--\nkernel/bpf/Kconfig-94-\tdepends on BPF_EVENTS\nkernel/bpf/Kconfig:95:\tdepends on BPF_SYSCALL\nkernel/bpf/Kconfig-96-\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_PERF_EVENTS),y)\nkernel/bpf/Makefile:31:obj-$(CONFIG_BPF_SYSCALL) += stackmap.o\nkernel/bpf/Makefile-32-endif\nkernel/bpf/Makefile=33=ifeq ($(CONFIG_CGROUPS),y)\nkernel/bpf/Makefile:34:obj-$(CONFIG_BPF_SYSCALL) += cgroup_iter.o bpf_cgrp_storage.o\nkernel/bpf/Makefile-35-endif\n--\nkernel/bpf/Makefile=37=ifeq ($(CONFIG_INET),y)\nkernel/bpf/Makefile:38:obj-$(CONFIG_BPF_SYSCALL) += reuseport_array.o\nkernel/bpf/Makefile-39-endif\n--\nkernel/bpf/Makefile=43=ifeq ($(CONFIG_BPF_JIT),y)\nkernel/bpf/Makefile:44:obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o\nkernel/bpf/Makefile:45:obj-$(CONFIG_BPF_SYSCALL) += cpumask.o\nkernel/bpf/Makefile-46-# bpf_lsm_proto.o must precede bpf_lsm.o. The current pahole logic\n--\nkernel/bpf/Makefile=58=ifneq ($(CONFIG_CRYPTO),)\nkernel/bpf/Makefile:59:obj-$(CONFIG_BPF_SYSCALL) += crypto.o\nkernel/bpf/Makefile-60-endif\nkernel/bpf/Makefile=61=obj-$(CONFIG_BPF_PRELOAD) += preload/\nkernel/bpf/Makefile-62-\nkernel/bpf/Makefile:63:obj-$(CONFIG_BPF_SYSCALL) += relo_core.o\nkernel/bpf/Makefile:64:obj-$(CONFIG_BPF_SYSCALL) += btf_iter.o\nkernel/bpf/Makefile:65:obj-$(CONFIG_BPF_SYSCALL) += btf_relocate.o\nkernel/bpf/Makefile:66:obj-$(CONFIG_BPF_SYSCALL) += kmem_cache_iter.o\nkernel/bpf/Makefile-67-ifeq ($(CONFIG_DMA_SHARED_BUFFER),y)\nkernel/bpf/Makefile:68:obj-$(CONFIG_BPF_SYSCALL) += dmabuf_iter.o\nkernel/bpf/Makefile-69-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=3036=void __bpf_free_used_btfs(struct btf_mod_pair *used_btfs, u32 len)\nkernel/bpf/core.c-3037-{\nkernel/bpf/core.c:3038:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3039-\tstruct btf_mod_pair *btf_mod;\n--\nkernel/bpf/core.c=3057=static void bpf_prog_free_deferred(struct work_struct *work)\n--\nkernel/bpf/core.c-3062-\taux = container_of(work, struct bpf_prog_aux, work);\nkernel/bpf/core.c:3063:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3064-\tbpf_free_kfunc_btf_tab(aux-\u003ekfunc_btf_tab);\n--\nkernel/bpf/core.c=3384=static noinline void bpf_prog_report_may_goto_violation(void)\nkernel/bpf/core.c-3385-{\nkernel/bpf/core.c:3386:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3387-\tstruct bpf_stream_stage ss;\n--\nkernel/bpf/core.c=3424=__weak u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)\n--\nkernel/bpf/core.c-3428-\nkernel/bpf/core.c:3429:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3430-__weak bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write,\n--\nkernel/bpf/core.c=3455=EXPORT_TRACEPOINT_SYMBOL_GPL(xdp_bulk_tx);\nkernel/bpf/core.c-3456-\nkernel/bpf/core.c:3457:#ifdef CONFIG_BPF_SYSCALL\nkernel/bpf/core.c-3458-\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/06 11:55 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 15444 output: 409 thoughts: 1035 Thoughts: **My Analysis of BPF Verifier Changes** Okay, let's break this down. I need to understand the scope and impact of these changes to the BPF verifier. My first instinct is to look at the function changes and their callers. I've got a list of the modified functions across the `kernel/bpf/` directory: `bpf_check`, `refine_map_lookup_value`, `map_set_for_each_callback_args`, `set_timer_callback_state`, `set_task_work_schedule_callback_state`, `map_value_has_static_identity` (in verifier.c), `check_pkt_ptr_ids`, `check_ids`, `regsafe` (in states.c), `push_insn` (in cfg.c), `bpf_patch_insn_data`, `verifier_remove_insns`, and `bpf_rewrite_must_abort` (in fixups.c). My main entry point of concern is `bpf_check`, as that's the core verifier, and the fact that `refine_map_lookup_value`, `map_set_for_each_callback_args`, `set_timer_callback_state`, and `set_task_work_schedule_callback_state` are added in relation to the new logic, seems that the major change is the tracking of spinlocks for inner maps and callbacks. I'll need to pay close attention to the state tracking changes (`check_pkt_ptr_ids`, `regsafe`). The Control Flow Graph (CFG) changes with `push_insn` are important too. And then, the instruction patching and removal routines (`bpf_patch_insn_data`, `verifier_remove_insns`) and signal checks are critical for safety, of course. I need to confirm the non-static symbols for a potential list of focus symbols. So I've identified the following symbols as potential focus points: `bpf_check`, `regsafe`, `check_pkt_ptr_ids`, `bpf_patch_insn_data`, `verifier_remove_insns`, `refine_map_lookup_value`, `map_set_for_each_callback_args`, `set_timer_callback_state`, `set_task_work_schedule_callback_state`, `push_insn`. These seem specific to BPF verifier functionality. The key configuration option here is `BPF_SYSCALL`, and I need to confirm that these source files are only built when that is enabled. The build rules confirm it. My focus symbols are specific and not ubiquitous. I have a clean picture of the dependencies, so now let's verify my findings. These patches are directly related to the core verifier components, primarily impacting the following: - State pruning in `states.c`, specifically focusing on `regsafe` and `check_pkt_ptr_ids` for improved packet pointer displacement tracking. - `verifier.c` updates address inner map lookup and spinlock handling and callback registration state. And it also includes additional fatal signal checking in `bpf_check`. - `cfg.c` adds CFG validation with `push_insn`. - `fixups.c` adds preemption and fatal signal checks in patching and removal routines. All of these directly impact BPF program loading correctness, verification, and overall safety and are involved in the path of BPF program loading. So, now it's time to check if I can double-check everything before calling `set-results`. Everything appears to be aligned correctly. |
| 6/2 | 2026/09/06 11:56 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_check",
"regsafe",
"check_pkt_ptr_ids",
"bpf_patch_insn_data",
"verifier_remove_insns",
"refine_map_lookup_value",
"map_set_for_each_callback_args",
"set_timer_callback_state",
"set_task_work_schedule_callback_state",
"push_insn"
],
"Reasoning": "The patch introduces functional changes to the BPF verifier subsystem across several files:\n1. kernel/bpf/states.c: Adds check_pkt_ptr_ids() and modifies regsafe() to preserve displacement within packet pointer ID classes during state equivalence pruning.\n2. kernel/bpf/verifier.c: Adds map_value_has_static_identity(), refines spinlock map UID tracking in refine_map_lookup_value(), generates unique register IDs for spinlock-containing map values in callback helpers (map_set_for_each_callback_args, set_timer_callback_state, set_task_work_schedule_callback_state), and adds fatal signal abort checks in bpf_check().\n3. kernel/bpf/cfg.c: Adds fallthrough validation across subprogram boundaries in push_insn().\n4. kernel/bpf/fixups.c: Adds fatal signal checks (bpf_rewrite_must_abort) to bpf_patch_insn_data() and verifier_remove_insns().\n\nThese modifications affect reachable code in the core BPF verifier reachable via sys_bpf(BPF_PROG_LOAD) and warrant fuzzing for state pruning correctness, verifier edge cases, and lock safety.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_check",
"regsafe",
"check_pkt_ptr_ids",
"bpf_patch_insn_data",
"verifier_remove_insns",
"refine_map_lookup_value",
"map_set_for_each_callback_args",
"set_timer_callback_state",
"set_task_work_schedule_callback_state",
"push_insn"
],
"Reasoning": "The patch introduces functional changes to the BPF verifier subsystem across several files:\n1. kernel/bpf/states.c: Adds check_pkt_ptr_ids() and modifies regsafe() to preserve displacement within packet pointer ID classes during state equivalence pruning.\n2. kernel/bpf/verifier.c: Adds map_value_has_static_identity(), refines spinlock map UID tracking in refine_map_lookup_value(), generates unique register IDs for spinlock-containing map values in callback helpers (map_set_for_each_callback_args, set_timer_callback_state, set_task_work_schedule_callback_state), and adds fatal signal abort checks in bpf_check().\n3. kernel/bpf/cfg.c: Adds fallthrough validation across subprogram boundaries in push_insn().\n4. kernel/bpf/fixups.c: Adds fatal signal checks (bpf_rewrite_must_abort) to bpf_patch_insn_data() and verifier_remove_insns().\n\nThese modifications affect reachable code in the core BPF verifier reachable via sys_bpf(BPF_PROG_LOAD) and warrant fuzzing for state pruning correctness, verifier edge cases, and lock safety.",
"WorthFuzzing": true
} |
| 7/1 | 2026/09/06 11:56 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 8/2 | 2026/09/06 11:56 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch makes internal correctness and safety improvements to the BPF verifier and instruction rewriting passes:\n1. Adds a subprogram boundary fallthrough check in cfg.c to ensure CO-RE relocations cannot violate subprogram boundaries.\n2. Introduces fatal signal checks and preemption points (cond_resched) in fixups.c and verifier.c during instruction patching.\n3. Enhances state equivalence pruning in states.c for packet pointers by tracking intra-class pointer displacement (pkt_ptr_delta / pkt_ptr_delta_set in struct bpf_id_pair).\n4. Corrects spinlock identity tracking for inner map lookups and callback argument states in verifier.c.\n\nNone of these changes allocate or modify kernel data buffers copied to user space, nor do they introduce reads of uninitialized stack, heap, or page memory. All newly added struct fields (such as pkt_ptr_delta_set in struct bpf_id_pair) are explicitly initialized before access. Any potential memory safety or logic regressions (e.g. out-of-bounds access or verifier state bugs) fall within the scope of standard KASAN/verifier test suites. Therefore, dedicated KMSAN fuzzing is not required.",
"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 e4d2667ab5d8809179d28d9351054834ad1eb9ce
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Sep 6 11:55:48 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 36b65797877d0..5cf92ce185204 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -854,6 +854,8 @@ struct backtrack_state {
struct bpf_id_pair {
u32 old;
u32 cur;
+ s32 pkt_ptr_delta;
+ bool pkt_ptr_delta_set;
};
struct bpf_idmap {
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 842c7d1eabccc..e3c904328ede0 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -102,6 +102,7 @@ enum {
*/
static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
{
+ struct bpf_subprog_info *subprog;
int *insn_stack = env->cfg.insn_stack;
int *insn_state = env->cfg.insn_state;
@@ -121,6 +122,26 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
return -EINVAL;
}
+ /*
+ * check_subprogs() prevents control flow from falling through a
+ * subprogram boundary, but runs before CO-RE relocations can rewrite an
+ * instruction. Reestablish the invariant on the final instruction stream
+ * before constructing the CFG used by later per-subprogram passes.
+ */
+ if (e == FALLTHROUGH) {
+ subprog = bpf_find_containing_subprog(env, t);
+ if (w < subprog->start || w >= (subprog + 1)->start) {
+ verbose_linfo(env, t, "%d: ", t);
+ verbose(env, "fall-through out of subprog from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "fall-through leaves subprogram",
+ "Keep fall-through control flow inside the current subprogram.",
+ "Instruction %d falls through to instruction %d outside its subprogram.",
+ t, w);
+ return -EINVAL;
+ }
+ }
+
if (e == BRANCH) {
/* mark branch target for state pruning */
mark_prune_point(env, w);
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 52d3cec336727..9401fffcedfd6 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -8,6 +8,7 @@
#include <linux/bsearch.h>
#include <linux/sort.h>
#include <linux/perf_event.h>
+#include <linux/sched/signal.h>
#include <net/xdp.h>
#include "disasm.h"
@@ -306,12 +307,28 @@ static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len)
}
}
+/*
+ * Some post-verification instruction rewriting passes require an
+ * O(prog->len) operation per instruction. Keep their shared primitives
+ * killable and preemptible.
+ */
+static bool bpf_rewrite_must_abort(void)
+{
+ if (fatal_signal_pending(current))
+ return true;
+ cond_resched();
+ return false;
+}
+
struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 off,
const struct bpf_insn *patch, u32 len)
{
struct bpf_prog *new_prog;
struct bpf_insn_aux_data *new_data = NULL;
+ if (bpf_rewrite_must_abort())
+ return NULL;
+
if (len > 1) {
new_data = vrealloc(env->insn_aux_data,
array_size(env->prog->len + len - 1,
@@ -523,6 +540,9 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt)
unsigned int orig_prog_len = env->prog->len;
int err;
+ if (bpf_rewrite_must_abort())
+ return -EINTR;
+
if (bpf_prog_is_offloaded(env->prog->aux))
bpf_prog_offload_remove_insns(env, off, cnt);
@@ -2666,4 +2686,3 @@ int bpf_remove_fastcall_spills_fills(struct bpf_verifier_env *env)
return 0;
}
-
diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
index 66fb11b6c6a76..2f8f3fe164b01 100644
--- a/kernel/bpf/states.c
+++ b/kernel/bpf/states.c
@@ -339,6 +339,7 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)
if (idmap->cnt < BPF_ID_MAP_SIZE) {
map[idmap->cnt].old = old_id;
map[idmap->cnt].cur = cur_id;
+ map[idmap->cnt].pkt_ptr_delta_set = false;
idmap->cnt++;
return true;
}
@@ -352,6 +353,43 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)
return false;
}
+static bool check_pkt_ptr_ids(const struct bpf_reg_state *old,
+ const struct bpf_reg_state *cur,
+ struct bpf_idmap *idmap)
+{
+ struct bpf_id_pair *map = idmap->map;
+ s64 delta;
+ unsigned int i;
+
+ if (!check_ids(old->id, cur->id, idmap))
+ return false;
+ if (!old->id)
+ return true;
+
+ /*
+ * Packet range is shared by all pointers with the same ID. Preserve
+ * their relative displacement, while allowing the whole class to move.
+ * Packet pointer offsets are bounded by BPF_MAX_VAR_OFF, so the delta
+ * between two valid offsets fits in s32.
+ */
+ delta = (s64)(cur->r64.base - old->r64.base);
+ if (delta < S32_MIN || delta > S32_MAX)
+ return false;
+
+ for (i = 0; i < idmap->cnt; i++) {
+ if (map[i].old != old->id)
+ continue;
+ if (!map[i].pkt_ptr_delta_set) {
+ map[i].pkt_ptr_delta = delta;
+ map[i].pkt_ptr_delta_set = true;
+ return true;
+ }
+ return map[i].pkt_ptr_delta == delta;
+ }
+
+ return false;
+}
+
/*
* Compare scalar register IDs for state equivalence.
*
@@ -632,8 +670,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
} else if (rold->range > rcur->range) {
return false;
}
- /* id relations must be preserved */
- if (!check_ids(rold->id, rcur->id, idmap))
+ /* id relations and intra-class displacement must be preserved */
+ if (!check_pkt_ptr_ids(rold, rcur, idmap))
return false;
/* new val must satisfy old val knowledge */
return range_within(rold, rcur) &&
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 1c3039f3fc323..ad14a7fbed72f 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -19,6 +19,7 @@
#include <linux/bsearch.h>
#include <linux/sort.h>
#include <linux/perf_event.h>
+#include <linux/sched/signal.h>
#include <linux/ctype.h>
#include <linux/error-injection.h>
#include <linux/bpf_lsm.h>
@@ -447,6 +448,13 @@ static bool reg_may_point_to_spin_lock(const struct bpf_reg_state *reg)
return btf_record_has_field(reg_btf_record(reg), BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK);
}
+static bool map_value_has_static_identity(const struct bpf_reg_state *reg)
+{
+ const struct bpf_map *map = reg->map_ptr;
+
+ return !reg->map_uid && map->map_type == BPF_MAP_TYPE_ARRAY && map->max_entries == 1;
+}
+
static bool type_is_rdonly_mem(u32 type)
{
return type & MEM_RDONLY;
@@ -1925,11 +1933,18 @@ static void refine_map_lookup_value(struct bpf_reg_state *reg)
if (map->inner_map_meta) {
reg->type = CONST_PTR_TO_MAP | maybe_null;
reg->map_ptr = map->inner_map_meta;
- /* transfer reg's id which is unique for every map_lookup_elem
- * as UID of the inner map.
+ /*
+ * Concrete inner maps share the verifier-visible inner_map_meta.
+ * Preserve the lookup identity only for embedded objects whose
+ * verification needs to distinguish concrete map instances. Doing
+ * this for every inner map makes state pruning too conservative.
+ *
+ * Each map lookup has a unique register ID, so use it as the UID of
+ * the inner map.
*/
if (btf_record_has_field(map->inner_map_meta->record,
- BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK))
+ BPF_TIMER | BPF_WORKQUEUE | BPF_TASK_WORK |
+ BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK))
reg->map_uid = reg->id;
} else if (map->map_type == BPF_MAP_TYPE_XSKMAP) {
reg->type = PTR_TO_XDP_SOCK | maybe_null;
@@ -10035,6 +10050,9 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env,
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr;
callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid;
+ if (reg_may_point_to_spin_lock(&callee->regs[BPF_REG_3]) &&
+ !map_value_has_static_identity(&callee->regs[BPF_REG_3]))
+ callee->regs[BPF_REG_3].id = ++env->id_gen;
/* pointer to stack or null */
callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3];
@@ -10131,6 +10149,9 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = map_ptr;
callee->regs[BPF_REG_3].map_uid = map_uid;
+ if (reg_may_point_to_spin_lock(&callee->regs[BPF_REG_3]) &&
+ !map_value_has_static_identity(&callee->regs[BPF_REG_3]))
+ callee->regs[BPF_REG_3].id = ++env->id_gen;
/* unused */
bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
@@ -10249,6 +10270,9 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = map_ptr;
callee->regs[BPF_REG_3].map_uid = map_uid;
+ if (reg_may_point_to_spin_lock(&callee->regs[BPF_REG_3]) &&
+ !map_value_has_static_identity(&callee->regs[BPF_REG_3]))
+ callee->regs[BPF_REG_3].id = ++env->id_gen;
/* unused */
bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
@@ -21367,6 +21391,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret == 0)
ret = bpf_fixup_call_args(env);
+ /*
+ * JIT constant blinding treats instruction patching failures as a
+ * request to fall back to the interpreter. Do not let such fallback
+ * consume a fatal-signal cancellation from bpf_patch_insn_data().
+ */
+ if (ret == 0 && fatal_signal_pending(current))
+ ret = -EINTR;
env->verification_time = ktime_get_ns() - start_time;
print_verification_stats(env);
@@ -21425,6 +21456,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
env->prog->expected_attach_type = 0;
env->prog = __bpf_prog_select_runtime(env, env->prog, &ret);
+ if (ret == 0 && fatal_signal_pending(current))
+ ret = -EINTR;
err_release_maps:
if (ret)
diff --git a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
index a18d3680fb169..c350fbb958455 100644
--- a/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
+++ b/tools/testing/selftests/bpf/prog_tests/core_reloc_raw.c
@@ -14,6 +14,117 @@
static char log[16 * 1024];
+static int load_core_relo_subprog(int btf_fd, int main_id, int sub_id,
+ int enum_id, int access_str_off, bool relocate)
+{
+ struct bpf_insn insns[] = {
+ BPF_CALL_REL(2),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ struct bpf_func_info funcs[] = {
+ { .insn_off = 0, .type_id = main_id },
+ { .insn_off = 3, .type_id = sub_id },
+ };
+ struct bpf_core_relo relo = {
+ .insn_off = 2 * sizeof(struct bpf_insn),
+ .type_id = enum_id,
+ .access_str_off = access_str_off,
+ .kind = BPF_CORE_ENUMVAL_VALUE,
+ };
+ union bpf_attr attr = {
+ .prog_type = BPF_PROG_TYPE_SOCKET_FILTER,
+ .insn_cnt = ARRAY_SIZE(insns),
+ .insns = (__u64)insns,
+ .license = (__u64)"GPL",
+ .log_buf = (__u64)log,
+ .log_size = sizeof(log),
+ .log_level = 1,
+ .prog_btf_fd = btf_fd,
+ .func_info_rec_size = sizeof(struct bpf_func_info),
+ .func_info = (__u64)funcs,
+ .func_info_cnt = ARRAY_SIZE(funcs),
+ };
+
+ if (relocate) {
+ attr.core_relo_cnt = 1;
+ attr.core_relos = (__u64)&relo;
+ attr.core_relo_rec_size = sizeof(relo);
+ }
+ memset(log, 0, sizeof(log));
+ return sys_bpf_prog_load(&attr, sizeof(attr), 1);
+}
+
+static void test_poisoned_subprog_terminator(void)
+{
+ const void *raw_btf;
+ struct btf *btf = NULL;
+ __u32 raw_btf_size;
+ int access_str_off, btf_fd = -1, enum_id;
+ int int_id, main_id, prog_fd = -1, proto_id, sub_id;
+
+ btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
+ return;
+ int_id = btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
+ if (!ASSERT_GT(int_id, 0, "add_int"))
+ goto cleanup;
+ proto_id = btf__add_func_proto(btf, int_id);
+ if (!ASSERT_GT(proto_id, 0, "add_func_proto"))
+ goto cleanup;
+ main_id = btf__add_func(btf, "main_fn", BTF_FUNC_STATIC, proto_id);
+ if (!ASSERT_GT(main_id, 0, "add_main_func"))
+ goto cleanup;
+ sub_id = btf__add_func(btf, "sub_fn", BTF_FUNC_STATIC, proto_id);
+ if (!ASSERT_GT(sub_id, 0, "add_sub_func"))
+ goto cleanup;
+ enum_id = btf__add_enum(btf, "core_relo_subprog_poison_missing", 4);
+ if (!ASSERT_GT(enum_id, 0, "add_enum") ||
+ !ASSERT_OK(btf__add_enum_value(btf, "value", 0), "add_enum_value"))
+ goto cleanup;
+ access_str_off = btf__add_str(btf, "0");
+ if (!ASSERT_GT(access_str_off, 0, "add_access_str"))
+ goto cleanup;
+
+ raw_btf = btf__raw_data(btf, &raw_btf_size);
+ if (!ASSERT_OK_PTR(raw_btf, "raw_btf"))
+ goto cleanup;
+ btf_fd = bpf_btf_load(raw_btf, raw_btf_size, NULL);
+ if (!ASSERT_GE(btf_fd, 0, "btf_load"))
+ goto cleanup;
+
+ /* The same two-subprogram program is valid before the relocation. */
+ prog_fd = load_core_relo_subprog(btf_fd, main_id, sub_id, enum_id,
+ access_str_off, false);
+ if (!ASSERT_GE(prog_fd, 0, "control_load"))
+ goto cleanup;
+ close(prog_fd);
+ prog_fd = -1;
+
+ /*
+ * Poison the first subprogram's terminal exit. The verifier must reject
+ * the resulting control flow across the subprogram boundary in the CFG.
+ */
+ prog_fd = load_core_relo_subprog(btf_fd, main_id, sub_id, enum_id,
+ access_str_off, true);
+ if (!ASSERT_LT(prog_fd, 0, "poisoned_load"))
+ goto cleanup;
+ ASSERT_HAS_SUBSTR(log, "fall-through out of subprog from insn 2 to 3",
+ "poisoned_load_log");
+
+cleanup:
+ if (env.verbosity > VERBOSE_NORMAL && log[0]) {
+ printf("-------- program load log start --------\n");
+ printf("%s", log);
+ printf("-------- program load log end ----------\n");
+ }
+ close(prog_fd);
+ close(btf_fd);
+ btf__free(btf);
+}
+
/* Check that verifier rejects BPF program containing relocation
* pointing to non-existent BTF type.
*/
@@ -120,6 +231,8 @@ static void test_bad_local_id(void)
void test_core_reloc_raw(void)
{
+ if (test__start_subtest("poisoned_subprog_terminator"))
+ test_poisoned_subprog_terminator();
if (test__start_subtest("bad_local_id"))
test_bad_local_id();
}
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 64ac49ad67e63..5c572dd725e75 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -25,6 +25,7 @@
#include "verifier_btf_ctx_access.skel.h"
#include "verifier_btf_unreliable_prog.skel.h"
#include "verifier_call_large_imm.skel.h"
+#include "verifier_callback_lock.skel.h"
#include "verifier_cfg.skel.h"
#include "verifier_cgroup_inv_retcode.skel.h"
#include "verifier_cgroup_skb.skel.h"
@@ -188,6 +189,7 @@ void test_verifier_bswap(void) { RUN(verifier_bswap); }
void test_verifier_btf_ctx_access(void) { RUN(verifier_btf_ctx_access); }
void test_verifier_btf_unreliable_prog(void) { RUN(verifier_btf_unreliable_prog); }
void test_verifier_call_large_imm(void) { RUN(verifier_call_large_imm); }
+void test_verifier_callback_lock(void) { RUN(verifier_callback_lock); }
void test_verifier_cfg(void) { RUN(verifier_cfg); }
void test_verifier_cgroup_inv_retcode(void) { RUN(verifier_cgroup_inv_retcode); }
void test_verifier_cgroup_skb(void) { RUN(verifier_cgroup_skb); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_callback_lock.c b/tools/testing/selftests/bpf/progs/verifier_callback_lock.c
new file mode 100644
index 0000000000000..d23e13908cebf
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_callback_lock.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+struct bpf_map;
+
+struct lock_value {
+ struct bpf_spin_lock lock;
+};
+
+struct inner_lock_map {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct lock_value);
+} inner_lock_map_a SEC(".maps"), inner_lock_map_b SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
+ __uint(max_entries, 2);
+ __type(key, int);
+ __array(values, struct inner_lock_map);
+} lock_map_of_maps SEC(".maps") = {
+ .values = {
+ [0] = &inner_lock_map_a,
+ [1] = &inner_lock_map_b,
+ },
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 2);
+ __type(key, int);
+ __type(value, struct lock_value);
+} lock_map SEC(".maps");
+
+struct callback_ctx {
+ struct lock_value *value;
+};
+
+static long lock_different_value(struct bpf_map *map, int *key,
+ struct lock_value *value, struct callback_ctx *ctx)
+{
+ bpf_spin_lock(&value->lock);
+ bpf_spin_unlock(&ctx->value->lock);
+ return 0;
+}
+
+static long nest_lock_different_value(struct bpf_map *map, int *key,
+ struct lock_value *value, void *data)
+{
+ struct callback_ctx ctx = { .value = value };
+
+ bpf_for_each_map_elem(&lock_map, lock_different_value, &ctx, 0);
+ return 0;
+}
+
+SEC("?tc")
+__description("callback map value has a distinct lock identity")
+__failure __msg("bpf_spin_unlock of different lock")
+int callback_value_lock_identity(void *ctx)
+{
+ bpf_for_each_map_elem(&lock_map, nest_lock_different_value, NULL, 0);
+ return 0;
+}
+
+static long nest_lock_different_inner_value(struct bpf_map *map, int *key,
+ struct lock_value *value, void *data)
+{
+ struct callback_ctx ctx = { .value = value };
+ int inner_key = 1;
+ void *inner_map;
+
+ inner_map = bpf_map_lookup_elem(&lock_map_of_maps, &inner_key);
+ if (!inner_map)
+ return 0;
+ bpf_for_each_map_elem(inner_map, lock_different_value, &ctx, 0);
+ return 0;
+}
+
+SEC("?tc")
+__description("distinct one-element inner maps have distinct lock identities")
+__failure __msg("bpf_spin_unlock of different lock")
+int callback_inner_map_value_lock_identity(void *ctx)
+{
+ int inner_key = 0;
+ void *inner_map;
+
+ inner_map = bpf_map_lookup_elem(&lock_map_of_maps, &inner_key);
+ if (!inner_map)
+ return 0;
+ bpf_for_each_map_elem(inner_map, nest_lock_different_inner_value, NULL, 0);
+ return 0;
+}
+
+static long lock_same_value(struct bpf_map *map, int *key,
+ struct lock_value *value, void *data)
+{
+ bpf_spin_lock(&value->lock);
+ bpf_spin_unlock(&value->lock);
+ return 0;
+}
+
+static long nest_lock_same_value(struct bpf_map *map, int *key,
+ struct lock_value *value, void *data)
+{
+ bpf_for_each_map_elem(&lock_map, lock_same_value, NULL, 0);
+ return 0;
+}
+
+SEC("?tc")
+__description("nested callback can lock its own map value")
+__success
+int callback_value_lock_identity_same(void *ctx)
+{
+ bpf_for_each_map_elem(&lock_map, nest_lock_same_value, NULL, 0);
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
index 0b86d95a41335..692a8468f3d74 100644
--- a/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_xdp_direct_packet_access.c
@@ -5,6 +5,50 @@
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
+SEC("xdp")
+__description("XDP pkt regsafe preserves packet pointer class displacement")
+__failure __msg("R2 min value is outside of the allowed memory range")
+__flag(BPF_F_ANY_ALIGNMENT)
+__naked void pkt_regsafe_class_displacement(void)
+{
+ asm volatile ("\
+ r8 = *(u32 *)(r1 + %[xdp_md_data_end]);\
+ r9 = *(u32 *)(r1 + %[xdp_md_data]);\
+ r6 = r9;\
+ r6 += 8;\
+ if r6 > r8 goto l_exit_%=;\
+ r0 = *(u64 *)(r9 + 0);\
+ r4 = r0;\
+ r4 &= 15;\
+ r7 = r0;\
+ r7 >>= 63;\
+ if r7 != 0 goto l_path_b_%=;\
+ r2 = r9;\
+ r2 += r4;\
+ r3 = r2;\
+ r3 += 4;\
+ goto l_join_%=;\
+l_path_b_%=: \
+ r4 &= 3;\
+ r4 += 8;\
+ r2 = r9;\
+ r2 += r4;\
+ r3 = r2;\
+ r3 -= 4;\
+l_join_%=: \
+ r5 = r3;\
+ r5 += 4;\
+ if r5 > r8 goto l_exit_%=;\
+ r0 = *(u64 *)(r2 + 0);\
+l_exit_%=: \
+ r0 = 0;\
+ exit;\
+" :
+ : __imm_const(xdp_md_data, offsetof(struct xdp_md, data)),
+ __imm_const(xdp_md_data_end, offsetof(struct xdp_md, data_end))
+ : __clobber_all);
+}
+
SEC("xdp")
__description("XDP pkt read, pkt_end mangling, bad access 1")
__failure __msg("R3 pointer arithmetic on pkt_end")
|
| 9/3 | 2026/09/06 11:56 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 10425 output: 29 thoughts: 750 Thoughts: **Analyzing this BPF Patch for KMSAN Implications** Alright, let's dive into this patch and figure out whether we need to crank up the KMSAN fuzzing. My initial impression is that this is all contained within the BPF verifier and related subsystems, which is good. But, we have to be thorough. First, I need to understand what's been changed. The patch introduces several modifications across various BPF components: changes to `bpf_id_pair`, control flow and fall-through checks, preemption checks, packet pointer state management within ID classes, and spinlock tracking. It seems like the core focus is on enhancing the verifier's correctness, concurrency, and robustness, specifically related to CO-RE relocations, preemption, and state pruning. Now, the critical question: Does any of this potentially introduce or expose uninitialized memory issues? KMSAN is our best friend for catching that. Let's analyze the areas of modification one by one. The addition of `pkt_ptr_delta` and `pkt_ptr_delta_set` to `struct bpf_id_pair` has my immediate attention. The fact that the patch initializes `pkt_ptr_delta_set` to `false` in `check_ids()` is a good sign. But, I must verify where `struct bpf_idmap` is allocated and initialized in `kernel/bpf/states.c` and elsewhere. Understanding the initialization of the struct is key to determining any KMSAN risk. It could be possible there is a case for a more in-depth look using KMSAN. The other changes look like verifier improvements and correctness fixes. While valuable, they seem less likely to directly trigger uninitialized memory issues, but must be assessed to confirm. The checks for `fatal_signal_pending(current)` and `cond_resched()` are more likely to mitigate potential concurrency bugs, or ensure proper behavior during asynchronous events. I think I'll lean towards the `NeedsKMSAN=false` for now, but will make sure to check the initialization code for `bpf_idmap` and `bpf_id_pair` to be certain. |
| 10/3 | 2026/09/06 11:56 | tool | grepper |
0mArgs:{
"Expression": "bpf_idmap",
"PathPrefix": "kernel/bpf"
}Results: {
"Output": "kernel/bpf/states.c=302=static bool range_within(const struct bpf_reg_state *old,\n--\nkernel/bpf/states.c-318- */\nkernel/bpf/states.c:319:static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)\nkernel/bpf/states.c-320-{\n--\nkernel/bpf/states.c=356=static bool check_pkt_ptr_ids(const struct bpf_reg_state *old,\nkernel/bpf/states.c-357-\t\t\t const struct bpf_reg_state *cur,\nkernel/bpf/states.c:358:\t\t\t struct bpf_idmap *idmap)\nkernel/bpf/states.c-359-{\n--\nkernel/bpf/states.c-417- */\nkernel/bpf/states.c:418:static bool check_scalar_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)\nkernel/bpf/states.c-419-{\n--\nkernel/bpf/states.c=526=static bool regs_exact(const struct bpf_reg_state *rold,\nkernel/bpf/states.c-527-\t\t const struct bpf_reg_state *rcur,\nkernel/bpf/states.c:528:\t\t struct bpf_idmap *idmap)\nkernel/bpf/states.c-529-{\n--\nkernel/bpf/states.c=542=static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,\nkernel/bpf/states.c:543:\t\t struct bpf_reg_state *rcur, struct bpf_idmap *idmap,\nkernel/bpf/states.c-544-\t\t enum exact_level exact)\n--\nkernel/bpf/states.c=737=static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old,\nkernel/bpf/states.c:738:\t\t struct bpf_func_state *cur, struct bpf_idmap *idmap,\nkernel/bpf/states.c-739-\t\t enum exact_level exact)\n--\nkernel/bpf/states.c=882=static bool stack_arg_safe(struct bpf_verifier_env *env, struct bpf_func_state *old,\nkernel/bpf/states.c:883:\t\t\t struct bpf_func_state *cur, struct bpf_idmap *idmap,\nkernel/bpf/states.c-884-\t\t\t enum exact_level exact)\n--\nkernel/bpf/states.c=904=static bool refsafe(struct bpf_verifier_state *old, struct bpf_verifier_state *cur,\nkernel/bpf/states.c:905:\t\t struct bpf_idmap *idmap)\nkernel/bpf/states.c-906-{\n--\nkernel/bpf/states.c=1007=static void reset_idmap_scratch(struct bpf_verifier_env *env)\nkernel/bpf/states.c-1008-{\nkernel/bpf/states.c:1009:\tstruct bpf_idmap *idmap = \u0026env-\u003eidmap_scratch;\nkernel/bpf/states.c-1010-\n--\nkernel/bpf/verifier.c=9437=static int release_reference_nomark(struct bpf_verifier_env *env, int id)\n--\nkernel/bpf/verifier.c-9446-\nkernel/bpf/verifier.c:9447:static int idstack_push(struct bpf_idmap *idmap, u32 id)\nkernel/bpf/verifier.c-9448-{\n--\nkernel/bpf/verifier.c-9464-\nkernel/bpf/verifier.c:9465:static int idstack_pop(struct bpf_idmap *idmap)\nkernel/bpf/verifier.c-9466-{\n--\nkernel/bpf/verifier.c=9474=static int release_reference(struct bpf_verifier_env *env, int id)\n--\nkernel/bpf/verifier.c-9477-\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\nkernel/bpf/verifier.c:9478:\tstruct bpf_idmap *idstack = \u0026env-\u003eidmap_scratch;\nkernel/bpf/verifier.c-9479-\tstruct bpf_stack_state *stack;\n"
} |
| 11/3 | 2026/09/06 11:56 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 12850 output: 36 thoughts: 30 Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in the weeds of kernel BPF development: **Examining `check_ids` and `check_pkt_ptr_ids`** Right, time to dive into `kernel/bpf/states.c`. The task at hand is clearly to understand how the system is handling ID checks, specifically looking at the functions `check_ids` and `check_pkt_ptr_ids`. I need to get a clear picture of how these functions work, their purpose, and what data they're manipulating within the BPF context. I'm going to carefully read the code within those functions, paying close attention to any data structures used and the logic involved in validating these IDs and packet pointer IDs. This might involve looking at their call sites and dependencies as well. Understanding their function is critical for any debugging or optimization efforts related to this part of the BPF implementation. |
| 12/3 | 2026/09/06 11:56 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/states.c",
"FirstLine": 315,
"LineCount": 90
}Results: {
"Contents": " 315:\t * that.\n 316:\t * So we look through our idmap to see if this old id has been seen before. If\n 317:\t * so, we require the new id to match; otherwise, we add the id pair to the map.\n 318:\t */\n 319:\tstatic bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)\n 320:\t{\n 321:\t\tstruct bpf_id_pair *map = idmap-\u003emap;\n 322:\t\tunsigned int i;\n 323:\t\n 324:\t\t/* either both IDs should be set or both should be zero */\n 325:\t\tif (!!old_id != !!cur_id)\n 326:\t\t\treturn false;\n 327:\t\n 328:\t\tif (old_id == 0) /* cur_id == 0 as well */\n 329:\t\t\treturn true;\n 330:\t\n 331:\t\tfor (i = 0; i \u003c idmap-\u003ecnt; i++) {\n 332:\t\t\tif (map[i].old == old_id)\n 333:\t\t\t\treturn map[i].cur == cur_id;\n 334:\t\t\tif (map[i].cur == cur_id)\n 335:\t\t\t\treturn false;\n 336:\t\t}\n 337:\t\n 338:\t\t/* Reached the end of known mappings; haven't seen this id before */\n 339:\t\tif (idmap-\u003ecnt \u003c BPF_ID_MAP_SIZE) {\n 340:\t\t\tmap[idmap-\u003ecnt].old = old_id;\n 341:\t\t\tmap[idmap-\u003ecnt].cur = cur_id;\n 342:\t\t\tmap[idmap-\u003ecnt].pkt_ptr_delta_set = false;\n 343:\t\t\tidmap-\u003ecnt++;\n 344:\t\t\treturn true;\n 345:\t\t}\n 346:\t\n 347:\t\t/*\n 348:\t\t * idmap slots are bounded by the number of registers and stack slots.\n 349:\t\t * Since referenced dynptrs acquire intermediate references that do\n 350:\t\t * not live in either, so the map can be exhausted. Since it is unlikely,\n 351:\t\t * fail the verification by treating the states as not equivalent.\n 352:\t\t */\n 353:\t\treturn false;\n 354:\t}\n 355:\t\n 356:\tstatic bool check_pkt_ptr_ids(const struct bpf_reg_state *old,\n 357:\t\t\t\t const struct bpf_reg_state *cur,\n 358:\t\t\t\t struct bpf_idmap *idmap)\n 359:\t{\n 360:\t\tstruct bpf_id_pair *map = idmap-\u003emap;\n 361:\t\ts64 delta;\n 362:\t\tunsigned int i;\n 363:\t\n 364:\t\tif (!check_ids(old-\u003eid, cur-\u003eid, idmap))\n 365:\t\t\treturn false;\n 366:\t\tif (!old-\u003eid)\n 367:\t\t\treturn true;\n 368:\t\n 369:\t\t/*\n 370:\t\t * Packet range is shared by all pointers with the same ID. Preserve\n 371:\t\t * their relative displacement, while allowing the whole class to move.\n 372:\t\t * Packet pointer offsets are bounded by BPF_MAX_VAR_OFF, so the delta\n 373:\t\t * between two valid offsets fits in s32.\n 374:\t\t */\n 375:\t\tdelta = (s64)(cur-\u003er64.base - old-\u003er64.base);\n 376:\t\tif (delta \u003c S32_MIN || delta \u003e S32_MAX)\n 377:\t\t\treturn false;\n 378:\t\n 379:\t\tfor (i = 0; i \u003c idmap-\u003ecnt; i++) {\n 380:\t\t\tif (map[i].old != old-\u003eid)\n 381:\t\t\t\tcontinue;\n 382:\t\t\tif (!map[i].pkt_ptr_delta_set) {\n 383:\t\t\t\tmap[i].pkt_ptr_delta = delta;\n 384:\t\t\t\tmap[i].pkt_ptr_delta_set = true;\n 385:\t\t\t\treturn true;\n 386:\t\t\t}\n 387:\t\t\treturn map[i].pkt_ptr_delta == delta;\n 388:\t\t}\n 389:\t\n 390:\t\treturn false;\n 391:\t}\n 392:\t\n 393:\t/*\n 394:\t * Compare scalar register IDs for state equivalence.\n 395:\t *\n 396:\t * When old_id == 0, the old register is independent - not linked to any\n 397:\t * other register. Any linking in the current state only adds constraints,\n 398:\t * making it more restrictive. Since the old state didn't rely on any ID\n 399:\t * relationships for this register, it's always safe to accept cur regardless\n 400:\t * of its ID. Hence, return true immediately.\n 401:\t *\n 402:\t * When old_id != 0 but cur_id == 0, we need to ensure that different\n 403:\t * independent registers in cur don't incorrectly satisfy the ID matching\n 404:\t * requirements of linked registers in old.\n"
} |
| 13/3 | 2026/09/06 11:56 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 14533 output: 37 thoughts: 75 Thoughts: Here's my summary, as I understand it: **Digging into ID Management** Okay, so I'm looking at how we're managing packet IDs, specifically within the `check_ids` function and the interaction with the `idmap` structure. It seems like the explicit initialization of `map[idmap->cnt].pkt_ptr_delta_set = false;` when adding a new ID pair at `idmap->cnt` is the key thing to focus on here. It’s pretty standard practice, but I want to make sure there's no subtle bug potential. Now, I also need to track the `idstack` operations within `verifier.c`. Pushing and popping from the `idstack` is where things can get hairy if not handled carefully, so I need to thoroughly analyze the logic. Let's start by closely examining the `idstack_push` function. That's likely where the potential issues might lie with ID lifecycle management. I want to make sure there are no unintended consequences. |
| 14/3 | 2026/09/06 11:56 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/verifier.c",
"FirstLine": 9440,
"LineCount": 40
}Results: {
"Contents": "9440:\t\n9441:\t\terr = __release_reference_nomark(env-\u003ecur_state, id);\n9442:\t\tif (!err)\n9443:\t\t\tbpf_diag_record_ref_release(env, env-\u003einsn_idx, id);\n9444:\t\treturn err;\n9445:\t}\n9446:\t\n9447:\tstatic int idstack_push(struct bpf_idmap *idmap, u32 id)\n9448:\t{\n9449:\t\tint i;\n9450:\t\n9451:\t\tif (!id)\n9452:\t\t\treturn 0;\n9453:\t\n9454:\t\tfor (i = 0; i \u003c idmap-\u003ecnt; i++)\n9455:\t\t\tif (idmap-\u003emap[i].old == id)\n9456:\t\t\t\treturn 0;\n9457:\t\n9458:\t\tif (WARN_ON_ONCE(idmap-\u003ecnt \u003e= BPF_ID_MAP_SIZE))\n9459:\t\t\treturn -EFAULT;\n9460:\t\n9461:\t\tidmap-\u003emap[idmap-\u003ecnt++].old = id;\n9462:\t\treturn 0;\n9463:\t}\n9464:\t\n9465:\tstatic int idstack_pop(struct bpf_idmap *idmap)\n9466:\t{\n9467:\t\tif (!idmap-\u003ecnt)\n9468:\t\t\treturn 0;\n9469:\t\n9470:\t\treturn idmap-\u003emap[--idmap-\u003ecnt].old;\n9471:\t}\n9472:\t\n9473:\t/* Release id and objects derived from it iteratively in a DFS manner */\n9474:\tstatic int release_reference(struct bpf_verifier_env *env, int id)\n9475:\t{\n9476:\t\tu32 mask = (1 \u003c\u003c STACK_SPILL) | (1 \u003c\u003c STACK_DYNPTR);\n9477:\t\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\n9478:\t\tstruct bpf_idmap *idstack = \u0026env-\u003eidmap_scratch;\n9479:\t\tstruct bpf_stack_state *stack;\n"
} |
| 15/3 | 2026/09/06 11:56 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 15467 output: 285 thoughts: 679 Thoughts: **My Assessment of the Patch's KMSAN Impact** Okay, let's break this down. First, I focused on `idstack_push` and `idstack_pop`, verifying they only touch `idmap->cnt` and `old`. That’s a clean start. Then, I meticulously examined the other parts of the patch: `bpf_rewrite_must_abort()`, `push_insn()`, `refine_map_lookup_value()`, `map_set_for_each_callback_args()`, `set_timer_callback_state()`, `set_task_work_schedule_callback_state()`, and `bpf_check()`. Each change was carefully scrutinized. The key was to identify potential uninitialized memory bugs or KMSAN-specific issues. I went through a rigorous checklist: 1. **Kernel structures to user space:** Does the patch touch any structures sent to user space where fields might be uninitialized? Nope, no such changes here. No `copy_to_user`, no netlink attributes, socket options, or ioctls are affected. We're in the clear. 2. **Conditional logic and uninitialized variables:** Does the patch introduce branching based on uninitialized data? No. I checked all the variables used (like `subprog`, `delta`, and `i`) and verified they're all initialized before being used. Specifically, the delta calculation and the use of `pkt_ptr_delta` within `check_pkt_ptr_ids` are sound. The `pkt_ptr_delta_set` boolean prevents a read of the delta prior to it being set. 3. **Complex data structure initialization:** Are there any new allocations where fields might be uninitialized and read later? No new allocations. The fields added to `struct bpf_id_pair` are properly set. 4. **Bounds checks and buffer accesses:** Could the changes affect bounds checks and introduce uninitialized byte reads? Not in this case. The changes *tighten* BPF verifier checks, which is a good thing – preventing fallthrough, incorrect packet pointer state, preserving spinlock identities, and graceful cancellation. This leads to *fewer* bugs, not more. Given all of this, the verifier changes are fundamentally about logic and bytecode verification. The potential bugs are more likely logical verification errors, or memory safety bugs (which KASAN would catch). So, I'm confident there are no uninitialized kernel memory reads or info-leaks here. Therefore, `NeedsKMSAN` should be `false`. Now I need to write a concise explanation of the rationale contrasting KASAN vs KMSAN applicability. |
| 16/3 | 2026/09/06 11:56 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch makes internal correctness and safety improvements to the BPF verifier and instruction rewriting passes:\n1. Adds a subprogram boundary fallthrough check in cfg.c to ensure CO-RE relocations cannot violate subprogram boundaries.\n2. Introduces fatal signal checks and preemption points (cond_resched) in fixups.c and verifier.c during instruction patching.\n3. Enhances state equivalence pruning in states.c for packet pointers by tracking intra-class pointer displacement (pkt_ptr_delta / pkt_ptr_delta_set in struct bpf_id_pair).\n4. Corrects spinlock identity tracking for inner map lookups and callback argument states in verifier.c.\n\nNone of these changes allocate or modify kernel data buffers copied to user space, nor do they introduce reads of uninitialized stack, heap, or page memory. All newly added struct fields (such as pkt_ptr_delta_set in struct bpf_id_pair) are explicitly initialized before access. Any potential memory safety or logic regressions (e.g. out-of-bounds access or verifier state bugs) fall within the scope of standard KASAN/verifier test suites. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch makes internal correctness and safety improvements to the BPF verifier and instruction rewriting passes:\n1. Adds a subprogram boundary fallthrough check in cfg.c to ensure CO-RE relocations cannot violate subprogram boundaries.\n2. Introduces fatal signal checks and preemption points (cond_resched) in fixups.c and verifier.c during instruction patching.\n3. Enhances state equivalence pruning in states.c for packet pointers by tracking intra-class pointer displacement (pkt_ptr_delta / pkt_ptr_delta_set in struct bpf_id_pair).\n4. Corrects spinlock identity tracking for inner map lookups and callback argument states in verifier.c.\n\nNone of these changes allocate or modify kernel data buffers copied to user space, nor do they introduce reads of uninitialized stack, heap, or page memory. All newly added struct fields (such as pkt_ptr_delta_set in struct bpf_id_pair) are explicitly initialized before access. Any potential memory safety or logic regressions (e.g. out-of-bounds access or verifier state bugs) fall within the scope of standard KASAN/verifier test suites. Therefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|