| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/15 07:05 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": [
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_diag_register_type",
"bpf_diag_invalid_deref",
"bpf_diag_unreadable_reg",
"bpf_diag_stack_arg_uninit",
"bpf_diag_memory",
"bpf_diag_mem_bounds",
"bpf_diag_res",
"bpf_diag_lock",
"bpf_diag_irq",
"bpf_diag_leak",
"bpf_diag_call_type",
"bpf_diag_ctx_forbidden",
"bpf_diag_ctx_active",
"bpf_diag_ctx_required",
"bpf_diag_ctx_underflow",
"bpf_diag_program_structure",
"bpf_diag_policy",
"bpf_diag_record_branch",
"bpf_diag_mod_begin",
"bpf_diag_mod_end",
"bpf_diag_record_scrub",
"bpf_diag_record_scrub_stack",
"bpf_diag_record_ref_acquire",
"bpf_diag_record_ref_release",
"bpf_diag_record_context"
],
"KMSANReasoning": "The patch introduces a new diagnostics framework for the BPF verifier to provide better error messages and causal history of events leading to verifier errors. It adds new internal structures (`struct bpf_diag`, `struct bpf_diag_log`, `struct bpf_diag_history_event`, etc.) to track register and stack modifications, context changes, and branches during verification. These structures are allocated using `kzalloc` (via the `kzalloc_obj` macro) and are only used internally to generate diagnostic text.\n\nWhen a verifier error occurs, the framework formats the recorded events into strings using `vsnprintf` and `scnprintf`, which ensure the strings are properly null-terminated. These strings are then written to the verifier log buffer using `%s` format specifiers. The verifier log is the only data copied to user space, and since it is populated using standard string formatting functions, no uninitialized memory or padding bytes from the internal structures are leaked.\n\nThe patch does not introduce any new structures sent directly to user space via `copy_to_user`, netlink, ioctl, or BPF buffers. Therefore, the changes do not expose any uninitialized memory risks, and a dedicated KMSAN fuzzing session is not justified. Standard KASAN fuzzing is sufficient to catch any potential memory safety issues (e.g., out-of-bounds accesses or use-after-free) in the new verifier code.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces a new diagnostic framework to the BPF verifier, adding detailed diagnostics for verifier errors. This is a significant functional change to the BPF verifier and is reachable when the BPF verifier rejects a program or when logging is enabled.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/15 07:05 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit d335ac0304d75ca7fd84c386623517953d26cfa8\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Aug 15 07:05:20 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/include/linux/bpf.h b/include/linux/bpf.h\nindex f4e8d372253ac..018823e277293 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -4146,8 +4146,16 @@ static inline bool bpf_is_subprog(const struct bpf_prog *prog)\n }\n \n const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off);\n-void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,\n-\t\t\t const char **filep, const char **linep, int *nump);\n+struct bpf_linfo_source {\n+\tconst char *file;\n+\tconst char *line;\n+\tu32 file_name_off;\n+\tint line_num;\n+\tint line_col;\n+};\n+\n+void bpf_get_linfo_source(struct btf *btf, const struct bpf_line_info *linfo,\n+\t\t\t struct bpf_linfo_source *src);\n int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,\n \t\t\t const char **linep, int *nump);\n struct bpf_prog *bpf_prog_find_from_stack(void);\ndiff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex 27b43fda9b178..bc2af02547fe5 100644\n--- a/include/linux/bpf_verifier.h\n+++ b/include/linux/bpf_verifier.h\n@@ -354,6 +354,11 @@ struct bpf_func_state {\n \t * 0 = main function, 1 = first callee.\n \t */\n \tu32 frameno;\n+\t/*\n+\t * Unique diagnostic identity for this function invocation. Frame depth is\n+\t * reused after returns, while this ID is preserved across state clones.\n+\t */\n+\tu32 diag_frame_id;\n \t/* subprog number == index within subprog_info\n \t * zero == main subprog\n \t */\n@@ -833,6 +838,7 @@ static inline u16 bpf_in_stack_arg_cnt(const struct bpf_subprog_info *sub)\n \treturn 0;\n }\n \n+struct bpf_diag;\n struct bpf_verifier_env;\n \n struct backtrack_state {\n@@ -950,6 +956,7 @@ struct bpf_verifier_env {\n \tstruct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */\n \tconst struct bpf_line_info *prev_linfo;\n \tstruct bpf_verifier_log log;\n+\tstruct bpf_diag *diag;\n \tstruct bpf_subprog_info subprog_info[BPF_MAX_SUBPROGS + 2]; /* max + 2 for the fake and exception subprogs */\n \t/* subprog indices sorted in topological order: leaves first, callers last */\n \tint subprog_topo_order[BPF_MAX_SUBPROGS + 2];\n@@ -1349,6 +1356,18 @@ static inline bool type_is_non_owning_ref(u32 type)\n \treturn type_is_ptr_alloc_obj(type) \u0026\u0026 type_flag(type) \u0026 NON_OWN_REF;\n }\n \n+static inline bool type_is_map_ptr(enum bpf_reg_type type)\n+{\n+\tswitch (base_type(type)) {\n+\tcase CONST_PTR_TO_MAP:\n+\tcase PTR_TO_MAP_KEY:\n+\tcase PTR_TO_MAP_VALUE:\n+\t\treturn true;\n+\tdefault:\n+\t\treturn false;\n+\t}\n+}\n+\n static inline bool type_is_pkt_pointer(enum bpf_reg_type type)\n {\n \ttype = base_type(type);\n@@ -1433,8 +1452,10 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie\n void print_insn_state(struct bpf_verifier_env *env, const struct bpf_verifier_state *vstate,\n \t\t u32 frameno);\n u32 bpf_vlog_alignment(u32 pos);\n+const char *bpf_disasm_kfunc_name(void *data, const struct bpf_insn *insn);\n \n struct bpf_subprog_info *bpf_find_containing_subprog(struct bpf_verifier_env *env, int off);\n+const char *bpf_subprog_name(const struct bpf_verifier_env *env, int subprog);\n int bpf_jmp_offset(struct bpf_insn *insn);\n struct bpf_iarray *bpf_insn_successors(struct bpf_verifier_env *env, u32 idx);\n void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask);\ndiff --git a/include/linux/btf.h b/include/linux/btf.h\nindex 3f5255d095a27..7ea13768c9795 100644\n--- a/include/linux/btf.h\n+++ b/include/linux/btf.h\n@@ -214,6 +214,7 @@ int btf_type_seq_show_flags(const struct btf *btf, u32 type_id, void *obj,\n */\n int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,\n \t\t\t char *buf, int len, u64 flags);\n+int btf_type_name_to_buf(const struct btf *btf, u32 type_id, char *buf, int len);\n \n int btf_get_fd_by_id(u32 id);\n u32 btf_obj_id(const struct btf *btf);\ndiff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile\nindex 4dc41bf5780cc..90255d80e5be6 100644\n--- a/kernel/bpf/Makefile\n+++ b/kernel/bpf/Makefile\n@@ -6,7 +6,7 @@ cflags-nogcse-$(CONFIG_X86)$(CONFIG_CC_IS_GCC) := -fno-gcse\n endif\n CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)\n \n-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\n+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\n obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o\n 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\n obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o\ndiff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c\nindex 87ffde865a503..5b9d767895c91 100644\n--- a/kernel/bpf/btf.c\n+++ b/kernel/bpf/btf.c\n@@ -8316,6 +8316,16 @@ int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,\n \treturn ssnprintf.len;\n }\n \n+int btf_type_name_to_buf(const struct btf *btf, u32 type_id, char *buf, int len)\n+{\n+\tstruct btf_show show = {\n+\t\t.btf = btf,\n+\t\t.state.type_id = type_id,\n+\t};\n+\n+\treturn snprintf(buf, len, \"%s\", btf_show_name(\u0026show));\n+}\n+\n #ifdef CONFIG_PROC_FS\n static void bpf_btf_show_fdinfo(struct seq_file *m, struct file *filp)\n {\ndiff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c\nindex 818f7afac83a5..0f13c13f41337 100644\n--- a/kernel/bpf/cfg.c\n+++ b/kernel/bpf/cfg.c\n@@ -5,6 +5,8 @@\n #include \u003clinux/filter.h\u003e\n #include \u003clinux/sort.h\u003e\n \n+#include \"diagnostics.h\"\n+\n #define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)\n \n /* non-recursive DFS pseudo code\n@@ -112,6 +114,10 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)\n \tif (w \u003c 0 || w \u003e= env-\u003eprog-\u003elen) {\n \t\tverbose_linfo(env, t, \"%d: \", t);\n \t\tverbose(env, \"jump out of range from insn %d to %d\\n\", t, w);\n+\t\tbpf_diag_program_structure(\n+\t\t\tenv, t, \"jump out of range\", \"Keep branch targets inside the program.\",\n+\t\t\t\"Instruction %d jumps to instruction %d, but the program only contains instructions 0 through %d.\",\n+\t\t\tt, w, env-\u003eprog-\u003elen - 1);\n \t\treturn -EINVAL;\n \t}\n \n@@ -135,6 +141,11 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)\n \t\tverbose_linfo(env, t, \"%d: \", t);\n \t\tverbose_linfo(env, w, \"%d: \", w);\n \t\tverbose(env, \"back-edge from insn %d to %d\\n\", t, w);\n+\t\tbpf_diag_program_structure(\n+\t\t\tenv, t, \"back-edge is not allowed\",\n+\t\t\t\"Load with privileges that allow this back-edge, or rewrite the control flow so it does not branch backward.\",\n+\t\t\t\"Instruction %d branches back to instruction %d. This program is being rejected without the privilege needed for this back-edge.\",\n+\t\t\tt, w);\n \t\treturn -EINVAL;\n \t} else if (insn_state[w] == EXPLORED) {\n \t\t/* forward- or cross-edge */\n@@ -315,6 +326,11 @@ static struct bpf_iarray *jt_from_subprog(struct bpf_verifier_env *env,\n \n \tif (!jt) {\n \t\tverbose(env, \"no jump tables found for subprog starting at %u\\n\", subprog_start);\n+\t\tbpf_diag_program_structure(\n+\t\t\tenv, subprog_start, \"missing jump table\",\n+\t\t\t\"Make sure subprograms containing gotox instructions are accompanied by jump tables referencing these subprograms.\",\n+\t\t\t\"No jump table was found for the subprogram that starts at instruction %u.\",\n+\t\t\tsubprog_start);\n \t\treturn ERR_PTR(-EINVAL);\n \t}\n \n@@ -342,6 +358,11 @@ create_jt(int t, struct bpf_verifier_env *env)\n \t\tif (jt-\u003eitems[i] \u003c subprog_start || jt-\u003eitems[i] \u003e= subprog_end) {\n \t\t\tverbose(env, \"jump table for insn %d points outside of the subprog [%u,%u]\\n\",\n \t\t\t\t\tt, subprog_start, subprog_end);\n+\t\t\tbpf_diag_program_structure(\n+\t\t\t\tenv, t, \"jump table target out of range\",\n+\t\t\t\t\"Keep every jump-table target inside the same subprogram.\",\n+\t\t\t\t\"The jump table for instruction %d points outside subprogram range [%u,%u).\",\n+\t\t\t\tt, subprog_start, subprog_end);\n \t\t\tkvfree(jt);\n \t\t\treturn ERR_PTR(-EINVAL);\n \t\t}\n@@ -373,6 +394,11 @@ static int visit_gotox_insn(int t, struct bpf_verifier_env *env)\n \t\tw = jt-\u003eitems[i];\n \t\tif (w \u003c 0 || w \u003e= env-\u003eprog-\u003elen) {\n \t\t\tverbose(env, \"indirect jump out of range from insn %d to %d\\n\", t, w);\n+\t\t\tbpf_diag_program_structure(\n+\t\t\t\tenv, t, \"indirect jump out of range\",\n+\t\t\t\t\"Keep indirect jump targets inside the program.\",\n+\t\t\t\t\"Instruction %d can jump indirectly to instruction %d, but the program only contains instructions 0 through %d.\",\n+\t\t\t\tt, w, env-\u003eprog-\u003elen - 1);\n \t\t\treturn -EINVAL;\n \t\t}\n \n@@ -623,12 +649,21 @@ int bpf_check_cfg(struct bpf_verifier_env *env)\n \n \t\tif (insn_state[i] != EXPLORED) {\n \t\t\tverbose(env, \"unreachable insn %d\\n\", i);\n+\t\t\tbpf_diag_program_structure(\n+\t\t\t\tenv, i, \"unreachable instruction\",\n+\t\t\t\t\"Remove the unreachable instruction or add valid control flow that reaches it.\",\n+\t\t\t\t\"Instruction %d is not reachable from the program entry point.\", i);\n \t\t\tret = -EINVAL;\n \t\t\tgoto err_free;\n \t\t}\n \t\tif (bpf_is_ldimm64(insn)) {\n \t\t\tif (insn_state[i + 1] != 0) {\n \t\t\t\tverbose(env, \"jump into the middle of ldimm64 insn %d\\n\", i);\n+\t\t\t\tbpf_diag_program_structure(\n+\t\t\t\t\tenv, i, \"jump into ldimm64 immediate\",\n+\t\t\t\t\t\"Target the first instruction of the ldimm64 pair, or restructure the jump target.\",\n+\t\t\t\t\t\"Control flow reaches the second half of the ldimm64 instruction pair that starts at instruction %d.\",\n+\t\t\t\t\ti);\n \t\t\t\tret = -EINVAL;\n \t\t\t\tgoto err_free;\n \t\t\t}\ndiff --git a/kernel/bpf/core.c b/kernel/bpf/core.c\nindex 6a94370a24488..d55e737ed75ad 100644\n--- a/kernel/bpf/core.c\n+++ b/kernel/bpf/core.c\n@@ -3461,24 +3461,14 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(xdp_bulk_tx);\n \n #ifdef CONFIG_BPF_SYSCALL\n \n-void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,\n-\t\t\t const char **filep, const char **linep, int *nump)\n+void bpf_get_linfo_source(struct btf *btf, const struct bpf_line_info *linfo,\n+\t\t\t struct bpf_linfo_source *src)\n {\n-\t/* Get base component of the file path. */\n-\tif (filep) {\n-\t\t*filep = btf_name_by_offset(btf, linfo-\u003efile_name_off);\n-\t\t*filep = kbasename(*filep);\n-\t}\n-\n-\t/* Obtain the source line, and strip whitespace in prefix. */\n-\tif (linep) {\n-\t\t*linep = btf_name_by_offset(btf, linfo-\u003eline_off);\n-\t\twhile (isspace(**linep))\n-\t\t\t*linep += 1;\n-\t}\n-\n-\tif (nump)\n-\t\t*nump = BPF_LINE_INFO_LINE_NUM(linfo-\u003eline_col);\n+\tsrc-\u003efile = kbasename(btf_name_by_offset(btf, linfo-\u003efile_name_off));\n+\tsrc-\u003eline = btf_name_by_offset(btf, linfo-\u003eline_off);\n+\tsrc-\u003efile_name_off = linfo-\u003efile_name_off;\n+\tsrc-\u003eline_num = BPF_LINE_INFO_LINE_NUM(linfo-\u003eline_col);\n+\tsrc-\u003eline_col = BPF_LINE_INFO_LINE_COL(linfo-\u003eline_col);\n }\n \n const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off)\n@@ -3521,6 +3511,7 @@ const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn\n int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,\n \t\t\t const char **linep, int *nump)\n {\n+\tstruct bpf_linfo_source src;\n \tint idx = -1, insn_start, insn_end, len;\n \tstruct bpf_line_info *linfo;\n \tvoid **jited_linfo;\n@@ -3552,7 +3543,15 @@ int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char *\n \tif (idx == -1)\n \t\treturn -ENOENT;\n \n-\tbpf_get_linfo_file_line(btf, \u0026linfo[idx], filep, linep, nump);\n+\tbpf_get_linfo_source(btf, \u0026linfo[idx], \u0026src);\n+\twhile (isspace(*src.line))\n+\t\tsrc.line++;\n+\tif (filep)\n+\t\t*filep = src.file;\n+\tif (linep)\n+\t\t*linep = src.line;\n+\tif (nump)\n+\t\t*nump = src.line_num;\n \treturn 0;\n }\n \ndiff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c\nnew file mode 100644\nindex 0000000000000..33b7d9e8e2c3a\n--- /dev/null\n+++ b/kernel/bpf/diagnostics.c\n@@ -0,0 +1,2355 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+// Copyright (c) 2026 Meta Platforms, Inc. and affiliates.\n+\n+#include \u003clinux/bpf.h\u003e\n+#include \u003clinux/bpf_verifier.h\u003e\n+#include \u003clinux/btf.h\u003e\n+#include \u003clinux/ctype.h\u003e\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/seq_buf.h\u003e\n+#include \u003clinux/overflow.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/stdarg.h\u003e\n+#include \u003clinux/string.h\u003e\n+\n+#include \"disasm.h\"\n+#include \"diagnostics.h\"\n+\n+#define REGISTER_TYPE_SAFETY \"Register Type Safety\"\n+#define MEMORY_SAFETY \"Memory Safety\"\n+#define RESOURCE_LIFETIME_SAFETY \"Resource Lifetime Safety\"\n+#define CALL_TYPE_SAFETY \"Call Type Safety\"\n+#define EXECUTION_CONTEXT_SAFETY \"Execution Context Safety\"\n+#define PROGRAM_STRUCTURE \"Program Structure\"\n+#define POLICY \"Policy\"\n+\n+#define BPF_DIAG_TEXT_WIDTH 100\n+#define BPF_DIAG_TEXT_INDENT \" \"\n+#define BPF_DIAG_CONTEXT 2\n+#define BPF_DIAG_CONTEXT_CNT (1 + BPF_DIAG_CONTEXT * 2)\n+#define BPF_DIAG_HISTORY_RENDER_MAX 64\n+#define BPF_DIAG_SOURCE_LANE_WIDTH 88\n+#define BPF_DIAG_TAB_WIDTH 8\n+#define BPF_DIAG_FMT_CHUNK_SIZE (PAGE_SIZE - sizeof(struct diag_fmt_chunk))\n+#define BPF_DIAG_FMT_BUF_SIZE 256\n+#define BPF_DIAG_EVENT_LOG_MAX_SIZE (64U \u003c\u003c 20)\n+#define DISASM_LINE_LEN 160\n+\n+enum bpf_diag_mod_target_kind {\n+\tBPF_DIAG_MOD_TARGET_NONE,\n+\tBPF_DIAG_MOD_TARGET_REG,\n+\tBPF_DIAG_MOD_TARGET_STACK_ARG,\n+\tBPF_DIAG_MOD_TARGET_STACK_SLOT,\n+\tBPF_DIAG_MOD_TARGET_STACK_RANGE,\n+};\n+\n+struct bpf_diag_mod_target {\n+\tu32 frame_id;\n+\tunion {\n+\t\tstruct {\n+\t\t\ts16 min_off;\n+\t\t\ts16 max_off;\n+\t\t} range;\n+\t\tu16 spi;\n+\t\tu8 regno;\n+\t\tu8 stack_arg;\n+\t};\n+\tu8 frameno;\n+\tu8 kind;\n+};\n+\n+static struct bpf_diag_mod_target diag_reg_target(u32 frame_id, u8 frameno, u8 regno)\n+{\n+\treturn (struct bpf_diag_mod_target){\n+\t\t.frame_id = frame_id,\n+\t\t.frameno = frameno,\n+\t\t.kind = BPF_DIAG_MOD_TARGET_REG,\n+\t\t.regno = regno,\n+\t};\n+}\n+\n+static struct bpf_diag_mod_target diag_stack_arg_target(u32 frame_id, u8 frameno, u8 slot)\n+{\n+\treturn (struct bpf_diag_mod_target){\n+\t\t.frame_id = frame_id,\n+\t\t.frameno = frameno,\n+\t\t.kind = BPF_DIAG_MOD_TARGET_STACK_ARG,\n+\t\t.stack_arg = slot,\n+\t};\n+}\n+\n+static struct bpf_diag_mod_target diag_stack_slot_target(u32 frame_id, u8 frameno, u16 spi)\n+{\n+\treturn (struct bpf_diag_mod_target){\n+\t\t.frame_id = frame_id,\n+\t\t.frameno = frameno,\n+\t\t.kind = BPF_DIAG_MOD_TARGET_STACK_SLOT,\n+\t\t.spi = spi,\n+\t};\n+}\n+\n+static struct bpf_diag_mod_target diag_stack_range_target(u32 frame_id, u8 frameno,\n+\t\t\t\t\t\t\t s16 min_off, s16 max_off)\n+{\n+\treturn (struct bpf_diag_mod_target){\n+\t\t.frame_id = frame_id,\n+\t\t.frameno = frameno,\n+\t\t.kind = BPF_DIAG_MOD_TARGET_STACK_RANGE,\n+\t\t.range.min_off = min_off,\n+\t\t.range.max_off = max_off,\n+\t};\n+}\n+\n+struct bpf_diag_reg_snapshot {\n+\tu32 type;\n+\tu32 btf_id;\n+\tconst struct bpf_map *map_ptr;\n+\tconst struct btf *btf;\n+\tstruct tnum var_off;\n+\tstruct cnum64 r64;\n+};\n+\n+enum bpf_diag_history_kind {\n+\tBPF_DIAG_HISTORY_BRANCH,\n+\tBPF_DIAG_HISTORY_MOD,\n+\tBPF_DIAG_HISTORY_REF_ACQUIRE,\n+\tBPF_DIAG_HISTORY_REF_RELEASE,\n+\tBPF_DIAG_HISTORY_CONTEXT,\n+};\n+\n+struct bpf_diag_history_event {\n+\tu32 insn_idx : 24;\n+\tu32 kind : 8;\n+\tu8 in_lineage : 1;\n+\tunion {\n+\t\tstruct {\n+\t\t\tbool cond_true;\n+\t\t} branch;\n+\t\tstruct {\n+\t\t\tstruct bpf_diag_mod_target target;\n+\t\t\tstruct bpf_diag_mod_target origin;\n+\t\t\tstruct bpf_diag_reg_snapshot old, new;\n+\t\t\tu8 reason;\n+\t\t\tbool origin_valid;\n+\t\t} mod;\n+\t\tstruct {\n+\t\t\tu32 ref_id;\n+\t\t} ref;\n+\t\tstruct {\n+\t\t\tu32 depth;\n+\t\t\tu8 kind;\n+\t\t\tbool enter;\n+\t\t} ctx;\n+\t};\n+};\n+\n+enum bpf_diag_history_scope {\n+\tBPF_DIAG_HISTORY_SCOPE_REG,\n+\tBPF_DIAG_HISTORY_SCOPE_STACK_ARG,\n+\tBPF_DIAG_HISTORY_SCOPE_REF,\n+\tBPF_DIAG_HISTORY_SCOPE_CONTEXT,\n+};\n+\n+struct bpf_diag_history_opts {\n+\tenum bpf_diag_history_scope scope;\n+\tu32 frame_id;\n+\tu32 frameno;\n+\tint regno;\n+\tint stack_arg_slot;\n+\tu32 ref_id;\n+\tenum bpf_diag_context_kind ctx_kind;\n+\tu32 ctx_depth;\n+};\n+\n+static void diag_print_history(struct bpf_verifier_env *env,\n+\t\t\t const struct bpf_diag_history_opts *opts);\n+static bool diag_target_matches(const struct bpf_diag_mod_target *event_target,\n+\t\t\t\tconst struct bpf_diag_mod_target *target);\n+static const char *diag_context_name(enum bpf_diag_context_kind kind);\n+struct disasm_line {\n+\tchar text[DISASM_LINE_LEN];\n+\tint idx;\n+\tbool valid;\n+};\n+\n+struct disasm_ctx {\n+\tstruct bpf_verifier_env *env;\n+\tstruct seq_buf seq;\n+};\n+\n+struct diag_fmt_chunk {\n+\tstruct list_head node;\n+\tstruct seq_buf seq;\n+\tchar data[];\n+};\n+\n+struct diag_fmt_mark {\n+\tstruct diag_fmt_chunk *chunk;\n+\tsize_t len;\n+};\n+\n+struct bpf_diag_log {\n+\tstruct bpf_diag_history_event *events;\n+\t/* Sequence number of the oldest retained event on the active path. */\n+\tu64 first_seq;\n+\tu32 cnt;\n+\tu32 cap;\n+\tu32 head;\n+\tbool growth_failed;\n+};\n+\n+struct bpf_diag_scratch {\n+\tstruct bpf_linfo_source source_lines[BPF_DIAG_CONTEXT_CNT];\n+\tstruct disasm_line disasm_lines[BPF_DIAG_CONTEXT_CNT];\n+};\n+\n+struct bpf_diag_mod_scope {\n+\tstruct bpf_reg_state target_reg_snapshot;\n+\tstruct bpf_diag_mod_target target;\n+\tstruct bpf_diag_mod_target origin;\n+\tenum bpf_diag_mod_reason reason;\n+\tu32 insn_idx;\n+\tbool active;\n+\tbool origin_valid;\n+};\n+\n+struct bpf_diag {\n+\tstruct bpf_diag_log log;\n+\tstruct bpf_diag_scratch scratch;\n+\tstruct list_head fmt_chunks;\n+\tstruct bpf_diag_mod_scope mod;\n+\tu32 frame_id_gen;\n+};\n+\n+bool bpf_diag_enabled(const struct bpf_verifier_env *env)\n+{\n+\treturn env-\u003elog.level \u0026 BPF_LOG_LEVEL;\n+}\n+\n+static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);\n+\n+int bpf_diag_init(struct bpf_verifier_env *env)\n+{\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn 0;\n+\n+\tenv-\u003ediag = kzalloc_obj(struct bpf_diag, GFP_KERNEL_ACCOUNT);\n+\tif (!env-\u003ediag)\n+\t\treturn -ENOMEM;\n+\n+\tINIT_LIST_HEAD(\u0026env-\u003ediag-\u003efmt_chunks);\n+\treturn 0;\n+}\n+\n+void bpf_diag_init_frame(struct bpf_verifier_env *env, struct bpf_func_state *state)\n+{\n+\tif (env-\u003ediag)\n+\t\tstate-\u003ediag_frame_id = ++env-\u003ediag-\u003eframe_id_gen;\n+}\n+\n+static char *diag_fmt_alloc(struct bpf_verifier_env *env, size_t size)\n+{\n+\tstruct bpf_diag *diag = env-\u003ediag;\n+\tstruct diag_fmt_chunk *chunk;\n+\tsize_t capacity, available;\n+\tchar *buf;\n+\n+\tif (!diag || !size || size \u003e INT_MAX)\n+\t\treturn NULL;\n+\n+\tif (!list_empty(\u0026diag-\u003efmt_chunks)) {\n+\t\tchunk = list_last_entry(\u0026diag-\u003efmt_chunks, struct diag_fmt_chunk, node);\n+\t\tavailable = seq_buf_get_buf(\u0026chunk-\u003eseq, \u0026buf);\n+\t\tif (available \u003e= size)\n+\t\t\tgoto commit;\n+\t}\n+\n+\tcapacity = max_t(size_t, BPF_DIAG_FMT_CHUNK_SIZE, size);\n+\tchunk = kmalloc(struct_size(chunk, data, capacity), GFP_KERNEL_ACCOUNT);\n+\tif (!chunk)\n+\t\treturn NULL;\n+\n+\tseq_buf_init(\u0026chunk-\u003eseq, chunk-\u003edata, capacity);\n+\tlist_add_tail(\u0026chunk-\u003enode, \u0026diag-\u003efmt_chunks);\n+\tavailable = seq_buf_get_buf(\u0026chunk-\u003eseq, \u0026buf);\n+\tif (WARN_ON_ONCE(available \u003c size))\n+\t\treturn NULL;\n+\n+commit:\n+\tseq_buf_commit(\u0026chunk-\u003eseq, size);\n+\treturn buf;\n+}\n+\n+char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size)\n+{\n+\tchar *buf;\n+\n+\tbuf = diag_fmt_alloc(env, size);\n+\tif (buf)\n+\t\tbuf[0] = '\\0';\n+\treturn buf;\n+}\n+\n+const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)\n+{\n+\tva_list copy;\n+\tchar *buf;\n+\tint len;\n+\n+\tva_copy(copy, args);\n+\tlen = vsnprintf(NULL, 0, fmt, copy);\n+\tva_end(copy);\n+\tif (len \u003c 0 || len == INT_MAX)\n+\t\treturn \"\";\n+\n+\tbuf = diag_fmt_alloc(env, len + 1);\n+\tif (buf)\n+\t\tvsnprintf(buf, len + 1, fmt, args);\n+\treturn buf ?: \"\";\n+}\n+\n+const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...)\n+{\n+\tconst char *buf;\n+\tva_list args;\n+\n+\tva_start(args, fmt);\n+\tbuf = bpf_diag_vfmt(env, fmt, args);\n+\tva_end(args);\n+\treturn buf;\n+}\n+\n+static struct diag_fmt_mark diag_fmt_save(struct bpf_verifier_env *env)\n+{\n+\tstruct bpf_diag *diag = env-\u003ediag;\n+\tstruct diag_fmt_mark mark = {};\n+\n+\tif (!diag || list_empty(\u0026diag-\u003efmt_chunks))\n+\t\treturn mark;\n+\n+\tmark.chunk = list_last_entry(\u0026diag-\u003efmt_chunks, struct diag_fmt_chunk, node);\n+\tmark.len = mark.chunk-\u003eseq.len;\n+\treturn mark;\n+}\n+\n+static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_mark mark)\n+{\n+\tstruct bpf_diag *diag = env-\u003ediag;\n+\tstruct diag_fmt_chunk *chunk;\n+\n+\tif (!diag)\n+\t\treturn;\n+\n+\twhile (!list_empty(\u0026diag-\u003efmt_chunks)) {\n+\t\tchunk = list_last_entry(\u0026diag-\u003efmt_chunks, struct diag_fmt_chunk, node);\n+\t\tif (chunk == mark.chunk)\n+\t\t\tbreak;\n+\t\tlist_del(\u0026chunk-\u003enode);\n+\t\tkfree(chunk);\n+\t}\n+\n+\tif (mark.chunk) {\n+\t\tmark.chunk-\u003eseq.len = mark.len;\n+\t\tseq_buf_str(\u0026mark.chunk-\u003eseq);\n+\t}\n+}\n+\n+void bpf_diag_free(struct bpf_verifier_env *env)\n+{\n+\tstruct bpf_diag *diag = env-\u003ediag;\n+\n+\tif (!diag)\n+\t\treturn;\n+\n+\tdiag_fmt_restore(env, (struct diag_fmt_mark){});\n+\tkvfree(diag-\u003elog.events);\n+\tkfree(diag);\n+\tenv-\u003ediag = NULL;\n+}\n+\n+static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...)\n+{\n+\tva_list args;\n+\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn;\n+\n+\tva_start(args, fmt);\n+\tbpf_verifier_vlog(\u0026env-\u003elog, fmt, args);\n+\tva_end(args);\n+}\n+\n+static u64 log_end(const struct bpf_diag_log *log)\n+{\n+\treturn log-\u003efirst_seq + log-\u003ecnt;\n+}\n+\n+static u32 log_pos(const struct bpf_diag_log *log, u32 idx)\n+{\n+\tu32 pos = log-\u003ehead + idx;\n+\n+\treturn pos \u003c log-\u003ecap ? pos : pos - log-\u003ecap;\n+}\n+\n+u64 bpf_diag_event_log_save(struct bpf_verifier_env *env)\n+{\n+\tstruct bpf_diag *diag = env-\u003ediag;\n+\n+\treturn diag ? log_end(\u0026diag-\u003elog) : 0;\n+}\n+\n+void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos)\n+{\n+\tstruct bpf_diag *diag = env-\u003ediag;\n+\tstruct bpf_diag_log *log;\n+\tu64 end_seq;\n+\n+\tif (!diag)\n+\t\treturn;\n+\n+\tlog = \u0026diag-\u003elog;\n+\tend_seq = log_end(log);\n+\tif (WARN_ON_ONCE(log_pos \u003e end_seq))\n+\t\tlog_pos = end_seq;\n+\n+\t/*\n+\t * A deep abandoned path may have rotated away the shared prefix. In\n+\t * that case, restart with an empty retained suffix and remember that\n+\t * every event before the restored mark is unavailable.\n+\t */\n+\tif (log_pos \u003c= log-\u003efirst_seq) {\n+\t\tlog-\u003efirst_seq = log_pos;\n+\t\tlog-\u003ehead = 0;\n+\t\tlog-\u003ecnt = 0;\n+\t\treturn;\n+\t}\n+\n+\tlog-\u003ecnt = log_pos - log-\u003efirst_seq;\n+}\n+\n+u32 bpf_diag_irq_depth(const struct bpf_verifier_state *state)\n+{\n+\tu32 depth = 0;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c state-\u003eacquired_refs; i++) {\n+\t\tif (state-\u003erefs[i].type == REF_TYPE_IRQ)\n+\t\t\tdepth++;\n+\t}\n+\n+\treturn depth;\n+}\n+\n+static void diag_append_history(struct bpf_verifier_env *env,\n+\t\t\t\tconst struct bpf_diag_history_event *event)\n+{\n+\tstruct bpf_diag_history_event *events;\n+\tstruct bpf_diag *diag = env-\u003ediag;\n+\tstruct bpf_diag_log *log;\n+\tu32 cap, max_events;\n+\n+\tif (!diag)\n+\t\treturn;\n+\tlog = \u0026diag-\u003elog;\n+\n+\tif (log-\u003ecnt \u003c log-\u003ecap) {\n+\t\tlog-\u003eevents[log_pos(log, log-\u003ecnt++)] = *event;\n+\t\treturn;\n+\t}\n+\n+\tmax_events = BPF_DIAG_EVENT_LOG_MAX_SIZE / sizeof(*events);\n+\tif (log-\u003egrowth_failed || log-\u003ecap == max_events)\n+\t\tgoto rotate;\n+\n+\tcap = min(log-\u003ecap ? log-\u003ecap * 2 : 64, max_events);\n+\tevents = kvrealloc(log-\u003eevents, array_size(cap, sizeof(*events)), GFP_KERNEL_ACCOUNT);\n+\tif (!events) {\n+\t\tlog-\u003egrowth_failed = true;\n+\t\tgoto rotate;\n+\t}\n+\tlog-\u003eevents = events;\n+\tlog-\u003ecap = cap;\n+\tlog-\u003eevents[log-\u003ecnt++] = *event;\n+\treturn;\n+\n+rotate:\n+\tif (log-\u003ecap) {\n+\t\tlog-\u003eevents[log-\u003ehead++] = *event;\n+\t\tif (log-\u003ehead == log-\u003ecap)\n+\t\t\tlog-\u003ehead = 0;\n+\t}\n+\tlog-\u003efirst_seq++;\n+}\n+\n+static void diag_print_wrapped_prefixed(struct bpf_verifier_env *env, const char *first_prefix,\n+\t\t\t\t\tconst char *next_prefix, const char *text)\n+{\n+\tconst char *prefix = first_prefix;\n+\n+\twhile (*text) {\n+\t\tconst char *line = text;\n+\t\tint prefix_len = strlen(prefix);\n+\t\tint text_width = BPF_DIAG_TEXT_WIDTH - prefix_len;\n+\t\tint len = 0, last_space = -1;\n+\n+\t\tif (text_width \u003c 1)\n+\t\t\ttext_width = 1;\n+\n+\t\twhile (line[len] \u0026\u0026 line[len] != '\\n' \u0026\u0026 len \u003c text_width) {\n+\t\t\tif (line[len] == ' ')\n+\t\t\t\tlast_space = len;\n+\t\t\tlen++;\n+\t\t}\n+\n+\t\tif (line[len] \u0026\u0026 line[len] != '\\n' \u0026\u0026 line[len] != ' ' \u0026\u0026 last_space \u003e 0)\n+\t\t\tlen = last_space;\n+\n+\t\tdiag_write(env, \"%s%.*s\\n\", prefix, len, line);\n+\n+\t\ttext = line + len;\n+\t\twhile (*text == ' ')\n+\t\t\ttext++;\n+\t\tif (*text == '\\n')\n+\t\t\ttext++;\n+\n+\t\tprefix = next_prefix;\n+\t}\n+}\n+\n+const char *bpf_diag_fmt_btf_type(struct bpf_verifier_env *env, const struct btf *btf, u32 type_id)\n+{\n+\tchar *buf = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);\n+\tsize_t len;\n+\tint ret;\n+\n+\tif (!buf)\n+\t\treturn \"\";\n+\n+\tbuf[0] = '\\0';\n+\tret = btf_type_name_to_buf(btf, type_id, buf, BPF_DIAG_FMT_BUF_SIZE);\n+\tif (ret \u003c 0 || !buf[0]) {\n+\t\tscnprintf(buf, BPF_DIAG_FMT_BUF_SIZE, \"BTF type ID %u\", type_id);\n+\t\treturn buf;\n+\t}\n+\n+\tlen = strlen(buf);\n+\tif (len \u0026\u0026 buf[len - 1] == '{')\n+\t\tbuf[len - 1] = '\\0';\n+\treturn buf;\n+}\n+\n+static void diag_vprint_indented(struct bpf_verifier_env *env, const char *fmt, va_list args)\n+\t__printf(2, 0);\n+\n+static void diag_vprint_indented(struct bpf_verifier_env *env, const char *fmt, va_list args)\n+{\n+\tchar *buf;\n+\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn;\n+\n+\tbuf = kvasprintf(GFP_KERNEL_ACCOUNT, fmt, args);\n+\tif (!buf) {\n+\t\tdiag_write(env, \"%s\u003cfailed to allocate diagnostic text\u003e\\n\", BPF_DIAG_TEXT_INDENT);\n+\t\treturn;\n+\t}\n+\n+\tdiag_print_wrapped_prefixed(env, BPF_DIAG_TEXT_INDENT, BPF_DIAG_TEXT_INDENT, buf);\n+\tkfree(buf);\n+}\n+\n+static int diag_line_width(unsigned int line)\n+{\n+\tint width = 1;\n+\n+\twhile (line \u003e= 10) {\n+\t\tline /= 10;\n+\t\twidth++;\n+\t}\n+\n+\treturn width;\n+}\n+\n+static int diag_line_indent(const char *line)\n+{\n+\tint indent = 0;\n+\n+\twhile (*line == ' ' || *line == '\\t') {\n+\t\tif (*line == '\\t')\n+\t\t\tindent = round_up(indent + 1, BPF_DIAG_TAB_WIDTH);\n+\t\telse\n+\t\t\tindent++;\n+\t\tline++;\n+\t}\n+\n+\treturn indent;\n+}\n+\n+static void disasm_print(void *private_data, const char *fmt, ...) __printf(2, 3);\n+\n+static void disasm_print(void *private_data, const char *fmt, ...)\n+{\n+\tstruct disasm_ctx *ctx = private_data;\n+\tva_list args;\n+\n+\tva_start(args, fmt);\n+\tseq_buf_vprintf(\u0026ctx-\u003eseq, fmt, args);\n+\tva_end(args);\n+}\n+\n+static const char *disasm_kfunc_name(void *private_data, const struct bpf_insn *insn)\n+{\n+\tstruct disasm_ctx *ctx = private_data;\n+\n+\treturn bpf_disasm_kfunc_name(ctx-\u003eenv, insn);\n+}\n+\n+static void format_disasm_line(struct bpf_verifier_env *env, int insn_idx,\n+\t\t\t struct disasm_line *line)\n+{\n+\tstruct disasm_ctx ctx = { .env = env };\n+\tstruct bpf_insn *insn;\n+\tconst struct bpf_insn_cbs cbs = {\n+\t\t.cb_call = disasm_kfunc_name,\n+\t\t.cb_print = disasm_print,\n+\t\t.private_data = \u0026ctx,\n+\t};\n+\n+\tline-\u003eidx = insn_idx;\n+\tline-\u003evalid = false;\n+\tseq_buf_init(\u0026ctx.seq, line-\u003etext, sizeof(line-\u003etext));\n+\n+\tif (insn_idx \u003c 0 || insn_idx \u003e= env-\u003eprog-\u003elen)\n+\t\treturn;\n+\n+\tif (insn_idx \u003e 0 \u0026\u0026 bpf_is_ldimm64(\u0026env-\u003eprog-\u003einsnsi[insn_idx - 1]))\n+\t\treturn;\n+\n+\tinsn = \u0026env-\u003eprog-\u003einsnsi[insn_idx];\n+\tif (bpf_is_ldimm64(insn) \u0026\u0026 insn_idx + 1 \u003e= env-\u003eprog-\u003elen)\n+\t\treturn;\n+\n+\tprint_bpf_insn(\u0026cbs, insn, env-\u003eallow_ptr_leaks);\n+\tseq_buf_str(\u0026ctx.seq);\n+\tctx.seq.len = strnlen(line-\u003etext, sizeof(line-\u003etext));\n+\twhile (ctx.seq.len \u0026\u0026 line-\u003etext[ctx.seq.len - 1] == '\\n')\n+\t\tseq_buf_pop(\u0026ctx.seq);\n+\tseq_buf_str(\u0026ctx.seq);\n+\n+\tline-\u003evalid = true;\n+}\n+\n+static void diag_format_source_text(char *buf, size_t size, const char *line, int width)\n+{\n+\tint col = 0, len = 0;\n+\n+\tif (!size)\n+\t\treturn;\n+\tif (width \u003c= 0) {\n+\t\tbuf[0] = '\\0';\n+\t\treturn;\n+\t}\n+\n+\tline = line ?: \"...\";\n+\twhile (*line \u0026\u0026 col \u003c width \u0026\u0026 len + 1 \u003c size) {\n+\t\tif (*line == '\\t') {\n+\t\t\tint next = round_up(col + 1, BPF_DIAG_TAB_WIDTH);\n+\n+\t\t\twhile (col \u003c next \u0026\u0026 col \u003c width \u0026\u0026 len + 1 \u003c size) {\n+\t\t\t\tbuf[len++] = ' ';\n+\t\t\t\tcol++;\n+\t\t\t}\n+\t\t\tline++;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tbuf[len++] = *line++;\n+\t\tcol++;\n+\t}\n+\n+\tif (*line) {\n+\t\tint ellipsis_len = min(3, width);\n+\n+\t\twhile (len \u003e 0 \u0026\u0026 col \u003e width - ellipsis_len) {\n+\t\t\tlen--;\n+\t\t\tcol--;\n+\t\t}\n+\t\twhile (ellipsis_len-- \u0026\u0026 len + 1 \u003c size)\n+\t\t\tbuf[len++] = '.';\n+\t}\n+\n+\tbuf[len] = '\\0';\n+}\n+\n+static void diag_format_source_lane(char *buf, size_t size, const char *source_prefix,\n+\t\t\t\t int source_line_width, int line_num, const char *line)\n+{\n+\tint len, text_width;\n+\n+\tif (line_num \u003c= 0) {\n+\t\tbuf[0] = '\\0';\n+\t\treturn;\n+\t}\n+\n+\tlen = scnprintf(buf, size, \"%s%*d | \", source_prefix, source_line_width, line_num);\n+\ttext_width = BPF_DIAG_SOURCE_LANE_WIDTH - len;\n+\tdiag_format_source_text(buf + len, size - len, line, text_width);\n+}\n+\n+static void bpf_diag_header(struct bpf_verifier_env *env, const char *category,\n+\t\t\t const char *problem)\n+{\n+\tchar first;\n+\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn;\n+\n+\tcategory = category ?: \"Verifier Error\";\n+\tproblem = problem ?: \"\";\n+\n+\tif (!problem[0]) {\n+\t\tdiag_write(env, \"\\nVerification failed: %s\\n\", category);\n+\t\treturn;\n+\t}\n+\n+\tfirst = toupper(problem[0]);\n+\tdiag_write(env, \"\\nVerification failed: %s: %c%s\\n\", category, first, problem + 1);\n+}\n+\n+static void diag_reason(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);\n+static void diag_suggestion(struct bpf_verifier_env *env, const char *fmt, ...)\n+\t__printf(2, 3);\n+\n+static void diag_section(struct bpf_verifier_env *env, const char *title)\n+{\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn;\n+\n+\tdiag_write(env, \"\\n%s:\\n\", title);\n+}\n+\n+static void diag_reason(struct bpf_verifier_env *env, const char *fmt, ...)\n+{\n+\tva_list args;\n+\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn;\n+\n+\tdiag_section(env, \"Reason\");\n+\n+\tva_start(args, fmt);\n+\tdiag_vprint_indented(env, fmt, args);\n+\tva_end(args);\n+}\n+\n+static void diag_suggestion(struct bpf_verifier_env *env, const char *fmt, ...)\n+{\n+\tva_list args;\n+\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn;\n+\n+\tdiag_section(env, \"Suggestion\");\n+\n+\tva_start(args, fmt);\n+\tdiag_vprint_indented(env, fmt, args);\n+\tva_end(args);\n+\tdiag_write(env, \"\\n\");\n+}\n+\n+static void diag_print_source_annotation(struct bpf_verifier_env *env, int line_width, int indent,\n+\t\t\t\t\t const char *label, const char *msg)\n+{\n+\tconst char *first_prefix, *next_prefix, *text;\n+\n+\tindent = min_t(int, indent, max_t(int, 0, BPF_DIAG_SOURCE_LANE_WIDTH - line_width - 8));\n+\ttext = bpf_diag_fmt(env, \"%s: %s\", label, msg);\n+\tfirst_prefix = bpf_diag_fmt(env, \" %*s | %*s^-- \", line_width + 4, \"\", indent, \"\");\n+\tnext_prefix = bpf_diag_fmt(env, \" %*s | %*s \", line_width + 4, \"\", indent, \"\");\n+\n+\tdiag_print_wrapped_prefixed(env, first_prefix, next_prefix, text);\n+}\n+\n+static void diag_print_insn_context(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t\t struct disasm_line *disasm_lines)\n+{\n+\tint insn_width = diag_line_width(env-\u003eprog-\u003elen ? env-\u003eprog-\u003elen - 1 : 0);\n+\tint i;\n+\n+\tfor (i = 0; i \u003c BPF_DIAG_CONTEXT_CNT; i++) {\n+\t\tint row = i - BPF_DIAG_CONTEXT;\n+\n+\t\tformat_disasm_line(env, insn_idx + row, \u0026disasm_lines[i]);\n+\t}\n+\n+\tdiag_write(env, \" Instruction context:\\n\");\n+\tfor (i = 0; i \u003c BPF_DIAG_CONTEXT_CNT; i++) {\n+\t\tstruct disasm_line *line = \u0026disasm_lines[i];\n+\n+\t\tif (line-\u003evalid)\n+\t\t\tdiag_write(env, \" %s%*d | %s\\n\",\n+\t\t\t\t line-\u003eidx == insn_idx ? \"\u003e\u003e\u003e \" : \" \",\n+\t\t\t\t insn_width, line-\u003eidx, line-\u003etext);\n+\t}\n+}\n+\n+static void bpf_diag_source(struct bpf_verifier_env *env, u32 insn_idx, const char *label,\n+\t\t\t const char *fmt, ...)\n+{\n+\tstruct bpf_diag_scratch *scratch;\n+\tstruct bpf_linfo_source *source_lines;\n+\tstruct disasm_line *disasm_lines;\n+\tstruct bpf_linfo_source src = {};\n+\tstruct diag_fmt_mark mark;\n+\tconst struct bpf_line_info *linfo;\n+\tconst struct bpf_subprog_info *subprog;\n+\tstruct btf *btf = env-\u003eprog-\u003eaux-\u003ebtf;\n+\tchar *source_lane;\n+\tconst char *msg;\n+\tconst char *func;\n+\tint start_line, end_line, width, indent, subprogno, linfo_start, linfo_end, i;\n+\tva_list args;\n+\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn;\n+\tif (!env-\u003ediag)\n+\t\treturn;\n+\n+\tmark = diag_fmt_save(env);\n+\tlabel = label ?: \"note\";\n+\tscratch = \u0026env-\u003ediag-\u003escratch;\n+\tsource_lines = scratch-\u003esource_lines;\n+\tdisasm_lines = scratch-\u003edisasm_lines;\n+\tmemset(source_lines, 0, sizeof(scratch-\u003esource_lines));\n+\tmemset(disasm_lines, 0, sizeof(scratch-\u003edisasm_lines));\n+\n+\tva_start(args, fmt);\n+\tmsg = bpf_diag_vfmt(env, fmt, args);\n+\tva_end(args);\n+\tif (!*msg)\n+\t\tmsg = \"\u003cfailed to allocate diagnostic text\u003e\";\n+\n+\tlinfo = bpf_find_linfo(env-\u003eprog, insn_idx);\n+\tif (btf \u0026\u0026 linfo)\n+\t\tbpf_get_linfo_source(btf, linfo, \u0026src);\n+\tif (!src.file || !*src.file || !src.line || !*src.line) {\n+\t\tdiag_write(env, \" insn %u\\n\", insn_idx);\n+\t\tdiag_print_source_annotation(env, 0, 0, label, msg);\n+\t\tdiag_print_insn_context(env, insn_idx, disasm_lines);\n+\t\tgoto out_restore;\n+\t}\n+\n+\tsubprog = bpf_find_containing_subprog(env, insn_idx);\n+\tsubprogno = subprog ? subprog - env-\u003esubprog_info : -ENOENT;\n+\tfunc = subprogno \u003e= 0 ? bpf_subprog_name(env, subprogno) : NULL;\n+\tif (func \u0026\u0026 *func)\n+\t\tdiag_write(env, \" %s @ %s:%d:%d\\n\", func, src.file, src.line_num, src.line_col);\n+\telse\n+\t\tdiag_write(env, \" %s:%d:%d\\n\", src.file, src.line_num, src.line_col);\n+\n+\tstart_line = src.line_num - BPF_DIAG_CONTEXT;\n+\tend_line = src.line_num + BPF_DIAG_CONTEXT;\n+\twidth = diag_line_width(end_line);\n+\tindent = diag_line_indent(src.line);\n+\tfor (i = 0; i \u003c BPF_DIAG_CONTEXT_CNT; i++)\n+\t\tsource_lines[i].line_num = start_line + i;\n+\n+\tlinfo = env-\u003eprog-\u003eaux-\u003elinfo;\n+\tlinfo_start = subprog ? subprog-\u003elinfo_idx : 0;\n+\tlinfo_end = subprogno \u003e= 0 \u0026\u0026 subprogno + 1 \u003c env-\u003esubprog_cnt ?\n+\t\t env-\u003esubprog_info[subprogno + 1].linfo_idx : env-\u003eprog-\u003eaux-\u003enr_linfo;\n+\tfor (i = linfo_start; i \u003c linfo_end; i++) {\n+\t\tstruct bpf_linfo_source line_src;\n+\t\tint idx;\n+\n+\t\tbpf_get_linfo_source(btf, \u0026linfo[i], \u0026line_src);\n+\t\tif (line_src.file_name_off != src.file_name_off ||\n+\t\t line_src.line_num \u003c start_line || line_src.line_num \u003e end_line ||\n+\t\t !line_src.line || !*line_src.line)\n+\t\t\tcontinue;\n+\n+\t\tidx = line_src.line_num - start_line;\n+\t\tif (!source_lines[idx].line)\n+\t\t\tsource_lines[idx] = line_src;\n+\t}\n+\n+\tdiag_write(env, \" Source context:\\n\");\n+\tsource_lane = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);\n+\tif (!source_lane)\n+\t\tgoto out_restore;\n+\tfor (i = 0; i \u003c BPF_DIAG_CONTEXT_CNT; i++) {\n+\t\tconst char *source_prefix;\n+\n+\t\tsource_prefix = source_lines[i].line_num == src.line_num ? \"\u003e\u003e\u003e \" : \" \";\n+\t\tdiag_format_source_lane(source_lane, BPF_DIAG_FMT_BUF_SIZE, source_prefix, width,\n+\t\t\t\t\tsource_lines[i].line_num, source_lines[i].line);\n+\t\tdiag_write(env, \" %s\\n\", source_lane);\n+\t\tif (source_lines[i].line_num == src.line_num)\n+\t\t\tdiag_print_source_annotation(env, width, indent, label, msg);\n+\t}\n+\tdiag_print_insn_context(env, insn_idx, disasm_lines);\n+\n+out_restore:\n+\tdiag_fmt_restore(env, mark);\n+}\n+\n+static const struct bpf_func_state *diag_current_frame(const struct bpf_verifier_env *env)\n+{\n+\treturn env-\u003ecur_state-\u003eframe[env-\u003ecur_state-\u003ecurframe];\n+}\n+\n+void bpf_diag_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,\n+\t\t\t const char *problem, const char *reason, const char *suggestion)\n+{\n+\tconst struct bpf_func_state *frame = diag_current_frame(env);\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_REG,\n+\t\t.frame_id = frame-\u003ediag_frame_id,\n+\t\t.frameno = frame-\u003eframeno,\n+\t\t.regno = regno,\n+\t};\n+\n+\tbpf_diag_header(env, REGISTER_TYPE_SAFETY, problem);\n+\tdiag_reason(env, \"%s\", reason);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s\", problem);\n+\n+\tif (regno \u003e= 0)\n+\t\tdiag_print_history(env, \u0026opts);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type)\n+{\n+\tswitch (base_type(type)) {\n+\tcase NOT_INIT:\n+\t\treturn \"an uninitialized value\";\n+\tcase SCALAR_VALUE:\n+\t\treturn \"an integer scalar\";\n+\tcase PTR_TO_CTX:\n+\t\treturn \"a context pointer\";\n+\tcase PTR_TO_STACK:\n+\t\treturn \"a stack pointer\";\n+\tcase PTR_TO_MAP_VALUE:\n+\t\tif (type_may_be_null(type))\n+\t\t\treturn \"a nullable map value pointer\";\n+\t\treturn \"a map value pointer\";\n+\tcase PTR_TO_MEM:\n+\t\tif (type_may_be_null(type))\n+\t\t\treturn \"a nullable memory pointer\";\n+\t\treturn \"a memory pointer\";\n+\tcase PTR_TO_BTF_ID:\n+\t\tif (type_may_be_null(type))\n+\t\t\treturn \"a nullable kernel object pointer\";\n+\t\tif (type_is_non_owning_ref(type))\n+\t\t\treturn \"a borrowed allocated object pointer\";\n+\t\tif (type_is_ptr_alloc_obj(type))\n+\t\t\treturn \"an owned allocated object pointer\";\n+\t\tif (type_flag(type) \u0026 PTR_UNTRUSTED)\n+\t\t\treturn \"an untrusted kernel object pointer\";\n+\t\treturn \"a kernel object pointer\";\n+\tdefault:\n+\t\treturn reg_type_str(env, type);\n+\t}\n+}\n+\n+static const char *diag_arg_ordinal(int argno)\n+{\n+\tswitch (argno) {\n+\tcase 1:\n+\t\treturn \"first\";\n+\tcase 2:\n+\t\treturn \"second\";\n+\tcase 3:\n+\t\treturn \"third\";\n+\tcase 4:\n+\t\treturn \"fourth\";\n+\tcase 5:\n+\t\treturn \"fifth\";\n+\tcase 6:\n+\t\treturn \"sixth\";\n+\tcase 7:\n+\t\treturn \"seventh\";\n+\tcase 8:\n+\t\treturn \"eighth\";\n+\tcase 9:\n+\t\treturn \"ninth\";\n+\tcase 10:\n+\t\treturn \"tenth\";\n+\tcase 11:\n+\t\treturn \"eleventh\";\n+\tcase 12:\n+\t\treturn \"twelfth\";\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+void bpf_diag_call_type(struct bpf_verifier_env *env, u32 insn_idx, int argno, int regno,\n+\t\t\tint stack_arg_slot, const char *call_name, const char *arg_name,\n+\t\t\tconst char *reason, const char *suggestion)\n+{\n+\tconst struct bpf_func_state *frame = diag_current_frame(env);\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.frame_id = frame-\u003ediag_frame_id,\n+\t\t.frameno = frame-\u003eframeno,\n+\t};\n+\tconst char *ordinal = diag_arg_ordinal(argno);\n+\tconst char *arg_desc;\n+\tbool print_history = true;\n+\n+\tif (regno \u003e= 0) {\n+\t\topts.scope = BPF_DIAG_HISTORY_SCOPE_REG;\n+\t\topts.regno = regno;\n+\t} else if (stack_arg_slot \u003e= 0) {\n+\t\topts.scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG;\n+\t\topts.stack_arg_slot = stack_arg_slot;\n+\t} else {\n+\t\tprint_history = false;\n+\t}\n+\n+\tif (ordinal \u0026\u0026 arg_name)\n+\t\targ_desc = bpf_diag_fmt(env, \"%s argument (%s)\", ordinal, arg_name);\n+\telse if (ordinal)\n+\t\targ_desc = bpf_diag_fmt(env, \"%s argument\", ordinal);\n+\telse if (arg_name)\n+\t\targ_desc = bpf_diag_fmt(env, \"argument %s\", arg_name);\n+\telse\n+\t\targ_desc = \"argument\";\n+\n+\tbpf_diag_header(env, CALL_TYPE_SAFETY, \"invalid call argument\");\n+\tdiag_reason(env, \"The %s to %s does not satisfy the verifier contract: %s.\",\n+\t\t arg_desc, call_name, reason);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"invalid %s for %s\", arg_desc, call_name);\n+\n+\tif (print_history)\n+\t\tdiag_print_history(env, \u0026opts);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+static const char *diag_context_constraint(enum bpf_diag_context_kind kind)\n+{\n+\tswitch (kind) {\n+\tcase BPF_DIAG_CONTEXT_RCU:\n+\t\treturn \"RCU read-side critical sections cannot call operations that may sleep\";\n+\tcase BPF_DIAG_CONTEXT_PREEMPT:\n+\t\treturn \"preemption-disabled code cannot call operations that may sleep\";\n+\tcase BPF_DIAG_CONTEXT_IRQ:\n+\t\treturn \"IRQ-disabled code cannot call operations that may sleep\";\n+\tcase BPF_DIAG_CONTEXT_LOCK:\n+\t\treturn \"code holding a BPF spin lock cannot call operations that may sleep\";\n+\tcase BPF_DIAG_CONTEXT_NONE:\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+static const char *diag_active_context(struct bpf_verifier_env *env, u32 depth,\n+\t\t\t\t const char *context)\n+{\n+\tif (depth == 1)\n+\t\treturn bpf_diag_fmt(env, \"an active %s (depth 1)\", context);\n+\treturn bpf_diag_fmt(env, \"%u active %ss (depth %u)\", depth, context, depth);\n+}\n+\n+static u32 diag_context_depth(struct bpf_verifier_env *env, enum bpf_diag_context_kind kind)\n+{\n+\tswitch (kind) {\n+\tcase BPF_DIAG_CONTEXT_RCU:\n+\t\treturn env-\u003ecur_state-\u003eactive_rcu_locks;\n+\tcase BPF_DIAG_CONTEXT_PREEMPT:\n+\t\treturn env-\u003ecur_state-\u003eactive_preempt_locks;\n+\tcase BPF_DIAG_CONTEXT_IRQ:\n+\t\treturn bpf_diag_irq_depth(env-\u003ecur_state);\n+\tcase BPF_DIAG_CONTEXT_LOCK:\n+\t\treturn env-\u003ecur_state-\u003eactive_locks;\n+\tcase BPF_DIAG_CONTEXT_NONE:\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+}\n+\n+void bpf_diag_ctx_forbidden(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t const char *operation, const char *suggestion)\n+{\n+\tstruct bpf_diag_history_opts opts;\n+\tenum bpf_diag_context_kind ctx_kind;\n+\tconst char *constraint, *context;\n+\tu32 depth;\n+\n+\tif (env-\u003ecur_state-\u003eactive_rcu_locks)\n+\t\tctx_kind = BPF_DIAG_CONTEXT_RCU;\n+\telse if (env-\u003ecur_state-\u003eactive_preempt_locks)\n+\t\tctx_kind = BPF_DIAG_CONTEXT_PREEMPT;\n+\telse if (env-\u003ecur_state-\u003eactive_irq_id)\n+\t\tctx_kind = BPF_DIAG_CONTEXT_IRQ;\n+\telse if (env-\u003ecur_state-\u003eactive_locks)\n+\t\tctx_kind = BPF_DIAG_CONTEXT_LOCK;\n+\telse\n+\t\tctx_kind = BPF_DIAG_CONTEXT_NONE;\n+\n+\tdepth = diag_context_depth(env, ctx_kind);\n+\topts = (struct bpf_diag_history_opts) {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,\n+\t\t.ctx_kind = ctx_kind,\n+\t\t.ctx_depth = depth,\n+\t};\n+\tconstraint = diag_context_constraint(ctx_kind);\n+\tcontext = diag_context_name(ctx_kind);\n+\n+\tbpf_diag_header(env, EXECUTION_CONTEXT_SAFETY,\n+\t\t\t\"operation is not allowed in this context\");\n+\tif (constraint) {\n+\t\tif (depth) {\n+\t\t\tdiag_reason(\n+\t\t\t\tenv, \"The operation %s cannot be used in %s because %s. This path is still inside %s.\",\n+\t\t\t\toperation, context, constraint, diag_active_context(env, depth, context));\n+\t\t} else {\n+\t\t\tdiag_reason(env, \"The operation %s cannot be used in %s because %s.\",\n+\t\t\t\t operation, context, constraint);\n+\t\t}\n+\t} else {\n+\t\tdiag_reason(env, \"The operation %s cannot be used in %s.\", operation,\n+\t\t\t context);\n+\t}\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s is not allowed in %s\", operation,\n+\t\t\tcontext);\n+\n+\tif (ctx_kind != BPF_DIAG_CONTEXT_NONE)\n+\t\tdiag_print_history(env, \u0026opts);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_ctx_active(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,\n+\t\t\t enum bpf_diag_context_kind ctx_kind, const char *suggestion)\n+{\n+\tu32 depth = diag_context_depth(env, ctx_kind);\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,\n+\t\t.ctx_kind = ctx_kind,\n+\t\t.ctx_depth = depth,\n+\t};\n+\tconst char *context = diag_context_name(ctx_kind);\n+\n+\tbpf_diag_header(env, EXECUTION_CONTEXT_SAFETY,\n+\t\t\t\"operation is not allowed in this context\");\n+\tdiag_reason(\n+\t\tenv, \"The operation %s cannot be used while this path is still inside %s. Leave the region before this operation.\",\n+\t\toperation, diag_active_context(env, depth, context));\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s is not allowed before leaving %s\",\n+\t\t\toperation, context);\n+\n+\tdiag_print_history(env, \u0026opts);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_ctx_required(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,\n+\t\t\t enum bpf_diag_context_kind ctx_kind, const char *suggestion)\n+{\n+\tconst char *context = diag_context_name(ctx_kind);\n+\n+\tbpf_diag_header(env, EXECUTION_CONTEXT_SAFETY, \"required context is not active\");\n+\tdiag_reason(env, \"The operation %s requires an active %s, but this path is outside one.\",\n+\t\t operation, context);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s requires %s\", operation, context);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_ctx_underflow(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t const char *operation, enum bpf_diag_context_kind ctx_kind,\n+\t\t\t const char *suggestion)\n+{\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,\n+\t\t.ctx_kind = ctx_kind,\n+\t};\n+\tconst char *context = diag_context_name(ctx_kind);\n+\n+\tbpf_diag_header(env, EXECUTION_CONTEXT_SAFETY, \"unmatched context exit\");\n+\tdiag_reason(\n+\t\tenv, \"The operation %s tries to leave %s, but this path has no active %s to leave. The current depth is 0.\",\n+\t\toperation, context, context);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s has no matching enter on this path\",\n+\t\t\toperation);\n+\n+\tdiag_print_history(env, \u0026opts);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_program_structure(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t\tconst char *problem, const char *suggestion,\n+\t\t\t\tconst char *reason_fmt, ...)\n+{\n+\tva_list args;\n+\n+\tbpf_diag_header(env, PROGRAM_STRUCTURE, problem);\n+\tdiag_section(env, \"Reason\");\n+\n+\tva_start(args, reason_fmt);\n+\tdiag_vprint_indented(env, reason_fmt, args);\n+\tva_end(args);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s\", problem);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_policy(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,\n+\t\t const char *reason, const char *suggestion)\n+{\n+\tbpf_diag_header(env, POLICY, \"operation is not allowed\");\n+\tdiag_reason(env, \"The %s is not allowed: %s.\", operation, reason);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"policy check failed for %s\", operation);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,\n+\t\t\t const char *reg_name, const struct bpf_reg_state *reg,\n+\t\t\t enum bpf_diag_invalid_deref_kind kind, s64 offset)\n+{\n+\tconst struct bpf_func_state *frame = diag_current_frame(env);\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_REG,\n+\t\t.frame_id = frame-\u003ediag_frame_id,\n+\t\t.frameno = frame-\u003eframeno,\n+\t\t.regno = regno,\n+\t};\n+\tconst char *type_name = bpf_diag_reg_type_plain(env, reg-\u003etype);\n+\n+\tbpf_diag_header(env, REGISTER_TYPE_SAFETY, \"invalid dereference\");\n+\n+\tswitch (kind) {\n+\tcase BPF_DIAG_DEREF_SCALAR:\n+\t\tdiag_reason(env, \"%s is an integer scalar here, not a pointer to memory.\",\n+\t\t\t reg_name);\n+\t\tbreak;\n+\tcase BPF_DIAG_DEREF_NULLABLE_PTR:\n+\t\tdiag_reason(\n+\t\t\tenv, \"%s may be NULL here (%s). The program could dereference NULL on this path, so the verifier cannot prove this access is safe.\",\n+\t\t\treg_name, type_name);\n+\t\tbreak;\n+\tcase BPF_DIAG_DEREF_MODIFIED_PTR:\n+\t\tdiag_reason(\n+\t\t\tenv, \"%s has offset %lld here, but this pointer type must be dereferenced in its original form.\",\n+\t\t\treg_name, offset);\n+\t\tbreak;\n+\tcase BPF_DIAG_DEREF_INVALID_PTR:\n+\tdefault:\n+\t\tdiag_reason(\n+\t\t\tenv, \"%s has type %s here, which is not valid for this memory access.\",\n+\t\t\treg_name, type_name);\n+\t\tbreak;\n+\t}\n+\n+\tdiag_section(env, \"At\");\n+\tif (kind == BPF_DIAG_DEREF_MODIFIED_PTR)\n+\t\tbpf_diag_source(env, insn_idx, \"error\",\n+\t\t\t\t\"dereference requires the original %s pointer\", type_name);\n+\telse\n+\t\tbpf_diag_source(env, insn_idx, \"error\", \"invalid dereference of %s (%s)\",\n+\t\t\t\treg_name, type_name);\n+\n+\tif (regno \u003e= 0)\n+\t\tdiag_print_history(env, \u0026opts);\n+\n+\tswitch (kind) {\n+\tcase BPF_DIAG_DEREF_NULLABLE_PTR:\n+\t\tdiag_suggestion(\n+\t\t\tenv, \"Add a NULL check before the access and dereference the pointer only on the non-NULL path.\");\n+\t\tbreak;\n+\tcase BPF_DIAG_DEREF_MODIFIED_PTR:\n+\t\tdiag_suggestion(\n+\t\t\tenv, \"Preserve the original pointer in another register, or use only offsets this pointer type permits before dereferencing it.\");\n+\t\tbreak;\n+\tcase BPF_DIAG_DEREF_SCALAR:\n+\tcase BPF_DIAG_DEREF_INVALID_PTR:\n+\tdefault:\n+\t\tdiag_suggestion(\n+\t\t\tenv, \"Preserve a pointer-valued register where needed, or reload and revalidate the pointer after scalar arithmetic, helper calls, or other operations that can invalidate it.\");\n+\t\tbreak;\n+\t}\n+}\n+\n+void bpf_diag_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno)\n+{\n+\tconst struct bpf_func_state *frame = diag_current_frame(env);\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_REG,\n+\t\t.frame_id = frame-\u003ediag_frame_id,\n+\t\t.frameno = frame-\u003eframeno,\n+\t\t.regno = regno,\n+\t};\n+\tconst struct bpf_diag_log *log = env-\u003ediag ? \u0026env-\u003ediag-\u003elog : NULL;\n+\tstruct bpf_diag_mod_target target;\n+\tbool invalidated = false;\n+\tint i;\n+\n+\ttarget = diag_reg_target(opts.frame_id, opts.frameno, regno);\n+\tfor (i = log ? log-\u003ecnt : 0; i \u003e 0; i--) {\n+\t\tconst struct bpf_diag_history_event *event;\n+\n+\t\tevent = \u0026log-\u003eevents[log_pos(log, i - 1)];\n+\n+\t\tif (event-\u003ekind != BPF_DIAG_HISTORY_MOD ||\n+\t\t !diag_target_matches(\u0026event-\u003emod.target, \u0026target))\n+\t\t\tcontinue;\n+\t\tinvalidated = event-\u003emod.new.type == NOT_INIT;\n+\t\tbreak;\n+\t}\n+\n+\tbpf_diag_header(env, REGISTER_TYPE_SAFETY, \"unreadable register\");\n+\tif (invalidated)\n+\t\tdiag_reason(\n+\t\t\tenv, \"R%d is not readable here. A previous operation invalidated this register, so the verifier cannot use it as an input.\",\n+\t\t\tregno);\n+\telse if (log \u0026\u0026 !log-\u003efirst_seq)\n+\t\tdiag_reason(env,\n+\t\t\t \"R%d has never been initialized on this path, so the verifier cannot use it as an input.\",\n+\t\t\t regno);\n+\telse\n+\t\tdiag_reason(\n+\t\t\tenv, \"R%d is not readable here. It may never have been initialized, or an earlier operation may have invalidated it.\",\n+\t\t\tregno);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"R%d is not readable\", regno);\n+\n+\tif (regno \u003e= 0)\n+\t\tdiag_print_history(env, \u0026opts);\n+\n+\tif (invalidated)\n+\t\tdiag_suggestion(\n+\t\t\tenv, \"Avoid using the register after it is invalidated, or initialize it again before this instruction.\");\n+\telse if (log \u0026\u0026 !log-\u003efirst_seq)\n+\t\tdiag_suggestion(env, \"Initialize R%d on every path before this instruction.\", regno);\n+\telse\n+\t\tdiag_suggestion(\n+\t\t\tenv, \"Initialize the register on every path, or initialize it again after any operation that invalidates it.\");\n+}\n+\n+static int diag_stack_argno(u8 slot)\n+{\n+\treturn MAX_BPF_FUNC_REG_ARGS + slot + 1;\n+}\n+\n+static void diag_format_stack_arg(char *buf, size_t size, u8 slot, const char *arg_name)\n+{\n+\tint argno = diag_stack_argno(slot);\n+\tconst char *ordinal = diag_arg_ordinal(argno);\n+\n+\tif (ordinal \u0026\u0026 arg_name)\n+\t\tscnprintf(buf, size, \"outgoing stack argument %u (%s argument, %s)\", slot + 1,\n+\t\t\t ordinal, arg_name);\n+\telse if (ordinal)\n+\t\tscnprintf(buf, size, \"outgoing stack argument %u (%s argument)\", slot + 1, ordinal);\n+\telse if (arg_name)\n+\t\tscnprintf(buf, size, \"outgoing stack argument %u (%s)\", slot + 1, arg_name);\n+\telse\n+\t\tscnprintf(buf, size, \"outgoing stack argument %u\", slot + 1);\n+}\n+\n+void bpf_diag_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,\n+\t\t\t int stack_arg_slot, const char *callee_name,\n+\t\t\t const char *arg_name)\n+{\n+\tconst struct bpf_func_state *frame = diag_current_frame(env);\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG,\n+\t\t.frame_id = frame-\u003ediag_frame_id,\n+\t\t.frameno = frame-\u003eframeno,\n+\t\t.stack_arg_slot = stack_arg_slot,\n+\t};\n+\tconst char *arg_buf;\n+\n+\targ_buf = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);\n+\tif (arg_buf)\n+\t\tdiag_format_stack_arg((char *)arg_buf, BPF_DIAG_FMT_BUF_SIZE, stack_arg_slot,\n+\t\t\t\t arg_name);\n+\telse\n+\t\targ_buf = \"\";\n+\tbpf_diag_header(env, REGISTER_TYPE_SAFETY, \"missing stack argument\");\n+\tif (callee_name \u0026\u0026 *callee_name)\n+\t\tdiag_reason(\n+\t\t\tenv, \"Function %s expects %d arguments, but %s is not initialized at this call.\",\n+\t\t\tcallee_name, nargs, arg_buf);\n+\telse\n+\t\tdiag_reason(\n+\t\t\tenv, \"The callee expects %d arguments, but %s is not initialized at this call.\",\n+\t\t\tnargs, arg_buf);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s is not initialized\", arg_buf);\n+\n+\tif (stack_arg_slot \u003e= 0)\n+\t\tdiag_print_history(env, \u0026opts);\n+\n+\tdiag_suggestion(\n+\t\tenv, \"Write the outgoing stack argument after any operation that may invalidate stored pointer values, and before making this call.\");\n+}\n+\n+void bpf_diag_memory(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,\n+\t\t const char *reason, const char *suggestion)\n+{\n+\tbpf_diag_header(env, MEMORY_SAFETY, problem);\n+\tdiag_reason(env, \"%s\", reason);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s\", problem);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true)\n+{\n+\tstruct bpf_diag_history_event event = {\n+\t\t.insn_idx = insn_idx,\n+\t\t.kind = BPF_DIAG_HISTORY_BRANCH,\n+\t\t.branch = {\n+\t\t\t.cond_true = cond_true,\n+\t\t},\n+\t};\n+\n+\tdiag_append_history(env, \u0026event);\n+}\n+\n+static void diag_snapshot_reg(struct bpf_diag_reg_snapshot *snapshot,\n+\t\t\t const struct bpf_reg_state *reg)\n+{\n+\tsnapshot-\u003etype = reg-\u003etype;\n+\tif (type_is_map_ptr(reg-\u003etype))\n+\t\tsnapshot-\u003emap_ptr = reg-\u003emap_ptr;\n+\tif (base_type(reg-\u003etype) == PTR_TO_BTF_ID \u0026\u0026 reg-\u003ebtf \u0026\u0026 reg-\u003ebtf_id) {\n+\t\tsnapshot-\u003ebtf_id = reg-\u003ebtf_id;\n+\t\tsnapshot-\u003ebtf = reg-\u003ebtf;\n+\t}\n+\tsnapshot-\u003evar_off = reg-\u003evar_off;\n+\tsnapshot-\u003er64 = reg-\u003er64;\n+}\n+\n+static bool diag_mod_insn_origin(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t\t const struct bpf_diag_mod_target *target,\n+\t\t\t\t struct bpf_diag_mod_target *origin)\n+{\n+\tconst struct bpf_insn *insn = \u0026env-\u003eprog-\u003einsnsi[insn_idx];\n+\tu8 class = BPF_CLASS(insn-\u003ecode);\n+\tconst struct bpf_func_state *state;\n+\n+\tif (target-\u003ekind == BPF_DIAG_MOD_TARGET_REG \u0026\u0026 (class == BPF_ALU || class == BPF_ALU64) \u0026\u0026\n+\t BPF_OP(insn-\u003ecode) == BPF_MOV \u0026\u0026 BPF_SRC(insn-\u003ecode) == BPF_X) {\n+\t\t*origin = diag_reg_target(target-\u003eframe_id, target-\u003eframeno, insn-\u003esrc_reg);\n+\t\treturn true;\n+\t}\n+\n+\tif ((target-\u003ekind != BPF_DIAG_MOD_TARGET_STACK_ARG \u0026\u0026\n+\t target-\u003ekind != BPF_DIAG_MOD_TARGET_STACK_SLOT) ||\n+\t class != BPF_STX)\n+\t\treturn false;\n+\n+\tstate = env-\u003ecur_state-\u003eframe[env-\u003ecur_state-\u003ecurframe];\n+\t*origin = diag_reg_target(state-\u003ediag_frame_id, state-\u003eframeno, insn-\u003esrc_reg);\n+\treturn true;\n+}\n+\n+static bool diag_mod_keeps_lineage(struct bpf_verifier_env *env,\n+\t\t\t\t const struct bpf_diag_history_event *event)\n+{\n+\tconst struct bpf_insn *insn;\n+\tu8 class;\n+\n+\tif (event-\u003emod.reason != BPF_DIAG_MOD_WRITE ||\n+\t event-\u003emod.target.kind != BPF_DIAG_MOD_TARGET_REG)\n+\t\treturn false;\n+\n+\tinsn = \u0026env-\u003eprog-\u003einsnsi[event-\u003einsn_idx];\n+\tclass = BPF_CLASS(insn-\u003ecode);\n+\tif (class != BPF_ALU \u0026\u0026 class != BPF_ALU64)\n+\t\treturn false;\n+\n+\tswitch (BPF_OP(insn-\u003ecode)) {\n+\tcase BPF_ADD:\n+\tcase BPF_SUB:\n+\tcase BPF_MUL:\n+\tcase BPF_OR:\n+\tcase BPF_AND:\n+\tcase BPF_LSH:\n+\tcase BPF_RSH:\n+\tcase BPF_ARSH:\n+\tcase BPF_XOR:\n+\tcase BPF_NEG:\n+\tcase BPF_END:\n+\t\treturn true;\n+\tdefault:\n+\t\treturn false;\n+\t}\n+}\n+\n+static void diag_record_mod(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t struct bpf_diag_mod_target target,\n+\t\t\t enum bpf_diag_mod_reason reason,\n+\t\t\t const struct bpf_reg_state *old_reg,\n+\t\t\t const struct bpf_reg_state *new_reg,\n+\t\t\t const struct bpf_diag_mod_target *origin)\n+{\n+\tstruct bpf_diag_history_event event = {\n+\t\t.insn_idx = insn_idx,\n+\t\t.kind = BPF_DIAG_HISTORY_MOD,\n+\t\t.mod = {\n+\t\t\t.target = target,\n+\t\t\t.reason = reason,\n+\t\t},\n+\t};\n+\n+\tif (old_reg)\n+\t\tdiag_snapshot_reg(\u0026event.mod.old, old_reg);\n+\tif (new_reg)\n+\t\tdiag_snapshot_reg(\u0026event.mod.new, new_reg);\n+\tif (origin) {\n+\t\tevent.mod.origin = *origin;\n+\t\tevent.mod.origin_valid = true;\n+\t} else if (diag_mod_insn_origin(env, insn_idx, \u0026target, \u0026event.mod.origin)) {\n+\t\tevent.mod.origin_valid = true;\n+\t}\n+\tif (old_reg \u0026\u0026 new_reg \u0026\u0026\n+\t (reason == BPF_DIAG_MOD_WRITE || reason == BPF_DIAG_MOD_SPILL) \u0026\u0026\n+\t !memcmp(\u0026event.mod.old, \u0026event.mod.new, sizeof(event.mod.old)) \u0026\u0026\n+\t !event.mod.origin_valid \u0026\u0026\n+\t diag_mod_keeps_lineage(env, \u0026event))\n+\t\treturn;\n+\n+\tdiag_append_history(env, \u0026event);\n+}\n+\n+static struct bpf_reg_state *target_to_reg(struct bpf_verifier_env *env,\n+\t\t\t\t\t const struct bpf_diag_mod_target *target)\n+{\n+\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\n+\tstruct bpf_func_state *state;\n+\n+\tstate = target-\u003eframeno \u003c= vstate-\u003ecurframe ? vstate-\u003eframe[target-\u003eframeno] : NULL;\n+\n+\tif (!state)\n+\t\treturn NULL;\n+\tif (state-\u003ediag_frame_id != target-\u003eframe_id)\n+\t\treturn NULL;\n+\n+\tswitch (target-\u003ekind) {\n+\tcase BPF_DIAG_MOD_TARGET_REG:\n+\t\tif (target-\u003eregno \u003e= MAX_BPF_REG)\n+\t\t\treturn NULL;\n+\t\treturn \u0026state-\u003eregs[target-\u003eregno];\n+\tcase BPF_DIAG_MOD_TARGET_STACK_ARG:\n+\t\tif (target-\u003estack_arg \u003e= state-\u003eout_stack_arg_cnt)\n+\t\t\treturn NULL;\n+\t\treturn \u0026state-\u003estack_arg_regs[target-\u003estack_arg];\n+\tcase BPF_DIAG_MOD_TARGET_STACK_SLOT:\n+\t\tif (target-\u003espi \u003e= state-\u003eallocated_stack / BPF_REG_SIZE)\n+\t\t\treturn NULL;\n+\t\treturn \u0026state-\u003estack[target-\u003espi].spilled_ptr;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+static bool reg_to_target(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,\n+\t\t\t struct bpf_diag_mod_target *target)\n+{\n+\tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\n+\tunsigned long addr = (unsigned long)reg;\n+\tint frame;\n+\n+\tfor (frame = 0; frame \u003c= vstate-\u003ecurframe; frame++) {\n+\t\tstruct bpf_func_state *state = vstate-\u003eframe[frame];\n+\t\tunsigned long start, end;\n+\t\tu32 nslots = state-\u003eallocated_stack / BPF_REG_SIZE;\n+\t\tint spi;\n+\n+\t\tstart = (unsigned long)state-\u003eregs;\n+\t\tend = (unsigned long)(state-\u003eregs + MAX_BPF_REG);\n+\t\tif (addr \u003e= start \u0026\u0026 addr \u003c end) {\n+\t\t\t*target = diag_reg_target(state-\u003ediag_frame_id, state-\u003eframeno,\n+\t\t\t\t\t\t reg - state-\u003eregs);\n+\t\t\treturn true;\n+\t\t}\n+\n+\t\tstart = (unsigned long)state-\u003estack_arg_regs;\n+\t\tend = (unsigned long)(state-\u003estack_arg_regs + state-\u003eout_stack_arg_cnt);\n+\t\tif (state-\u003eout_stack_arg_cnt \u0026\u0026 addr \u003e= start \u0026\u0026 addr \u003c end) {\n+\t\t\t*target = diag_stack_arg_target(state-\u003ediag_frame_id, state-\u003eframeno,\n+\t\t\t\t\t\t\treg - state-\u003estack_arg_regs);\n+\t\t\treturn true;\n+\t\t}\n+\n+\t\tstart = (unsigned long)state-\u003estack;\n+\t\tend = (unsigned long)(state-\u003estack + nslots);\n+\t\tif (nslots \u0026\u0026 addr \u003e= start \u0026\u0026 addr \u003c end) {\n+\t\t\tspi = ((const char *)reg - (const char *)state-\u003estack) /\n+\t\t\t sizeof(*state-\u003estack);\n+\t\t\t*target = diag_stack_slot_target(state-\u003ediag_frame_id, state-\u003eframeno, spi);\n+\t\t\treturn true;\n+\t\t}\n+\t}\n+\treturn false;\n+}\n+\n+void bpf_diag_mod_begin(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,\n+\t\t\tconst struct bpf_reg_state *origin, enum bpf_diag_mod_reason reason)\n+{\n+\tstruct bpf_diag *diag = env-\u003ediag;\n+\n+\tif (!diag)\n+\t\treturn;\n+\tdiag-\u003emod.active = reg_to_target(env, reg, \u0026diag-\u003emod.target);\n+\tif (!diag-\u003emod.active)\n+\t\treturn;\n+\tdiag-\u003emod.target_reg_snapshot = *reg;\n+\tdiag-\u003emod.insn_idx = env-\u003einsn_idx;\n+\tdiag-\u003emod.reason = reason;\n+\tdiag-\u003emod.origin_valid = origin \u0026\u0026 reg_to_target(env, origin, \u0026diag-\u003emod.origin);\n+}\n+\n+void bpf_diag_mod_end(struct bpf_verifier_env *env)\n+{\n+\tstruct bpf_diag *diag = env-\u003ediag;\n+\tconst struct bpf_reg_state *new_reg;\n+\n+\tif (!diag || !diag-\u003emod.active)\n+\t\treturn;\n+\tdiag-\u003emod.active = false;\n+\t/*\n+\t * Resolve the target again because the enclosing function state's stack\n+\t * may have been reallocated while the modification was in progress.\n+\t */\n+\tnew_reg = target_to_reg(env, \u0026diag-\u003emod.target);\n+\tif (!new_reg)\n+\t\treturn;\n+\tdiag_record_mod(env, diag-\u003emod.insn_idx, diag-\u003emod.target, diag-\u003emod.reason,\n+\t\t\t\u0026diag-\u003emod.target_reg_snapshot, new_reg,\n+\t\t\tdiag-\u003emod.origin_valid ? \u0026diag-\u003emod.origin : NULL);\n+}\n+\n+void bpf_diag_record_scrub(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,\n+\t\t\t enum bpf_diag_mod_reason reason)\n+{\n+\tstruct bpf_diag_mod_target target;\n+\n+\tif (!env-\u003ediag || reg-\u003etype == NOT_INIT || !reg_to_target(env, reg, \u0026target))\n+\t\treturn;\n+\tdiag_record_mod(env, env-\u003einsn_idx, target, reason, reg, NULL, NULL);\n+}\n+\n+void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,\n+\t\t\t\t const struct bpf_func_state *state, s16 min_off, s16 max_off,\n+\t\t\t\t enum bpf_diag_mod_reason reason)\n+{\n+\tdiag_record_mod(env, env-\u003einsn_idx,\n+\t\t\tdiag_stack_range_target(state-\u003ediag_frame_id, state-\u003eframeno, min_off, max_off),\n+\t\t\treason, NULL, NULL, NULL);\n+}\n+\n+static void diag_record_ref(struct bpf_verifier_env *env, u32 insn_idx, u8 kind, u32 ref_id)\n+{\n+\tstruct bpf_diag_history_event event = {\n+\t\t.insn_idx = insn_idx,\n+\t\t.kind = kind,\n+\t\t.ref = {\n+\t\t\t.ref_id = ref_id,\n+\t\t},\n+\t};\n+\n+\tdiag_append_history(env, \u0026event);\n+}\n+\n+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)\n+{\n+\tdiag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_ACQUIRE, ref_id);\n+}\n+\n+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)\n+{\n+\tdiag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_RELEASE, ref_id);\n+}\n+\n+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t enum bpf_diag_context_kind ctx_kind, bool enter, u32 depth)\n+{\n+\t/*\n+\t * Keep leave events so context rendering can stop at a depth-zero exit\n+\t * and show nested-region depth accurately for the active path.\n+\t */\n+\tstruct bpf_diag_history_event event = {\n+\t\t.insn_idx = insn_idx,\n+\t\t.kind = BPF_DIAG_HISTORY_CONTEXT,\n+\t\t.ctx = {\n+\t\t\t.kind = ctx_kind,\n+\t\t\t.enter = enter,\n+\t\t\t.depth = depth,\n+\t\t},\n+\t};\n+\n+\tdiag_append_history(env, \u0026event);\n+}\n+\n+static int diag_history_context_start_idx(const struct bpf_diag_log *log,\n+\t\t\t\t\t const struct bpf_diag_history_opts *opts)\n+{\n+\tint i;\n+\n+\tif (!opts-\u003ectx_depth)\n+\t\treturn 0;\n+\n+\t/* Find the most recent outermost entry, or a depth-zero exit. */\n+\tfor (i = log-\u003ecnt; i \u003e 0; i--) {\n+\t\tconst struct bpf_diag_history_event *event;\n+\n+\t\tevent = \u0026log-\u003eevents[log_pos(log, i - 1)];\n+\n+\t\tif (event-\u003ekind != BPF_DIAG_HISTORY_CONTEXT || event-\u003ectx.kind != opts-\u003ectx_kind)\n+\t\t\tcontinue;\n+\n+\t\tif (event-\u003ectx.enter \u0026\u0026 event-\u003ectx.depth == 1)\n+\t\t\treturn i - 1;\n+\t\tif (!event-\u003ectx.enter \u0026\u0026 event-\u003ectx.depth == 0)\n+\t\t\treturn 0;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+struct bpf_diag_history_filter {\n+\tconst struct bpf_diag_history_opts *opts;\n+\tu32 lineage_start;\n+\tbool lineage_valid;\n+};\n+\n+static bool diag_target_matches(const struct bpf_diag_mod_target *event_target,\n+\t\t\t\tconst struct bpf_diag_mod_target *target)\n+{\n+\tint slot_off;\n+\n+\tif (event_target-\u003eframe_id != target-\u003eframe_id || event_target-\u003eframeno != target-\u003eframeno)\n+\t\treturn false;\n+\n+\tif (event_target-\u003ekind == BPF_DIAG_MOD_TARGET_STACK_RANGE \u0026\u0026\n+\t target-\u003ekind == BPF_DIAG_MOD_TARGET_STACK_SLOT) {\n+\t\tslot_off = -(target-\u003espi + 1) * BPF_REG_SIZE;\n+\t\treturn event_target-\u003erange.min_off \u003c slot_off + BPF_REG_SIZE \u0026\u0026\n+\t\t event_target-\u003erange.max_off \u003e slot_off;\n+\t}\n+\n+\tif (event_target-\u003ekind != target-\u003ekind)\n+\t\treturn false;\n+\n+\tswitch (target-\u003ekind) {\n+\tcase BPF_DIAG_MOD_TARGET_REG:\n+\t\treturn event_target-\u003eregno == target-\u003eregno;\n+\tcase BPF_DIAG_MOD_TARGET_STACK_ARG:\n+\t\treturn event_target-\u003estack_arg == target-\u003estack_arg;\n+\tcase BPF_DIAG_MOD_TARGET_STACK_SLOT:\n+\t\treturn event_target-\u003espi == target-\u003espi;\n+\tdefault:\n+\t\treturn false;\n+\t}\n+}\n+\n+static void diag_build_lineage(struct bpf_verifier_env *env, struct bpf_diag_log *log,\n+\t\t\t struct bpf_diag_history_filter *filter)\n+{\n+\tconst struct bpf_diag_history_opts *opts = filter-\u003eopts;\n+\tstruct bpf_diag_mod_target target;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c log-\u003ecnt; i++)\n+\t\tlog-\u003eevents[log_pos(log, i)].in_lineage = false;\n+\n+\tif (opts-\u003escope == BPF_DIAG_HISTORY_SCOPE_REG)\n+\t\ttarget = diag_reg_target(opts-\u003eframe_id, opts-\u003eframeno, opts-\u003eregno);\n+\telse if (opts-\u003escope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG)\n+\t\ttarget = diag_stack_arg_target(opts-\u003eframe_id, opts-\u003eframeno,\n+\t\t\t\t\t opts-\u003estack_arg_slot);\n+\telse\n+\t\treturn;\n+\n+\t/*\n+\t * Find the nearest mutation of the active target. A fill or spill changes\n+\t * the target to its origin, so the same walk follows register/stack\n+\t * lineage recursively until it reaches the write that created the value.\n+\t */\n+\tfor (i = log-\u003ecnt; i \u003e 0; i--) {\n+\t\tstruct bpf_diag_history_event *event;\n+\n+\t\tevent = \u0026log-\u003eevents[log_pos(log, i - 1)];\n+\t\tif (event-\u003ekind != BPF_DIAG_HISTORY_MOD ||\n+\t\t !diag_target_matches(\u0026event-\u003emod.target, \u0026target))\n+\t\t\tcontinue;\n+\n+\t\tevent-\u003ein_lineage = true;\n+\t\tfilter-\u003elineage_start = i - 1;\n+\t\tfilter-\u003elineage_valid = true;\n+\n+\t\tif (event-\u003emod.origin_valid) {\n+\t\t\ttarget = event-\u003emod.origin;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (event-\u003emod.reason != BPF_DIAG_MOD_WRITE \u0026\u0026\n+\t\t event-\u003emod.reason != BPF_DIAG_MOD_SPILL)\n+\t\t\tcontinue;\n+\t\tif (diag_mod_keeps_lineage(env, event))\n+\t\t\tcontinue;\n+\t\tbreak;\n+\t}\n+}\n+\n+static int diag_history_start_idx(const struct bpf_diag_log *log,\n+\t\t\t\t const struct bpf_diag_history_filter *filter)\n+{\n+\tconst struct bpf_diag_history_opts *opts = filter-\u003eopts;\n+\tint i;\n+\n+\tif (opts-\u003escope == BPF_DIAG_HISTORY_SCOPE_CONTEXT)\n+\t\treturn diag_history_context_start_idx(log, opts);\n+\tif (filter-\u003elineage_valid)\n+\t\treturn filter-\u003elineage_start;\n+\tif (opts-\u003escope != BPF_DIAG_HISTORY_SCOPE_REF)\n+\t\treturn 0;\n+\n+\tfor (i = log-\u003ecnt; i \u003e 0; i--) {\n+\t\tconst struct bpf_diag_history_event *event;\n+\n+\t\tevent = \u0026log-\u003eevents[log_pos(log, i - 1)];\n+\t\tif (event-\u003ekind == BPF_DIAG_HISTORY_REF_ACQUIRE \u0026\u0026\n+\t\t event-\u003eref.ref_id == opts-\u003eref_id)\n+\t\t\treturn i - 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static bool diag_history_event_visible(const struct bpf_diag_history_event *event,\n+\t\t\t\t const struct bpf_diag_history_filter *filter)\n+{\n+\tconst struct bpf_diag_history_opts *opts = filter-\u003eopts;\n+\n+\tswitch (event-\u003ekind) {\n+\tcase BPF_DIAG_HISTORY_BRANCH:\n+\t\treturn true;\n+\tcase BPF_DIAG_HISTORY_MOD:\n+\t\treturn filter-\u003elineage_valid \u0026\u0026 event-\u003ein_lineage;\n+\tcase BPF_DIAG_HISTORY_REF_ACQUIRE:\n+\tcase BPF_DIAG_HISTORY_REF_RELEASE:\n+\t\treturn opts-\u003escope == BPF_DIAG_HISTORY_SCOPE_REF \u0026\u0026\n+\t\t event-\u003eref.ref_id == opts-\u003eref_id;\n+\tcase BPF_DIAG_HISTORY_CONTEXT:\n+\t\treturn opts-\u003escope == BPF_DIAG_HISTORY_SCOPE_CONTEXT \u0026\u0026\n+\t\t event-\u003ectx.kind == opts-\u003ectx_kind;\n+\tdefault:\n+\t\treturn false;\n+\t}\n+}\n+\n+static const char *diag_s64_bound_name(s64 value)\n+{\n+\tif (value == S64_MIN)\n+\t\treturn \"S64_MIN\";\n+\tif (value == S64_MAX)\n+\t\treturn \"S64_MAX\";\n+\treturn NULL;\n+}\n+\n+static const char *diag_u64_bound_name(u64 value)\n+{\n+\tif (value == U64_MAX)\n+\t\treturn \"U64_MAX\";\n+\treturn NULL;\n+}\n+\n+static const char *diag_s64_str(struct bpf_verifier_env *env, s64 value)\n+{\n+\treturn diag_s64_bound_name(value) ?: bpf_diag_fmt(env, \"%lld\", value);\n+}\n+\n+static const char *diag_u64_str(struct bpf_verifier_env *env, u64 value)\n+{\n+\treturn diag_u64_bound_name(value) ?: bpf_diag_fmt(env, \"%llu\", value);\n+}\n+\n+static bool diag_cnum64_unknown(struct cnum64 range)\n+{\n+\treturn cnum64_smin(range) == S64_MIN \u0026\u0026 cnum64_smax(range) == S64_MAX \u0026\u0026\n+\t cnum64_umin(range) == 0 \u0026\u0026 cnum64_umax(range) == U64_MAX;\n+}\n+\n+static bool diag_snapshot_unknown(const struct bpf_diag_reg_snapshot *snapshot)\n+{\n+\treturn tnum_is_unknown(snapshot-\u003evar_off) \u0026\u0026 diag_cnum64_unknown(snapshot-\u003er64);\n+}\n+\n+static const char *diag_scalar_range(struct bpf_verifier_env *env, struct cnum64 range)\n+{\n+\treturn bpf_diag_fmt(env, \"signed range [%s, %s], unsigned range [%s, %s]\",\n+\t\t\t diag_s64_str(env, cnum64_smin(range)),\n+\t\t\t diag_s64_str(env, cnum64_smax(range)),\n+\t\t\t diag_u64_str(env, cnum64_umin(range)),\n+\t\t\t diag_u64_str(env, cnum64_umax(range)));\n+}\n+\n+const char *bpf_diag_fmt_s64_sum(struct bpf_verifier_env *env, s64 value, int addend)\n+{\n+\ts64 sum;\n+\n+\tif (check_add_overflow(value, (s64)addend, \u0026sum))\n+\t\treturn bpf_diag_fmt(env, \"%lld plus %d (%s)\", value, addend,\n+\t\t\t\t addend \u003c 0 ? \"below S64_MIN\" : \"above S64_MAX\");\n+\n+\treturn bpf_diag_fmt(env, \"%lld\", sum);\n+}\n+\n+static const char *diag_access_offset(struct bpf_verifier_env *env, int off,\n+\t\t\t\t const struct bpf_reg_state *reg)\n+{\n+\tif (tnum_is_const(reg-\u003evar_off))\n+\t\treturn bpf_diag_fmt(env, \"constant %s\",\n+\t\t\t\t bpf_diag_fmt_s64_sum(env, (s64)reg-\u003evar_off.value, off));\n+\n+\tif (tnum_is_unknown(reg-\u003evar_off) \u0026\u0026 diag_cnum64_unknown(reg-\u003er64))\n+\t\treturn bpf_diag_fmt(env, \"unbounded\");\n+\n+\tif (off)\n+\t\treturn bpf_diag_fmt(env,\n+\t\t\t\"variable: known bits %#llx, unknown mask %#llx, plus fixed offset %d; %s\",\n+\t\t\t(u64)reg-\u003evar_off.value, reg-\u003evar_off.mask, off,\n+\t\t\tdiag_scalar_range(env, reg-\u003er64));\n+\treturn bpf_diag_fmt(env, \"variable: known bits %#llx, unknown mask %#llx; %s\",\n+\t\t\t (u64)reg-\u003evar_off.value, reg-\u003evar_off.mask,\n+\t\t\t diag_scalar_range(env, reg-\u003er64));\n+}\n+\n+void bpf_diag_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx, int regno,\n+\t\t\t const char *reg_name, const char *type_name, const char *proof,\n+\t\t\t int off, int size, u32 mem_size, const struct bpf_reg_state *reg)\n+{\n+\tconst struct bpf_func_state *frame = diag_current_frame(env);\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_REG,\n+\t\t.frame_id = frame-\u003ediag_frame_id,\n+\t\t.frameno = frame-\u003eframeno,\n+\t\t.regno = regno,\n+\t};\n+\tconst char *offset_desc;\n+\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn;\n+\n+\toffset_desc = diag_access_offset(env, off, reg);\n+\n+\tbpf_diag_header(env, MEMORY_SAFETY, \"access outside bounds\");\n+\tdiag_reason(\n+\t\tenv, \"The verifier cannot prove offset + access_size \u003c= object_size. Here, %s. %s is %s; offset is %s; access_size is %d; object_size is %u.\",\n+\t\tproof, reg_name, type_name, offset_desc, size, mem_size);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"access may be outside object bounds\");\n+\n+\tif (regno \u003e= 0)\n+\t\tdiag_print_history(env, \u0026opts);\n+\n+\tdiag_suggestion(\n+\t\tenv, \"Add or adjust a bounds check that proves offset + access_size stays within the object.\");\n+}\n+\n+static const char *diag_lock_name(const struct bpf_reference_state *lock)\n+{\n+\tswitch (lock-\u003etype) {\n+\tcase REF_TYPE_LOCK:\n+\t\treturn \"bpf_spin_lock\";\n+\tcase REF_TYPE_RES_LOCK:\n+\t\treturn \"resource spin lock\";\n+\tcase REF_TYPE_RES_LOCK_IRQ:\n+\t\treturn \"IRQ-saving resource spin lock\";\n+\tdefault:\n+\t\treturn \"lock\";\n+\t}\n+}\n+\n+static void diag_res_report(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,\n+\t\t\t const char *reason)\n+{\n+\tbpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, problem);\n+\tdiag_reason(env, \"%s\", reason);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s\", problem);\n+}\n+\n+void bpf_diag_res(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,\n+\t\t const char *reason, const char *suggestion)\n+{\n+\tdiag_res_report(env, insn_idx, problem, reason);\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_lock(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,\n+\t\t const char *reason, const char *suggestion,\n+\t\t const struct bpf_reference_state *active_lock)\n+{\n+\tdiag_res_report(env, insn_idx, problem, reason);\n+\n+\tif (active_lock) {\n+\t\tdiag_section(env, \"Active lock\");\n+\t\tbpf_diag_source(env, active_lock-\u003einsn_idx, \"acquired\",\n+\t\t\t\t\"active %s has verifier identity %d\",\n+\t\t\t\tdiag_lock_name(active_lock), active_lock-\u003eid);\n+\t}\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_irq(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,\n+\t\t const char *reason, const char *suggestion, u32 depth)\n+{\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,\n+\t\t.ctx_kind = BPF_DIAG_CONTEXT_IRQ,\n+\t\t.ctx_depth = depth,\n+\t};\n+\n+\tbpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, problem);\n+\tdiag_reason(env, \"%s\", reason);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, insn_idx, \"error\", \"%s\", problem);\n+\n+\tif (depth)\n+\t\tdiag_print_history(env, \u0026opts);\n+\n+\tdiag_suggestion(env, \"%s\", suggestion);\n+}\n+\n+void bpf_diag_leak(struct bpf_verifier_env *env, u32 ref_id, u32 alloc_insn, u32 fail_insn)\n+{\n+\tstruct bpf_diag_history_opts opts = {\n+\t\t.scope = BPF_DIAG_HISTORY_SCOPE_REF,\n+\t\t.ref_id = ref_id,\n+\t};\n+\n+\tbpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, \"unreleased resource\");\n+\tdiag_reason(\n+\t\tenv, \"Owned resource (id=%u) was acquired at instruction %u and still needs to be released before this exit path.\",\n+\t\tref_id, alloc_insn);\n+\n+\tdiag_section(env, \"At\");\n+\tbpf_diag_source(env, fail_insn, \"error\",\n+\t\t\t\"owned resource (id=%u) still needs release\", ref_id);\n+\n+\tdiag_print_history(env, \u0026opts);\n+\n+\tdiag_suggestion(\n+\t\tenv, \"Release or transfer ownership of the acquired resource on every path before the program exits.\");\n+}\n+\n+static const char *diag_var_offset(struct bpf_verifier_env *env,\n+\t\t\t\t const struct bpf_diag_reg_snapshot *snapshot)\n+{\n+\tif (tnum_is_const(snapshot-\u003evar_off))\n+\t\treturn bpf_diag_fmt(env, \"at offset %lld\", (s64)snapshot-\u003evar_off.value);\n+\n+\tif (diag_snapshot_unknown(snapshot))\n+\t\treturn bpf_diag_fmt(env, \"with unknown offset\");\n+\n+\treturn bpf_diag_fmt(env,\n+\t\t\t \"with variable offset: known bits %#llx, unknown mask %#llx, %s\",\n+\t\t\t snapshot-\u003evar_off.value, snapshot-\u003evar_off.mask,\n+\t\t\t diag_scalar_range(env, snapshot-\u003er64));\n+}\n+\n+static const char *diag_reg_map_name(const struct bpf_map *map)\n+{\n+\tif (!map || !map-\u003ename[0])\n+\t\treturn NULL;\n+\n+\treturn map-\u003ename;\n+}\n+\n+static const char *diag_reg_snapshot(struct bpf_verifier_env *env,\n+\t\t\t\t const struct bpf_diag_reg_snapshot *snapshot)\n+{\n+\tconst char *type_name = reg_type_str(env, snapshot-\u003etype);\n+\tconst char *offset = diag_var_offset(env, snapshot);\n+\tconst char *btf = snapshot-\u003ebtf \u0026\u0026 snapshot-\u003ebtf_id ?\n+\t\t\t bpf_diag_fmt_btf_type(env, snapshot-\u003ebtf, snapshot-\u003ebtf_id) : NULL;\n+\tconst char *map_name;\n+\n+\tif (snapshot-\u003etype == SCALAR_VALUE) {\n+\t\tif (tnum_is_const(snapshot-\u003evar_off))\n+\t\t\treturn bpf_diag_fmt(env, \"integer scalar value %lld\",\n+\t\t\t\t\t (s64)snapshot-\u003evar_off.value);\n+\t\tif (diag_snapshot_unknown(snapshot))\n+\t\t\treturn bpf_diag_fmt(env, \"integer scalar with unknown value\");\n+\t\tif (cnum64_is_const(snapshot-\u003er64))\n+\t\t\treturn bpf_diag_fmt(env, \"integer scalar value %lld\",\n+\t\t\t\t\t cnum64_smin(snapshot-\u003er64));\n+\t\treturn bpf_diag_fmt(env, \"integer scalar with %s\",\n+\t\t\t\t diag_scalar_range(env, snapshot-\u003er64));\n+\t}\n+\n+\tif (snapshot-\u003etype == NOT_INIT)\n+\t\treturn bpf_diag_fmt(env, \"uninitialized value\");\n+\n+\tif (base_type(snapshot-\u003etype) == PTR_TO_CTX)\n+\t\treturn bpf_diag_fmt(env, \"context pointer %s\", offset);\n+\n+\tif (base_type(snapshot-\u003etype) == PTR_TO_STACK)\n+\t\treturn bpf_diag_fmt(env, \"stack pointer %s\", offset);\n+\n+\tif (base_type(snapshot-\u003etype) == PTR_TO_MAP_VALUE) {\n+\t\tconst char *kind = type_may_be_null(snapshot-\u003etype) ? \"nullable map value\" :\n+\t\t\t\t\t\t\t\t \"map value\";\n+\n+\t\tmap_name = diag_reg_map_name(snapshot-\u003emap_ptr);\n+\t\tif (map_name)\n+\t\t\treturn bpf_diag_fmt(env, \"%s from %s %s\", kind, map_name, offset);\n+\t\treturn bpf_diag_fmt(env, \"%s %s\", kind, offset);\n+\t}\n+\n+\tif (base_type(snapshot-\u003etype) == CONST_PTR_TO_MAP) {\n+\t\tmap_name = diag_reg_map_name(snapshot-\u003emap_ptr);\n+\t\tif (map_name)\n+\t\t\treturn bpf_diag_fmt(env, \"map pointer for map %s\", map_name);\n+\t\treturn bpf_diag_fmt(env, \"map pointer\");\n+\t}\n+\n+\tif (type_is_non_owning_ref(snapshot-\u003etype)) {\n+\t\tif (btf)\n+\t\t\treturn bpf_diag_fmt(env, \"borrowed allocated object pointer type=%s\", btf);\n+\t\treturn bpf_diag_fmt(env, \"borrowed allocated object pointer\");\n+\t}\n+\n+\tif (type_is_ptr_alloc_obj(snapshot-\u003etype)) {\n+\t\tif (btf)\n+\t\t\treturn bpf_diag_fmt(env, \"owned allocated object pointer type=%s\", btf);\n+\t\treturn bpf_diag_fmt(env, \"owned allocated object pointer\");\n+\t}\n+\n+\tif (base_type(snapshot-\u003etype) == PTR_TO_BTF_ID \u0026\u0026 btf)\n+\t\treturn bpf_diag_fmt(env, \"%s type=%s %s\", type_name, btf, offset);\n+\n+\treturn bpf_diag_fmt(env, \"%s %s\", type_name, offset);\n+}\n+\n+static const char *diag_mod_target_desc(struct bpf_verifier_env *env,\n+\t\t\t\t\tconst struct bpf_diag_mod_target *target)\n+{\n+\tswitch (target-\u003ekind) {\n+\tcase BPF_DIAG_MOD_TARGET_REG:\n+\t\treturn bpf_diag_fmt(env, \"R%u\", target-\u003eregno);\n+\tcase BPF_DIAG_MOD_TARGET_STACK_ARG:\n+\t\treturn bpf_diag_fmt(env, \"stack arg%d\", diag_stack_argno(target-\u003estack_arg));\n+\tcase BPF_DIAG_MOD_TARGET_STACK_SLOT:\n+\t\treturn bpf_diag_fmt(env, \"stack slot fp%d\", -(target-\u003espi + 1) * BPF_REG_SIZE);\n+\tdefault:\n+\t\treturn \"value\";\n+\t}\n+}\n+\n+static void diag_print_mod(struct bpf_verifier_env *env, const struct bpf_diag_history_event *event)\n+{\n+\tconst struct bpf_diag_mod_target *target = \u0026event-\u003emod.target;\n+\tconst char *target_desc, *reason = NULL, *old, *new;\n+\tconst char *label = \"update\";\n+\n+\tif (target-\u003ekind == BPF_DIAG_MOD_TARGET_STACK_RANGE) {\n+\t\tbpf_diag_source(\n+\t\t\tenv, event-\u003einsn_idx, \"invalidated\",\n+\t\t\t\"variable-offset stack write may affect bytes fp%d through fp%d\",\n+\t\t\ttarget-\u003erange.min_off, target-\u003erange.max_off - 1);\n+\t\treturn;\n+\t}\n+\n+\told = diag_reg_snapshot(env, \u0026event-\u003emod.old);\n+\tnew = diag_reg_snapshot(env, \u0026event-\u003emod.new);\n+\ttarget_desc = diag_mod_target_desc(env, target);\n+\n+\tswitch (event-\u003emod.reason) {\n+\tcase BPF_DIAG_MOD_REF_RELEASE:\n+\t\treason = target-\u003ekind == BPF_DIAG_MOD_TARGET_REG ? \"resource release invalidated \"\n+\t\t\t\t\t\t\t\t \"this pointer\" :\n+\t\t\t\t\t\t\t\t \"resource release invalidated \"\n+\t\t\t\t\t\t\t\t \"this value\";\n+\t\tbreak;\n+\tcase BPF_DIAG_MOD_PKT_DATA_CHANGE:\n+\t\treason = \"packet data may have moved\";\n+\t\tbreak;\n+\tcase BPF_DIAG_MOD_NON_OWN_REF:\n+\t\treason = \"leaving the protected region invalidated this borrowed pointer\";\n+\t\tbreak;\n+\tcase BPF_DIAG_MOD_CALLER_SAVED:\n+\t\treason = target-\u003ekind == BPF_DIAG_MOD_TARGET_STACK_ARG ?\n+\t\t\t \"call invalidated this outgoing stack argument\" :\n+\t\t\t \"call invalidated this caller-saved register\";\n+\t\tbreak;\n+\tcase BPF_DIAG_MOD_WRITE:\n+\t\tif (target-\u003ekind == BPF_DIAG_MOD_TARGET_STACK_SLOT)\n+\t\t\treason = \"a later stack write overwrote this spilled value\";\n+\t\tbreak;\n+\tcase BPF_DIAG_MOD_SPILL:\n+\t\tlabel = \"spilled\";\n+\t\tbreak;\n+\tcase BPF_DIAG_MOD_VAR_WRITE:\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\tif (reason) {\n+\t\tbpf_diag_source(env, event-\u003einsn_idx, \"invalidated\",\n+\t\t\t\t\"%s: %s; previous value was %s\", target_desc, reason, old);\n+\t\treturn;\n+\t}\n+\n+\tbpf_diag_source(env, event-\u003einsn_idx, label, \"%s changed from %s to %s\", target_desc,\n+\t\t\told, new);\n+}\n+\n+static void diag_print_ref_event(struct bpf_verifier_env *env,\n+\t\t\t\t const struct bpf_diag_history_event *event)\n+{\n+\tconst char *label;\n+\n+\tlabel = event-\u003ekind == BPF_DIAG_HISTORY_REF_ACQUIRE ? \"acquired\" : \"released\";\n+\tbpf_diag_source(env, event-\u003einsn_idx, label, \"owned resource (id=%u)\",\n+\t\t\tevent-\u003eref.ref_id);\n+}\n+\n+static const char *diag_context_name(enum bpf_diag_context_kind kind)\n+{\n+\tswitch (kind) {\n+\tcase BPF_DIAG_CONTEXT_RCU:\n+\t\treturn \"RCU read lock region\";\n+\tcase BPF_DIAG_CONTEXT_PREEMPT:\n+\t\treturn \"non-preemptible region\";\n+\tcase BPF_DIAG_CONTEXT_IRQ:\n+\t\treturn \"IRQ-disabled region\";\n+\tcase BPF_DIAG_CONTEXT_LOCK:\n+\t\treturn \"lock region\";\n+\tcase BPF_DIAG_CONTEXT_NONE:\n+\tdefault:\n+\t\treturn \"non-sleepable program\";\n+\t}\n+}\n+\n+static void diag_print_context_event(struct bpf_verifier_env *env,\n+\t\t\t\t const struct bpf_diag_history_event *event)\n+{\n+\tbpf_diag_source(env, event-\u003einsn_idx, \"context\", \"%s %s; depth is now %u\",\n+\t\t\tevent-\u003ectx.enter ? \"entered\" : \"left\",\n+\t\t\tdiag_context_name(event-\u003ectx.kind), event-\u003ectx.depth);\n+}\n+\n+static void diag_print_history(struct bpf_verifier_env *env,\n+\t\t\t const struct bpf_diag_history_opts *opts)\n+{\n+\tconst struct bpf_diag_history_event *event;\n+\tstruct bpf_diag_history_filter filter = {\n+\t\t.opts = opts,\n+\t};\n+\tstruct bpf_diag_log *log;\n+\tstruct diag_fmt_mark mark;\n+\tbool first = true;\n+\tint start_idx;\n+\tu32 i, visible_cnt = 0, visible_idx = 0;\n+\n+\tif (!bpf_diag_enabled(env))\n+\t\treturn;\n+\n+\tif (!env-\u003ediag)\n+\t\treturn;\n+\tlog = \u0026env-\u003ediag-\u003elog;\n+\n+\tdiag_build_lineage(env, log, \u0026filter);\n+\n+\tstart_idx = diag_history_start_idx(log, \u0026filter);\n+\tfor (i = start_idx; i \u003c log-\u003ecnt; i++) {\n+\t\tevent = \u0026log-\u003eevents[log_pos(log, i)];\n+\t\tif (diag_history_event_visible(event, \u0026filter))\n+\t\t\tvisible_cnt++;\n+\t}\n+\n+\tif (!visible_cnt \u0026\u0026 !log-\u003efirst_seq \u0026\u0026 opts-\u003escope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG)\n+\t\treturn;\n+\n+\tdiag_section(env, \"Causal path\");\n+\tmark = diag_fmt_save(env);\n+\tfor (i = start_idx; i \u003c log-\u003ecnt; i++) {\n+\t\tevent = \u0026log-\u003eevents[log_pos(log, i)];\n+\t\tif (!diag_history_event_visible(event, \u0026filter))\n+\t\t\tcontinue;\n+\n+\t\tdiag_fmt_restore(env, mark);\n+\t\tif (visible_cnt \u003e BPF_DIAG_HISTORY_RENDER_MAX \u0026\u0026\n+\t\t visible_idx \u003e= BPF_DIAG_HISTORY_RENDER_MAX / 2 \u0026\u0026\n+\t\t visible_idx \u003c visible_cnt - BPF_DIAG_HISTORY_RENDER_MAX / 2) {\n+\t\t\tif (visible_idx++ != BPF_DIAG_HISTORY_RENDER_MAX / 2)\n+\t\t\t\tcontinue;\n+\t\t\tif (!first)\n+\t\t\t\tdiag_write(env, \"\\n\");\n+\t\t\tfirst = false;\n+\t\t\tdiag_write(env, \" %u intermediate causal-history events omitted\\n\",\n+\t\t\t\t visible_cnt - BPF_DIAG_HISTORY_RENDER_MAX);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tvisible_idx++;\n+\n+\t\tif (!first)\n+\t\t\tdiag_write(env, \"\\n\");\n+\t\tfirst = false;\n+\n+\t\tswitch (event-\u003ekind) {\n+\t\tcase BPF_DIAG_HISTORY_BRANCH:\n+\t\t\tbpf_diag_source(env, event-\u003einsn_idx, \"branch\",\n+\t\t\t\t\t\"took the %s branch of this conditional, goto %s\",\n+\t\t\t\t\tevent-\u003ebranch.cond_true ? \"true\" : \"false\",\n+\t\t\t\t\tevent-\u003ebranch.cond_true ? \"followed\" : \"not followed\");\n+\t\t\tbreak;\n+\t\tcase BPF_DIAG_HISTORY_MOD:\n+\t\t\tdiag_print_mod(env, event);\n+\t\t\tbreak;\n+\t\tcase BPF_DIAG_HISTORY_REF_ACQUIRE:\n+\t\tcase BPF_DIAG_HISTORY_REF_RELEASE:\n+\t\t\tdiag_print_ref_event(env, event);\n+\t\t\tbreak;\n+\t\tcase BPF_DIAG_HISTORY_CONTEXT:\n+\t\t\tdiag_print_context_event(env, event);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (!visible_cnt)\n+\t\tdiag_write(env, \" no retained diagnostic events on this path\\n\");\n+\tif (log-\u003efirst_seq)\n+\t\tdiag_write(env, \" %llu older causal-history event%s not retained because diagnostic \"\n+\t\t\t \"event storage reached capacity\\n\",\n+\t\t\t log-\u003efirst_seq, log-\u003efirst_seq == 1 ? \"\" : \"s\");\n+\tdiag_fmt_restore(env, mark);\n+}\ndiff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h\nnew file mode 100644\nindex 0000000000000..d1b79945008a8\n--- /dev/null\n+++ b/kernel/bpf/diagnostics.h\n@@ -0,0 +1,111 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */\n+\n+#ifndef __BPF_DIAGNOSTICS_H\n+#define __BPF_DIAGNOSTICS_H\n+\n+#include \u003clinux/bpf.h\u003e\n+#include \u003clinux/compiler_attributes.h\u003e\n+#include \u003clinux/stdarg.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+struct bpf_reference_state;\n+struct bpf_func_state;\n+struct bpf_reg_state;\n+struct bpf_verifier_env;\n+struct bpf_verifier_state;\n+struct btf;\n+\n+const char *bpf_diag_fmt_s64_sum(struct bpf_verifier_env *env, s64 value, int addend);\n+enum bpf_diag_mod_reason {\n+\tBPF_DIAG_MOD_WRITE,\n+\tBPF_DIAG_MOD_SPILL,\n+\tBPF_DIAG_MOD_VAR_WRITE,\n+\tBPF_DIAG_MOD_REF_RELEASE,\n+\tBPF_DIAG_MOD_PKT_DATA_CHANGE,\n+\tBPF_DIAG_MOD_NON_OWN_REF,\n+\tBPF_DIAG_MOD_CALLER_SAVED,\n+};\n+\n+enum bpf_diag_context_kind {\n+\tBPF_DIAG_CONTEXT_NONE,\n+\tBPF_DIAG_CONTEXT_RCU,\n+\tBPF_DIAG_CONTEXT_PREEMPT,\n+\tBPF_DIAG_CONTEXT_IRQ,\n+\tBPF_DIAG_CONTEXT_LOCK,\n+};\n+\n+enum bpf_diag_invalid_deref_kind {\n+\tBPF_DIAG_DEREF_SCALAR,\n+\tBPF_DIAG_DEREF_NULLABLE_PTR,\n+\tBPF_DIAG_DEREF_MODIFIED_PTR,\n+\tBPF_DIAG_DEREF_INVALID_PTR,\n+};\n+\n+bool bpf_diag_enabled(const struct bpf_verifier_env *env);\n+int bpf_diag_init(struct bpf_verifier_env *env);\n+void bpf_diag_init_frame(struct bpf_verifier_env *env, struct bpf_func_state *state);\n+char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size);\n+const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)\n+\t__printf(2, 0);\n+const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);\n+const char *bpf_diag_fmt_btf_type(struct bpf_verifier_env *env, const struct btf *btf, u32 type_id);\n+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type);\n+u64 bpf_diag_event_log_save(struct bpf_verifier_env *env);\n+void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos);\n+u32 bpf_diag_irq_depth(const struct bpf_verifier_state *state);\n+void bpf_diag_free(struct bpf_verifier_env *env);\n+void bpf_diag_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,\n+\t\t\t const char *problem, const char *reason, const char *suggestion);\n+void bpf_diag_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,\n+\t\t\t const char *reg_name, const struct bpf_reg_state *reg,\n+\t\t\t enum bpf_diag_invalid_deref_kind kind, s64 offset);\n+void bpf_diag_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno);\n+void bpf_diag_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,\n+\t\t\t int stack_arg_slot, const char *callee_name,\n+\t\t\t const char *arg_name);\n+void bpf_diag_memory(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,\n+\t\t const char *reason, const char *suggestion);\n+void bpf_diag_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx, int regno,\n+\t\t\t const char *reg_name, const char *type_name, const char *proof,\n+\t\t\t int off, int size, u32 mem_size, const struct bpf_reg_state *reg);\n+void bpf_diag_res(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,\n+\t\t const char *reason, const char *suggestion);\n+void bpf_diag_lock(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,\n+\t\t const char *reason, const char *suggestion,\n+\t\t const struct bpf_reference_state *active_lock);\n+void bpf_diag_irq(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,\n+\t\t const char *reason, const char *suggestion, u32 depth);\n+void bpf_diag_leak(struct bpf_verifier_env *env, u32 ref_id, u32 alloc_insn, u32 fail_insn);\n+void bpf_diag_call_type(struct bpf_verifier_env *env, u32 insn_idx, int argno, int regno,\n+\t\t\tint stack_arg_slot, const char *call_name, const char *arg_name,\n+\t\t\tconst char *reason, const char *suggestion);\n+void bpf_diag_ctx_forbidden(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t const char *operation, const char *suggestion);\n+void bpf_diag_ctx_active(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,\n+\t\t\t enum bpf_diag_context_kind ctx_kind, const char *suggestion);\n+void bpf_diag_ctx_required(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,\n+\t\t\t enum bpf_diag_context_kind ctx_kind, const char *suggestion);\n+void bpf_diag_ctx_underflow(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t const char *operation, enum bpf_diag_context_kind ctx_kind,\n+\t\t\t const char *suggestion);\n+void bpf_diag_program_structure(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t\tconst char *problem, const char *suggestion,\n+\t\t\t\tconst char *reason_fmt, ...) __printf(5, 6);\n+void bpf_diag_policy(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,\n+\t\t const char *reason, const char *suggestion);\n+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true);\n+void bpf_diag_mod_begin(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,\n+\t\t\tconst struct bpf_reg_state *origin, enum bpf_diag_mod_reason reason);\n+void bpf_diag_mod_end(struct bpf_verifier_env *env);\n+void bpf_diag_record_scrub(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,\n+\t\t\t enum bpf_diag_mod_reason reason);\n+void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,\n+\t\t\t\t const struct bpf_func_state *state, s16 min_off, s16 max_off,\n+\t\t\t\t enum bpf_diag_mod_reason reason);\n+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);\n+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);\n+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t enum bpf_diag_context_kind ctx_kind, bool enter, u32 depth);\n+\n+#endif /* __BPF_DIAGNOSTICS_H */\ndiff --git a/kernel/bpf/log.c b/kernel/bpf/log.c\nindex b740fa73ee26d..589770ca3d3a0 100644\n--- a/kernel/bpf/log.c\n+++ b/kernel/bpf/log.c\n@@ -615,17 +615,6 @@ static void print_scalar_ranges(struct bpf_verifier_env *env,\n \t}\n }\n \n-static bool type_is_map_ptr(enum bpf_reg_type t) {\n-\tswitch (base_type(t)) {\n-\tcase CONST_PTR_TO_MAP:\n-\tcase PTR_TO_MAP_KEY:\n-\tcase PTR_TO_MAP_VALUE:\n-\t\treturn true;\n-\tdefault:\n-\t\treturn false;\n-\t}\n-}\n-\n /*\n * _a stands for append, was shortened to avoid multiline statements below.\n * This macro is used to output a comma separated list of attributes.\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex 6ac1afced20bf..ff028a8c1cca1 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -34,6 +34,7 @@\n #include \u003clinux/trace_events.h\u003e\n #include \u003clinux/kallsyms.h\u003e\n \n+#include \"diagnostics.h\"\n #include \"disasm.h\"\n \n static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {\n@@ -193,6 +194,7 @@ struct bpf_verifier_stack_elem {\n \tstruct bpf_verifier_stack_elem *next;\n \t/* length of verifier log at the time this state was pushed on stack */\n \tu32 log_pos;\n+\tu64 diag_log_pos;\n };\n \n #define BPF_COMPLEXITY_LIMIT_JMP_SEQ\t8192\n@@ -203,7 +205,8 @@ struct bpf_verifier_stack_elem {\n #define BPF_PRIV_STACK_MIN_SIZE\t\t64\n \n static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id);\n-static int release_reference_nomark(struct bpf_verifier_state *state, int id);\n+static int __release_reference_nomark(struct bpf_verifier_state *state, int id);\n+static int release_reference_nomark(struct bpf_verifier_env *env, int id);\n static int release_reference(struct bpf_verifier_env *env, int id);\n static void invalidate_non_owning_refs(struct bpf_verifier_env *env);\n static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env);\n@@ -405,7 +408,7 @@ static bool subprog_returns_void(struct bpf_verifier_env *env, int subprog)\n \treturn btf_type_is_void(type);\n }\n \n-static const char *subprog_name(const struct bpf_verifier_env *env, int subprog)\n+const char *bpf_subprog_name(const struct bpf_verifier_env *env, int subprog)\n {\n \tstruct bpf_func_info *info;\n \n@@ -813,6 +816,10 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,\n \tif (dynptr_type_referenced(state-\u003estack[spi].spilled_ptr.dynptr.type) \u0026\u0026\n \t dynptr_ref_cnt(env, state-\u003estack[spi].spilled_ptr.parent_id) \u003c= 1) {\n \t\tverbose(env, \"cannot overwrite referenced dynptr\\n\");\n+\t\tbpf_diag_res(\n+\t\t\tenv, env-\u003einsn_idx, \"referenced dynptr overwrite\",\n+\t\t\t\"This stack slot contains a dynptr that owns or protects a referenced resource. Overwriting the last dynptr for that resource would lose the verifier-tracked release path.\",\n+\t\t\t\"Release or clone the dynptr so another live dynptr still tracks the referenced resource before overwriting this stack slot.\");\n \t\treturn -EINVAL;\n \t}\n \n@@ -883,26 +890,29 @@ static bool is_dynptr_reg_valid_init(struct bpf_verifier_env *env, struct bpf_re\n \treturn true;\n }\n \n-static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n-\t\t\t\t enum bpf_arg_type arg_type)\n+static enum bpf_dynptr_type dynptr_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg)\n {\n-\tstruct bpf_func_state *state = bpf_func(env, reg);\n-\tenum bpf_dynptr_type dynptr_type;\n+\tstruct bpf_func_state *state;\n \tint spi;\n \n+\tif (reg-\u003etype == CONST_PTR_TO_DYNPTR)\n+\t\treturn reg-\u003edynptr.type;\n+\n+\tspi = dynptr_get_spi(env, reg);\n+\tif (spi \u003c 0)\n+\t\treturn BPF_DYNPTR_TYPE_INVALID;\n+\tstate = bpf_func(env, reg);\n+\treturn state-\u003estack[spi].spilled_ptr.dynptr.type;\n+}\n+\n+static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n+\t\t\t\t enum bpf_arg_type arg_type)\n+{\n \t/* ARG_PTR_TO_DYNPTR takes any type of dynptr */\n \tif (arg_type == ARG_PTR_TO_DYNPTR)\n \t\treturn true;\n \n-\tdynptr_type = arg_to_dynptr_type(arg_type);\n-\tif (reg-\u003etype == CONST_PTR_TO_DYNPTR) {\n-\t\treturn reg-\u003edynptr.type == dynptr_type;\n-\t} else {\n-\t\tspi = dynptr_get_spi(env, reg);\n-\t\tif (spi \u003c 0)\n-\t\t\treturn false;\n-\t\treturn state-\u003estack[spi].spilled_ptr.dynptr.type == dynptr_type;\n-\t}\n+\treturn dynptr_reg_type(env, reg) == arg_to_dynptr_type(arg_type);\n }\n \n static void __mark_reg_known_zero(struct bpf_reg_state *reg);\n@@ -1042,7 +1052,7 @@ static int is_iter_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_s\n }\n \n static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx);\n-static int release_irq_state(struct bpf_verifier_state *state, int id);\n+static int release_irq_state(struct bpf_verifier_env *env, int id);\n \n static int mark_stack_slot_irq_flag(struct bpf_verifier_env *env,\n \t\t\t\t struct bpf_call_arg_meta *meta,\n@@ -1095,13 +1105,23 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r\n \tif (st-\u003eirq.kfunc_class != kfunc_class) {\n \t\tconst char *flag_kfunc = st-\u003eirq.kfunc_class == IRQ_NATIVE_KFUNC ? \"native\" : \"lock\";\n \t\tconst char *used_kfunc = kfunc_class == IRQ_NATIVE_KFUNC ? \"native\" : \"lock\";\n+\t\tconst char *reason;\n \n \t\tverbose(env, \"irq flag acquired by %s kfuncs cannot be restored with %s kfuncs\\n\",\n \t\t\tflag_kfunc, used_kfunc);\n+\t\treason = bpf_diag_fmt(env,\n+\t\t\t\t \"This IRQ flag was saved by %s IRQ kfuncs, but the restore call \"\n+\t\t\t\"belongs to the %s IRQ kfunc family. Save and restore operations \"\n+\t\t\t\"must use the same family.\",\n+\t\t\tflag_kfunc, used_kfunc);\n+\t\tbpf_diag_irq(env, env-\u003einsn_idx, \"IRQ flag restore mismatch\", reason,\n+\t\t\t \"Restore the flag with the matching IRQ restore kfunc for the save \"\n+\t\t\t \"operation that created it.\",\n+\t\t\t bpf_diag_irq_depth(env-\u003ecur_state));\n \t\treturn -EINVAL;\n \t}\n \n-\terr = release_irq_state(env-\u003ecur_state, st-\u003eid);\n+\terr = release_irq_state(env, st-\u003eid);\n \tWARN_ON_ONCE(err \u0026\u0026 err != -EACCES);\n \tif (err) {\n \t\tint insn_idx = 0;\n@@ -1115,6 +1135,11 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r\n \n \t\tverbose(env, \"cannot restore irq state out of order, expected id=%d acquired at insn_idx=%d\\n\",\n \t\t\tenv-\u003ecur_state-\u003eactive_irq_id, insn_idx);\n+\t\tbpf_diag_irq(env, env-\u003einsn_idx, \"IRQ flag restore out of order\",\n+\t\t\t \"IRQ-disabled regions must be restored in last-in, first-out order, \"\n+\t\t\t \"but this restore does not match the currently active IRQ flag.\",\n+\t\t\t \"Restore nested IRQ flags in the reverse order they were saved.\",\n+\t\t\t bpf_diag_irq_depth(env-\u003ecur_state));\n \t\treturn err;\n \t}\n \n@@ -1416,6 +1441,7 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par\n \ts-\u003etype = REF_TYPE_PTR;\n \ts-\u003eid = ++env-\u003eid_gen;\n \ts-\u003eparent_id = parent_id;\n+\tbpf_diag_record_ref_acquire(env, insn_idx, s-\u003eid);\n \treturn s-\u003eid;\n }\n \n@@ -1435,6 +1461,8 @@ static int acquire_lock_state(struct bpf_verifier_env *env, int insn_idx, enum r\n \tstate-\u003eactive_locks++;\n \tstate-\u003eactive_lock_id = id;\n \tstate-\u003eactive_lock_ptr = ptr;\n+\tbpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_LOCK, true,\n+\t\t\t\tstate-\u003eactive_locks);\n \treturn 0;\n }\n \n@@ -1450,6 +1478,8 @@ static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx)\n \ts-\u003eid = ++env-\u003eid_gen;\n \n \tstate-\u003eactive_irq_id = s-\u003eid;\n+\tbpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_IRQ, true,\n+\t\t\t\tbpf_diag_irq_depth(state));\n \treturn s-\u003eid;\n }\n \n@@ -1491,8 +1521,9 @@ static bool reg_is_referenced(struct bpf_verifier_env *env, const struct bpf_reg\n \treturn find_reference_state(env-\u003ecur_state, reg-\u003eid);\n }\n \n-static int release_lock_state(struct bpf_verifier_state *state, int type, int id, void *ptr)\n+static int release_lock_state(struct bpf_verifier_env *env, int type, int id, void *ptr)\n {\n+\tstruct bpf_verifier_state *state = env-\u003ecur_state;\n \tvoid *prev_ptr = NULL;\n \tu32 prev_id = 0;\n \tint i;\n@@ -1505,6 +1536,8 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id\n \t\t\t/* Reassign active lock (id, ptr). */\n \t\t\tstate-\u003eactive_lock_id = prev_id;\n \t\t\tstate-\u003eactive_lock_ptr = prev_ptr;\n+\t\t\tbpf_diag_record_context(env, env-\u003einsn_idx, BPF_DIAG_CONTEXT_LOCK,\n+\t\t\t\t\t\tfalse, state-\u003eactive_locks);\n \t\t\treturn 0;\n \t\t}\n \t\tif (state-\u003erefs[i].type \u0026 REF_TYPE_LOCK_MASK) {\n@@ -1515,8 +1548,9 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id\n \treturn -EINVAL;\n }\n \n-static int release_irq_state(struct bpf_verifier_state *state, int id)\n+static int release_irq_state(struct bpf_verifier_env *env, int id)\n {\n+\tstruct bpf_verifier_state *state = env-\u003ecur_state;\n \tu32 prev_id = 0;\n \tint i;\n \n@@ -1529,6 +1563,8 @@ static int release_irq_state(struct bpf_verifier_state *state, int id)\n \t\tif (state-\u003erefs[i].id == id) {\n \t\t\trelease_reference_state(state, i);\n \t\t\tstate-\u003eactive_irq_id = prev_id;\n+\t\t\tbpf_diag_record_context(env, env-\u003einsn_idx, BPF_DIAG_CONTEXT_IRQ,\n+\t\t\t\t\t\tfalse, bpf_diag_irq_depth(state));\n \t\t\treturn 0;\n \t\t} else {\n \t\t\tprev_id = state-\u003erefs[i].id;\n@@ -1699,6 +1735,7 @@ static int pop_stack(struct bpf_verifier_env *env, int *prev_insn_idx,\n \t\terr = bpf_copy_verifier_state(cur, \u0026head-\u003est);\n \t\tif (err)\n \t\t\treturn err;\n+\t\tbpf_diag_event_log_restore(env, head-\u003ediag_log_pos);\n \t}\n \tif (pop_log)\n \t\tbpf_vlog_reset(\u0026env-\u003elog, head-\u003elog_pos);\n@@ -1742,6 +1779,7 @@ static struct bpf_verifier_state *push_stack(struct bpf_verifier_env *env,\n \telem-\u003eprev_insn_idx = prev_insn_idx;\n \telem-\u003enext = env-\u003ehead;\n \telem-\u003elog_pos = env-\u003elog.end_pos;\n+\telem-\u003ediag_log_pos = bpf_diag_event_log_save(env);\n \tenv-\u003ehead = elem;\n \tenv-\u003estack_size++;\n \terr = bpf_copy_verifier_state(\u0026elem-\u003est, cur);\n@@ -1788,6 +1826,17 @@ static const int caller_saved[CALLER_SAVED_REGS] = {\n \tBPF_REG_0, BPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5\n };\n \n+static void bpf_diag_record_caller_saved(struct bpf_verifier_env *env,\n+\t\t\t\t\t struct bpf_reg_state *regs)\n+{\n+\tint i;\n+\n+\tfor (i = 1; i \u003c CALLER_SAVED_REGS; i++) {\n+\t\tbpf_diag_record_scrub(env, \u0026regs[caller_saved[i]],\n+\t\t\t\t BPF_DIAG_MOD_CALLER_SAVED);\n+\t}\n+}\n+\n /* This helper doesn't clear reg-\u003eid */\n static void ___mark_reg_known(struct bpf_reg_state *reg, u64 imm)\n {\n@@ -2241,6 +2290,7 @@ static void init_func_state(struct bpf_verifier_env *env,\n {\n \tstate-\u003ecallsite = callsite;\n \tstate-\u003eframeno = frameno;\n+\tbpf_diag_init_frame(env, state);\n \tstate-\u003esubprogno = subprogno;\n \tstate-\u003ecallback_ret_range = retval_range(0, 0);\n \tinit_reg_state(env, state);\n@@ -2263,6 +2313,7 @@ static struct bpf_verifier_state *push_async_cb(struct bpf_verifier_env *env,\n \telem-\u003eprev_insn_idx = prev_insn_idx;\n \telem-\u003enext = env-\u003ehead;\n \telem-\u003elog_pos = env-\u003elog.end_pos;\n+\telem-\u003ediag_log_pos = bpf_diag_event_log_save(env);\n \tenv-\u003ehead = elem;\n \tenv-\u003estack_size++;\n \tif (env-\u003estack_size \u003e BPF_COMPLEXITY_LIMIT_JMP_SEQ) {\n@@ -2624,6 +2675,26 @@ static struct btf *find_kfunc_desc_btf(struct bpf_verifier_env *env, s16 offset)\n \treturn btf_vmlinux ?: ERR_PTR(-ENOENT);\n }\n \n+static struct btf *find_kfunc_desc_btf_cached(struct bpf_verifier_env *env, s16 offset)\n+{\n+\tstruct bpf_kfunc_btf kf_btf = { .offset = offset };\n+\tstruct bpf_kfunc_btf_tab *tab;\n+\tstruct bpf_kfunc_btf *b;\n+\n+\tif (!offset)\n+\t\treturn btf_vmlinux ?: ERR_PTR(-ENOENT);\n+\tif (offset \u003c 0)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\ttab = env-\u003eprog-\u003eaux-\u003ekfunc_btf_tab;\n+\tif (!tab)\n+\t\treturn ERR_PTR(-ENOENT);\n+\n+\tb = bsearch(\u0026kf_btf, tab-\u003edescs, tab-\u003enr_descs,\n+\t\t sizeof(tab-\u003edescs[0]), kfunc_btf_cmp_by_off);\n+\treturn b ? b-\u003ebtf : ERR_PTR(-ENOENT);\n+}\n+\n #define KF_IMPL_SUFFIX \"_impl\"\n \n static const struct btf_type *find_kfunc_impl_proto(struct bpf_verifier_log *log,\n@@ -2853,6 +2924,10 @@ static int add_subprogs(struct bpf_verifier_env *env)\n \n \t\tif (!env-\u003ebpf_capable) {\n \t\t\tverbose(env, \"loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\\n\");\n+\t\t\tbpf_diag_policy(\n+\t\t\t\tenv, i, \"BPF-to-BPF function call\",\n+\t\t\t\t\"loading or calling other BPF functions requires CAP_BPF or CAP_SYS_ADMIN\",\n+\t\t\t\t\"Load this program with the required capability, or avoid BPF-to-BPF function calls in unprivileged programs.\");\n \t\t\treturn -EPERM;\n \t\t}\n \n@@ -2905,6 +2980,10 @@ static int add_kfuncs(struct bpf_verifier_env *env)\n \n \t\tif (!env-\u003ebpf_capable) {\n \t\t\tverbose(env, \"loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\\n\");\n+\t\t\tbpf_diag_policy(\n+\t\t\t\tenv, i, \"kernel function call\",\n+\t\t\t\t\"calling kernel functions requires CAP_BPF or CAP_SYS_ADMIN\",\n+\t\t\t\t\"Load this program with the required capability, or avoid kernel function calls in unprivileged programs.\");\n \t\t\treturn -EPERM;\n \t\t}\n \n@@ -2949,6 +3028,12 @@ static int check_subprogs(struct bpf_verifier_env *env)\n \t\toff = i + bpf_jmp_offset(\u0026insn[i]) + 1;\n \t\tif (off \u003c subprog_start || off \u003e= subprog_end) {\n \t\t\tverbose(env, \"jump out of range from insn %d to %d\\n\", i, off);\n+\t\t\tbpf_diag_program_structure(\n+\t\t\t\tenv, i, \"jump out of range\",\n+\t\t\t\t\"Keep branch targets within the same subprogram, or use an explicit subprogram call.\",\n+\t\t\t\t\"Instruction %d jumps to instruction %d, but subprogram %d only contains instructions %d through %d. \"\n+\t\t\t\t\"A branch target must stay inside the same subprogram.\",\n+\t\t\t\ti, off, cur_subprog, subprog_start, subprog_end - 1);\n \t\t\treturn -EINVAL;\n \t\t}\n next:\n@@ -2961,6 +3046,11 @@ static int check_subprogs(struct bpf_verifier_env *env)\n \t\t\t code != (BPF_JMP32 | BPF_JA) \u0026\u0026\n \t\t\t code != (BPF_JMP | BPF_JA)) {\n \t\t\t\tverbose(env, \"last insn is not an exit or jmp\\n\");\n+\t\t\t\tbpf_diag_program_structure(\n+\t\t\t\t\tenv, i, \"subprogram can fall through\",\n+\t\t\t\t\t\"End each subprogram with an exit or an explicit jump that keeps control flow inside the subprogram.\",\n+\t\t\t\t\t\"Subprogram %d reaches its last instruction %d without an exit or jump, so control could continue into the next subprogram.\",\n+\t\t\t\t\tcur_subprog, i);\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t\tsubprog_start = subprog_end;\n@@ -3031,8 +3121,13 @@ static int sort_subprogs_topo(struct bpf_verifier_env *env)\n \t\t\t\t\tif (bpf_pseudo_func(\u0026insn[idx]))\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\tverbose(env, \"recursive call from %s() to %s()\\n\",\n-\t\t\t\t\t\tsubprog_name(env, cur),\n-\t\t\t\t\t\tsubprog_name(env, callee));\n+\t\t\t\t\t\tbpf_subprog_name(env, cur),\n+\t\t\t\t\t\tbpf_subprog_name(env, callee));\n+\t\t\t\t\tbpf_diag_program_structure(\n+\t\t\t\t\t\tenv, idx, \"recursive subprogram call\",\n+\t\t\t\t\t\t\"Rewrite the recursion as an explicit bounded loop, or split the logic so subprogram calls do not form a cycle.\",\n+\t\t\t\t\t\t\"This bpf2bpf call would make the subprogram call graph recursive. \"\n+\t\t\t\t\t\t\"The verifier requires a finite, acyclic call graph so it can bound stack depth and analysis.\");\n \t\t\t\t\tret = -EINVAL;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n@@ -3053,7 +3148,7 @@ static int sort_subprogs_topo(struct bpf_verifier_env *env)\n \tif (env-\u003elog.level \u0026 BPF_LOG_LEVEL2)\n \t\tfor (i = 0; i \u003c cnt; i++)\n \t\t\tverbose(env, \"topo_order[%d] = %s\\n\",\n-\t\t\t\ti, subprog_name(env, env-\u003esubprog_topo_order[i]));\n+\t\t\t\ti, bpf_subprog_name(env, env-\u003esubprog_topo_order[i]));\n out:\n \tkvfree(dfs_stack);\n \tkvfree(color);\n@@ -3081,6 +3176,7 @@ static int __check_reg_arg(struct bpf_verifier_env *env, struct bpf_reg_state *r\n \t\t/* check whether register used as source operand can be read */\n \t\tif (reg-\u003etype == NOT_INIT) {\n \t\t\tverbose(env, \"R%d !read_ok\\n\", regno);\n+\t\t\tbpf_diag_unreadable_reg(env, env-\u003einsn_idx, regno);\n \t\t\treturn -EACCES;\n \t\t}\n \t\t/* We don't need to worry about FP liveness because it's read-only */\n@@ -3197,7 +3293,7 @@ static void linked_regs_unpack(u64 val, struct linked_regs *s)\n \t}\n }\n \n-static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)\n+const char *bpf_disasm_kfunc_name(void *data, const struct bpf_insn *insn)\n {\n \tconst struct btf_type *func;\n \tstruct btf *desc_btf;\n@@ -3205,18 +3301,20 @@ static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)\n \tif (insn-\u003esrc_reg != BPF_PSEUDO_KFUNC_CALL)\n \t\treturn NULL;\n \n-\tdesc_btf = find_kfunc_desc_btf(data, insn-\u003eoff);\n+\tdesc_btf = find_kfunc_desc_btf_cached(data, insn-\u003eoff);\n \tif (IS_ERR(desc_btf))\n \t\treturn \"\u003cerror\u003e\";\n \n \tfunc = btf_type_by_id(desc_btf, insn-\u003eimm);\n+\tif (!func || !btf_type_is_func(func))\n+\t\treturn \"\u003cerror\u003e\";\n \treturn btf_name_by_offset(desc_btf, func-\u003ename_off);\n }\n \n void bpf_verbose_insn(struct bpf_verifier_env *env, struct bpf_insn *insn)\n {\n \tconst struct bpf_insn_cbs cbs = {\n-\t\t.cb_call\t= disasm_kfunc_name,\n+\t\t.cb_call\t= bpf_disasm_kfunc_name,\n \t\t.cb_print\t= verbose,\n \t\t.private_data\t= env,\n \t};\n@@ -3335,6 +3433,7 @@ static void save_register_state(struct bpf_verifier_env *env,\n {\n \tint i;\n \n+\tbpf_diag_mod_begin(env, \u0026state-\u003estack[spi].spilled_ptr, reg, BPF_DIAG_MOD_SPILL);\n \tstate-\u003estack[spi].spilled_ptr = *reg;\n \n \tfor (i = BPF_REG_SIZE; i \u003e BPF_REG_SIZE - size; i--)\n@@ -3343,6 +3442,8 @@ static void save_register_state(struct bpf_verifier_env *env,\n \t/* size \u003c 8 bytes spill */\n \tfor (; i; i--)\n \t\tmark_stack_slot_misc(env, \u0026state-\u003estack[spi].slot_type[i - 1]);\n+\n+\tbpf_diag_mod_end(env);\n }\n \n static bool is_bpf_st_mem(struct bpf_insn *insn)\n@@ -3415,7 +3516,16 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,\n \t bpf_is_spilled_reg(\u0026state-\u003estack[spi]) \u0026\u0026\n \t !bpf_is_spilled_scalar_reg(\u0026state-\u003estack[spi]) \u0026\u0026\n \t size != BPF_REG_SIZE) {\n+\t\tconst char *reason;\n+\n \t\tverbose(env, \"attempt to corrupt spilled pointer on stack\\n\");\n+\t\treason = bpf_diag_fmt(env,\n+\t\t\t\t \"This store writes %d bytes at stack offset %d into a stack slot that currently holds a spilled pointer. \"\n+\t\t\t\"Partial writes to spilled pointers are rejected because they can corrupt pointer metadata and leak kernel pointers.\",\n+\t\t\tsize, off);\n+\t\tbpf_diag_memory(\n+\t\t\tenv, insn_idx, \"stack spill corruption\", reason,\n+\t\t\t\"Write the full 8-byte spilled pointer slot, or use a separate stack slot for scalar data before overwriting only part of it.\");\n \t\treturn -EACCES;\n \t}\n \n@@ -3479,6 +3589,9 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,\n \t} else {\n \t\tu8 type = STACK_MISC;\n \n+\t\tif (bpf_is_spilled_reg(\u0026state-\u003estack[spi]))\n+\t\t\tbpf_diag_record_scrub(env, \u0026state-\u003estack[spi].spilled_ptr,\n+\t\t\t\t\t BPF_DIAG_MOD_WRITE);\n \t\tscrub_special_slot(state, spi);\n \n \t\t/* when we zero initialize stack slots mark them as such */\n@@ -3639,6 +3752,8 @@ static int check_stack_write_var_off(struct bpf_verifier_env *env,\n \t\tif (err)\n \t\t\treturn err;\n \t}\n+\tbpf_diag_record_scrub_stack(env, state, min_off, max_off,\n+\t\t\t\t BPF_DIAG_MOD_VAR_WRITE);\n \treturn 0;\n }\n \n@@ -3702,6 +3817,21 @@ static int mark_reg_stack_read(struct bpf_verifier_env *env,\n \treturn 0;\n }\n \n+static void bpf_diag_stack_read_uninit(struct bpf_verifier_env *env, int off, int i,\n+\t\t\t\t int size)\n+{\n+\tconst char *reason;\n+\n+\treason = bpf_diag_fmt(env,\n+\t\t\t \"This rejected read uses %d bytes at stack offset %d, but byte %d in that range is uninitialized on this path. \"\n+\t\t\"Programs loaded with CAP_PERFMON can be allowed to read uninitialized stack bytes, but this program is being rejected without that allowance.\",\n+\t\tsize, off, i);\n+\tbpf_diag_memory(\n+\t\tenv, env-\u003einsn_idx, \"uninitialized stack read\", reason,\n+\t\t\"Initialize every byte in the stack range before reading it, adjust the offset and size so the read covers only initialized bytes, \"\n+\t\t\"or load with CAP_PERFMON if uninitialized stack reads are intended.\");\n+}\n+\n /* Read the stack at 'off' and put the results into the register indicated by\n * 'dst_regno'. It handles reg filling if the addressed stack slot is a\n * spilled reg.\n@@ -3731,6 +3861,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,\n \tmark_stack_slot_scratched(env, spi);\n \tcheck_fastcall_stack_contract(env, state, env-\u003einsn_idx, off);\n \n+\t/*\n+\t * Refine the in-progress load record's origin to the source stack slot.\n+\t */\n+\tif (dst_regno \u003e= 0)\n+\t\tbpf_diag_mod_begin(env, \u0026state-\u003eregs[dst_regno], reg, BPF_DIAG_MOD_WRITE);\n+\n \tif (bpf_is_spilled_reg(\u0026reg_state-\u003estack[spi])) {\n \t\tu8 spill_size = 1;\n \n@@ -3785,6 +3921,8 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,\n \t\t\t\t\t} else {\n \t\t\t\t\t\tverbose(env, \"invalid read from stack off %d+%d size %d\\n\",\n \t\t\t\t\t\t\toff, i, size);\n+\t\t\t\t\t\tbpf_diag_stack_read_uninit(env, off, i,\n+\t\t\t\t\t\t\t\t\t size);\n \t\t\t\t\t}\n \t\t\t\t\treturn -EACCES;\n \t\t\t\t}\n@@ -3843,6 +3981,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,\n \t\t\t} else {\n \t\t\t\tverbose(env, \"invalid read from stack off %d+%d size %d\\n\",\n \t\t\t\t\toff, i, size);\n+\t\t\t\tbpf_diag_stack_read_uninit(env, off, i, size);\n \t\t\t}\n \t\t\treturn -EACCES;\n \t\t}\n@@ -3935,11 +4074,19 @@ static int check_stack_read(struct bpf_verifier_env *env,\n \t * check_stack_read_fixed_off).\n \t */\n \tif (dst_regno \u003c 0 \u0026\u0026 var_off) {\n+\t\tconst char *reason;\n \t\tchar tn_buf[48];\n \n \t\ttnum_strn(tn_buf, sizeof(tn_buf), reg-\u003evar_off);\n \t\tverbose(env, \"variable offset stack pointer cannot be passed into helper function; var_off=%s off=%d size=%d\\n\",\n \t\t\ttn_buf, off, size);\n+\t\treason = bpf_diag_fmt(env,\n+\t\t\t\t \"The helper would access the stack through variable offset %s plus fixed offset %d and size %d. \"\n+\t\t\t\"Helper stack memory arguments require a constant stack offset and a precise initialized range.\",\n+\t\t\ttn_buf, off, size);\n+\t\tbpf_diag_memory(\n+\t\t\tenv, env-\u003einsn_idx, \"variable stack access\", reason,\n+\t\t\t\"Use a fixed stack offset for helper memory arguments, or copy the needed bytes into a fixed stack slot first.\");\n \t\treturn -EACCES;\n \t}\n \t/* Variable offset is prohibited for unprivileged mode for simplicity\n@@ -4024,14 +4171,17 @@ static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_s\n \tif (spi + 1 \u003e subprog-\u003emax_out_stack_arg_cnt)\n \t\tsubprog-\u003emax_out_stack_arg_cnt = spi + 1;\n \n+\targ = \u0026state-\u003estack_arg_regs[spi];\n+\tbpf_diag_mod_begin(env, arg, value_reg, BPF_DIAG_MOD_WRITE);\n+\n \tif (value_reg) {\n \t\tstate-\u003estack_arg_regs[spi] = *value_reg;\n \t} else {\n \t\t/* BPF_ST: store immediate, treat as scalar */\n-\t\targ = \u0026state-\u003estack_arg_regs[spi];\n \t\targ-\u003etype = SCALAR_VALUE;\n \t\t__mark_reg_known(arg, env-\u003eprog-\u003einsnsi[env-\u003einsn_idx].imm);\n \t}\n+\tbpf_diag_mod_end(env);\n \tstate-\u003eno_stack_arg_load = true;\n \treturn bpf_push_jmp_history(env, env-\u003ecur_state,\n \t\t\t\t INSN_F_STACK_ARG_ACCESS, spi, 0, 0);\n@@ -4064,7 +4214,9 @@ static int check_stack_arg_read(struct bpf_verifier_env *env, struct bpf_func_st\n \tcaller = vstate-\u003eframe[vstate-\u003ecurframe - 1];\n \targ = \u0026caller-\u003estack_arg_regs[spi];\n \tcur = vstate-\u003eframe[vstate-\u003ecurframe];\n+\tbpf_diag_mod_begin(env, \u0026cur-\u003eregs[dst_regno], arg, BPF_DIAG_MOD_WRITE);\n \tcur-\u003eregs[dst_regno] = *arg;\n+\tbpf_diag_mod_end(env);\n \treturn bpf_push_jmp_history(env, env-\u003ecur_state,\n \t\t\t\t INSN_F_STACK_ARG_ACCESS, spi, 0, 0);\n }\n@@ -4079,7 +4231,8 @@ static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx)\n }\n \n static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller,\n-\t\t\t\t int nargs)\n+\t\t\t\t int nargs, const char *callee_name, const struct btf *btf,\n+\t\t\t\t const struct btf_param *args)\n {\n \tint i, spi;\n \n@@ -4087,8 +4240,14 @@ static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_fu\n \t\tspi = i - MAX_BPF_FUNC_REG_ARGS;\n \t\tif (spi \u003e= caller-\u003eout_stack_arg_cnt ||\n \t\t caller-\u003estack_arg_regs[spi].type == NOT_INIT) {\n+\t\t\tconst char *arg_name = NULL;\n+\n+\t\t\tif (args \u0026\u0026 args[i].name_off)\n+\t\t\t\targ_name = btf_name_by_offset(btf, args[i].name_off);\n \t\t\tverbose(env, \"callee expects %d args, stack arg%d is not initialized\\n\",\n \t\t\t\tnargs, spi + 1);\n+\t\t\tbpf_diag_stack_arg_uninit(env, env-\u003einsn_idx, nargs, spi,\n+\t\t\t\t\t\t callee_name, arg_name);\n \t\t\treturn -EFAULT;\n \t\t}\n \t}\n@@ -4169,6 +4328,9 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_\n \t\t\t\t int off, int size, u32 mem_size,\n \t\t\t\t bool zero_size_allowed)\n {\n+\tconst char *proof = \"\";\n+\tconst char *start;\n+\ts64 max_start, max_end;\n \tint err;\n \n \t/* We may have adjusted the register pointing to memory region, so we\n@@ -4187,14 +4349,28 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_\n \t reg_smin(reg) + off \u003c 0)) {\n \t\tverbose(env, \"%s min value is negative, either use unsigned index or do a if (index \u003e=0) check.\\n\",\n \t\t\treg_arg_name(env, argno));\n-\t\treturn -EACCES;\n+\t\terr = -EACCES;\n+\t\tif (bpf_diag_enabled(env)) {\n+\t\t\tstart = bpf_diag_fmt_s64_sum(env, reg_smin(reg), off);\n+\t\t\tproof = bpf_diag_fmt(\n+\t\t\t\tenv, \"the minimal bound for a memory access is a negative value: %s\",\n+\t\t\t\tstart);\n+\t\t}\n+\t\tgoto report_error;\n \t}\n+\n \terr = __check_mem_access(env, reg, argno, reg_smin(reg) + off, size,\n \t\t\t\t mem_size, zero_size_allowed);\n \tif (err) {\n \t\tverbose(env, \"%s min value is outside of the allowed memory range\\n\",\n \t\t\treg_arg_name(env, argno));\n-\t\treturn err;\n+\t\tif (bpf_diag_enabled(env)) {\n+\t\t\tstart = bpf_diag_fmt_s64_sum(env, reg_smin(reg), off);\n+\t\t\tproof = bpf_diag_fmt(\n+\t\t\t\tenv, \"the minimal bound for a memory access is %s and is outside of the object of size %u\",\n+\t\t\t\tstart, mem_size);\n+\t\t}\n+\t\tgoto report_error;\n \t}\n \n \t/* If we haven't set a max value then we need to bail since we can't be\n@@ -4204,17 +4380,36 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_\n \tif (reg_umax(reg) \u003e= BPF_MAX_VAR_OFF) {\n \t\tverbose(env, \"%s unbounded memory access, make sure to bounds check any such access\\n\",\n \t\t\treg_arg_name(env, argno));\n-\t\treturn -EACCES;\n+\t\terr = -EACCES;\n+\t\tif (bpf_diag_enabled(env))\n+\t\t\tproof = bpf_diag_fmt(\n+\t\t\t\tenv, \"the maximal bound for a memory access is %llu and exceeds maximum allowed offset of %u\",\n+\t\t\t\treg_umax(reg), BPF_MAX_VAR_OFF);\n+\t\tgoto report_error;\n \t}\n+\n \terr = __check_mem_access(env, reg, argno, reg_umax(reg) + off, size,\n \t\t\t\t mem_size, zero_size_allowed);\n \tif (err) {\n \t\tverbose(env, \"%s max value is outside of the allowed memory range\\n\",\n \t\t\treg_arg_name(env, argno));\n-\t\treturn err;\n+\t\tif (bpf_diag_enabled(env)) {\n+\t\t\tmax_start = (s64)reg_umax(reg) + off;\n+\t\t\tmax_end = max_start + size;\n+\t\t\tproof = bpf_diag_fmt(\n+\t\t\t\tenv, \"the maximal bound for a memory access is %lld: start %lld + access_size %d, beyond object_size %u\",\n+\t\t\t\tmax_end, max_start, size, mem_size);\n+\t\t}\n+\t\tgoto report_error;\n \t}\n \n \treturn 0;\n+\n+report_error:\n+\tbpf_diag_mem_bounds(env, env-\u003einsn_idx, reg_from_argno(argno),\n+\t\t\t reg_arg_name(env, argno), reg_type_str(env, reg-\u003etype), proof,\n+\t\t\t\t off, size, mem_size, reg);\n+\treturn err;\n }\n \n static int __check_ptr_off_reg(struct bpf_verifier_env *env,\n@@ -4243,6 +4438,9 @@ static int __check_ptr_off_reg(struct bpf_verifier_env *env,\n \tif (!fixed_off_ok \u0026\u0026 reg-\u003evar_off.value != 0) {\n \t\tverbose(env, \"dereference of modified %s ptr %s off=%lld disallowed\\n\",\n \t\t\treg_type_str(env, reg-\u003etype), reg_arg_name(env, argno), reg-\u003evar_off.value);\n+\t\tbpf_diag_invalid_deref(env, env-\u003einsn_idx, reg_from_argno(argno),\n+\t\t\t\t reg_arg_name(env, argno), reg,\n+\t\t\t\t\t BPF_DIAG_DEREF_MODIFIED_PTR, reg-\u003evar_off.value);\n \t\treturn -EACCES;\n \t}\n \n@@ -6188,6 +6386,9 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b\n \t\tif (type_may_be_null(reg-\u003etype)) {\n \t\t\tverbose(env, \"%s invalid mem access '%s'\\n\", reg_arg_name(env, argno),\n \t\t\t\treg_type_str(env, reg-\u003etype));\n+\t\t\tbpf_diag_invalid_deref(env, insn_idx, reg_from_argno(argno),\n+\t\t\t\t\t reg_arg_name(env, argno), reg,\n+\t\t\t\t\t\t BPF_DIAG_DEREF_NULLABLE_PTR, 0);\n \t\t\treturn -EACCES;\n \t\t}\n \n@@ -6338,8 +6539,16 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b\n \t\tif (t == BPF_READ \u0026\u0026 value_regno \u003e= 0)\n \t\t\tmark_reg_unknown(env, regs, value_regno);\n \t} else {\n+\t\tenum bpf_diag_invalid_deref_kind kind = BPF_DIAG_DEREF_INVALID_PTR;\n+\n \t\tverbose(env, \"%s invalid mem access '%s'\\n\", reg_arg_name(env, argno),\n \t\t\treg_type_str(env, reg-\u003etype));\n+\t\tif (reg-\u003etype == SCALAR_VALUE)\n+\t\t\tkind = BPF_DIAG_DEREF_SCALAR;\n+\t\telse if (type_may_be_null(reg-\u003etype))\n+\t\t\tkind = BPF_DIAG_DEREF_NULLABLE_PTR;\n+\t\tbpf_diag_invalid_deref(env, insn_idx, reg_from_argno(argno),\n+\t\t\t\t reg_arg_name(env, argno), reg, kind, 0);\n \t\treturn -EACCES;\n \t}\n \n@@ -6399,15 +6608,19 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \n \tsrc_reg_type = regs[insn-\u003esrc_reg].type;\n \n-\t/* Check if (src_reg + off) is readable. The state of dst_reg will be\n-\t * updated by this call.\n+\t/*\n+\t * check_stack_read_fixed_off() may refine the modification's origin to\n+\t * the source stack slot.\n \t */\n+\tbpf_diag_mod_begin(env, \u0026regs[insn-\u003edst_reg], NULL, BPF_DIAG_MOD_WRITE);\n \terr = check_mem_access(env, env-\u003einsn_idx, regs + insn-\u003esrc_reg, argno_from_reg(insn-\u003esrc_reg), insn-\u003eoff,\n \t\t\t BPF_SIZE(insn-\u003ecode), BPF_READ, insn-\u003edst_reg,\n \t\t\t strict_alignment_once, is_ldsx);\n \terr = err ?: save_aux_ptr_type(env, src_reg_type,\n \t\t\t\t allow_trust_mismatch);\n \terr = err ?: reg_bounds_sanity_check(env, \u0026regs[insn-\u003edst_reg], ctx);\n+\tif (!err)\n+\t\tbpf_diag_mod_end(env);\n \n \treturn err;\n }\n@@ -6513,10 +6726,14 @@ static int check_atomic_rmw(struct bpf_verifier_env *env,\n \t */\n \terr = check_mem_access(env, env-\u003einsn_idx, dst_reg, argno_from_reg(insn-\u003edst_reg), insn-\u003eoff,\n \t\t\t BPF_SIZE(insn-\u003ecode), BPF_READ, -1, true, false);\n-\tif (!err \u0026\u0026 load_reg \u003e= 0)\n+\tif (!err \u0026\u0026 load_reg \u003e= 0) {\n+\t\tbpf_diag_mod_begin(env, cur_regs(env) + load_reg, NULL, BPF_DIAG_MOD_WRITE);\n \t\terr = check_mem_access(env, env-\u003einsn_idx, dst_reg, argno_from_reg(insn-\u003edst_reg),\n \t\t\t\t insn-\u003eoff, BPF_SIZE(insn-\u003ecode),\n \t\t\t\t BPF_READ, load_reg, true, false);\n+\t\tif (!err)\n+\t\t\tbpf_diag_mod_end(env);\n+\t}\n \tif (err)\n \t\treturn err;\n \n@@ -6734,14 +6951,17 @@ static int check_stack_range_initialized(\n \treturn 0;\n }\n \n-static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\n-\t\t\t\t int access_size, enum bpf_access_type access_type,\n-\t\t\t\t bool zero_size_allowed,\n-\t\t\t\t struct bpf_call_arg_meta *meta)\n+static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n+\t\t\t\t argno_t argno, int access_size,\n+\t\t\t\t enum bpf_access_type access_type, bool zero_size_allowed,\n+\t\t\t\t struct bpf_call_arg_meta *meta, bool *known_memory)\n {\n \tstruct bpf_reg_state *regs = cur_regs(env);\n \tu32 *max_access;\n \n+\tif (known_memory)\n+\t\t*known_memory = true;\n+\n \tswitch (base_type(reg-\u003etype)) {\n \tcase PTR_TO_PACKET:\n \tcase PTR_TO_PACKET_META:\n@@ -6811,6 +7031,8 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_\n \t\tif (zero_size_allowed \u0026\u0026 access_size == 0 \u0026\u0026\n \t\t bpf_register_is_null(reg))\n \t\t\treturn 0;\n+\t\tif (known_memory \u0026\u0026 base_type(reg-\u003etype) != PTR_TO_CTX)\n+\t\t\t*known_memory = false;\n \n \t\tverbose(env, \"%s type=%s \", reg_arg_name(env, argno),\n \t\t\treg_type_str(env, reg-\u003etype));\n@@ -6819,6 +7041,12 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_\n \t}\n }\n \n+enum bpf_mem_size_failure {\n+\tBPF_MEM_SIZE_FAIL_NONE,\n+\tBPF_MEM_SIZE_FAIL_MEMORY,\n+\tBPF_MEM_SIZE_FAIL_SIZE,\n+};\n+\n /* verify arguments to helpers or kfuncs consisting of a pointer and an access\n * size.\n *\n@@ -6829,10 +7057,14 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,\n \t\t\t struct bpf_reg_state *size_reg, argno_t mem_argno,\n \t\t\t argno_t size_argno, u32 access_type,\n \t\t\t bool zero_size_allowed,\n-\t\t\t struct bpf_call_arg_meta *meta)\n+\t\t\t struct bpf_call_arg_meta *meta,\n+\t\t\t enum bpf_mem_size_failure *failure)\n {\n \tint err = 0;\n \n+\tif (failure)\n+\t\t*failure = BPF_MEM_SIZE_FAIL_NONE;\n+\n \t/* This is used to refine r0 return value bounds for helpers\n \t * that enforce this value as an upper bound on return values.\n \t * See do_refine_retval_range() for helpers that can refine\n@@ -6854,27 +7086,32 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,\n \tif (reg_smin(size_reg) \u003c 0) {\n \t\tverbose(env, \"%s min value is negative, either use unsigned or 'var \u0026= const'\\n\",\n \t\t\treg_arg_name(env, size_argno));\n-\t\treturn -EACCES;\n+\t\terr = -EACCES;\n+\t\tgoto size_error;\n \t}\n \n \tif (reg_umin(size_reg) == 0 \u0026\u0026 !zero_size_allowed) {\n \t\tverbose(env, \"%s invalid zero-sized read: u64=[%lld,%lld]\\n\",\n \t\t\treg_arg_name(env, size_argno), reg_umin(size_reg), reg_umax(size_reg));\n-\t\treturn -EACCES;\n+\t\terr = -EACCES;\n+\t\tgoto size_error;\n \t}\n \n \tif (reg_umax(size_reg) \u003e= BPF_MAX_VAR_SIZ) {\n \t\tverbose(env, \"%s unbounded memory access, use 'var \u0026= const' or 'if (var \u003c const)'\\n\",\n \t\t\treg_arg_name(env, size_argno));\n-\t\treturn -EACCES;\n+\t\terr = -EACCES;\n+\t\tgoto size_error;\n \t}\n \n \tif (access_type \u0026 BPF_READ)\n \t\terr = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),\n-\t\t\t\t\t BPF_READ, zero_size_allowed, meta);\n+\t\t\t\t\t BPF_READ, zero_size_allowed, meta, NULL);\n \tif (!err \u0026\u0026 access_type \u0026 BPF_WRITE)\n \t\terr = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),\n-\t\t\t\t\t BPF_WRITE, zero_size_allowed, meta);\n+\t\t\t\t\t BPF_WRITE, zero_size_allowed, meta, NULL);\n+\tif (err \u0026\u0026 failure)\n+\t\t*failure = BPF_MEM_SIZE_FAIL_MEMORY;\n \n \tif (!err) {\n \t\tint regno = reg_from_argno(size_argno);\n@@ -6886,16 +7123,23 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,\n \t}\n \n \treturn err;\n+\n+size_error:\n+\tif (failure)\n+\t\t*failure = BPF_MEM_SIZE_FAIL_SIZE;\n+\treturn err;\n }\n \n static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n \t\t\t argno_t argno, u32 mem_size, enum bpf_access_type access_type,\n-\t\t\t struct bpf_call_arg_meta *meta)\n+\t\t\t struct bpf_call_arg_meta *meta, bool *known_memory)\n {\n \tint size, err = 0;\n \n \tif (bpf_register_is_null(reg))\n \t\treturn 0;\n+\tif (known_memory)\n+\t\t*known_memory = true;\n \n \tif (mem_size \u003e S32_MAX) {\n \t\tverbose(env, \"%s memory size %u is too large\\n\",\n@@ -6910,9 +7154,11 @@ static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg\n \tsize = (!meta \u0026\u0026 base_type(reg-\u003etype) == PTR_TO_STACK) ? -(int)mem_size : mem_size;\n \n \tif (access_type \u0026 BPF_READ)\n-\t\terr = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta);\n+\t\terr = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta,\n+\t\t\t\t\t known_memory);\n \tif (!err \u0026\u0026 (access_type \u0026 BPF_WRITE))\n-\t\terr = check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, meta);\n+\t\terr = check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, meta,\n+\t\t\t\t\t known_memory);\n \n \treturn err;\n }\n@@ -7013,6 +7259,7 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state\n \tbool is_lock = flags \u0026 PROCESS_SPIN_LOCK, is_res_lock = flags \u0026 PROCESS_RES_LOCK;\n \tconst char *lock_str = is_res_lock ? \"bpf_res_spin\" : \"bpf_spin\";\n \tstruct bpf_verifier_state *cur = env-\u003ecur_state;\n+\tstruct bpf_reference_state *lock;\n \tbool is_const = tnum_is_const(reg-\u003evar_off);\n \tbool is_irq = flags \u0026 PROCESS_LOCK_IRQ;\n \tu64 val = reg-\u003evar_off.value;\n@@ -7062,14 +7309,25 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state\n \t\t\tptr = btf;\n \n \t\tif (!is_res_lock \u0026\u0026 cur-\u003eactive_locks) {\n-\t\t\tif (find_lock_state(env-\u003ecur_state, REF_TYPE_LOCK, 0, NULL)) {\n+\t\t\tlock = find_lock_state(cur, REF_TYPE_LOCK, 0, NULL);\n+\t\t\tif (lock) {\n \t\t\t\tverbose(env,\n \t\t\t\t\t\"Locking two bpf_spin_locks are not allowed\\n\");\n+\t\t\t\tbpf_diag_lock(\n+\t\t\t\t\tenv, env-\u003einsn_idx, \"nested spin lock\",\n+\t\t\t\t\t\"This path already holds a bpf_spin_lock. The verifier allows only one regular BPF spin lock at a time.\",\n+\t\t\t\t\t\"Unlock the current bpf_spin_lock before taking another one.\", lock);\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t} else if (is_res_lock \u0026\u0026 cur-\u003eactive_locks) {\n-\t\t\tif (find_lock_state(env-\u003ecur_state, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ, reg-\u003eid, ptr)) {\n+\t\t\tlock = find_lock_state(cur, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ,\n+\t\t\t\t\t reg-\u003eid, ptr);\n+\t\t\tif (lock) {\n \t\t\t\tverbose(env, \"Acquiring the same lock again, AA deadlock detected\\n\");\n+\t\t\t\tbpf_diag_lock(\n+\t\t\t\t\tenv, env-\u003einsn_idx, \"recursive resource spin lock\",\n+\t\t\t\t\t\"This path already holds the same resource spin lock. Taking it again would deadlock.\",\n+\t\t\t\t\t\"Avoid reacquiring the same resource spin lock before it is unlocked.\", lock);\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t}\n@@ -7096,6 +7354,10 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state\n \n \t\tif (!cur-\u003eactive_locks) {\n \t\t\tverbose(env, \"%s_unlock without taking a lock\\n\", lock_str);\n+\t\t\tbpf_diag_res(\n+\t\t\t\tenv, env-\u003einsn_idx, \"unlock without lock\",\n+\t\t\t\t\"This unlock operation has no matching active lock on the current path.\",\n+\t\t\t\t\"Take the matching lock before this unlock, or remove the unmatched unlock path.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \n@@ -7105,16 +7367,35 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state\n \t\t\ttype = REF_TYPE_RES_LOCK;\n \t\telse\n \t\t\ttype = REF_TYPE_LOCK;\n-\t\tif (!find_lock_state(cur, type, reg-\u003eid, ptr)) {\n+\n+\t\tlock = find_lock_state(cur, type, reg-\u003eid, ptr);\n+\t\tif (!lock) {\n \t\t\tverbose(env, \"%s_unlock of different lock\\n\", lock_str);\n+\t\t\tlock = find_lock_state(cur, REF_TYPE_LOCK_MASK, cur-\u003eactive_lock_id,\n+\t\t\t\t\t cur-\u003eactive_lock_ptr);\n+\t\t\tbpf_diag_lock(\n+\t\t\t\tenv, env-\u003einsn_idx, \"unlock of a different lock\",\n+\t\t\t\t\"This unlock does not match any active lock with the same tracked identity on the current path.\",\n+\t\t\t\t\"Unlock the same lock object that was most recently acquired.\", lock);\n \t\t\treturn -EINVAL;\n \t\t}\n \t\tif (reg-\u003eid != cur-\u003eactive_lock_id || ptr != cur-\u003eactive_lock_ptr) {\n \t\t\tverbose(env, \"%s_unlock cannot be out of order\\n\", lock_str);\n+\t\t\tlock = find_lock_state(cur, REF_TYPE_LOCK_MASK, cur-\u003eactive_lock_id,\n+\t\t\t\t\t cur-\u003eactive_lock_ptr);\n+\t\t\tbpf_diag_lock(\n+\t\t\t\tenv, env-\u003einsn_idx, \"unlock out of order\",\n+\t\t\t\t\"Locks must be released in last-in, first-out order, but this unlock does not match the currently active lock.\",\n+\t\t\t\t\"Release nested locks in the reverse order they were acquired.\", lock);\n \t\t\treturn -EINVAL;\n \t\t}\n-\t\tif (release_lock_state(cur, type, reg-\u003eid, ptr)) {\n+\t\tif (release_lock_state(env, type, reg-\u003eid, ptr)) {\n \t\t\tverbose(env, \"%s_unlock of different lock\\n\", lock_str);\n+\t\t\tbpf_diag_lock(\n+\t\t\t\tenv, env-\u003einsn_idx, \"unlock of a different lock\",\n+\t\t\t\t\"The verifier could not release a lock state matching this unlock operation.\",\n+\t\t\t\t\"Pass the same lock object and lock kind that were used for the matching lock operation.\",\n+\t\t\t\tlock);\n \t\t\treturn -EINVAL;\n \t\t}\n \t\tif (!in_rcu_cs(env))\n@@ -7237,6 +7518,12 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,\n \treturn 0;\n }\n \n+static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,\n+\t\t\t const char *call_name, const char *reason, const char *suggestion);\n+__printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t\t\t\t argno_t argno, const char *call_name,\n+\t\t\t\t\t\t const char *suggestion, const char *fmt, ...);\n+\n /*\n * Validate dynptr arguments for helper, kfunc and subprog.\n *\n@@ -7261,7 +7548,8 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,\n * and checked dynamically during runtime.\n */\n static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n-\t\t\t argno_t argno, int insn_idx, enum bpf_arg_type arg_type,\n+\t\t\t argno_t argno, int insn_idx, const char *call_name,\n+\t\t\t enum bpf_arg_type arg_type,\n \t\t\t struct ref_obj_desc *ref_obj, struct bpf_dynptr_desc *dynptr)\n {\n \tint spi, err = 0;\n@@ -7270,6 +7558,11 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat\n \t\tverbose(env,\n \t\t\t\"%s expected pointer to stack or const struct bpf_dynptr\\n\",\n \t\t\treg_arg_name(env, argno));\n+\t\tbpf_diag_call_arg_fmt(\n+\t\t\tenv, insn_idx, argno, call_name,\n+\t\t\t\"Pass the address of a stack dynptr object, or use a const dynptr pointer returned by the verifier-supported path.\",\n+\t\t\t\"a dynptr argument must be a pointer to a dynptr stack slot or a verifier-provided const struct bpf_dynptr, but %s is %s\",\n+\t\t\treg_arg_name(env, argno), bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\treturn -EINVAL;\n \t}\n \n@@ -7292,6 +7585,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat\n \n \t\tif (!is_dynptr_reg_valid_uninit(env, reg)) {\n \t\t\tverbose(env, \"Dynptr has to be an uninitialized dynptr\\n\");\n+\t\t\tbpf_diag_res(\n+\t\t\t\tenv, insn_idx, \"dynptr is already initialized\",\n+\t\t\t\t\"This kfunc constructs a dynptr and requires an uninitialized dynptr stack slot, but the selected slot already holds dynptr state.\",\n+\t\t\t\t\"Use a fresh stack dynptr slot, or release/destroy the existing dynptr before reusing the slot.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \n@@ -7308,21 +7605,35 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat\n \t\t/* For the reg-\u003etype == PTR_TO_STACK case, bpf_dynptr is never const */\n \t\tif (reg-\u003etype == CONST_PTR_TO_DYNPTR \u0026\u0026 (arg_type \u0026 OBJ_RELEASE)) {\n \t\t\tverbose(env, \"CONST_PTR_TO_DYNPTR cannot be released\\n\");\n+\t\t\tbpf_diag_res(\n+\t\t\t\tenv, insn_idx, \"const dynptr release\",\n+\t\t\t\t\"This release operation was given a const dynptr. Const dynptr values are verifier-provided views and cannot be released by the program.\",\n+\t\t\t\t\"Release only mutable dynptrs that the program initialized or reserved.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \n \t\tif (!is_dynptr_reg_valid_init(env, reg)) {\n \t\t\tverbose(env, \"Expected an initialized dynptr as %s\\n\",\n \t\t\t\treg_arg_name(env, argno));\n+\t\t\tbpf_diag_res(\n+\t\t\t\tenv, insn_idx, \"uninitialized dynptr use\",\n+\t\t\t\t\"This operation requires an initialized dynptr, but the stack slot does not currently hold a valid dynptr on this path.\",\n+\t\t\t\t\"Initialize the dynptr on every path before this call, and avoid overwriting or releasing it before this use.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \n \t\t/* Fold modifiers (in this case, OBJ_RELEASE) when checking expected type */\n \t\tif (!is_dynptr_type_expected(env, reg, arg_type \u0026 ~OBJ_RELEASE)) {\n-\t\t\tverbose(env,\n-\t\t\t\t\"Expected a dynptr of type %s as %s\\n\",\n-\t\t\t\tdynptr_type_str(arg_to_dynptr_type(arg_type)),\n-\t\t\t\treg_arg_name(env, argno));\n+\t\t\tenum bpf_dynptr_type expected_type = arg_to_dynptr_type(arg_type);\n+\t\t\tenum bpf_dynptr_type actual_type = dynptr_reg_type(env, reg);\n+\n+\t\t\tverbose(env, \"Expected a dynptr of type %s as %s\\n\",\n+\t\t\t\tdynptr_type_str(expected_type), reg_arg_name(env, argno));\n+\t\t\tbpf_diag_call_arg_fmt(\n+\t\t\t\tenv, insn_idx, argno, call_name,\n+\t\t\t\t\"Use a dynptr constructor that matches this operation, or call an operation that accepts the dynptr's current type.\",\n+\t\t\t\t\"the dynptr is initialized with backing object type %s, but this operation expects dynptr type %s\",\n+\t\t\t\tdynptr_type_str(actual_type), dynptr_type_str(expected_type));\n \t\t\treturn -EINVAL;\n \t\t}\n \n@@ -7386,6 +7697,11 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *\n \tif (reg-\u003etype != PTR_TO_STACK) {\n \t\tverbose(env, \"%s expected pointer to an iterator on stack\\n\",\n \t\t\treg_arg_name(env, argno));\n+\t\tbpf_diag_call_arg_fmt(\n+\t\t\tenv, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t\"Pass the address of a stack iterator object for iterator new, next, and destroy calls.\",\n+\t\t\t\"iterator state must live in verifier-tracked stack memory, but %s is %s\",\n+\t\t\treg_arg_name(env, argno), bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\treturn -EINVAL;\n \t}\n \n@@ -7399,6 +7715,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *\n \tif (btf_id \u003c 0) {\n \t\tverbose(env, \"expected valid iter pointer as %s\\n\",\n \t\t\treg_arg_name(env, argno));\n+\t\tbpf_diag_call_arg(\n+\t\t\tenv, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t\"the kfunc expects a recognized iterator state pointer, but this argument does not match a valid iterator type\",\n+\t\t\t\"Pass the exact iterator state type expected by this kfunc.\");\n \t\treturn -EINVAL;\n \t}\n \tt = btf_type_by_id(meta-\u003ebtf, btf_id);\n@@ -7409,6 +7729,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *\n \t\tif (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {\n \t\t\tverbose(env, \"expected uninitialized iter_%s as %s\\n\",\n \t\t\t\titer_type_str(meta-\u003ebtf, btf_id), reg_arg_name(env, argno));\n+\t\t\tbpf_diag_res(\n+\t\t\t\tenv, insn_idx, \"iterator is already initialized\",\n+\t\t\t\t\"Iterator creation requires an uninitialized iterator stack object, but this stack range already contains iterator state.\",\n+\t\t\t\t\"Use a fresh iterator stack slot, or destroy the existing iterator before reusing the slot.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \n@@ -7433,9 +7757,16 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *\n \t\tcase -EINVAL:\n \t\t\tverbose(env, \"expected an initialized iter_%s as %s\\n\",\n \t\t\t\titer_type_str(meta-\u003ebtf, btf_id), reg_arg_name(env, argno));\n+\t\t\tbpf_diag_res(\n+\t\t\t\tenv, insn_idx, \"uninitialized iterator use\",\n+\t\t\t\t\"This iterator operation requires an initialized iterator state object, but the stack range does not contain a live iterator on this path.\",\n+\t\t\t\t\"Call the matching iterator new kfunc on every path before calling next or destroy, and do not destroy the iterator before this use.\");\n \t\t\treturn err;\n \t\tcase -EPROTO:\n \t\t\tverbose(env, \"expected an RCU CS when using %s\\n\", meta-\u003efunc_name);\n+\t\t\tbpf_diag_ctx_required(\n+\t\t\t\tenv, insn_idx, meta-\u003efunc_name, BPF_DIAG_CONTEXT_RCU,\n+\t\t\t\t\"Wrap iterator use in bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.\");\n \t\t\treturn err;\n \t\tdefault:\n \t\t\treturn err;\n@@ -7855,13 +8186,70 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {\n \t[ARG_PTR_TO_DYNPTR]\t\t= \u0026dynptr_types,\n };\n \n+static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,\n+\t\t\t const char *call_name, const char *reason,\n+\t\t\t const char *suggestion)\n+{\n+\tint arg = arg_from_argno(argno);\n+\tint regno = reg_from_argno(argno);\n+\tint stack_slot = -1;\n+\n+\tif (arg \u003c 0 \u0026\u0026 regno \u003e= BPF_REG_1 \u0026\u0026 regno \u003c= BPF_REG_5)\n+\t\targ = regno;\n+\tif (arg \u003e MAX_BPF_FUNC_REG_ARGS)\n+\t\tstack_slot = arg - MAX_BPF_FUNC_REG_ARGS - 1;\n+\n+\tbpf_diag_call_type(env, insn_idx, arg, regno, stack_slot,\n+\t\t\t call_name \u0026\u0026 *call_name ? call_name : \"call\",\n+\t\t\t reg_arg_name(env, argno), reason, suggestion);\n+}\n+\n+static const char *bpf_diag_arg_name(struct bpf_verifier_env *env, argno_t argno)\n+{\n+\treturn bpf_diag_fmt(env, \"%s\", reg_arg_name(env, argno));\n+}\n+\n+__printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u32 insn_idx,\n+\t\t\t\t\t\t argno_t argno, const char *call_name,\n+\t\t\t\t\t\t const char *suggestion, const char *fmt, ...)\n+{\n+\tconst char *reason;\n+\tva_list args;\n+\n+\tva_start(args, fmt);\n+\treason = bpf_diag_vfmt(env, fmt, args);\n+\tva_end(args);\n+\n+\tbpf_diag_call_arg(env, insn_idx, argno, call_name, reason, suggestion);\n+}\n+\n+static const char *bpf_diag_expected_reg_types(struct bpf_verifier_env *env,\n+\t\t\t\t\t const enum bpf_reg_type *types, int count)\n+{\n+\tsize_t len = 0, size = 1;\n+\tchar *buf;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c count; i++)\n+\t\tsize += strlen(reg_type_str(env, types[i])) + (i ? 2 : 0);\n+\n+\tbuf = bpf_diag_fmt_buf(env, size);\n+\tif (!buf)\n+\t\treturn \"\";\n+\n+\tfor (i = 0; i \u003c count; i++)\n+\t\tlen += scnprintf(buf + len, size - len, \"%s%s\", i ? \", \" : \"\",\n+\t\t\t\t reg_type_str(env, types[i]));\n+\treturn buf;\n+}\n+\n static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\n-\t\t\t enum bpf_arg_type arg_type,\n-\t\t\t const u32 *arg_btf_id,\n-\t\t\t struct bpf_call_arg_meta *meta)\n+\t\t\t enum bpf_arg_type arg_type, const u32 *arg_btf_id,\n+\t\t\t struct bpf_call_arg_meta *meta, const char *call_name)\n {\n \tenum bpf_reg_type expected, type = reg-\u003etype;\n \tconst struct bpf_reg_types *compatible;\n+\tconst char *actual, *accepted;\n \tint i, j, err;\n \n \tcompatible = compatible_reg_types[base_type(arg_type)];\n@@ -7908,6 +8296,12 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re\n \tfor (j = 0; j + 1 \u003c i; j++)\n \t\tverbose(env, \"%s, \", reg_type_str(env, compatible-\u003etypes[j]));\n \tverbose(env, \"%s\\n\", reg_type_str(env, compatible-\u003etypes[j]));\n+\tactual = bpf_diag_fmt(env, \"%s\", reg_type_str(env, reg-\u003etype));\n+\taccepted = bpf_diag_expected_reg_types(env, compatible-\u003etypes, i);\n+\tbpf_diag_call_arg_fmt(env, env-\u003einsn_idx, argno, call_name,\n+\t\t\t \"Pass a value with one of the accepted pointer or scalar types for this call.\",\n+\t\t\t \"it has type %s, but this argument accepts %s\",\n+\t\t\t actual, accepted);\n \treturn -EACCES;\n \n found:\n@@ -7944,6 +8338,10 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re\n \t\t (!type_may_be_null(arg_type) || arg_type_is_release(arg_type))) {\n \t\t\tverbose(env, \"Possibly NULL pointer passed to helper %s\\n\",\n \t\t\t\treg_arg_name(env, argno));\n+\t\t\tbpf_diag_call_arg(\n+\t\t\t\tenv, env-\u003einsn_idx, argno, call_name,\n+\t\t\t\t\"the pointer may be NULL, but this call requires a non-NULL pointer\",\n+\t\t\t\t\"Add a NULL check and make the call only on the non-NULL path.\");\n \t\t\treturn -EACCES;\n \t\t}\n \n@@ -8330,7 +8728,8 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK)\n \t\targ_btf_id = fn-\u003earg_btf_id[arg];\n \n-\terr = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta);\n+\terr = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta,\n+\t\t\t func_id_name(meta-\u003efunc_id));\n \tif (err)\n \t\treturn err;\n \n@@ -8343,6 +8742,10 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t !reg_is_referenced(env, reg) \u0026\u0026 !bpf_register_is_null(reg)) {\n \t\tverbose(env, \"release helper %s expects referenced PTR_TO_BTF_ID passed to %s\\n\",\n \t\t\tfunc_id_name(meta-\u003efunc_id), reg_arg_name(env, argno));\n+\t\tbpf_diag_call_arg(\n+\t\t\tenv, insn_idx, argno, func_id_name(meta-\u003efunc_id),\n+\t\t\t\"release helpers require a value that owns a live resource returned by a matching acquire helper\",\n+\t\t\t\"Pass the resource-owning pointer returned by the matching acquire helper, and avoid calling the release helper after ownership has already been transferred or released.\");\n \t\treturn -EINVAL;\n \t}\n \n@@ -8371,7 +8774,8 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t\t\treturn -EFAULT;\n \t\t}\n \t\tkey_size = meta-\u003emap.ptr-\u003ekey_size;\n-\t\terr = check_helper_mem_access(env, reg, argno, key_size, BPF_READ, false, NULL);\n+\t\terr = check_helper_mem_access(env, reg, argno, key_size, BPF_READ, false, NULL,\n+\t\t\t\t\t NULL);\n \t\tif (err)\n \t\t\treturn err;\n \t\tif (can_elide_value_nullness(meta-\u003emap.ptr)) {\n@@ -8408,7 +8812,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \n \t\terr = check_helper_mem_access(env, reg, argno, meta-\u003emap.ptr-\u003evalue_size,\n \t\t\t\t\t arg_type \u0026 MEM_WRITE ? BPF_WRITE : BPF_READ,\n-\t\t\t\t\t false, meta);\n+\t\t\t\t\t false, meta, NULL);\n \t\tbreak;\n \tcase ARG_PTR_TO_PERCPU_BTF_ID:\n \t\tif (!reg-\u003ebtf_id) {\n@@ -8450,7 +8854,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t\t */\n \t\tif (arg_type \u0026 MEM_FIXED_SIZE) {\n \t\t\terr = check_mem_reg(env, reg, argno_from_reg(regno), fn-\u003earg_size[arg],\n-\t\t\t\t\t arg_type \u0026 MEM_WRITE ? BPF_WRITE : BPF_READ, meta);\n+\t\t\t\t\t arg_type \u0026 MEM_WRITE ? BPF_WRITE : BPF_READ, meta, NULL);\n \t\t\tif (err)\n \t\t\t\treturn err;\n \t\t\tif (arg_type \u0026 MEM_ALIGNED)\n@@ -8461,17 +8865,17 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t\terr = check_mem_size_reg(env, reg_state(env, regno - 1), reg,\n \t\t\t\t\t argno_from_reg(regno - 1), argno,\n \t\t\t\t\t fn-\u003earg_type[arg - 1] \u0026 MEM_WRITE ? BPF_WRITE : BPF_READ,\n-\t\t\t\t\t false, meta);\n+\t\t\t\t\t false, meta, NULL);\n \t\tbreak;\n \tcase ARG_MEM_SIZE_OR_ZERO:\n \t\terr = check_mem_size_reg(env, reg_state(env, regno - 1), reg,\n \t\t\t\t\t argno_from_reg(regno - 1), argno,\n \t\t\t\t\t fn-\u003earg_type[arg - 1] \u0026 MEM_WRITE ? BPF_WRITE : BPF_READ,\n-\t\t\t\t\t true, meta);\n+\t\t\t\t\t true, meta, NULL);\n \t\tbreak;\n \tcase ARG_PTR_TO_DYNPTR:\n-\t\terr = process_dynptr_func(env, reg, argno, insn_idx, arg_type, \u0026meta-\u003eref_obj,\n-\t\t\t\t\t \u0026meta-\u003edynptr);\n+\t\terr = process_dynptr_func(env, reg, argno, insn_idx, func_id_name(meta-\u003efunc_id),\n+\t\t\t\t\t arg_type, \u0026meta-\u003eref_obj, \u0026meta-\u003edynptr);\n \t\tif (err)\n \t\t\treturn err;\n \t\tbreak;\n@@ -8918,8 +9322,10 @@ static void clear_all_pkt_pointers(struct bpf_verifier_env *env)\n \tstruct bpf_reg_state *reg;\n \n \tbpf_for_each_reg_in_vstate(env-\u003ecur_state, state, reg, ({\n-\t\tif (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg))\n+\t\tif (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) {\n+\t\t\tbpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_PKT_DATA_CHANGE);\n \t\t\tmark_reg_invalid(env, reg);\n+\t\t}\n \t}));\n }\n \n@@ -8949,7 +9355,7 @@ static void mark_pkt_end(struct bpf_verifier_state *vstate, int regn, bool range\n \t\treg-\u003erange = AT_PKT_END;\n }\n \n-static int release_reference_nomark(struct bpf_verifier_state *state, int id)\n+static int __release_reference_nomark(struct bpf_verifier_state *state, int id)\n {\n \tint i;\n \n@@ -8964,6 +9370,16 @@ static int release_reference_nomark(struct bpf_verifier_state *state, int id)\n \treturn -EINVAL;\n }\n \n+static int release_reference_nomark(struct bpf_verifier_env *env, int id)\n+{\n+\tint err;\n+\n+\terr = __release_reference_nomark(env-\u003ecur_state, id);\n+\tif (!err)\n+\t\tbpf_diag_record_ref_release(env, env-\u003einsn_idx, id);\n+\treturn err;\n+}\n+\n static int idstack_push(struct bpf_idmap *idmap, u32 id)\n {\n \tint i;\n@@ -9006,8 +9422,10 @@ static int release_reference(struct bpf_verifier_env *env, int id)\n \tif (err)\n \t\treturn err;\n \n-\tif (find_reference_state(vstate, id))\n-\t\tWARN_ON_ONCE(release_reference_nomark(vstate, id));\n+\tif (find_reference_state(vstate, id)) {\n+\t\terr = release_reference_nomark(env, id);\n+\t\tWARN_ON_ONCE(err);\n+\t}\n \n \twhile ((id = idstack_pop(idstack))) {\n \t\t/*\n@@ -9035,10 +9453,25 @@ static int release_reference(struct bpf_verifier_env *env, int id)\n \t\t\t\t\treturn err;\n \t\t\t}\n \n+\t\t\t/*\n+\t\t\t * A dynptr occupies two stack slots that invalidate_dynptr()\n+\t\t\t * clears together. Record both scrubs before invalidating it.\n+\t\t\t */\n+\t\t\tif (stack \u0026\u0026 stack-\u003eslot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR) {\n+\t\t\t\tstruct bpf_stack_state *dyn_stack = stack;\n+\n+\t\t\t\tif (reg-\u003edynptr.first_slot)\n+\t\t\t\t\tdyn_stack--;\n+\t\t\t\tbpf_diag_record_scrub(env, \u0026dyn_stack[0].spilled_ptr,\n+\t\t\t\t\t\t BPF_DIAG_MOD_REF_RELEASE);\n+\t\t\t\tbpf_diag_record_scrub(env, \u0026dyn_stack[1].spilled_ptr,\n+\t\t\t\t\t\t BPF_DIAG_MOD_REF_RELEASE);\n+\t\t\t\tinvalidate_dynptr(env, dyn_stack);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tbpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_REF_RELEASE);\n \t\t\tif (!stack || stack-\u003eslot_type[BPF_REG_SIZE - 1] == STACK_SPILL)\n \t\t\t\tmark_reg_invalid(env, reg);\n-\t\t\telse if (stack-\u003eslot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR)\n-\t\t\t\tinvalidate_dynptr(env, stack);\n \t\t}));\n \t}\n \n@@ -9051,8 +9484,10 @@ static void invalidate_non_owning_refs(struct bpf_verifier_env *env)\n \tstruct bpf_reg_state *reg;\n \n \tbpf_for_each_reg_in_vstate(env-\u003ecur_state, unused, reg, ({\n-\t\tif (type_is_non_owning_ref(reg-\u003etype))\n+\t\tif (type_is_non_owning_ref(reg-\u003etype)) {\n+\t\t\tbpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_NON_OWN_REF);\n \t\t\tmark_reg_invalid(env, reg);\n+\t\t}\n \t}));\n }\n \n@@ -9065,8 +9500,10 @@ static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)\n \n \tbpf_for_each_reg_in_vstate_mask(env-\u003ecur_state, state, reg, stack, clear_mask, ({\n \t\tif (reg-\u003etype \u0026 MEM_RCU) {\n+\t\t\tbpf_diag_mod_begin(env, reg, NULL, BPF_DIAG_MOD_WRITE);\n \t\t\treg-\u003etype \u0026= ~(MEM_RCU | PTR_MAYBE_NULL);\n \t\t\treg-\u003etype |= PTR_UNTRUSTED;\n+\t\t\tbpf_diag_mod_end(env);\n \t\t}\n \t}));\n }\n@@ -9077,15 +9514,19 @@ static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 id)\n \tstruct bpf_reg_state *reg;\n \tint err;\n \n-\terr = release_reference_nomark(env-\u003ecur_state, id);\n+\terr = release_reference_nomark(env, id);\n+\tif (err)\n+\t\treturn err;\n \n \tbpf_for_each_reg_in_vstate(env-\u003ecur_state, state, reg, ({\n \t\tif (reg-\u003eid != id)\n \t\t\tcontinue;\n \t\tif ((reg-\u003etype \u0026 MEM_ALLOC) \u0026\u0026 (reg-\u003etype \u0026 MEM_PERCPU)) {\n+\t\t\tbpf_diag_mod_begin(env, reg, NULL, BPF_DIAG_MOD_WRITE);\n \t\t\treg-\u003eid = 0;\n \t\t\treg-\u003etype \u0026= ~MEM_ALLOC;\n \t\t\treg-\u003etype |= MEM_RCU;\n+\t\t\tbpf_diag_mod_end(env);\n \t\t}\n \t}));\n \n@@ -9097,6 +9538,8 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,\n {\n \tint i;\n \n+\tbpf_diag_record_caller_saved(env, regs);\n+\n \t/* after the call registers r0 - r5 were scratched */\n \tfor (i = 0; i \u003c CALLER_SAVED_REGS; i++) {\n \t\tbpf_mark_reg_not_init(env, \u0026regs[caller_saved[i]]);\n@@ -9104,13 +9547,15 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,\n \t}\n }\n \n-static void invalidate_outgoing_stack_args(const struct bpf_verifier_env *env,\n+static void invalidate_outgoing_stack_args(struct bpf_verifier_env *env,\n \t\t\t\t\t struct bpf_func_state *state)\n {\n \tint i, nslots = state-\u003eout_stack_arg_cnt;\n \n-\tfor (i = 0; i \u003c nslots; i++)\n+\tfor (i = 0; i \u003c nslots; i++) {\n+\t\tbpf_diag_record_scrub(env, \u0026state-\u003estack_arg_regs[i], BPF_DIAG_MOD_CALLER_SAVED);\n \t\tbpf_mark_reg_not_init(env, \u0026state-\u003estack_arg_regs[i]);\n+\t}\n }\n \n typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env,\n@@ -9178,20 +9623,28 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n \tstruct bpf_func_state *caller = cur_func(env);\n \tstruct bpf_verifier_log *log = \u0026env-\u003elog;\n \tstruct ref_obj_desc ref_obj = {};\n+\tconst struct btf_param *args;\n+\tconst struct btf_type *func, *func_proto;\n \tu32 i;\n \tint ret, err;\n \n \tret = btf_prepare_func_args(env, subprog);\n \tif (ret) {\n \t\tif (bpf_in_stack_arg_cnt(sub) \u003e 0) {\n-\t\t\terr = check_outgoing_stack_args(env, caller, sub-\u003earg_cnt);\n+\t\t\terr = check_outgoing_stack_args(env, caller, sub-\u003earg_cnt,\n+\t\t\t\t\t\t\tbpf_subprog_name(env, subprog),\n+\t\t\t\t\t\t\tNULL, NULL);\n \t\t\tif (err)\n \t\t\t\treturn err;\n \t\t}\n \t\treturn ret;\n \t}\n \n-\tret = check_outgoing_stack_args(env, caller, sub-\u003earg_cnt);\n+\tfunc = btf_type_by_id(btf, env-\u003eprog-\u003eaux-\u003efunc_info[subprog].type_id);\n+\tfunc_proto = btf_type_by_id(btf, func-\u003etype);\n+\targs = btf_params(func_proto);\n+\tret = check_outgoing_stack_args(env, caller, sub-\u003earg_cnt,\n+\t\t\t\t\tbpf_subprog_name(env, subprog), btf, args);\n \tif (ret)\n \t\treturn ret;\n \n@@ -9230,7 +9683,8 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n \t\t\tret = check_func_arg_reg_off(env, reg, argno, ARG_DONTCARE);\n \t\t\tif (ret \u003c 0)\n \t\t\t\treturn ret;\n-\t\t\tif (check_mem_reg(env, reg, argno, arg-\u003emem_size, BPF_READ | BPF_WRITE, NULL))\n+\t\t\tif (check_mem_reg(env, reg, argno, arg-\u003emem_size, BPF_READ | BPF_WRITE, NULL,\n+\t\t\t\t\t NULL))\n \t\t\t\treturn -EINVAL;\n \t\t\tif (!(arg-\u003earg_type \u0026 PTR_MAYBE_NULL) \u0026\u0026\n \t\t\t (type_may_be_null(reg-\u003etype) || bpf_register_is_null(reg))) {\n@@ -9256,7 +9710,9 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n \t\t\tif (ret)\n \t\t\t\treturn ret;\n \n-\t\t\tret = process_dynptr_func(env, reg, argno, -1, arg-\u003earg_type, \u0026ref_obj, NULL);\n+\t\t\tret = process_dynptr_func(env, reg, argno, env-\u003einsn_idx,\n+\t\t\t\t\t\t bpf_subprog_name(env, subprog), arg-\u003earg_type,\n+\t\t\t\t\t\t \u0026ref_obj, NULL);\n \t\t\tif (ret)\n \t\t\t\treturn ret;\n \t\t} else if (base_type(arg-\u003earg_type) == ARG_PTR_TO_BTF_ID) {\n@@ -9267,7 +9723,8 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n \t\t\t\tcontinue;\n \n \t\t\tmemset(\u0026meta, 0, sizeof(meta)); /* leave func_id as zero */\n-\t\t\terr = check_reg_type(env, reg, argno, arg-\u003earg_type, \u0026arg-\u003ebtf_id, \u0026meta);\n+\t\t\terr = check_reg_type(env, reg, argno, arg-\u003earg_type, \u0026arg-\u003ebtf_id, \u0026meta,\n+\t\t\t\t\t bpf_subprog_name(env, subprog));\n \t\t\terr = err ?: check_func_arg_reg_off(env, reg, argno, arg-\u003earg_type);\n \t\t\tif (err)\n \t\t\t\treturn err;\n@@ -9408,17 +9865,25 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \tif (err == -EFAULT)\n \t\treturn err;\n \tif (bpf_subprog_is_global(env, subprog)) {\n-\t\tconst char *sub_name = subprog_name(env, subprog);\n+\t\tconst char *sub_name = bpf_subprog_name(env, subprog);\n+\t\tconst char *operation;\n+\t\tbool returns_void;\n \n \t\tif (env-\u003ecur_state-\u003eactive_locks) {\n \t\t\tverbose(env, \"global function calls are not allowed while holding a lock,\\n\"\n \t\t\t\t \"use static function instead\\n\");\n+\t\t\toperation = bpf_diag_fmt(env, \"global function %s()\", sub_name);\n+\t\t\tbpf_diag_ctx_active(env, *insn_idx, operation, BPF_DIAG_CONTEXT_LOCK,\n+\t\t\t\t\t \"Release the lock before calling the global function, or use a static function instead.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \n \t\tif (env-\u003esubprog_info[subprog].might_sleep \u0026\u0026 !in_sleepable_context(env)) {\n \t\t\tverbose(env, \"sleepable global function %s() called in %s\\n\",\n \t\t\t\tsub_name, non_sleepable_context_description(env));\n+\t\t\toperation = bpf_diag_fmt(env, \"sleepable global function %s()\", sub_name);\n+\t\t\tbpf_diag_ctx_forbidden(env, *insn_idx, operation,\n+\t\t\t\t\"Move the call outside the critical section, or use a non-sleepable function.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \n@@ -9431,16 +9896,22 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \t\tif (env-\u003elog.level \u0026 BPF_LOG_LEVEL)\n \t\t\tverbose(env, \"Func#%d ('%s') is global and assumed valid.\\n\",\n \t\t\t\tsubprog, sub_name);\n+\t\treturns_void = subprog_returns_void(env, subprog);\n \t\tif (env-\u003esubprog_info[subprog].changes_pkt_data)\n \t\t\tclear_all_pkt_pointers(env);\n \t\t/* mark global subprog for verifying after main prog */\n \t\tsubprog_aux(env, subprog)-\u003ecalled = true;\n+\t\tif (returns_void)\n+\t\t\tbpf_diag_record_scrub(env, \u0026caller-\u003eregs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED);\n+\t\telse\n+\t\t\tbpf_diag_mod_begin(env, \u0026caller-\u003eregs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);\n \t\tclear_caller_saved_regs(env, caller-\u003eregs);\n \t\tinvalidate_outgoing_stack_args(env, cur_func(env));\n \n \t\t/* All non-void global functions return a 64-bit SCALAR_VALUE. */\n-\t\tif (!subprog_returns_void(env, subprog)) {\n+\t\tif (!returns_void) {\n \t\t\tmark_reg_unknown(env, caller-\u003eregs, BPF_REG_0);\n+\t\t\tbpf_diag_mod_end(env);\n \t\t}\n \n \t\tif (env-\u003esubprog_info[subprog].might_throw) {\n@@ -9475,6 +9946,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \tif (err)\n \t\treturn err;\n \n+\tbpf_diag_record_scrub(env, \u0026caller-\u003eregs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED);\n \tclear_caller_saved_regs(env, caller-\u003eregs);\n \n \t/* and go analyze first insn of the callee */\n@@ -9838,7 +10310,9 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)\n \t\t}\n \t} else {\n \t\t/* return to the caller whatever r0 had in the callee */\n+\t\tbpf_diag_mod_begin(env, \u0026caller-\u003eregs[BPF_REG_0], r0, BPF_DIAG_MOD_WRITE);\n \t\tcaller-\u003eregs[BPF_REG_0] = *r0;\n+\t\tbpf_diag_mod_end(env);\n \t}\n \n \t/* for callbacks like bpf_loop or bpf_for_each_map_elem go back to callsite,\n@@ -10044,6 +10518,7 @@ static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exi\n \t\t\tcontinue;\n \t\tverbose(env, \"Unreleased reference id=%d alloc_insn=%d\\n\",\n \t\t\tstate-\u003erefs[i].id, state-\u003erefs[i].insn_idx);\n+\t\tbpf_diag_leak(env, state-\u003erefs[i].id, state-\u003erefs[i].insn_idx, env-\u003einsn_idx);\n \t\trefs_lingering = true;\n \t}\n \treturn refs_lingering ? -EINVAL : 0;\n@@ -10055,6 +10530,8 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit\n \n \tif (check_lock \u0026\u0026 env-\u003ecur_state-\u003eactive_locks) {\n \t\tverbose(env, \"%s cannot be used inside bpf_spin_lock-ed region\\n\", prefix);\n+\t\tbpf_diag_ctx_active(env, env-\u003einsn_idx, prefix, BPF_DIAG_CONTEXT_LOCK,\n+\t\t\t\t \"Release the BPF spin lock before this operation on every path.\");\n \t\treturn -EINVAL;\n \t}\n \n@@ -10066,16 +10543,23 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit\n \n \tif (check_lock \u0026\u0026 env-\u003ecur_state-\u003eactive_irq_id) {\n \t\tverbose(env, \"%s cannot be used inside bpf_local_irq_save-ed region\\n\", prefix);\n+\t\tbpf_diag_ctx_active(env, env-\u003einsn_idx, prefix, BPF_DIAG_CONTEXT_IRQ,\n+\t\t\t\t \"Restore the saved IRQ state before this operation on every path.\");\n \t\treturn -EINVAL;\n \t}\n \n \tif (check_lock \u0026\u0026 env-\u003ecur_state-\u003eactive_rcu_locks) {\n \t\tverbose(env, \"%s cannot be used inside bpf_rcu_read_lock-ed region\\n\", prefix);\n+\t\tbpf_diag_ctx_active(env, env-\u003einsn_idx, prefix, BPF_DIAG_CONTEXT_RCU,\n+\t\t\t\t \"Call bpf_rcu_read_unlock() before this operation on every path.\");\n \t\treturn -EINVAL;\n \t}\n \n \tif (check_lock \u0026\u0026 env-\u003ecur_state-\u003eactive_preempt_locks) {\n \t\tverbose(env, \"%s cannot be used inside bpf_preempt_disable-ed region\\n\", prefix);\n+\t\tbpf_diag_ctx_active(\n+\t\t\tenv, env-\u003einsn_idx, prefix, BPF_DIAG_CONTEXT_PREEMPT,\n+\t\t\t\"Call bpf_preempt_enable() before this operation on every path.\");\n \t\treturn -EINVAL;\n \t}\n \n@@ -10257,6 +10741,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn\n \tenum bpf_type_flag ret_flag;\n \tstruct bpf_reg_state *regs;\n \tstruct bpf_call_arg_meta meta;\n+\tconst char *operation;\n \tint insn_idx = *insn_idx_p;\n \tbool changes_data;\n \tint i, err, func_id;\n@@ -10272,17 +10757,31 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn\n \tif (err) {\n \t\tverbose(env, \"program of this type cannot use helper %s#%d\\n\",\n \t\t\tfunc_id_name(func_id), func_id);\n+\t\toperation = bpf_diag_fmt(env, \"helper %s#%d\", func_id_name(func_id), func_id);\n+\t\tbpf_diag_policy(\n+\t\t\tenv, insn_idx, operation, \"this program type does not allow the helper\",\n+\t\t\t\"Use a helper allowed for this program type, or move the logic to a compatible program type.\");\n \t\treturn err;\n \t}\n \n \t/* eBPF programs must be GPL compatible to use GPL-ed functions */\n \tif (!env-\u003eprog-\u003egpl_compatible \u0026\u0026 fn-\u003egpl_only) {\n \t\tverbose(env, \"cannot call GPL-restricted function from non-GPL compatible program\\n\");\n+\t\toperation = bpf_diag_fmt(env, \"helper %s#%d\", func_id_name(func_id), func_id);\n+\t\tbpf_diag_policy(\n+\t\t\tenv, insn_idx, operation,\n+\t\t\t\"this helper is restricted to GPL-compatible programs\",\n+\t\t\t\"Use a GPL-compatible license, or replace the helper with one that is available to non-GPL programs.\");\n \t\treturn -EINVAL;\n \t}\n \n \tif (fn-\u003eallowed \u0026\u0026 !fn-\u003eallowed(env-\u003eprog)) {\n \t\tverbose(env, \"helper call is not allowed in probe\\n\");\n+\t\toperation = bpf_diag_fmt(env, \"helper %s#%d\", func_id_name(func_id), func_id);\n+\t\tbpf_diag_policy(\n+\t\t\tenv, insn_idx, operation,\n+\t\t\t\"the helper-specific policy callback rejected this program\",\n+\t\t\t\"Use the helper only from an allowed attach point or program configuration.\");\n \t\treturn -EINVAL;\n \t}\n \n@@ -10304,6 +10803,10 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn\n \tif (fn-\u003emight_sleep \u0026\u0026 !in_sleepable_context(env)) {\n \t\tverbose(env, \"sleepable helper %s#%d in %s\\n\", func_id_name(func_id), func_id,\n \t\t\tnon_sleepable_context_description(env));\n+\t\toperation = bpf_diag_fmt(env, \"sleepable helper %s#%d\",\n+\t\t\t\t\t func_id_name(func_id), func_id);\n+\t\tbpf_diag_ctx_forbidden(env, insn_idx, operation,\n+\t\t\t\"Move the helper call outside the critical section, or use a non-sleepable helper.\");\n \t\treturn -EINVAL;\n \t}\n \n@@ -10491,12 +10994,14 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn\n \t\treturn err;\n \n \t/* reset caller saved regs */\n+\tbpf_diag_record_caller_saved(env, regs);\n \tfor (i = 0; i \u003c CALLER_SAVED_REGS; i++) {\n \t\tbpf_mark_reg_not_init(env, \u0026regs[caller_saved[i]]);\n \t\tcheck_reg_arg(env, caller_saved[i], DST_OP_NO_MARK);\n \t}\n \tinvalidate_outgoing_stack_args(env, cur_func(env));\n \n+\tbpf_diag_mod_begin(env, \u0026regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);\n \t/* update return register (already marked as written above) */\n \tret_type = fn-\u003eret_type;\n \tret_flag = type_flag(ret_type);\n@@ -10645,6 +11150,8 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn\n \t\tif (err)\n \t\t\treturn err;\n \n+\t\tbpf_diag_mod_end(env);\n+\n \t\t/*\n \t\t * In order for a release of any of the original or cast pointers\n \t\t * to invalidate all other pointers, reuse the same reference id for\n@@ -10661,6 +11168,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn\n \t\t__mark_reg_known_zero(r0);\n \t\tr0-\u003etype = SCALAR_VALUE;\n \n+\t\tbpf_diag_mod_begin(env, \u0026regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);\n \t\tregs[BPF_REG_0].type \u0026= ~PTR_MAYBE_NULL;\n \t\tregs[BPF_REG_0].id = meta.ref_obj.id;\n \t} else if (is_acquire_function(func_id, meta.map.ptr)) {\n@@ -10679,6 +11187,8 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn\n \tif (err)\n \t\treturn err;\n \n+\tbpf_diag_mod_end(env);\n+\n \terr = check_map_func_compatibility(env, meta.map.ptr, func_id);\n \tif (err)\n \t\treturn err;\n@@ -11587,6 +12097,12 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *\n \t\tif (!is_irq_flag_reg_valid_uninit(env, reg)) {\n \t\t\tverbose(env, \"expected uninitialized irq flag as %s\\n\",\n \t\t\t\treg_arg_name(env, argno));\n+\t\t\tbpf_diag_res(env, env-\u003einsn_idx, \"IRQ flag is already initialized\",\n+\t\t\t\t \"Saving IRQ state requires an uninitialized stack slot for \"\n+\t\t\t\t \"the IRQ flag, but this slot already contains tracked IRQ \"\n+\t\t\t\t \"flag state.\",\n+\t\t\t\t \"Use a fresh stack slot for this save operation, or restore \"\n+\t\t\t\t \"the existing IRQ flag before reusing the slot.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \n@@ -11603,6 +12119,11 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *\n \t\tif (err) {\n \t\t\tverbose(env, \"expected an initialized irq flag as %s\\n\",\n \t\t\t\treg_arg_name(env, argno));\n+\t\t\tbpf_diag_res(env, env-\u003einsn_idx, \"uninitialized IRQ flag restore\",\n+\t\t\t\t \"Restoring IRQ state requires a stack slot that was \"\n+\t\t\t\t \"initialized by a matching IRQ save operation on this path.\",\n+\t\t\t\t \"Pass the same stack slot that was previously initialized by \"\n+\t\t\t\t \"the matching IRQ save kfunc.\");\n \t\t\treturn err;\n \t\t}\n \n@@ -11647,8 +12168,10 @@ static void ref_convert_owning_non_owning(struct bpf_verifier_env *env, u32 id)\n {\n \tstruct bpf_func_state *unused;\n \tstruct bpf_reg_state *reg;\n+\tint err;\n \n-\tWARN_ON_ONCE(release_reference_nomark(env-\u003ecur_state, id));\n+\terr = release_reference_nomark(env, id);\n+\tWARN_ON_ONCE(err);\n \n \tbpf_for_each_reg_in_vstate(env-\u003ecur_state, unused, reg, ({\n \t\tif (reg-\u003eid == id) {\n@@ -12053,7 +12576,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \targs = (const struct btf_param *)(meta-\u003efunc_proto + 1);\n \tnargs = btf_type_vlen(meta-\u003efunc_proto);\n \n-\tret = check_outgoing_stack_args(env, caller, nargs);\n+\tret = check_outgoing_stack_args(env, caller, nargs, func_name, btf, args);\n \tif (ret)\n \t\treturn ret;\n \n@@ -12067,7 +12590,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\targno_t argno = argno_from_arg(i + 1);\n \t\tint regno = reg_from_argno(argno);\n \t\tbool btf_id_fixed_off_ok = true;\n-\t\tu32 ref_id, type_size;\n+\t\tu32 ref_id = args[i].type, type_size;\n \t\tint kf_arg_type = meta-\u003efn-\u003earg_type[i];\n \n \t\tif (is_kfunc_arg_prog_aux(btf, \u0026args[i])) {\n@@ -12091,29 +12614,43 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \n \t\tt = btf_type_skip_modifiers(btf, args[i].type, NULL);\n \n-\t\tif (btf_type_is_ptr(t) \u0026\u0026 (bpf_register_is_null(reg) || type_may_be_null(reg-\u003etype)) \u0026\u0026\n+\t\tif (btf_type_is_ptr(t)) {\n+\t\t\tref_t = btf_type_skip_modifiers(btf, t-\u003etype, \u0026ref_id);\n+\t\t\tref_tname = btf_name_by_offset(btf, ref_t-\u003ename_off);\n+\t\t}\n+\n+\t\tif (btf_type_is_ptr(t) \u0026\u0026\n+\t\t (bpf_register_is_null(reg) || type_may_be_null(reg-\u003etype)) \u0026\u0026\n \t\t !type_may_be_null(kf_arg_type)) {\n+\t\t\tconst char *expected_type;\n+\n+\t\t\texpected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);\n \t\t\tverbose(env, \"Possibly NULL pointer passed to trusted %s\\n\",\n \t\t\t\treg_arg_name(env, argno));\n+\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t \"Add a NULL check and call the kfunc only on the non-NULL path.\",\n+\t\t\t\t\t \"the pointer may be NULL, but this kfunc requires a non-NULL pointer to %s\",\n+\t\t\t\t\t expected_type);\n \t\t\treturn -EACCES;\n \t\t}\n \n \t\tif (regno == meta-\u003erelease_regno \u0026\u0026 !is_kfunc_arg_dynptr(meta-\u003ebtf, \u0026args[i]) \u0026\u0026\n \t\t !reg_is_referenced(env, reg) \u0026\u0026 !bpf_register_is_null(reg)) {\n+\t\t\tconst char *expected_type;\n+\n+\t\t\texpected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);\n \t\t\tverbose(env, \"release kfunc %s expects referenced PTR_TO_BTF_ID passed to %s\\n\",\n \t\t\t\tfunc_name, reg_arg_name(env, argno));\n+\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t \"Pass the resource-owning pointer returned by the matching acquire kfunc, and avoid calling the release kfunc after ownership has already been transferred or released.\",\n+\t\t\t\t\t \"release kfuncs require a resource-owning value of type %s returned by a matching acquire kfunc\",\n+\t\t\t\t\t expected_type);\n \t\t\treturn -EINVAL;\n \t\t}\n \n \t\tif (reg_is_referenced(env, reg))\n \t\t\tupdate_ref_obj(\u0026meta-\u003eref_obj, reg);\n \n-\t\tif (btf_type_is_ptr(t)) {\n-\t\t\tref_t = btf_type_skip_modifiers(btf, t-\u003etype, \u0026ref_id);\n-\t\t\tref_tname = btf_name_by_offset(btf, ref_t-\u003ename_off);\n-\t\t}\n-\n-\n \t\tif (bpf_register_is_null(reg) \u0026\u0026 type_may_be_null(kf_arg_type))\n \t\t\tcontinue;\n \n@@ -12173,35 +12710,67 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\tcase KF_ARG_CONST:\n \t\t\tif (reg-\u003etype != SCALAR_VALUE) {\n \t\t\t\tverbose(env, \"%s is not a scalar\\n\", reg_arg_name(env, argno));\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass an integer scalar value for this argument, not a pointer or resource object.\",\n+\t\t\t\t\t\t \"the kfunc expects an integer scalar, but %s is %s\",\n+\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \n \t\t\tret = process_const_arg(env, reg, argno, meta);\n-\t\t\tif (ret \u003c 0)\n+\t\t\tif (ret \u003c 0) {\n+\t\t\t\tif (ret == -EINVAL)\n+\t\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t\t \"Pass a compile-time constant or a value the verifier can prove is constant at this call.\",\n+\t\t\t\t\t\t\t \"the kfunc requires this scalar argument to be a verifier-known constant, but %s is variable on this path\",\n+\t\t\t\t\t\t\t reg_arg_name(env, argno));\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t\tbreak;\n \t\tcase KF_ARG_ANYTHING:\n \t\t\tif (reg-\u003etype != SCALAR_VALUE) {\n \t\t\t\tverbose(env, \"%s is not a scalar\\n\", reg_arg_name(env, argno));\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass an integer scalar value for this argument, not a pointer or resource object.\",\n+\t\t\t\t\t\t \"the kfunc expects an integer scalar, but %s is %s\",\n+\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t\tbreak;\n \t\tcase KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:\n \t\t\tif (reg-\u003etype != SCALAR_VALUE) {\n \t\t\t\tverbose(env, \"%s is not a scalar\\n\", reg_arg_name(env, argno));\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass an integer scalar value for this argument, not a pointer or resource object.\",\n+\t\t\t\t\t\t \"the kfunc expects an integer scalar, but %s is %s\",\n+\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \n \t\t\tif (is_kfunc_arg_scalar_with_name(btf, \u0026args[i], \"rdonly_buf_size\"))\n \t\t\t\tmeta-\u003er0_rdonly = true;\n \t\t\tret = process_const_alloc_mem_size(env, reg, argno, \u0026meta-\u003eret_mem);\n-\t\t\tif (ret \u003c 0)\n+\t\t\tif (ret \u003c 0) {\n+\t\t\t\tif (ret == -EINVAL)\n+\t\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t\t \"Pass a verifier-known constant size for this kfunc buffer argument.\",\n+\t\t\t\t\t\t\t \"the kfunc uses this argument as a return-buffer size, but %s is invalid or variable on this path\",\n+\t\t\t\t\t\t\t reg_arg_name(env, argno));\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t\tbreak;\n \t\tcase KF_ARG_PTR_TO_CTX:\n \t\t\tif (reg-\u003etype != PTR_TO_CTX) {\n \t\t\t\tverbose(env, \"%s expected pointer to ctx, but got %s\\n\",\n \t\t\t\t\treg_arg_name(env, argno), reg_type_str(env, reg-\u003etype));\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass the original program context pointer or preserve it before modifying registers.\",\n+\t\t\t\t\t\t \"the kfunc expects a context pointer, but %s is %s\",\n+\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \n@@ -12235,10 +12804,19 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\t} else {\n \t\t\t\tverbose(env, \"%s expected pointer to allocated object\\n\",\n \t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass a pointer returned by the matching BPF object allocation path.\",\n+\t\t\t\t\t\t \"the kfunc expects an allocated object pointer, but %s is %s\",\n+\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t\tif (!reg_is_referenced(env, reg)) {\n \t\t\t\tverbose(env, \"allocated object must be referenced\\n\");\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass the owned object pointer before it is released or transferred.\",\n+\t\t\t\t\t\t \"the allocated object pointer in %s must still carry verifier-tracked ownership, but this pointer no longer owns a live resource\",\n+\t\t\t\t\t\t reg_arg_name(env, argno));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t\tif (meta-\u003ebtf == btf_vmlinux) {\n@@ -12275,8 +12853,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\t\tdynptr_arg_type |= (unsigned int)get_dynptr_type_flag(parent_type);\n \t\t\t}\n \n-\t\t\tret = process_dynptr_func(env, reg, argno, insn_idx, dynptr_arg_type,\n-\t\t\t\t\t\t \u0026meta-\u003eref_obj, \u0026meta-\u003edynptr);\n+\t\t\tret = process_dynptr_func(env, reg, argno, insn_idx, func_name,\n+\t\t\t\t\t\t dynptr_arg_type, \u0026meta-\u003eref_obj, \u0026meta-\u003edynptr);\n \t\t\tif (ret \u003c 0)\n \t\t\t\treturn ret;\n \t\t\tbreak;\n@@ -12391,13 +12969,31 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\t\tif (!is_trusted_reg(env, reg) ||\n \t\t\t\t bpf_type_has_unsafe_modifiers(reg-\u003etype)) {\n \t\t\t\t\tif (!is_kfunc_rcu(meta)) {\n+\t\t\t\t\t\tconst char *expected_type;\n+\n+\t\t\t\t\t\texpected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);\n \t\t\t\t\t\tverbose(env, \"%s must be referenced or trusted\\n\",\n \t\t\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t\t\t \"Pass a pointer acquired from a verifier-tracked source, or call this kfunc only inside the required protection if it accepts RCU pointers.\",\n+\t\t\t\t\t\t\t\t \"the kfunc requires a trusted or resource-owning pointer to %s, but %s is %s\",\n+\t\t\t\t\t\t\t\t expected_type,\n+\t\t\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\t\t\treturn -EINVAL;\n \t\t\t\t\t}\n \t\t\t\t\tif (!is_rcu_reg(reg)) {\n+\t\t\t\t\t\tconst char *expected_type;\n+\n+\t\t\t\t\t\texpected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);\n \t\t\t\t\t\tverbose(env, \"%s must be a rcu pointer\\n\",\n \t\t\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t\t\t \"Use this kfunc with a pointer that is valid in an RCU read lock region.\",\n+\t\t\t\t\t\t\t\t \"the kfunc requires an RCU-protected pointer to %s, but %s is %s\",\n+\t\t\t\t\t\t\t\t expected_type,\n+\t\t\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\t\t\treturn -EINVAL;\n \t\t\t\t\t}\n \t\t\t\t}\n@@ -12410,6 +13006,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \n \t\t\tif (!__btf_type_is_scalar_struct(env, meta-\u003ebtf, ref_t, 0)) {\n \t\t\t\tenum bpf_reg_type reg2btf_type = lookup_reg2btf_ids(ref_id);\n+\t\t\t\tconst char *expected_type;\n \n \t\t\t\tverbose(env, \"%s is %s expected %s %s\",\n \t\t\t\t\treg_arg_name(env, argno), reg_type_str(env, reg-\u003etype),\n@@ -12417,6 +13014,12 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\t\tif (reg2btf_type != NOT_INIT)\n \t\t\t\t\tverbose(env, \" or %s\", reg_type_str(env, reg2btf_type));\n \t\t\t\tverbose(env, \"\\n\");\n+\t\t\t\texpected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass a verifier-tracked pointer to the expected kernel object type, not a pointer to stack storage or another memory buffer.\",\n+\t\t\t\t\t\t \"the kfunc expects a pointer to %s, but this argument is %s and cannot be used as that kernel object pointer\",\n+\t\t\t\t\t\t expected_type,\n+\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \n@@ -12428,6 +13031,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\tfallthrough;\n \t\tcase KF_ARG_PTR_TO_MEM:\n \t\t\tif (kf_arg_type \u0026 MEM_FIXED_SIZE) {\n+\t\t\t\tbool known_memory;\n+\n \t\t\t\tresolve_ret = btf_resolve_size(btf, ref_t, \u0026type_size);\n \t\t\t\tif (IS_ERR(resolve_ret)) {\n \t\t\t\t\tverbose(env, \"%s reference type('%s %s') size cannot be determined: %ld\\n\",\n@@ -12435,9 +13040,28 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\t\t\t\tref_tname, PTR_ERR(resolve_ret));\n \t\t\t\t\treturn -EINVAL;\n \t\t\t\t}\n-\t\t\t\tret = check_mem_reg(env, reg, argno, type_size, BPF_READ | BPF_WRITE, meta);\n-\t\t\t\tif (ret \u003c 0)\n+\t\t\t\tret = check_mem_reg(env, reg, argno, type_size, BPF_READ | BPF_WRITE,\n+\t\t\t\t\t\t meta, \u0026known_memory);\n+\t\t\t\tif (ret \u003c 0) {\n+\t\t\t\t\tconst char *expected_type;\n+\n+\t\t\t\t\texpected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);\n+\t\t\t\t\tif (known_memory)\n+\t\t\t\t\t\tbpf_diag_call_arg_fmt(\n+\t\t\t\t\t\t\tenv, insn_idx, argno, func_name,\n+\t\t\t\t\t\t\t\"Pass memory with at least the required number of accessible bytes and suitable read and write access.\",\n+\t\t\t\t\t\t\t\"the kfunc expects %u bytes of memory for %s, but the verifier cannot prove that %s provides a readable and writable range of that size\",\n+\t\t\t\t\t\t\ttype_size, expected_type,\n+\t\t\t\t\t\t\tbpf_diag_reg_type_plain(env, reg-\u003etype));\n+\t\t\t\t\telse\n+\t\t\t\t\t\tbpf_diag_call_arg_fmt(\n+\t\t\t\t\t\t\tenv, insn_idx, argno, func_name,\n+\t\t\t\t\t\t\t\"Pass stack, map, context, or other verifier-known memory of the expected type and size, not an integer cast to a pointer.\",\n+\t\t\t\t\t\t\t\"the kfunc expects %u bytes of memory for %s, but it is %s and not verifier-known memory\",\n+\t\t\t\t\t\t\ttype_size, expected_type,\n+\t\t\t\t\t\t\tbpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\t\treturn ret;\n+\t\t\t\t}\n \t\t\t}\n \t\t\tbreak;\n \t\tcase KF_ARG_CONST_MEM_SIZE:\n@@ -12450,9 +13074,15 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\tstruct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, i - 1);\n \t\t\tstruct bpf_reg_state *size_reg = reg;\n \t\t\targno_t buff_argno = argno_from_arg(i);\n+\t\t\tenum bpf_mem_size_failure failure;\n \n \t\t\tif (reg-\u003etype != SCALAR_VALUE) {\n \t\t\t\tverbose(env, \"%s is not a scalar\\n\", reg_arg_name(env, argno));\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass an integer scalar length for this memory argument.\",\n+\t\t\t\t\t\t \"the kfunc expects a scalar memory size, but %s is %s\",\n+\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \n@@ -12460,11 +13090,34 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\t\tbreak;\n \n \t\t\tret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,\n-\t\t\t\t\t\t BPF_READ | BPF_WRITE, true, meta);\n+\t\t\t\t\t\t BPF_READ | BPF_WRITE, true, meta, \u0026failure);\n \t\t\tif (ret \u003c 0) {\n+\t\t\t\tconst char *buff_arg, *size_arg;\n+\n+\t\t\t\tbuff_arg = bpf_diag_arg_name(env, buff_argno);\n+\t\t\t\tsize_arg = bpf_diag_arg_name(env, argno);\n \t\t\t\tverbose(env, \"%s and \", reg_arg_name(env, buff_argno));\n \t\t\t\tverbose(env, \"%s memory, len pair leads to invalid memory access\\n\",\n \t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\tif (failure == BPF_MEM_SIZE_FAIL_MEMORY) {\n+\t\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, buff_argno, func_name,\n+\t\t\t\t\t\t\t \"Pass a stack, map, context, or other verifier-known memory pointer, and keep the paired length within that object.\",\n+\t\t\t\t\t\t\t \"it is the memory pointer in a memory/length pair with %s, but %s does not describe verifier-readable memory for the requested length\",\n+\t\t\t\t\t\t\t size_arg, buff_arg);\n+\t\t\t\t} else if (failure == BPF_MEM_SIZE_FAIL_SIZE) {\n+\t\t\t\t\tif (reg_smin(size_reg) \u003c 0)\n+\t\t\t\t\t\tbpf_diag_call_arg_fmt(\n+\t\t\t\t\t\t\tenv, insn_idx, argno, func_name,\n+\t\t\t\t\t\t\t\"Constrain the memory size to a non-negative value smaller than BPF_MAX_VAR_SIZ before this call.\",\n+\t\t\t\t\t\t\t\"the memory size in %s may be negative because its signed minimum is %lld\",\n+\t\t\t\t\t\t\tsize_arg, reg_smin(size_reg));\n+\t\t\t\t\telse\n+\t\t\t\t\t\tbpf_diag_call_arg_fmt(\n+\t\t\t\t\t\t\tenv, insn_idx, argno, func_name,\n+\t\t\t\t\t\t\t\"Constrain the memory size to a non-negative value smaller than BPF_MAX_VAR_SIZ before this call.\",\n+\t\t\t\t\t\t\t\"the memory size in %s may reach %llu bytes, but variable memory accesses must stay below %u bytes\",\n+\t\t\t\t\t\t\tsize_arg, reg_umax(size_reg), BPF_MAX_VAR_SIZ);\n+\t\t\t\t}\n \t\t\t\treturn ret;\n \t\t\t}\n \t\t\tbreak;\n@@ -12478,8 +13131,15 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\tbreak;\n \t\tcase KF_ARG_PTR_TO_REFCOUNTED_KPTR:\n \t\t\tif (!type_is_ptr_alloc_obj(reg-\u003etype)) {\n+\t\t\t\tconst char *expected_type;\n+\n+\t\t\t\texpected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);\n \t\t\t\tverbose(env, \"%s is neither owning or non-owning ref\\n\",\n \t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass a pointer returned by the matching BPF object allocation or lookup operation for this kfunc.\",\n+\t\t\t\t\t\t \"the kfunc expects a pointer to BPF-managed refcounted object type %s, but this argument is not such an object pointer\",\n+\t\t\t\t\t\t expected_type);\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t\tif (!type_is_non_owning_ref(reg-\u003etype))\n@@ -12504,6 +13164,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\tif (reg-\u003etype != PTR_TO_MAP_VALUE) {\n \t\t\t\tverbose(env, \"%s doesn't point to a const string\\n\",\n \t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass a constant string pointer that the verifier recognizes, such as a string stored in a read-only map value.\",\n+\t\t\t\t\t\t \"the kfunc expects a pointer to a constant string stored in verifier-known memory, but %s is %s\",\n+\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t\tret = check_arg_const_str(env, reg, argno);\n@@ -12544,6 +13209,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me\n \t\t\tif (reg-\u003etype != PTR_TO_STACK) {\n \t\t\t\tverbose(env, \"%s doesn't point to an irq flag on stack\\n\",\n \t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,\n+\t\t\t\t\t\t \"Pass the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave().\",\n+\t\t\t\t\t\t \"the kfunc expects a stack pointer to an IRQ flag slot, but %s is %s\",\n+\t\t\t\t\t\t reg_arg_name(env, argno),\n+\t\t\t\t\t\t bpf_diag_reg_type_plain(env, reg-\u003etype));\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t\tret = process_irq_flag(env, reg, argno, meta);\n@@ -12984,6 +13654,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \tconst struct btf_type *t, *ptr_type;\n \tstruct bpf_call_arg_meta meta;\n \tstruct bpf_insn_aux_data *insn_aux;\n+\tconst char *operation;\n \tint err, insn_idx = *insn_idx_p;\n \tu32 i, nargs, ptr_type_id;\n \tstruct bpf_kfunc_desc *desc;\n@@ -12995,8 +13666,13 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \t\treturn 0;\n \n \terr = bpf_fetch_kfunc_arg_meta(env, insn-\u003eimm, insn-\u003eoff, \u0026meta);\n-\tif (err == -EACCES \u0026\u0026 meta.func_name)\n+\tif (err == -EACCES \u0026\u0026 meta.func_name) {\n \t\tverbose(env, \"calling kernel function %s is not allowed\\n\", meta.func_name);\n+\t\toperation = bpf_diag_fmt(env, \"kfunc %s\", meta.func_name);\n+\t\tbpf_diag_policy(\n+\t\t\tenv, insn_idx, operation, \"this program cannot call the kfunc\",\n+\t\t\t\"Use a kfunc allowed for this program type and attach point, or change the program context.\");\n+\t}\n \tif (err)\n \t\treturn err;\n \tdesc_btf = meta.btf;\n@@ -13043,12 +13719,19 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \n \tif (is_kfunc_destructive(\u0026meta) \u0026\u0026 !capable(CAP_SYS_BOOT)) {\n \t\tverbose(env, \"destructive kfunc calls require CAP_SYS_BOOT capability\\n\");\n+\t\toperation = bpf_diag_fmt(env, \"destructive kfunc %s\", meta.func_name);\n+\t\tbpf_diag_policy(\n+\t\t\tenv, insn_idx, operation, \"destructive kfuncs require CAP_SYS_BOOT\",\n+\t\t\t\"Load the program with CAP_SYS_BOOT, or avoid destructive kfuncs.\");\n \t\treturn -EACCES;\n \t}\n \n \tsleepable = bpf_is_kfunc_sleepable(\u0026meta);\n \tif (sleepable \u0026\u0026 !in_sleepable(env)) {\n \t\tverbose(env, \"program must be sleepable to call sleepable kfunc %s\\n\", func_name);\n+\t\toperation = bpf_diag_fmt(env, \"sleepable kfunc %s\", func_name);\n+\t\tbpf_diag_ctx_forbidden(env, insn_idx, operation,\n+\t\t\t\"Mark the program sleepable if the program type allows it, or use a non-sleepable kfunc.\");\n \t\treturn -EACCES;\n \t}\n \n@@ -13114,22 +13797,36 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \n \tif (rcu_lock) {\n \t\tenv-\u003ecur_state-\u003eactive_rcu_locks++;\n+\t\tbpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU, true,\n+\t\t\t\t\tenv-\u003ecur_state-\u003eactive_rcu_locks);\n \t} else if (rcu_unlock) {\n \t\tif (env-\u003ecur_state-\u003eactive_rcu_locks == 0) {\n \t\t\tverbose(env, \"unmatched rcu read unlock (kernel function %s)\\n\", func_name);\n+\t\t\tbpf_diag_ctx_underflow(\n+\t\t\t\tenv, insn_idx, func_name, BPF_DIAG_CONTEXT_RCU,\n+\t\t\t\t\"Remove the extra bpf_rcu_read_unlock() call, or ensure this path first enters an RCU read lock region.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \t\tenv-\u003ecur_state-\u003eactive_rcu_locks--;\n+\t\tbpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU, false,\n+\t\t\t\t\tenv-\u003ecur_state-\u003eactive_rcu_locks);\n \t\tif (!in_rcu_cs(env))\n \t\t\tinvalidate_rcu_protected_refs(env);\n \t} else if (preempt_disable) {\n \t\tenv-\u003ecur_state-\u003eactive_preempt_locks++;\n+\t\tbpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_PREEMPT, true,\n+\t\t\t\t\tenv-\u003ecur_state-\u003eactive_preempt_locks);\n \t} else if (preempt_enable) {\n \t\tif (env-\u003ecur_state-\u003eactive_preempt_locks == 0) {\n \t\t\tverbose(env, \"unmatched attempt to enable preemption (kernel function %s)\\n\", func_name);\n+\t\t\tbpf_diag_ctx_underflow(\n+\t\t\t\tenv, insn_idx, func_name, BPF_DIAG_CONTEXT_PREEMPT,\n+\t\t\t\t\"Remove the extra bpf_preempt_enable() call, or ensure this path first disables preemption.\");\n \t\t\treturn -EINVAL;\n \t\t}\n \t\tenv-\u003ecur_state-\u003eactive_preempt_locks--;\n+\t\tbpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_PREEMPT, false,\n+\t\t\t\t\tenv-\u003ecur_state-\u003eactive_preempt_locks);\n \t\tif (!in_rcu_cs(env))\n \t\t\tinvalidate_rcu_protected_refs(env);\n \t}\n@@ -13137,6 +13834,9 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \tif (sleepable \u0026\u0026 !in_sleepable_context(env)) {\n \t\tverbose(env, \"kernel func %s is sleepable within %s\\n\",\n \t\t\tfunc_name, non_sleepable_context_description(env));\n+\t\toperation = bpf_diag_fmt(env, \"sleepable kfunc %s\", func_name);\n+\t\tbpf_diag_ctx_forbidden(env, insn_idx, operation,\n+\t\t\t\"Move the kfunc call outside the critical section, or use a non-sleepable kfunc.\");\n \t\treturn -EACCES;\n \t}\n \n@@ -13147,6 +13847,9 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \n \tif (is_kfunc_rcu_protected(\u0026meta) \u0026\u0026 !in_rcu_cs(env)) {\n \t\tverbose(env, \"kernel func %s requires RCU critical section protection\\n\", func_name);\n+\t\tbpf_diag_ctx_required(\n+\t\t\tenv, insn_idx, func_name, BPF_DIAG_CONTEXT_RCU,\n+\t\t\t\"Call this kfunc between bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.\");\n \t\treturn -EACCES;\n \t}\n \n@@ -13184,6 +13887,8 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \t\t}\n \t}\n \n+\tbpf_diag_record_caller_saved(env, regs);\n+\tbpf_diag_mod_begin(env, \u0026regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);\n \tfor (i = 0; i \u003c CALLER_SAVED_REGS; i++) {\n \t\tu32 regno = caller_saved[i];\n \n@@ -13335,6 +14040,12 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \t\t\tcaller_info-\u003estack_arg_cnt = stack_arg_cnt;\n \t}\n \n+\t/*\n+\t * Record R0 before process_iter_next_call() snapshots the alternate\n+\t * iterator path's diagnostic position.\n+\t */\n+\tbpf_diag_mod_end(env);\n+\n \tif (bpf_is_iter_next_kfunc(\u0026meta)) {\n \t\terr = process_iter_next_call(env, insn_idx, \u0026meta);\n \t\tif (err)\n@@ -13718,9 +14429,8 @@ static int sanitize_check_bounds(struct bpf_verifier_env *env,\n * If we return -EACCES, caller may want to try again treating pointer as a\n * scalar. So we only emit a diagnostic if !env-\u003eallow_ptr_leaks.\n */\n-static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,\n-\t\t\t\t struct bpf_insn *insn,\n-\t\t\t\t const struct bpf_reg_state *ptr_reg,\n+static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn *insn,\n+\t\t\t\t u32 ptr_regno, const struct bpf_reg_state *ptr_reg,\n \t\t\t\t const struct bpf_reg_state *off_reg)\n {\n \tstruct bpf_verifier_state *vstate = env-\u003ecur_state;\n@@ -13732,6 +14442,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,\n \tstruct bpf_sanitize_info info = {};\n \tu8 opcode = BPF_OP(insn-\u003ecode);\n \tu32 dst = insn-\u003edst_reg;\n+\tconst char *reason;\n \tint ret, bounds_ret;\n \n \tdst_reg = \u0026regs[dst];\n@@ -13755,12 +14466,24 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,\n \t\tverbose(env,\n \t\t\t\"R%d 32-bit pointer arithmetic prohibited\\n\",\n \t\t\tdst);\n+\t\treason = bpf_diag_fmt(\n+\t\t\tenv, \"R%d holds %s. 32-bit ALU operations on pointers discard pointer tracking, so the verifier cannot keep the result as a safe pointer.\",\n+\t\t\tptr_regno, bpf_diag_reg_type_plain(env, ptr_reg-\u003etype));\n+\t\tbpf_diag_register_type(\n+\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"32-bit pointer arithmetic\", reason,\n+\t\t\t\"Use a 64-bit ALU instruction with an allowed, bounded scalar offset.\");\n \t\treturn -EACCES;\n \t}\n \n \tif (ptr_reg-\u003etype \u0026 PTR_MAYBE_NULL) {\n \t\tverbose(env, \"R%d pointer arithmetic on %s prohibited, null-check it first\\n\",\n \t\t\tdst, reg_type_str(env, ptr_reg-\u003etype));\n+\t\treason = bpf_diag_fmt(\n+\t\t\tenv, \"R%d may be NULL (%s). Pointer arithmetic is allowed only after the program proves the pointer is non-NULL on this path.\",\n+\t\t\tptr_regno, reg_type_str(env, ptr_reg-\u003etype));\n+\t\tbpf_diag_register_type(\n+\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"pointer arithmetic before NULL check\", reason,\n+\t\t\t\"Make sure that a NULL check precedes any arithmetic performed on the pointer.\");\n \t\treturn -EACCES;\n \t}\n \n@@ -13790,6 +14513,12 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,\n \tdefault:\n \t\tverbose(env, \"R%d pointer arithmetic on %s prohibited\\n\",\n \t\t\tdst, reg_type_str(env, ptr_reg-\u003etype));\n+\t\treason = bpf_diag_fmt(\n+\t\t\tenv, \"R%d holds %s. This pointer kind does not allow offset arithmetic.\",\n+\t\t\tptr_regno, bpf_diag_reg_type_plain(env, ptr_reg-\u003etype));\n+\t\tbpf_diag_register_type(\n+\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"pointer arithmetic is not allowed\", reason,\n+\t\t\t\"Do not change this pointer's offset; use it only in operations accepted for its kind.\");\n \t\treturn -EACCES;\n \t}\n \n@@ -13807,9 +14536,25 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,\n \tif (base_type(ptr_reg-\u003etype) == PTR_TO_MEM \u0026\u0026 (ptr_reg-\u003etype \u0026 PTR_UNTRUSTED))\n \t\treturn 0;\n \n-\tif (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg-\u003etype) ||\n-\t !check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg-\u003etype))\n+\tif (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg-\u003etype)) {\n+\t\treason = bpf_diag_fmt(\n+\t\t\tenv, \"The scalar offset used with R%d is unbounded or outside the verifier's safe pointer-offset range [-%u, %u].\",\n+\t\t\tptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF);\n+\t\tbpf_diag_register_type(\n+\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"pointer offset is not safe\", reason,\n+\t\t\t\"Clamp or bounds-check the scalar offset before applying it to the pointer.\");\n \t\treturn -EINVAL;\n+\t}\n+\tif (!check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg-\u003etype)) {\n+\t\treason = bpf_diag_fmt(\n+\t\t\tenv, \"R%d already has an offset outside the verifier's safe range [-%u, %u] for %s.\",\n+\t\t\tptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,\n+\t\t\tbpf_diag_reg_type_plain(env, ptr_reg-\u003etype));\n+\t\tbpf_diag_register_type(\n+\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"pointer offset is not safe\", reason,\n+\t\t\t\"Keep the base pointer within the verifier's allowed offset range before applying more arithmetic.\");\n+\t\treturn -EINVAL;\n+\t}\n \n \t/* pointer types do not carry 32-bit bounds at the moment. */\n \t__mark_reg32_unbounded(dst_reg);\n@@ -13852,6 +14597,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,\n \t\t\t/* scalar -= pointer. Creates an unknown scalar */\n \t\t\tverbose(env, \"R%d tried to subtract pointer from scalar\\n\",\n \t\t\t\tdst);\n+\t\t\treason = bpf_diag_fmt(\n+\t\t\t\tenv, \"This operation subtracts pointer register R%d from scalar register R%d. \"\n+\t\t\t\t\"The verifier only tracks pointer-minus-scalar arithmetic for allowed pointer types.\",\n+\t\t\t\tptr_regno, dst);\n+\t\t\tbpf_diag_register_type(\n+\t\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"pointer subtracted from scalar\", reason,\n+\t\t\t\t\"Keep the pointer as the base; only add or subtract bounded scalars when permitted.\");\n \t\t\treturn -EACCES;\n \t\t}\n \t\t/* We don't allow subtraction from FP, because (according to\n@@ -13861,6 +14613,12 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,\n \t\tif (ptr_reg-\u003etype == PTR_TO_STACK) {\n \t\t\tverbose(env, \"R%d subtraction from stack pointer prohibited\\n\",\n \t\t\t\tdst);\n+\t\t\treason = bpf_diag_fmt(\n+\t\t\t\tenv, \"R%d is a stack pointer. The verifier does not allow BPF_SUB to move stack pointers.\",\n+\t\t\t\tptr_regno);\n+\t\t\tbpf_diag_register_type(\n+\t\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"subtraction from stack pointer\", reason,\n+\t\t\t\t\"Use addition from R10 to form stack addresses within the tracked stack frame.\");\n \t\t\treturn -EACCES;\n \t\t}\n \t\tdst_reg-\u003er64 = cnum64_add(ptr_reg-\u003er64, cnum64_negate(off_reg-\u003er64));\n@@ -13886,16 +14644,38 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,\n \t\t/* bitwise ops on pointers are troublesome, prohibit. */\n \t\tverbose(env, \"R%d bitwise operator %s on pointer prohibited\\n\",\n \t\t\tdst, bpf_alu_string[opcode \u003e\u003e 4]);\n+\t\treason = bpf_diag_fmt(\n+\t\t\tenv, \"R%d holds %s. Bitwise operator %s would destroy the pointer value the verifier is tracking.\",\n+\t\t\tptr_regno, bpf_diag_reg_type_plain(env, ptr_reg-\u003etype),\n+\t\t\tbpf_alu_string[opcode \u003e\u003e 4]);\n+\t\tbpf_diag_register_type(\n+\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"bitwise operation on pointer\", reason,\n+\t\t\t\"Do bitwise operations on scalar values, not on pointer-valued registers.\");\n \t\treturn -EACCES;\n \tdefault:\n \t\t/* other operators (e.g. MUL,LSH) produce non-pointer results */\n \t\tverbose(env, \"R%d pointer arithmetic with %s operator prohibited\\n\",\n \t\t\tdst, bpf_alu_string[opcode \u003e\u003e 4]);\n+\t\treason = bpf_diag_fmt(\n+\t\t\tenv, \"R%d holds %s. Operator %s is not one of the limited pointer arithmetic operations the verifier can track.\",\n+\t\t\tptr_regno, bpf_diag_reg_type_plain(env, ptr_reg-\u003etype),\n+\t\t\tbpf_alu_string[opcode \u003e\u003e 4]);\n+\t\tbpf_diag_register_type(\n+\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"invalid pointer arithmetic operator\", reason,\n+\t\t\t\"Use only verifier-supported addition or subtraction with a bounded scalar offset, or perform this operation on a scalar value.\");\n \t\treturn -EACCES;\n \t}\n \n-\tif (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg-\u003etype))\n+\tif (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg-\u003etype)) {\n+\t\treason = bpf_diag_fmt(\n+\t\t\tenv, \"After this arithmetic, R%d would be outside the verifier's safe offset range [-%u, %u] for %s.\",\n+\t\t\tdst, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,\n+\t\t\tbpf_diag_reg_type_plain(env, ptr_reg-\u003etype));\n+\t\tbpf_diag_register_type(\n+\t\t\tenv, env-\u003einsn_idx, ptr_regno, \"pointer offset is not safe\", reason,\n+\t\t\t\"Tighten the scalar bounds before the arithmetic so the resulting pointer remains within the allowed range.\");\n \t\treturn -EINVAL;\n+\t}\n \treg_bounds_sync(dst_reg);\n \tbounds_ret = sanitize_check_bounds(env, insn, dst_reg);\n \tif (bounds_ret == -EACCES)\n@@ -14867,15 +15647,15 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,\n \t\t\t\tif (err)\n \t\t\t\t\treturn err;\n \t\t\t\toff_reg = *dst_reg;\n-\t\t\t\treturn adjust_ptr_min_max_vals(env, insn, src_reg, \u0026off_reg);\n+\t\t\t\treturn adjust_ptr_min_max_vals(env, insn, insn-\u003esrc_reg, src_reg,\n+\t\t\t\t\t\t\t \u0026off_reg);\n \t\t\t}\n \t\t} else if (ptr_reg) {\n \t\t\t/* pointer += scalar */\n \t\t\terr = mark_chain_precision(env, insn-\u003esrc_reg);\n \t\t\tif (err)\n \t\t\t\treturn err;\n-\t\t\treturn adjust_ptr_min_max_vals(env, insn,\n-\t\t\t\t\t\t dst_reg, src_reg);\n+\t\t\treturn adjust_ptr_min_max_vals(env, insn, insn-\u003edst_reg, dst_reg, src_reg);\n \t\t} else if (dst_reg-\u003eprecise) {\n \t\t\t/* if dst_reg is precise, src_reg should be precise as well */\n \t\t\terr = mark_chain_precision(env, insn-\u003esrc_reg);\n@@ -14890,8 +15670,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,\n \t\t__mark_reg_known(\u0026off_reg, insn-\u003eimm);\n \t\tsrc_reg = \u0026off_reg;\n \t\tif (ptr_reg) /* pointer += K */\n-\t\t\treturn adjust_ptr_min_max_vals(env, insn,\n-\t\t\t\t\t\t ptr_reg, src_reg);\n+\t\t\treturn adjust_ptr_min_max_vals(env, insn, insn-\u003edst_reg, ptr_reg, src_reg);\n \t}\n \n \t/* Got here implies adding two SCALAR_VALUEs */\n@@ -14977,6 +15756,8 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \tu8 opcode = BPF_OP(insn-\u003ecode);\n \tint err;\n \n+\tbpf_diag_mod_begin(env, \u0026regs[insn-\u003edst_reg], NULL, BPF_DIAG_MOD_WRITE);\n+\n \tif (opcode == BPF_END || opcode == BPF_NEG) {\n \t\t/* check src operand */\n \t\terr = check_reg_arg(env, insn-\u003edst_reg, SRC_OP);\n@@ -15150,7 +15931,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \t\t\treturn err;\n \t}\n \n-\treturn reg_bounds_sanity_check(env, \u0026regs[insn-\u003edst_reg], \"alu\");\n+\terr = reg_bounds_sanity_check(env, \u0026regs[insn-\u003edst_reg], \"alu\");\n+\tif (err)\n+\t\treturn err;\n+\n+\tbpf_diag_mod_end(env);\n+\treturn 0;\n }\n \n static void find_good_pkt_pointers(struct bpf_verifier_state *vstate,\n@@ -15765,7 +16551,7 @@ static void mark_ptr_or_null_regs(struct bpf_verifier_state *vstate, u32 regno,\n \t\t * No one could have freed the reference state before\n \t\t * doing the NULL check.\n \t\t */\n-\t\tWARN_ON_ONCE(release_reference_nomark(vstate, id));\n+\t\tWARN_ON_ONCE(__release_reference_nomark(vstate, id));\n \n \tbpf_for_each_reg_in_vstate(vstate, state, reg, ({\n \t\tmark_ptr_or_null_reg(state, reg, id, is_null);\n@@ -16244,11 +17030,13 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \t\treturn err;\n \n \tdst_reg = \u0026regs[insn-\u003edst_reg];\n+\tbpf_diag_mod_begin(env, dst_reg, NULL, BPF_DIAG_MOD_WRITE);\n \tif (insn-\u003esrc_reg == 0) {\n \t\tu64 imm = ((u64)(insn + 1)-\u003eimm \u003c\u003c 32) | (u32)insn-\u003eimm;\n \n \t\tdst_reg-\u003etype = SCALAR_VALUE;\n \t\t__mark_reg_known(\u0026regs[insn-\u003edst_reg], imm);\n+\t\tbpf_diag_mod_end(env);\n \t\treturn 0;\n \t}\n \n@@ -16272,6 +17060,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \t\t\tverifier_bug(env, \"pseudo btf id: unexpected dst reg type\");\n \t\t\treturn -EFAULT;\n \t\t}\n+\t\tbpf_diag_mod_end(env);\n \t\treturn 0;\n \t}\n \n@@ -16291,6 +17080,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \n \t\tdst_reg-\u003etype = PTR_TO_FUNC;\n \t\tdst_reg-\u003esubprogno = subprogno;\n+\t\tbpf_diag_mod_end(env);\n \t\treturn 0;\n \t}\n \n@@ -16301,6 +17091,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \t\tif (map-\u003emap_type == BPF_MAP_TYPE_ARENA) {\n \t\t\t__mark_reg_unknown(env, dst_reg);\n \t\t\tdst_reg-\u003emap_ptr = map;\n+\t\t\tbpf_diag_mod_end(env);\n \t\t\treturn 0;\n \t\t}\n \t\t__mark_reg_known(dst_reg, aux-\u003emap_off);\n@@ -16318,6 +17109,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \t\treturn -EFAULT;\n \t}\n \n+\tbpf_diag_mod_end(env);\n \treturn 0;\n }\n \n@@ -16396,6 +17188,8 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \t\treturn err;\n \n \t/* reset caller saved regs to unreadable */\n+\tbpf_diag_record_caller_saved(env, regs);\n+\tbpf_diag_mod_begin(env, \u0026regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);\n \tfor (i = 0; i \u003c CALLER_SAVED_REGS; i++) {\n \t\tbpf_mark_reg_not_init(env, \u0026regs[caller_saved[i]]);\n \t\tcheck_reg_arg(env, caller_saved[i], DST_OP_NO_MARK);\n@@ -16406,6 +17200,7 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)\n \t * Already marked as written above.\n \t */\n \tmark_reg_unknown(env, regs, BPF_REG_0);\n+\tbpf_diag_mod_end(env);\n \t/*\n \t * See bpf_gen_ld_abs() which emits a hidden BPF_EXIT with r0=0\n \t * which must be explored by the verifier when in a subprog.\n@@ -17332,6 +18127,10 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)\n \t\t\t\t !kfunc_spin_allowed(env, insn-\u003eimm, insn-\u003eoff))) {\n \t\t\t\t\tverbose(env,\n \t\t\t\t\t\t\"function calls are not allowed while holding a lock\\n\");\n+\t\t\t\t\tbpf_diag_ctx_active(\n+\t\t\t\t\t\tenv, env-\u003einsn_idx,\n+\t\t\t\t\t\t\"function call\", BPF_DIAG_CONTEXT_LOCK,\n+\t\t\t\t\t\t\"Release the BPF spin lock before making this call, or move the call outside the locked region.\");\n \t\t\t\t\treturn -EINVAL;\n \t\t\t\t}\n \t\t\t}\n@@ -17416,6 +18215,27 @@ static int do_check(struct bpf_verifier_env *env)\n \n \t\tstate-\u003elast_insn_idx = env-\u003eprev_insn_idx;\n \t\tstate-\u003einsn_idx = env-\u003einsn_idx;\n+\t\t/*\n+\t\t * Record the incoming edge so active and queued paths use the same\n+\t\t * branch-recording path. A zero-offset conditional has identical\n+\t\t * successors, so its outcome cannot be reconstructed from the edge.\n+\t\t */\n+\t\tif (!state-\u003especulative \u0026\u0026 prev_insn_idx \u003e= 0 \u0026\u0026 prev_insn_idx \u003c insn_cnt) {\n+\t\t\tstruct bpf_insn *prev_insn = \u0026insns[prev_insn_idx];\n+\t\t\tint fallthrough_idx = prev_insn_idx + 1;\n+\t\t\tint branch_idx = prev_insn_idx + bpf_jmp_offset(prev_insn) + 1;\n+\t\t\tu8 class = BPF_CLASS(prev_insn-\u003ecode);\n+\t\t\tu8 opcode = BPF_OP(prev_insn-\u003ecode);\n+\n+\t\t\tif ((class == BPF_JMP || class == BPF_JMP32) \u0026\u0026\n+\t\t\t opcode != BPF_JA \u0026\u0026 opcode != BPF_CALL \u0026\u0026 opcode != BPF_EXIT \u0026\u0026\n+\t\t\t opcode \u003c= BPF_JCOND \u0026\u0026 branch_idx != fallthrough_idx) {\n+\t\t\t\tif (env-\u003einsn_idx == branch_idx)\n+\t\t\t\t\tbpf_diag_record_branch(env, prev_insn_idx, true);\n+\t\t\t\telse if (env-\u003einsn_idx == fallthrough_idx)\n+\t\t\t\t\tbpf_diag_record_branch(env, prev_insn_idx, false);\n+\t\t\t}\n+\t\t}\n \n \t\tif (bpf_is_prune_point(env, env-\u003einsn_idx)) {\n \t\t\terr = bpf_is_state_visited(env, env-\u003einsn_idx);\n@@ -18479,7 +19299,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)\n \n \tregs = state-\u003eframe[state-\u003ecurframe]-\u003eregs;\n \tif (subprog || env-\u003eprog-\u003etype == BPF_PROG_TYPE_EXT) {\n-\t\tconst char *sub_name = subprog_name(env, subprog);\n+\t\tconst char *sub_name = bpf_subprog_name(env, subprog);\n \t\tstruct bpf_subprog_arg_info *arg;\n \t\tstruct bpf_reg_state *reg;\n \n@@ -18591,8 +19411,11 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)\n \tret = do_check(env);\n out:\n \taccount_current_path(env);\n-\tif (!ret \u0026\u0026 pop_log)\n-\t\tbpf_vlog_reset(\u0026env-\u003elog, 0);\n+\tif (!ret) {\n+\t\tif (pop_log)\n+\t\t\tbpf_vlog_reset(\u0026env-\u003elog, 0);\n+\t\tbpf_diag_event_log_restore(env, 0);\n+\t}\n \tfree_states(env);\n \n \t/*\n@@ -18656,7 +19479,7 @@ static int do_check_subprogs(struct bpf_verifier_env *env)\n \t\t\treturn ret;\n \t\t} else if (env-\u003elog.level \u0026 BPF_LOG_LEVEL) {\n \t\t\tverbose(env, \"Func#%d ('%s') is safe for any args that match its prototype\\n\",\n-\t\t\t\ti, subprog_name(env, i));\n+\t\t\t\ti, bpf_subprog_name(env, i));\n \t\t}\n \n \t\t/* We verified new global subprog, it might have called some\n@@ -20188,6 +21011,9 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \tret = bpf_vlog_init(\u0026env-\u003elog, attr_log-\u003elevel, attr_log-\u003eubuf, attr_log-\u003esize);\n \tif (ret)\n \t\tgoto err_free_env;\n+\tret = bpf_diag_init(env);\n+\tif (ret)\n+\t\tgoto err_prep;\n \tif (env-\u003esignature) {\n \t\tret = bpf_prog_calc_tag(env-\u003eprog);\n \t\tif (ret \u003c 0)\n@@ -20478,6 +21304,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n \tkvfree(env-\u003escc_info);\n \tkvfree(env-\u003esucc);\n \tkvfree(env-\u003egotox_tmp_buf);\n+\tbpf_diag_free(env);\n \tkvfree(env);\n \treturn ret;\n }\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c\nindex 7918646e5bfcf..d3be69a9a7557 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c\n@@ -155,6 +155,7 @@ l0_%=:\tr0 = 0;\t\t\t\t\t\t\\\n SEC(\"socket\")\n __description(\"forgot null checking on the inner map pointer\")\n __failure __msg(\"R1 type=map_ptr_or_null expected=map_ptr\")\n+__msg(\"map_ptr_or_null, but this argument accepts map_ptr\")\n __failure_unpriv\n __naked void on_the_inner_map_pointer(void)\n {\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_uninit.c b/tools/testing/selftests/bpf/progs/verifier_uninit.c\nindex 7718cd7d19ce9..691018a460493 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_uninit.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_uninit.c\n@@ -9,6 +9,7 @@\n SEC(\"socket\")\n __description(\"read uninitialized register\")\n __failure __msg(\"R2 !read_ok\")\n+__msg(\"R2 has never been initialized on this path\")\n __failure_unpriv\n __naked void read_uninitialized_register(void)\n {\n"
} |
| 2/1 | 2026/08/15 07:05 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_diag_register_type",
"bpf_diag_invalid_deref",
"bpf_diag_unreadable_reg",
"bpf_diag_stack_arg_uninit",
"bpf_diag_memory",
"bpf_diag_mem_bounds",
"bpf_diag_res",
"bpf_diag_lock",
"bpf_diag_irq",
"bpf_diag_leak",
"bpf_diag_call_type",
"bpf_diag_ctx_forbidden",
"bpf_diag_ctx_active",
"bpf_diag_ctx_required",
"bpf_diag_ctx_underflow",
"bpf_diag_program_structure",
"bpf_diag_policy",
"bpf_diag_record_branch",
"bpf_diag_mod_begin",
"bpf_diag_mod_end",
"bpf_diag_record_scrub",
"bpf_diag_record_scrub_stack",
"bpf_diag_record_ref_acquire",
"bpf_diag_record_ref_release",
"bpf_diag_record_context"
],
"Reasoning": "The patch introduces a new diagnostic framework to the BPF verifier, adding detailed diagnostics for verifier errors. This is a significant functional change to the BPF verifier and is reachable when the BPF verifier rejects a program or when logging is enabled.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a '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: For your convenience, here is the diff of the changes:
commit d335ac0304d75ca7fd84c386623517953d26cfa8
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Aug 15 07:05:20 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index f4e8d372253ac..018823e277293 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -4146,8 +4146,16 @@ static inline bool bpf_is_subprog(const struct bpf_prog *prog)
}
const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off);
-void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
- const char **filep, const char **linep, int *nump);
+struct bpf_linfo_source {
+ const char *file;
+ const char *line;
+ u32 file_name_off;
+ int line_num;
+ int line_col;
+};
+
+void bpf_get_linfo_source(struct btf *btf, const struct bpf_line_info *linfo,
+ struct bpf_linfo_source *src);
int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
const char **linep, int *nump);
struct bpf_prog *bpf_prog_find_from_stack(void);
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 27b43fda9b178..bc2af02547fe5 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -354,6 +354,11 @@ struct bpf_func_state {
* 0 = main function, 1 = first callee.
*/
u32 frameno;
+ /*
+ * Unique diagnostic identity for this function invocation. Frame depth is
+ * reused after returns, while this ID is preserved across state clones.
+ */
+ u32 diag_frame_id;
/* subprog number == index within subprog_info
* zero == main subprog
*/
@@ -833,6 +838,7 @@ static inline u16 bpf_in_stack_arg_cnt(const struct bpf_subprog_info *sub)
return 0;
}
+struct bpf_diag;
struct bpf_verifier_env;
struct backtrack_state {
@@ -950,6 +956,7 @@ struct bpf_verifier_env {
struct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */
const struct bpf_line_info *prev_linfo;
struct bpf_verifier_log log;
+ struct bpf_diag *diag;
struct bpf_subprog_info subprog_info[BPF_MAX_SUBPROGS + 2]; /* max + 2 for the fake and exception subprogs */
/* subprog indices sorted in topological order: leaves first, callers last */
int subprog_topo_order[BPF_MAX_SUBPROGS + 2];
@@ -1349,6 +1356,18 @@ static inline bool type_is_non_owning_ref(u32 type)
return type_is_ptr_alloc_obj(type) && type_flag(type) & NON_OWN_REF;
}
+static inline bool type_is_map_ptr(enum bpf_reg_type type)
+{
+ switch (base_type(type)) {
+ case CONST_PTR_TO_MAP:
+ case PTR_TO_MAP_KEY:
+ case PTR_TO_MAP_VALUE:
+ return true;
+ default:
+ return false;
+ }
+}
+
static inline bool type_is_pkt_pointer(enum bpf_reg_type type)
{
type = base_type(type);
@@ -1433,8 +1452,10 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie
void print_insn_state(struct bpf_verifier_env *env, const struct bpf_verifier_state *vstate,
u32 frameno);
u32 bpf_vlog_alignment(u32 pos);
+const char *bpf_disasm_kfunc_name(void *data, const struct bpf_insn *insn);
struct bpf_subprog_info *bpf_find_containing_subprog(struct bpf_verifier_env *env, int off);
+const char *bpf_subprog_name(const struct bpf_verifier_env *env, int subprog);
int bpf_jmp_offset(struct bpf_insn *insn);
struct bpf_iarray *bpf_insn_successors(struct bpf_verifier_env *env, u32 idx);
void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask);
diff --git a/include/linux/btf.h b/include/linux/btf.h
index 3f5255d095a27..7ea13768c9795 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -214,6 +214,7 @@ int btf_type_seq_show_flags(const struct btf *btf, u32 type_id, void *obj,
*/
int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
char *buf, int len, u64 flags);
+int btf_type_name_to_buf(const struct btf *btf, u32 type_id, char *buf, int len);
int btf_get_fd_by_id(u32 id);
u32 btf_obj_id(const struct btf *btf);
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index 4dc41bf5780cc..90255d80e5be6 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -6,7 +6,7 @@ cflags-nogcse-$(CONFIG_X86)$(CONFIG_CC_IS_GCC) := -fno-gcse
endif
CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)
-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
+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
obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o
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
obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 87ffde865a503..5b9d767895c91 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8316,6 +8316,16 @@ int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
return ssnprintf.len;
}
+int btf_type_name_to_buf(const struct btf *btf, u32 type_id, char *buf, int len)
+{
+ struct btf_show show = {
+ .btf = btf,
+ .state.type_id = type_id,
+ };
+
+ return snprintf(buf, len, "%s", btf_show_name(&show));
+}
+
#ifdef CONFIG_PROC_FS
static void bpf_btf_show_fdinfo(struct seq_file *m, struct file *filp)
{
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 818f7afac83a5..0f13c13f41337 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -5,6 +5,8 @@
#include <linux/filter.h>
#include <linux/sort.h>
+#include "diagnostics.h"
+
#define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
/* non-recursive DFS pseudo code
@@ -112,6 +114,10 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
if (w < 0 || w >= env->prog->len) {
verbose_linfo(env, t, "%d: ", t);
verbose(env, "jump out of range from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "jump out of range", "Keep branch targets inside the program.",
+ "Instruction %d jumps to instruction %d, but the program only contains instructions 0 through %d.",
+ t, w, env->prog->len - 1);
return -EINVAL;
}
@@ -135,6 +141,11 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
verbose_linfo(env, t, "%d: ", t);
verbose_linfo(env, w, "%d: ", w);
verbose(env, "back-edge from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "back-edge is not allowed",
+ "Load with privileges that allow this back-edge, or rewrite the control flow so it does not branch backward.",
+ "Instruction %d branches back to instruction %d. This program is being rejected without the privilege needed for this back-edge.",
+ t, w);
return -EINVAL;
} else if (insn_state[w] == EXPLORED) {
/* forward- or cross-edge */
@@ -315,6 +326,11 @@ static struct bpf_iarray *jt_from_subprog(struct bpf_verifier_env *env,
if (!jt) {
verbose(env, "no jump tables found for subprog starting at %u\n", subprog_start);
+ bpf_diag_program_structure(
+ env, subprog_start, "missing jump table",
+ "Make sure subprograms containing gotox instructions are accompanied by jump tables referencing these subprograms.",
+ "No jump table was found for the subprogram that starts at instruction %u.",
+ subprog_start);
return ERR_PTR(-EINVAL);
}
@@ -342,6 +358,11 @@ create_jt(int t, struct bpf_verifier_env *env)
if (jt->items[i] < subprog_start || jt->items[i] >= subprog_end) {
verbose(env, "jump table for insn %d points outside of the subprog [%u,%u]\n",
t, subprog_start, subprog_end);
+ bpf_diag_program_structure(
+ env, t, "jump table target out of range",
+ "Keep every jump-table target inside the same subprogram.",
+ "The jump table for instruction %d points outside subprogram range [%u,%u).",
+ t, subprog_start, subprog_end);
kvfree(jt);
return ERR_PTR(-EINVAL);
}
@@ -373,6 +394,11 @@ static int visit_gotox_insn(int t, struct bpf_verifier_env *env)
w = jt->items[i];
if (w < 0 || w >= env->prog->len) {
verbose(env, "indirect jump out of range from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "indirect jump out of range",
+ "Keep indirect jump targets inside the program.",
+ "Instruction %d can jump indirectly to instruction %d, but the program only contains instructions 0 through %d.",
+ t, w, env->prog->len - 1);
return -EINVAL;
}
@@ -623,12 +649,21 @@ int bpf_check_cfg(struct bpf_verifier_env *env)
if (insn_state[i] != EXPLORED) {
verbose(env, "unreachable insn %d\n", i);
+ bpf_diag_program_structure(
+ env, i, "unreachable instruction",
+ "Remove the unreachable instruction or add valid control flow that reaches it.",
+ "Instruction %d is not reachable from the program entry point.", i);
ret = -EINVAL;
goto err_free;
}
if (bpf_is_ldimm64(insn)) {
if (insn_state[i + 1] != 0) {
verbose(env, "jump into the middle of ldimm64 insn %d\n", i);
+ bpf_diag_program_structure(
+ env, i, "jump into ldimm64 immediate",
+ "Target the first instruction of the ldimm64 pair, or restructure the jump target.",
+ "Control flow reaches the second half of the ldimm64 instruction pair that starts at instruction %d.",
+ i);
ret = -EINVAL;
goto err_free;
}
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 6a94370a24488..d55e737ed75ad 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3461,24 +3461,14 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(xdp_bulk_tx);
#ifdef CONFIG_BPF_SYSCALL
-void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
- const char **filep, const char **linep, int *nump)
+void bpf_get_linfo_source(struct btf *btf, const struct bpf_line_info *linfo,
+ struct bpf_linfo_source *src)
{
- /* Get base component of the file path. */
- if (filep) {
- *filep = btf_name_by_offset(btf, linfo->file_name_off);
- *filep = kbasename(*filep);
- }
-
- /* Obtain the source line, and strip whitespace in prefix. */
- if (linep) {
- *linep = btf_name_by_offset(btf, linfo->line_off);
- while (isspace(**linep))
- *linep += 1;
- }
-
- if (nump)
- *nump = BPF_LINE_INFO_LINE_NUM(linfo->line_col);
+ src->file = kbasename(btf_name_by_offset(btf, linfo->file_name_off));
+ src->line = btf_name_by_offset(btf, linfo->line_off);
+ src->file_name_off = linfo->file_name_off;
+ src->line_num = BPF_LINE_INFO_LINE_NUM(linfo->line_col);
+ src->line_col = BPF_LINE_INFO_LINE_COL(linfo->line_col);
}
const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off)
@@ -3521,6 +3511,7 @@ const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn
int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
const char **linep, int *nump)
{
+ struct bpf_linfo_source src;
int idx = -1, insn_start, insn_end, len;
struct bpf_line_info *linfo;
void **jited_linfo;
@@ -3552,7 +3543,15 @@ int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char *
if (idx == -1)
return -ENOENT;
- bpf_get_linfo_file_line(btf, &linfo[idx], filep, linep, nump);
+ bpf_get_linfo_source(btf, &linfo[idx], &src);
+ while (isspace(*src.line))
+ src.line++;
+ if (filep)
+ *filep = src.file;
+ if (linep)
+ *linep = src.line;
+ if (nump)
+ *nump = src.line_num;
return 0;
}
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
new file mode 100644
index 0000000000000..33b7d9e8e2c3a
--- /dev/null
+++ b/kernel/bpf/diagnostics.c
@@ -0,0 +1,2355 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+
+#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/btf.h>
+#include <linux/ctype.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/seq_buf.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/stdarg.h>
+#include <linux/string.h>
+
+#include "disasm.h"
+#include "diagnostics.h"
+
+#define REGISTER_TYPE_SAFETY "Register Type Safety"
+#define MEMORY_SAFETY "Memory Safety"
+#define RESOURCE_LIFETIME_SAFETY "Resource Lifetime Safety"
+#define CALL_TYPE_SAFETY "Call Type Safety"
+#define EXECUTION_CONTEXT_SAFETY "Execution Context Safety"
+#define PROGRAM_STRUCTURE "Program Structure"
+#define POLICY "Policy"
+
+#define BPF_DIAG_TEXT_WIDTH 100
+#define BPF_DIAG_TEXT_INDENT " "
+#define BPF_DIAG_CONTEXT 2
+#define BPF_DIAG_CONTEXT_CNT (1 + BPF_DIAG_CONTEXT * 2)
+#define BPF_DIAG_HISTORY_RENDER_MAX 64
+#define BPF_DIAG_SOURCE_LANE_WIDTH 88
+#define BPF_DIAG_TAB_WIDTH 8
+#define BPF_DIAG_FMT_CHUNK_SIZE (PAGE_SIZE - sizeof(struct diag_fmt_chunk))
+#define BPF_DIAG_FMT_BUF_SIZE 256
+#define BPF_DIAG_EVENT_LOG_MAX_SIZE (64U << 20)
+#define DISASM_LINE_LEN 160
+
+enum bpf_diag_mod_target_kind {
+ BPF_DIAG_MOD_TARGET_NONE,
+ BPF_DIAG_MOD_TARGET_REG,
+ BPF_DIAG_MOD_TARGET_STACK_ARG,
+ BPF_DIAG_MOD_TARGET_STACK_SLOT,
+ BPF_DIAG_MOD_TARGET_STACK_RANGE,
+};
+
+struct bpf_diag_mod_target {
+ u32 frame_id;
+ union {
+ struct {
+ s16 min_off;
+ s16 max_off;
+ } range;
+ u16 spi;
+ u8 regno;
+ u8 stack_arg;
+ };
+ u8 frameno;
+ u8 kind;
+};
+
+static struct bpf_diag_mod_target diag_reg_target(u32 frame_id, u8 frameno, u8 regno)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_REG,
+ .regno = regno,
+ };
+}
+
+static struct bpf_diag_mod_target diag_stack_arg_target(u32 frame_id, u8 frameno, u8 slot)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_STACK_ARG,
+ .stack_arg = slot,
+ };
+}
+
+static struct bpf_diag_mod_target diag_stack_slot_target(u32 frame_id, u8 frameno, u16 spi)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_STACK_SLOT,
+ .spi = spi,
+ };
+}
+
+static struct bpf_diag_mod_target diag_stack_range_target(u32 frame_id, u8 frameno,
+ s16 min_off, s16 max_off)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_STACK_RANGE,
+ .range.min_off = min_off,
+ .range.max_off = max_off,
+ };
+}
+
+struct bpf_diag_reg_snapshot {
+ u32 type;
+ u32 btf_id;
+ const struct bpf_map *map_ptr;
+ const struct btf *btf;
+ struct tnum var_off;
+ struct cnum64 r64;
+};
+
+enum bpf_diag_history_kind {
+ BPF_DIAG_HISTORY_BRANCH,
+ BPF_DIAG_HISTORY_MOD,
+ BPF_DIAG_HISTORY_REF_ACQUIRE,
+ BPF_DIAG_HISTORY_REF_RELEASE,
+ BPF_DIAG_HISTORY_CONTEXT,
+};
+
+struct bpf_diag_history_event {
+ u32 insn_idx : 24;
+ u32 kind : 8;
+ u8 in_lineage : 1;
+ union {
+ struct {
+ bool cond_true;
+ } branch;
+ struct {
+ struct bpf_diag_mod_target target;
+ struct bpf_diag_mod_target origin;
+ struct bpf_diag_reg_snapshot old, new;
+ u8 reason;
+ bool origin_valid;
+ } mod;
+ struct {
+ u32 ref_id;
+ } ref;
+ struct {
+ u32 depth;
+ u8 kind;
+ bool enter;
+ } ctx;
+ };
+};
+
+enum bpf_diag_history_scope {
+ BPF_DIAG_HISTORY_SCOPE_REG,
+ BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+ BPF_DIAG_HISTORY_SCOPE_REF,
+ BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+};
+
+struct bpf_diag_history_opts {
+ enum bpf_diag_history_scope scope;
+ u32 frame_id;
+ u32 frameno;
+ int regno;
+ int stack_arg_slot;
+ u32 ref_id;
+ enum bpf_diag_context_kind ctx_kind;
+ u32 ctx_depth;
+};
+
+static void diag_print_history(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_opts *opts);
+static bool diag_target_matches(const struct bpf_diag_mod_target *event_target,
+ const struct bpf_diag_mod_target *target);
+static const char *diag_context_name(enum bpf_diag_context_kind kind);
+struct disasm_line {
+ char text[DISASM_LINE_LEN];
+ int idx;
+ bool valid;
+};
+
+struct disasm_ctx {
+ struct bpf_verifier_env *env;
+ struct seq_buf seq;
+};
+
+struct diag_fmt_chunk {
+ struct list_head node;
+ struct seq_buf seq;
+ char data[];
+};
+
+struct diag_fmt_mark {
+ struct diag_fmt_chunk *chunk;
+ size_t len;
+};
+
+struct bpf_diag_log {
+ struct bpf_diag_history_event *events;
+ /* Sequence number of the oldest retained event on the active path. */
+ u64 first_seq;
+ u32 cnt;
+ u32 cap;
+ u32 head;
+ bool growth_failed;
+};
+
+struct bpf_diag_scratch {
+ struct bpf_linfo_source source_lines[BPF_DIAG_CONTEXT_CNT];
+ struct disasm_line disasm_lines[BPF_DIAG_CONTEXT_CNT];
+};
+
+struct bpf_diag_mod_scope {
+ struct bpf_reg_state target_reg_snapshot;
+ struct bpf_diag_mod_target target;
+ struct bpf_diag_mod_target origin;
+ enum bpf_diag_mod_reason reason;
+ u32 insn_idx;
+ bool active;
+ bool origin_valid;
+};
+
+struct bpf_diag {
+ struct bpf_diag_log log;
+ struct bpf_diag_scratch scratch;
+ struct list_head fmt_chunks;
+ struct bpf_diag_mod_scope mod;
+ u32 frame_id_gen;
+};
+
+bool bpf_diag_enabled(const struct bpf_verifier_env *env)
+{
+ return env->log.level & BPF_LOG_LEVEL;
+}
+
+static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
+
+int bpf_diag_init(struct bpf_verifier_env *env)
+{
+ if (!bpf_diag_enabled(env))
+ return 0;
+
+ env->diag = kzalloc_obj(struct bpf_diag, GFP_KERNEL_ACCOUNT);
+ if (!env->diag)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&env->diag->fmt_chunks);
+ return 0;
+}
+
+void bpf_diag_init_frame(struct bpf_verifier_env *env, struct bpf_func_state *state)
+{
+ if (env->diag)
+ state->diag_frame_id = ++env->diag->frame_id_gen;
+}
+
+static char *diag_fmt_alloc(struct bpf_verifier_env *env, size_t size)
+{
+ struct bpf_diag *diag = env->diag;
+ struct diag_fmt_chunk *chunk;
+ size_t capacity, available;
+ char *buf;
+
+ if (!diag || !size || size > INT_MAX)
+ return NULL;
+
+ if (!list_empty(&diag->fmt_chunks)) {
+ chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
+ available = seq_buf_get_buf(&chunk->seq, &buf);
+ if (available >= size)
+ goto commit;
+ }
+
+ capacity = max_t(size_t, BPF_DIAG_FMT_CHUNK_SIZE, size);
+ chunk = kmalloc(struct_size(chunk, data, capacity), GFP_KERNEL_ACCOUNT);
+ if (!chunk)
+ return NULL;
+
+ seq_buf_init(&chunk->seq, chunk->data, capacity);
+ list_add_tail(&chunk->node, &diag->fmt_chunks);
+ available = seq_buf_get_buf(&chunk->seq, &buf);
+ if (WARN_ON_ONCE(available < size))
+ return NULL;
+
+commit:
+ seq_buf_commit(&chunk->seq, size);
+ return buf;
+}
+
+char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size)
+{
+ char *buf;
+
+ buf = diag_fmt_alloc(env, size);
+ if (buf)
+ buf[0] = '\0';
+ return buf;
+}
+
+const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)
+{
+ va_list copy;
+ char *buf;
+ int len;
+
+ va_copy(copy, args);
+ len = vsnprintf(NULL, 0, fmt, copy);
+ va_end(copy);
+ if (len < 0 || len == INT_MAX)
+ return "";
+
+ buf = diag_fmt_alloc(env, len + 1);
+ if (buf)
+ vsnprintf(buf, len + 1, fmt, args);
+ return buf ?: "";
+}
+
+const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ const char *buf;
+ va_list args;
+
+ va_start(args, fmt);
+ buf = bpf_diag_vfmt(env, fmt, args);
+ va_end(args);
+ return buf;
+}
+
+static struct diag_fmt_mark diag_fmt_save(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+ struct diag_fmt_mark mark = {};
+
+ if (!diag || list_empty(&diag->fmt_chunks))
+ return mark;
+
+ mark.chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
+ mark.len = mark.chunk->seq.len;
+ return mark;
+}
+
+static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_mark mark)
+{
+ struct bpf_diag *diag = env->diag;
+ struct diag_fmt_chunk *chunk;
+
+ if (!diag)
+ return;
+
+ while (!list_empty(&diag->fmt_chunks)) {
+ chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
+ if (chunk == mark.chunk)
+ break;
+ list_del(&chunk->node);
+ kfree(chunk);
+ }
+
+ if (mark.chunk) {
+ mark.chunk->seq.len = mark.len;
+ seq_buf_str(&mark.chunk->seq);
+ }
+}
+
+void bpf_diag_free(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+
+ if (!diag)
+ return;
+
+ diag_fmt_restore(env, (struct diag_fmt_mark){});
+ kvfree(diag->log.events);
+ kfree(diag);
+ env->diag = NULL;
+}
+
+static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ va_start(args, fmt);
+ bpf_verifier_vlog(&env->log, fmt, args);
+ va_end(args);
+}
+
+static u64 log_end(const struct bpf_diag_log *log)
+{
+ return log->first_seq + log->cnt;
+}
+
+static u32 log_pos(const struct bpf_diag_log *log, u32 idx)
+{
+ u32 pos = log->head + idx;
+
+ return pos < log->cap ? pos : pos - log->cap;
+}
+
+u64 bpf_diag_event_log_save(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+
+ return diag ? log_end(&diag->log) : 0;
+}
+
+void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos)
+{
+ struct bpf_diag *diag = env->diag;
+ struct bpf_diag_log *log;
+ u64 end_seq;
+
+ if (!diag)
+ return;
+
+ log = &diag->log;
+ end_seq = log_end(log);
+ if (WARN_ON_ONCE(log_pos > end_seq))
+ log_pos = end_seq;
+
+ /*
+ * A deep abandoned path may have rotated away the shared prefix. In
+ * that case, restart with an empty retained suffix and remember that
+ * every event before the restored mark is unavailable.
+ */
+ if (log_pos <= log->first_seq) {
+ log->first_seq = log_pos;
+ log->head = 0;
+ log->cnt = 0;
+ return;
+ }
+
+ log->cnt = log_pos - log->first_seq;
+}
+
+u32 bpf_diag_irq_depth(const struct bpf_verifier_state *state)
+{
+ u32 depth = 0;
+ int i;
+
+ for (i = 0; i < state->acquired_refs; i++) {
+ if (state->refs[i].type == REF_TYPE_IRQ)
+ depth++;
+ }
+
+ return depth;
+}
+
+static void diag_append_history(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ struct bpf_diag_history_event *events;
+ struct bpf_diag *diag = env->diag;
+ struct bpf_diag_log *log;
+ u32 cap, max_events;
+
+ if (!diag)
+ return;
+ log = &diag->log;
+
+ if (log->cnt < log->cap) {
+ log->events[log_pos(log, log->cnt++)] = *event;
+ return;
+ }
+
+ max_events = BPF_DIAG_EVENT_LOG_MAX_SIZE / sizeof(*events);
+ if (log->growth_failed || log->cap == max_events)
+ goto rotate;
+
+ cap = min(log->cap ? log->cap * 2 : 64, max_events);
+ events = kvrealloc(log->events, array_size(cap, sizeof(*events)), GFP_KERNEL_ACCOUNT);
+ if (!events) {
+ log->growth_failed = true;
+ goto rotate;
+ }
+ log->events = events;
+ log->cap = cap;
+ log->events[log->cnt++] = *event;
+ return;
+
+rotate:
+ if (log->cap) {
+ log->events[log->head++] = *event;
+ if (log->head == log->cap)
+ log->head = 0;
+ }
+ log->first_seq++;
+}
+
+static void diag_print_wrapped_prefixed(struct bpf_verifier_env *env, const char *first_prefix,
+ const char *next_prefix, const char *text)
+{
+ const char *prefix = first_prefix;
+
+ while (*text) {
+ const char *line = text;
+ int prefix_len = strlen(prefix);
+ int text_width = BPF_DIAG_TEXT_WIDTH - prefix_len;
+ int len = 0, last_space = -1;
+
+ if (text_width < 1)
+ text_width = 1;
+
+ while (line[len] && line[len] != '\n' && len < text_width) {
+ if (line[len] == ' ')
+ last_space = len;
+ len++;
+ }
+
+ if (line[len] && line[len] != '\n' && line[len] != ' ' && last_space > 0)
+ len = last_space;
+
+ diag_write(env, "%s%.*s\n", prefix, len, line);
+
+ text = line + len;
+ while (*text == ' ')
+ text++;
+ if (*text == '\n')
+ text++;
+
+ prefix = next_prefix;
+ }
+}
+
+const char *bpf_diag_fmt_btf_type(struct bpf_verifier_env *env, const struct btf *btf, u32 type_id)
+{
+ char *buf = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
+ size_t len;
+ int ret;
+
+ if (!buf)
+ return "";
+
+ buf[0] = '\0';
+ ret = btf_type_name_to_buf(btf, type_id, buf, BPF_DIAG_FMT_BUF_SIZE);
+ if (ret < 0 || !buf[0]) {
+ scnprintf(buf, BPF_DIAG_FMT_BUF_SIZE, "BTF type ID %u", type_id);
+ return buf;
+ }
+
+ len = strlen(buf);
+ if (len && buf[len - 1] == '{')
+ buf[len - 1] = '\0';
+ return buf;
+}
+
+static void diag_vprint_indented(struct bpf_verifier_env *env, const char *fmt, va_list args)
+ __printf(2, 0);
+
+static void diag_vprint_indented(struct bpf_verifier_env *env, const char *fmt, va_list args)
+{
+ char *buf;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ buf = kvasprintf(GFP_KERNEL_ACCOUNT, fmt, args);
+ if (!buf) {
+ diag_write(env, "%s<failed to allocate diagnostic text>\n", BPF_DIAG_TEXT_INDENT);
+ return;
+ }
+
+ diag_print_wrapped_prefixed(env, BPF_DIAG_TEXT_INDENT, BPF_DIAG_TEXT_INDENT, buf);
+ kfree(buf);
+}
+
+static int diag_line_width(unsigned int line)
+{
+ int width = 1;
+
+ while (line >= 10) {
+ line /= 10;
+ width++;
+ }
+
+ return width;
+}
+
+static int diag_line_indent(const char *line)
+{
+ int indent = 0;
+
+ while (*line == ' ' || *line == '\t') {
+ if (*line == '\t')
+ indent = round_up(indent + 1, BPF_DIAG_TAB_WIDTH);
+ else
+ indent++;
+ line++;
+ }
+
+ return indent;
+}
+
+static void disasm_print(void *private_data, const char *fmt, ...) __printf(2, 3);
+
+static void disasm_print(void *private_data, const char *fmt, ...)
+{
+ struct disasm_ctx *ctx = private_data;
+ va_list args;
+
+ va_start(args, fmt);
+ seq_buf_vprintf(&ctx->seq, fmt, args);
+ va_end(args);
+}
+
+static const char *disasm_kfunc_name(void *private_data, const struct bpf_insn *insn)
+{
+ struct disasm_ctx *ctx = private_data;
+
+ return bpf_disasm_kfunc_name(ctx->env, insn);
+}
+
+static void format_disasm_line(struct bpf_verifier_env *env, int insn_idx,
+ struct disasm_line *line)
+{
+ struct disasm_ctx ctx = { .env = env };
+ struct bpf_insn *insn;
+ const struct bpf_insn_cbs cbs = {
+ .cb_call = disasm_kfunc_name,
+ .cb_print = disasm_print,
+ .private_data = &ctx,
+ };
+
+ line->idx = insn_idx;
+ line->valid = false;
+ seq_buf_init(&ctx.seq, line->text, sizeof(line->text));
+
+ if (insn_idx < 0 || insn_idx >= env->prog->len)
+ return;
+
+ if (insn_idx > 0 && bpf_is_ldimm64(&env->prog->insnsi[insn_idx - 1]))
+ return;
+
+ insn = &env->prog->insnsi[insn_idx];
+ if (bpf_is_ldimm64(insn) && insn_idx + 1 >= env->prog->len)
+ return;
+
+ print_bpf_insn(&cbs, insn, env->allow_ptr_leaks);
+ seq_buf_str(&ctx.seq);
+ ctx.seq.len = strnlen(line->text, sizeof(line->text));
+ while (ctx.seq.len && line->text[ctx.seq.len - 1] == '\n')
+ seq_buf_pop(&ctx.seq);
+ seq_buf_str(&ctx.seq);
+
+ line->valid = true;
+}
+
+static void diag_format_source_text(char *buf, size_t size, const char *line, int width)
+{
+ int col = 0, len = 0;
+
+ if (!size)
+ return;
+ if (width <= 0) {
+ buf[0] = '\0';
+ return;
+ }
+
+ line = line ?: "...";
+ while (*line && col < width && len + 1 < size) {
+ if (*line == '\t') {
+ int next = round_up(col + 1, BPF_DIAG_TAB_WIDTH);
+
+ while (col < next && col < width && len + 1 < size) {
+ buf[len++] = ' ';
+ col++;
+ }
+ line++;
+ continue;
+ }
+
+ buf[len++] = *line++;
+ col++;
+ }
+
+ if (*line) {
+ int ellipsis_len = min(3, width);
+
+ while (len > 0 && col > width - ellipsis_len) {
+ len--;
+ col--;
+ }
+ while (ellipsis_len-- && len + 1 < size)
+ buf[len++] = '.';
+ }
+
+ buf[len] = '\0';
+}
+
+static void diag_format_source_lane(char *buf, size_t size, const char *source_prefix,
+ int source_line_width, int line_num, const char *line)
+{
+ int len, text_width;
+
+ if (line_num <= 0) {
+ buf[0] = '\0';
+ return;
+ }
+
+ len = scnprintf(buf, size, "%s%*d | ", source_prefix, source_line_width, line_num);
+ text_width = BPF_DIAG_SOURCE_LANE_WIDTH - len;
+ diag_format_source_text(buf + len, size - len, line, text_width);
+}
+
+static void bpf_diag_header(struct bpf_verifier_env *env, const char *category,
+ const char *problem)
+{
+ char first;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ category = category ?: "Verifier Error";
+ problem = problem ?: "";
+
+ if (!problem[0]) {
+ diag_write(env, "\nVerification failed: %s\n", category);
+ return;
+ }
+
+ first = toupper(problem[0]);
+ diag_write(env, "\nVerification failed: %s: %c%s\n", category, first, problem + 1);
+}
+
+static void diag_reason(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
+static void diag_suggestion(struct bpf_verifier_env *env, const char *fmt, ...)
+ __printf(2, 3);
+
+static void diag_section(struct bpf_verifier_env *env, const char *title)
+{
+ if (!bpf_diag_enabled(env))
+ return;
+
+ diag_write(env, "\n%s:\n", title);
+}
+
+static void diag_reason(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ diag_section(env, "Reason");
+
+ va_start(args, fmt);
+ diag_vprint_indented(env, fmt, args);
+ va_end(args);
+}
+
+static void diag_suggestion(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ diag_section(env, "Suggestion");
+
+ va_start(args, fmt);
+ diag_vprint_indented(env, fmt, args);
+ va_end(args);
+ diag_write(env, "\n");
+}
+
+static void diag_print_source_annotation(struct bpf_verifier_env *env, int line_width, int indent,
+ const char *label, const char *msg)
+{
+ const char *first_prefix, *next_prefix, *text;
+
+ indent = min_t(int, indent, max_t(int, 0, BPF_DIAG_SOURCE_LANE_WIDTH - line_width - 8));
+ text = bpf_diag_fmt(env, "%s: %s", label, msg);
+ first_prefix = bpf_diag_fmt(env, " %*s | %*s^-- ", line_width + 4, "", indent, "");
+ next_prefix = bpf_diag_fmt(env, " %*s | %*s ", line_width + 4, "", indent, "");
+
+ diag_print_wrapped_prefixed(env, first_prefix, next_prefix, text);
+}
+
+static void diag_print_insn_context(struct bpf_verifier_env *env, u32 insn_idx,
+ struct disasm_line *disasm_lines)
+{
+ int insn_width = diag_line_width(env->prog->len ? env->prog->len - 1 : 0);
+ int i;
+
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
+ int row = i - BPF_DIAG_CONTEXT;
+
+ format_disasm_line(env, insn_idx + row, &disasm_lines[i]);
+ }
+
+ diag_write(env, " Instruction context:\n");
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
+ struct disasm_line *line = &disasm_lines[i];
+
+ if (line->valid)
+ diag_write(env, " %s%*d | %s\n",
+ line->idx == insn_idx ? ">>> " : " ",
+ insn_width, line->idx, line->text);
+ }
+}
+
+static void bpf_diag_source(struct bpf_verifier_env *env, u32 insn_idx, const char *label,
+ const char *fmt, ...)
+{
+ struct bpf_diag_scratch *scratch;
+ struct bpf_linfo_source *source_lines;
+ struct disasm_line *disasm_lines;
+ struct bpf_linfo_source src = {};
+ struct diag_fmt_mark mark;
+ const struct bpf_line_info *linfo;
+ const struct bpf_subprog_info *subprog;
+ struct btf *btf = env->prog->aux->btf;
+ char *source_lane;
+ const char *msg;
+ const char *func;
+ int start_line, end_line, width, indent, subprogno, linfo_start, linfo_end, i;
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+ if (!env->diag)
+ return;
+
+ mark = diag_fmt_save(env);
+ label = label ?: "note";
+ scratch = &env->diag->scratch;
+ source_lines = scratch->source_lines;
+ disasm_lines = scratch->disasm_lines;
+ memset(source_lines, 0, sizeof(scratch->source_lines));
+ memset(disasm_lines, 0, sizeof(scratch->disasm_lines));
+
+ va_start(args, fmt);
+ msg = bpf_diag_vfmt(env, fmt, args);
+ va_end(args);
+ if (!*msg)
+ msg = "<failed to allocate diagnostic text>";
+
+ linfo = bpf_find_linfo(env->prog, insn_idx);
+ if (btf && linfo)
+ bpf_get_linfo_source(btf, linfo, &src);
+ if (!src.file || !*src.file || !src.line || !*src.line) {
+ diag_write(env, " insn %u\n", insn_idx);
+ diag_print_source_annotation(env, 0, 0, label, msg);
+ diag_print_insn_context(env, insn_idx, disasm_lines);
+ goto out_restore;
+ }
+
+ subprog = bpf_find_containing_subprog(env, insn_idx);
+ subprogno = subprog ? subprog - env->subprog_info : -ENOENT;
+ func = subprogno >= 0 ? bpf_subprog_name(env, subprogno) : NULL;
+ if (func && *func)
+ diag_write(env, " %s @ %s:%d:%d\n", func, src.file, src.line_num, src.line_col);
+ else
+ diag_write(env, " %s:%d:%d\n", src.file, src.line_num, src.line_col);
+
+ start_line = src.line_num - BPF_DIAG_CONTEXT;
+ end_line = src.line_num + BPF_DIAG_CONTEXT;
+ width = diag_line_width(end_line);
+ indent = diag_line_indent(src.line);
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++)
+ source_lines[i].line_num = start_line + i;
+
+ linfo = env->prog->aux->linfo;
+ linfo_start = subprog ? subprog->linfo_idx : 0;
+ linfo_end = subprogno >= 0 && subprogno + 1 < env->subprog_cnt ?
+ env->subprog_info[subprogno + 1].linfo_idx : env->prog->aux->nr_linfo;
+ for (i = linfo_start; i < linfo_end; i++) {
+ struct bpf_linfo_source line_src;
+ int idx;
+
+ bpf_get_linfo_source(btf, &linfo[i], &line_src);
+ if (line_src.file_name_off != src.file_name_off ||
+ line_src.line_num < start_line || line_src.line_num > end_line ||
+ !line_src.line || !*line_src.line)
+ continue;
+
+ idx = line_src.line_num - start_line;
+ if (!source_lines[idx].line)
+ source_lines[idx] = line_src;
+ }
+
+ diag_write(env, " Source context:\n");
+ source_lane = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
+ if (!source_lane)
+ goto out_restore;
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
+ const char *source_prefix;
+
+ source_prefix = source_lines[i].line_num == src.line_num ? ">>> " : " ";
+ diag_format_source_lane(source_lane, BPF_DIAG_FMT_BUF_SIZE, source_prefix, width,
+ source_lines[i].line_num, source_lines[i].line);
+ diag_write(env, " %s\n", source_lane);
+ if (source_lines[i].line_num == src.line_num)
+ diag_print_source_annotation(env, width, indent, label, msg);
+ }
+ diag_print_insn_context(env, insn_idx, disasm_lines);
+
+out_restore:
+ diag_fmt_restore(env, mark);
+}
+
+static const struct bpf_func_state *diag_current_frame(const struct bpf_verifier_env *env)
+{
+ return env->cur_state->frame[env->cur_state->curframe];
+}
+
+void bpf_diag_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *problem, const char *reason, const char *suggestion)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type)
+{
+ switch (base_type(type)) {
+ case NOT_INIT:
+ return "an uninitialized value";
+ case SCALAR_VALUE:
+ return "an integer scalar";
+ case PTR_TO_CTX:
+ return "a context pointer";
+ case PTR_TO_STACK:
+ return "a stack pointer";
+ case PTR_TO_MAP_VALUE:
+ if (type_may_be_null(type))
+ return "a nullable map value pointer";
+ return "a map value pointer";
+ case PTR_TO_MEM:
+ if (type_may_be_null(type))
+ return "a nullable memory pointer";
+ return "a memory pointer";
+ case PTR_TO_BTF_ID:
+ if (type_may_be_null(type))
+ return "a nullable kernel object pointer";
+ if (type_is_non_owning_ref(type))
+ return "a borrowed allocated object pointer";
+ if (type_is_ptr_alloc_obj(type))
+ return "an owned allocated object pointer";
+ if (type_flag(type) & PTR_UNTRUSTED)
+ return "an untrusted kernel object pointer";
+ return "a kernel object pointer";
+ default:
+ return reg_type_str(env, type);
+ }
+}
+
+static const char *diag_arg_ordinal(int argno)
+{
+ switch (argno) {
+ case 1:
+ return "first";
+ case 2:
+ return "second";
+ case 3:
+ return "third";
+ case 4:
+ return "fourth";
+ case 5:
+ return "fifth";
+ case 6:
+ return "sixth";
+ case 7:
+ return "seventh";
+ case 8:
+ return "eighth";
+ case 9:
+ return "ninth";
+ case 10:
+ return "tenth";
+ case 11:
+ return "eleventh";
+ case 12:
+ return "twelfth";
+ default:
+ return NULL;
+ }
+}
+
+void bpf_diag_call_type(struct bpf_verifier_env *env, u32 insn_idx, int argno, int regno,
+ int stack_arg_slot, const char *call_name, const char *arg_name,
+ const char *reason, const char *suggestion)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ };
+ const char *ordinal = diag_arg_ordinal(argno);
+ const char *arg_desc;
+ bool print_history = true;
+
+ if (regno >= 0) {
+ opts.scope = BPF_DIAG_HISTORY_SCOPE_REG;
+ opts.regno = regno;
+ } else if (stack_arg_slot >= 0) {
+ opts.scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG;
+ opts.stack_arg_slot = stack_arg_slot;
+ } else {
+ print_history = false;
+ }
+
+ if (ordinal && arg_name)
+ arg_desc = bpf_diag_fmt(env, "%s argument (%s)", ordinal, arg_name);
+ else if (ordinal)
+ arg_desc = bpf_diag_fmt(env, "%s argument", ordinal);
+ else if (arg_name)
+ arg_desc = bpf_diag_fmt(env, "argument %s", arg_name);
+ else
+ arg_desc = "argument";
+
+ bpf_diag_header(env, CALL_TYPE_SAFETY, "invalid call argument");
+ diag_reason(env, "The %s to %s does not satisfy the verifier contract: %s.",
+ arg_desc, call_name, reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "invalid %s for %s", arg_desc, call_name);
+
+ if (print_history)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+static const char *diag_context_constraint(enum bpf_diag_context_kind kind)
+{
+ switch (kind) {
+ case BPF_DIAG_CONTEXT_RCU:
+ return "RCU read-side critical sections cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_PREEMPT:
+ return "preemption-disabled code cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_IRQ:
+ return "IRQ-disabled code cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_LOCK:
+ return "code holding a BPF spin lock cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_NONE:
+ default:
+ return NULL;
+ }
+}
+
+static const char *diag_active_context(struct bpf_verifier_env *env, u32 depth,
+ const char *context)
+{
+ if (depth == 1)
+ return bpf_diag_fmt(env, "an active %s (depth 1)", context);
+ return bpf_diag_fmt(env, "%u active %ss (depth %u)", depth, context, depth);
+}
+
+static u32 diag_context_depth(struct bpf_verifier_env *env, enum bpf_diag_context_kind kind)
+{
+ switch (kind) {
+ case BPF_DIAG_CONTEXT_RCU:
+ return env->cur_state->active_rcu_locks;
+ case BPF_DIAG_CONTEXT_PREEMPT:
+ return env->cur_state->active_preempt_locks;
+ case BPF_DIAG_CONTEXT_IRQ:
+ return bpf_diag_irq_depth(env->cur_state);
+ case BPF_DIAG_CONTEXT_LOCK:
+ return env->cur_state->active_locks;
+ case BPF_DIAG_CONTEXT_NONE:
+ default:
+ return 0;
+ }
+}
+
+void bpf_diag_ctx_forbidden(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, const char *suggestion)
+{
+ struct bpf_diag_history_opts opts;
+ enum bpf_diag_context_kind ctx_kind;
+ const char *constraint, *context;
+ u32 depth;
+
+ if (env->cur_state->active_rcu_locks)
+ ctx_kind = BPF_DIAG_CONTEXT_RCU;
+ else if (env->cur_state->active_preempt_locks)
+ ctx_kind = BPF_DIAG_CONTEXT_PREEMPT;
+ else if (env->cur_state->active_irq_id)
+ ctx_kind = BPF_DIAG_CONTEXT_IRQ;
+ else if (env->cur_state->active_locks)
+ ctx_kind = BPF_DIAG_CONTEXT_LOCK;
+ else
+ ctx_kind = BPF_DIAG_CONTEXT_NONE;
+
+ depth = diag_context_depth(env, ctx_kind);
+ opts = (struct bpf_diag_history_opts) {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = ctx_kind,
+ .ctx_depth = depth,
+ };
+ constraint = diag_context_constraint(ctx_kind);
+ context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY,
+ "operation is not allowed in this context");
+ if (constraint) {
+ if (depth) {
+ diag_reason(
+ env, "The operation %s cannot be used in %s because %s. This path is still inside %s.",
+ operation, context, constraint, diag_active_context(env, depth, context));
+ } else {
+ diag_reason(env, "The operation %s cannot be used in %s because %s.",
+ operation, context, constraint);
+ }
+ } else {
+ diag_reason(env, "The operation %s cannot be used in %s.", operation,
+ context);
+ }
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s is not allowed in %s", operation,
+ context);
+
+ if (ctx_kind != BPF_DIAG_CONTEXT_NONE)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_ctx_active(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion)
+{
+ u32 depth = diag_context_depth(env, ctx_kind);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = ctx_kind,
+ .ctx_depth = depth,
+ };
+ const char *context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY,
+ "operation is not allowed in this context");
+ diag_reason(
+ env, "The operation %s cannot be used while this path is still inside %s. Leave the region before this operation.",
+ operation, diag_active_context(env, depth, context));
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s is not allowed before leaving %s",
+ operation, context);
+
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_ctx_required(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion)
+{
+ const char *context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY, "required context is not active");
+ diag_reason(env, "The operation %s requires an active %s, but this path is outside one.",
+ operation, context);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s requires %s", operation, context);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_ctx_underflow(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, enum bpf_diag_context_kind ctx_kind,
+ const char *suggestion)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = ctx_kind,
+ };
+ const char *context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY, "unmatched context exit");
+ diag_reason(
+ env, "The operation %s tries to leave %s, but this path has no active %s to leave. The current depth is 0.",
+ operation, context, context);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s has no matching enter on this path",
+ operation);
+
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_program_structure(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *problem, const char *suggestion,
+ const char *reason_fmt, ...)
+{
+ va_list args;
+
+ bpf_diag_header(env, PROGRAM_STRUCTURE, problem);
+ diag_section(env, "Reason");
+
+ va_start(args, reason_fmt);
+ diag_vprint_indented(env, reason_fmt, args);
+ va_end(args);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_policy(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ const char *reason, const char *suggestion)
+{
+ bpf_diag_header(env, POLICY, "operation is not allowed");
+ diag_reason(env, "The %s is not allowed: %s.", operation, reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "policy check failed for %s", operation);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const struct bpf_reg_state *reg,
+ enum bpf_diag_invalid_deref_kind kind, s64 offset)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+ const char *type_name = bpf_diag_reg_type_plain(env, reg->type);
+
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, "invalid dereference");
+
+ switch (kind) {
+ case BPF_DIAG_DEREF_SCALAR:
+ diag_reason(env, "%s is an integer scalar here, not a pointer to memory.",
+ reg_name);
+ break;
+ case BPF_DIAG_DEREF_NULLABLE_PTR:
+ diag_reason(
+ env, "%s may be NULL here (%s). The program could dereference NULL on this path, so the verifier cannot prove this access is safe.",
+ reg_name, type_name);
+ break;
+ case BPF_DIAG_DEREF_MODIFIED_PTR:
+ diag_reason(
+ env, "%s has offset %lld here, but this pointer type must be dereferenced in its original form.",
+ reg_name, offset);
+ break;
+ case BPF_DIAG_DEREF_INVALID_PTR:
+ default:
+ diag_reason(
+ env, "%s has type %s here, which is not valid for this memory access.",
+ reg_name, type_name);
+ break;
+ }
+
+ diag_section(env, "At");
+ if (kind == BPF_DIAG_DEREF_MODIFIED_PTR)
+ bpf_diag_source(env, insn_idx, "error",
+ "dereference requires the original %s pointer", type_name);
+ else
+ bpf_diag_source(env, insn_idx, "error", "invalid dereference of %s (%s)",
+ reg_name, type_name);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ switch (kind) {
+ case BPF_DIAG_DEREF_NULLABLE_PTR:
+ diag_suggestion(
+ env, "Add a NULL check before the access and dereference the pointer only on the non-NULL path.");
+ break;
+ case BPF_DIAG_DEREF_MODIFIED_PTR:
+ diag_suggestion(
+ env, "Preserve the original pointer in another register, or use only offsets this pointer type permits before dereferencing it.");
+ break;
+ case BPF_DIAG_DEREF_SCALAR:
+ case BPF_DIAG_DEREF_INVALID_PTR:
+ default:
+ diag_suggestion(
+ env, "Preserve a pointer-valued register where needed, or reload and revalidate the pointer after scalar arithmetic, helper calls, or other operations that can invalidate it.");
+ break;
+ }
+}
+
+void bpf_diag_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+ const struct bpf_diag_log *log = env->diag ? &env->diag->log : NULL;
+ struct bpf_diag_mod_target target;
+ bool invalidated = false;
+ int i;
+
+ target = diag_reg_target(opts.frame_id, opts.frameno, regno);
+ for (i = log ? log->cnt : 0; i > 0; i--) {
+ const struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+
+ if (event->kind != BPF_DIAG_HISTORY_MOD ||
+ !diag_target_matches(&event->mod.target, &target))
+ continue;
+ invalidated = event->mod.new.type == NOT_INIT;
+ break;
+ }
+
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, "unreadable register");
+ if (invalidated)
+ diag_reason(
+ env, "R%d is not readable here. A previous operation invalidated this register, so the verifier cannot use it as an input.",
+ regno);
+ else if (log && !log->first_seq)
+ diag_reason(env,
+ "R%d has never been initialized on this path, so the verifier cannot use it as an input.",
+ regno);
+ else
+ diag_reason(
+ env, "R%d is not readable here. It may never have been initialized, or an earlier operation may have invalidated it.",
+ regno);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "R%d is not readable", regno);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ if (invalidated)
+ diag_suggestion(
+ env, "Avoid using the register after it is invalidated, or initialize it again before this instruction.");
+ else if (log && !log->first_seq)
+ diag_suggestion(env, "Initialize R%d on every path before this instruction.", regno);
+ else
+ diag_suggestion(
+ env, "Initialize the register on every path, or initialize it again after any operation that invalidates it.");
+}
+
+static int diag_stack_argno(u8 slot)
+{
+ return MAX_BPF_FUNC_REG_ARGS + slot + 1;
+}
+
+static void diag_format_stack_arg(char *buf, size_t size, u8 slot, const char *arg_name)
+{
+ int argno = diag_stack_argno(slot);
+ const char *ordinal = diag_arg_ordinal(argno);
+
+ if (ordinal && arg_name)
+ scnprintf(buf, size, "outgoing stack argument %u (%s argument, %s)", slot + 1,
+ ordinal, arg_name);
+ else if (ordinal)
+ scnprintf(buf, size, "outgoing stack argument %u (%s argument)", slot + 1, ordinal);
+ else if (arg_name)
+ scnprintf(buf, size, "outgoing stack argument %u (%s)", slot + 1, arg_name);
+ else
+ scnprintf(buf, size, "outgoing stack argument %u", slot + 1);
+}
+
+void bpf_diag_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,
+ int stack_arg_slot, const char *callee_name,
+ const char *arg_name)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .stack_arg_slot = stack_arg_slot,
+ };
+ const char *arg_buf;
+
+ arg_buf = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
+ if (arg_buf)
+ diag_format_stack_arg((char *)arg_buf, BPF_DIAG_FMT_BUF_SIZE, stack_arg_slot,
+ arg_name);
+ else
+ arg_buf = "";
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, "missing stack argument");
+ if (callee_name && *callee_name)
+ diag_reason(
+ env, "Function %s expects %d arguments, but %s is not initialized at this call.",
+ callee_name, nargs, arg_buf);
+ else
+ diag_reason(
+ env, "The callee expects %d arguments, but %s is not initialized at this call.",
+ nargs, arg_buf);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s is not initialized", arg_buf);
+
+ if (stack_arg_slot >= 0)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(
+ env, "Write the outgoing stack argument after any operation that may invalidate stored pointer values, and before making this call.");
+}
+
+void bpf_diag_memory(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion)
+{
+ bpf_diag_header(env, MEMORY_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true)
+{
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = BPF_DIAG_HISTORY_BRANCH,
+ .branch = {
+ .cond_true = cond_true,
+ },
+ };
+
+ diag_append_history(env, &event);
+}
+
+static void diag_snapshot_reg(struct bpf_diag_reg_snapshot *snapshot,
+ const struct bpf_reg_state *reg)
+{
+ snapshot->type = reg->type;
+ if (type_is_map_ptr(reg->type))
+ snapshot->map_ptr = reg->map_ptr;
+ if (base_type(reg->type) == PTR_TO_BTF_ID && reg->btf && reg->btf_id) {
+ snapshot->btf_id = reg->btf_id;
+ snapshot->btf = reg->btf;
+ }
+ snapshot->var_off = reg->var_off;
+ snapshot->r64 = reg->r64;
+}
+
+static bool diag_mod_insn_origin(struct bpf_verifier_env *env, u32 insn_idx,
+ const struct bpf_diag_mod_target *target,
+ struct bpf_diag_mod_target *origin)
+{
+ const struct bpf_insn *insn = &env->prog->insnsi[insn_idx];
+ u8 class = BPF_CLASS(insn->code);
+ const struct bpf_func_state *state;
+
+ if (target->kind == BPF_DIAG_MOD_TARGET_REG && (class == BPF_ALU || class == BPF_ALU64) &&
+ BPF_OP(insn->code) == BPF_MOV && BPF_SRC(insn->code) == BPF_X) {
+ *origin = diag_reg_target(target->frame_id, target->frameno, insn->src_reg);
+ return true;
+ }
+
+ if ((target->kind != BPF_DIAG_MOD_TARGET_STACK_ARG &&
+ target->kind != BPF_DIAG_MOD_TARGET_STACK_SLOT) ||
+ class != BPF_STX)
+ return false;
+
+ state = env->cur_state->frame[env->cur_state->curframe];
+ *origin = diag_reg_target(state->diag_frame_id, state->frameno, insn->src_reg);
+ return true;
+}
+
+static bool diag_mod_keeps_lineage(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ const struct bpf_insn *insn;
+ u8 class;
+
+ if (event->mod.reason != BPF_DIAG_MOD_WRITE ||
+ event->mod.target.kind != BPF_DIAG_MOD_TARGET_REG)
+ return false;
+
+ insn = &env->prog->insnsi[event->insn_idx];
+ class = BPF_CLASS(insn->code);
+ if (class != BPF_ALU && class != BPF_ALU64)
+ return false;
+
+ switch (BPF_OP(insn->code)) {
+ case BPF_ADD:
+ case BPF_SUB:
+ case BPF_MUL:
+ case BPF_OR:
+ case BPF_AND:
+ case BPF_LSH:
+ case BPF_RSH:
+ case BPF_ARSH:
+ case BPF_XOR:
+ case BPF_NEG:
+ case BPF_END:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void diag_record_mod(struct bpf_verifier_env *env, u32 insn_idx,
+ struct bpf_diag_mod_target target,
+ enum bpf_diag_mod_reason reason,
+ const struct bpf_reg_state *old_reg,
+ const struct bpf_reg_state *new_reg,
+ const struct bpf_diag_mod_target *origin)
+{
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = BPF_DIAG_HISTORY_MOD,
+ .mod = {
+ .target = target,
+ .reason = reason,
+ },
+ };
+
+ if (old_reg)
+ diag_snapshot_reg(&event.mod.old, old_reg);
+ if (new_reg)
+ diag_snapshot_reg(&event.mod.new, new_reg);
+ if (origin) {
+ event.mod.origin = *origin;
+ event.mod.origin_valid = true;
+ } else if (diag_mod_insn_origin(env, insn_idx, &target, &event.mod.origin)) {
+ event.mod.origin_valid = true;
+ }
+ if (old_reg && new_reg &&
+ (reason == BPF_DIAG_MOD_WRITE || reason == BPF_DIAG_MOD_SPILL) &&
+ !memcmp(&event.mod.old, &event.mod.new, sizeof(event.mod.old)) &&
+ !event.mod.origin_valid &&
+ diag_mod_keeps_lineage(env, &event))
+ return;
+
+ diag_append_history(env, &event);
+}
+
+static struct bpf_reg_state *target_to_reg(struct bpf_verifier_env *env,
+ const struct bpf_diag_mod_target *target)
+{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ struct bpf_func_state *state;
+
+ state = target->frameno <= vstate->curframe ? vstate->frame[target->frameno] : NULL;
+
+ if (!state)
+ return NULL;
+ if (state->diag_frame_id != target->frame_id)
+ return NULL;
+
+ switch (target->kind) {
+ case BPF_DIAG_MOD_TARGET_REG:
+ if (target->regno >= MAX_BPF_REG)
+ return NULL;
+ return &state->regs[target->regno];
+ case BPF_DIAG_MOD_TARGET_STACK_ARG:
+ if (target->stack_arg >= state->out_stack_arg_cnt)
+ return NULL;
+ return &state->stack_arg_regs[target->stack_arg];
+ case BPF_DIAG_MOD_TARGET_STACK_SLOT:
+ if (target->spi >= state->allocated_stack / BPF_REG_SIZE)
+ return NULL;
+ return &state->stack[target->spi].spilled_ptr;
+ default:
+ return NULL;
+ }
+}
+
+static bool reg_to_target(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ struct bpf_diag_mod_target *target)
+{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ unsigned long addr = (unsigned long)reg;
+ int frame;
+
+ for (frame = 0; frame <= vstate->curframe; frame++) {
+ struct bpf_func_state *state = vstate->frame[frame];
+ unsigned long start, end;
+ u32 nslots = state->allocated_stack / BPF_REG_SIZE;
+ int spi;
+
+ start = (unsigned long)state->regs;
+ end = (unsigned long)(state->regs + MAX_BPF_REG);
+ if (addr >= start && addr < end) {
+ *target = diag_reg_target(state->diag_frame_id, state->frameno,
+ reg - state->regs);
+ return true;
+ }
+
+ start = (unsigned long)state->stack_arg_regs;
+ end = (unsigned long)(state->stack_arg_regs + state->out_stack_arg_cnt);
+ if (state->out_stack_arg_cnt && addr >= start && addr < end) {
+ *target = diag_stack_arg_target(state->diag_frame_id, state->frameno,
+ reg - state->stack_arg_regs);
+ return true;
+ }
+
+ start = (unsigned long)state->stack;
+ end = (unsigned long)(state->stack + nslots);
+ if (nslots && addr >= start && addr < end) {
+ spi = ((const char *)reg - (const char *)state->stack) /
+ sizeof(*state->stack);
+ *target = diag_stack_slot_target(state->diag_frame_id, state->frameno, spi);
+ return true;
+ }
+ }
+ return false;
+}
+
+void bpf_diag_mod_begin(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ const struct bpf_reg_state *origin, enum bpf_diag_mod_reason reason)
+{
+ struct bpf_diag *diag = env->diag;
+
+ if (!diag)
+ return;
+ diag->mod.active = reg_to_target(env, reg, &diag->mod.target);
+ if (!diag->mod.active)
+ return;
+ diag->mod.target_reg_snapshot = *reg;
+ diag->mod.insn_idx = env->insn_idx;
+ diag->mod.reason = reason;
+ diag->mod.origin_valid = origin && reg_to_target(env, origin, &diag->mod.origin);
+}
+
+void bpf_diag_mod_end(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+ const struct bpf_reg_state *new_reg;
+
+ if (!diag || !diag->mod.active)
+ return;
+ diag->mod.active = false;
+ /*
+ * Resolve the target again because the enclosing function state's stack
+ * may have been reallocated while the modification was in progress.
+ */
+ new_reg = target_to_reg(env, &diag->mod.target);
+ if (!new_reg)
+ return;
+ diag_record_mod(env, diag->mod.insn_idx, diag->mod.target, diag->mod.reason,
+ &diag->mod.target_reg_snapshot, new_reg,
+ diag->mod.origin_valid ? &diag->mod.origin : NULL);
+}
+
+void bpf_diag_record_scrub(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ enum bpf_diag_mod_reason reason)
+{
+ struct bpf_diag_mod_target target;
+
+ if (!env->diag || reg->type == NOT_INIT || !reg_to_target(env, reg, &target))
+ return;
+ diag_record_mod(env, env->insn_idx, target, reason, reg, NULL, NULL);
+}
+
+void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,
+ const struct bpf_func_state *state, s16 min_off, s16 max_off,
+ enum bpf_diag_mod_reason reason)
+{
+ diag_record_mod(env, env->insn_idx,
+ diag_stack_range_target(state->diag_frame_id, state->frameno, min_off, max_off),
+ reason, NULL, NULL, NULL);
+}
+
+static void diag_record_ref(struct bpf_verifier_env *env, u32 insn_idx, u8 kind, u32 ref_id)
+{
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = kind,
+ .ref = {
+ .ref_id = ref_id,
+ },
+ };
+
+ diag_append_history(env, &event);
+}
+
+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)
+{
+ diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_ACQUIRE, ref_id);
+}
+
+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)
+{
+ diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_RELEASE, ref_id);
+}
+
+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,
+ enum bpf_diag_context_kind ctx_kind, bool enter, u32 depth)
+{
+ /*
+ * Keep leave events so context rendering can stop at a depth-zero exit
+ * and show nested-region depth accurately for the active path.
+ */
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = BPF_DIAG_HISTORY_CONTEXT,
+ .ctx = {
+ .kind = ctx_kind,
+ .enter = enter,
+ .depth = depth,
+ },
+ };
+
+ diag_append_history(env, &event);
+}
+
+static int diag_history_context_start_idx(const struct bpf_diag_log *log,
+ const struct bpf_diag_history_opts *opts)
+{
+ int i;
+
+ if (!opts->ctx_depth)
+ return 0;
+
+ /* Find the most recent outermost entry, or a depth-zero exit. */
+ for (i = log->cnt; i > 0; i--) {
+ const struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+
+ if (event->kind != BPF_DIAG_HISTORY_CONTEXT || event->ctx.kind != opts->ctx_kind)
+ continue;
+
+ if (event->ctx.enter && event->ctx.depth == 1)
+ return i - 1;
+ if (!event->ctx.enter && event->ctx.depth == 0)
+ return 0;
+ }
+
+ return 0;
+}
+
+struct bpf_diag_history_filter {
+ const struct bpf_diag_history_opts *opts;
+ u32 lineage_start;
+ bool lineage_valid;
+};
+
+static bool diag_target_matches(const struct bpf_diag_mod_target *event_target,
+ const struct bpf_diag_mod_target *target)
+{
+ int slot_off;
+
+ if (event_target->frame_id != target->frame_id || event_target->frameno != target->frameno)
+ return false;
+
+ if (event_target->kind == BPF_DIAG_MOD_TARGET_STACK_RANGE &&
+ target->kind == BPF_DIAG_MOD_TARGET_STACK_SLOT) {
+ slot_off = -(target->spi + 1) * BPF_REG_SIZE;
+ return event_target->range.min_off < slot_off + BPF_REG_SIZE &&
+ event_target->range.max_off > slot_off;
+ }
+
+ if (event_target->kind != target->kind)
+ return false;
+
+ switch (target->kind) {
+ case BPF_DIAG_MOD_TARGET_REG:
+ return event_target->regno == target->regno;
+ case BPF_DIAG_MOD_TARGET_STACK_ARG:
+ return event_target->stack_arg == target->stack_arg;
+ case BPF_DIAG_MOD_TARGET_STACK_SLOT:
+ return event_target->spi == target->spi;
+ default:
+ return false;
+ }
+}
+
+static void diag_build_lineage(struct bpf_verifier_env *env, struct bpf_diag_log *log,
+ struct bpf_diag_history_filter *filter)
+{
+ const struct bpf_diag_history_opts *opts = filter->opts;
+ struct bpf_diag_mod_target target;
+ int i;
+
+ for (i = 0; i < log->cnt; i++)
+ log->events[log_pos(log, i)].in_lineage = false;
+
+ if (opts->scope == BPF_DIAG_HISTORY_SCOPE_REG)
+ target = diag_reg_target(opts->frame_id, opts->frameno, opts->regno);
+ else if (opts->scope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG)
+ target = diag_stack_arg_target(opts->frame_id, opts->frameno,
+ opts->stack_arg_slot);
+ else
+ return;
+
+ /*
+ * Find the nearest mutation of the active target. A fill or spill changes
+ * the target to its origin, so the same walk follows register/stack
+ * lineage recursively until it reaches the write that created the value.
+ */
+ for (i = log->cnt; i > 0; i--) {
+ struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+ if (event->kind != BPF_DIAG_HISTORY_MOD ||
+ !diag_target_matches(&event->mod.target, &target))
+ continue;
+
+ event->in_lineage = true;
+ filter->lineage_start = i - 1;
+ filter->lineage_valid = true;
+
+ if (event->mod.origin_valid) {
+ target = event->mod.origin;
+ continue;
+ }
+ if (event->mod.reason != BPF_DIAG_MOD_WRITE &&
+ event->mod.reason != BPF_DIAG_MOD_SPILL)
+ continue;
+ if (diag_mod_keeps_lineage(env, event))
+ continue;
+ break;
+ }
+}
+
+static int diag_history_start_idx(const struct bpf_diag_log *log,
+ const struct bpf_diag_history_filter *filter)
+{
+ const struct bpf_diag_history_opts *opts = filter->opts;
+ int i;
+
+ if (opts->scope == BPF_DIAG_HISTORY_SCOPE_CONTEXT)
+ return diag_history_context_start_idx(log, opts);
+ if (filter->lineage_valid)
+ return filter->lineage_start;
+ if (opts->scope != BPF_DIAG_HISTORY_SCOPE_REF)
+ return 0;
+
+ for (i = log->cnt; i > 0; i--) {
+ const struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+ if (event->kind == BPF_DIAG_HISTORY_REF_ACQUIRE &&
+ event->ref.ref_id == opts->ref_id)
+ return i - 1;
+ }
+
+ return 0;
+}
+
+static bool diag_history_event_visible(const struct bpf_diag_history_event *event,
+ const struct bpf_diag_history_filter *filter)
+{
+ const struct bpf_diag_history_opts *opts = filter->opts;
+
+ switch (event->kind) {
+ case BPF_DIAG_HISTORY_BRANCH:
+ return true;
+ case BPF_DIAG_HISTORY_MOD:
+ return filter->lineage_valid && event->in_lineage;
+ case BPF_DIAG_HISTORY_REF_ACQUIRE:
+ case BPF_DIAG_HISTORY_REF_RELEASE:
+ return opts->scope == BPF_DIAG_HISTORY_SCOPE_REF &&
+ event->ref.ref_id == opts->ref_id;
+ case BPF_DIAG_HISTORY_CONTEXT:
+ return opts->scope == BPF_DIAG_HISTORY_SCOPE_CONTEXT &&
+ event->ctx.kind == opts->ctx_kind;
+ default:
+ return false;
+ }
+}
+
+static const char *diag_s64_bound_name(s64 value)
+{
+ if (value == S64_MIN)
+ return "S64_MIN";
+ if (value == S64_MAX)
+ return "S64_MAX";
+ return NULL;
+}
+
+static const char *diag_u64_bound_name(u64 value)
+{
+ if (value == U64_MAX)
+ return "U64_MAX";
+ return NULL;
+}
+
+static const char *diag_s64_str(struct bpf_verifier_env *env, s64 value)
+{
+ return diag_s64_bound_name(value) ?: bpf_diag_fmt(env, "%lld", value);
+}
+
+static const char *diag_u64_str(struct bpf_verifier_env *env, u64 value)
+{
+ return diag_u64_bound_name(value) ?: bpf_diag_fmt(env, "%llu", value);
+}
+
+static bool diag_cnum64_unknown(struct cnum64 range)
+{
+ return cnum64_smin(range) == S64_MIN && cnum64_smax(range) == S64_MAX &&
+ cnum64_umin(range) == 0 && cnum64_umax(range) == U64_MAX;
+}
+
+static bool diag_snapshot_unknown(const struct bpf_diag_reg_snapshot *snapshot)
+{
+ return tnum_is_unknown(snapshot->var_off) && diag_cnum64_unknown(snapshot->r64);
+}
+
+static const char *diag_scalar_range(struct bpf_verifier_env *env, struct cnum64 range)
+{
+ return bpf_diag_fmt(env, "signed range [%s, %s], unsigned range [%s, %s]",
+ diag_s64_str(env, cnum64_smin(range)),
+ diag_s64_str(env, cnum64_smax(range)),
+ diag_u64_str(env, cnum64_umin(range)),
+ diag_u64_str(env, cnum64_umax(range)));
+}
+
+const char *bpf_diag_fmt_s64_sum(struct bpf_verifier_env *env, s64 value, int addend)
+{
+ s64 sum;
+
+ if (check_add_overflow(value, (s64)addend, &sum))
+ return bpf_diag_fmt(env, "%lld plus %d (%s)", value, addend,
+ addend < 0 ? "below S64_MIN" : "above S64_MAX");
+
+ return bpf_diag_fmt(env, "%lld", sum);
+}
+
+static const char *diag_access_offset(struct bpf_verifier_env *env, int off,
+ const struct bpf_reg_state *reg)
+{
+ if (tnum_is_const(reg->var_off))
+ return bpf_diag_fmt(env, "constant %s",
+ bpf_diag_fmt_s64_sum(env, (s64)reg->var_off.value, off));
+
+ if (tnum_is_unknown(reg->var_off) && diag_cnum64_unknown(reg->r64))
+ return bpf_diag_fmt(env, "unbounded");
+
+ if (off)
+ return bpf_diag_fmt(env,
+ "variable: known bits %#llx, unknown mask %#llx, plus fixed offset %d; %s",
+ (u64)reg->var_off.value, reg->var_off.mask, off,
+ diag_scalar_range(env, reg->r64));
+ return bpf_diag_fmt(env, "variable: known bits %#llx, unknown mask %#llx; %s",
+ (u64)reg->var_off.value, reg->var_off.mask,
+ diag_scalar_range(env, reg->r64));
+}
+
+void bpf_diag_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const char *type_name, const char *proof,
+ int off, int size, u32 mem_size, const struct bpf_reg_state *reg)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+ const char *offset_desc;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ offset_desc = diag_access_offset(env, off, reg);
+
+ bpf_diag_header(env, MEMORY_SAFETY, "access outside bounds");
+ diag_reason(
+ env, "The verifier cannot prove offset + access_size <= object_size. Here, %s. %s is %s; offset is %s; access_size is %d; object_size is %u.",
+ proof, reg_name, type_name, offset_desc, size, mem_size);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "access may be outside object bounds");
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(
+ env, "Add or adjust a bounds check that proves offset + access_size stays within the object.");
+}
+
+static const char *diag_lock_name(const struct bpf_reference_state *lock)
+{
+ switch (lock->type) {
+ case REF_TYPE_LOCK:
+ return "bpf_spin_lock";
+ case REF_TYPE_RES_LOCK:
+ return "resource spin lock";
+ case REF_TYPE_RES_LOCK_IRQ:
+ return "IRQ-saving resource spin lock";
+ default:
+ return "lock";
+ }
+}
+
+static void diag_res_report(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason)
+{
+ bpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+}
+
+void bpf_diag_res(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion)
+{
+ diag_res_report(env, insn_idx, problem, reason);
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_lock(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion,
+ const struct bpf_reference_state *active_lock)
+{
+ diag_res_report(env, insn_idx, problem, reason);
+
+ if (active_lock) {
+ diag_section(env, "Active lock");
+ bpf_diag_source(env, active_lock->insn_idx, "acquired",
+ "active %s has verifier identity %d",
+ diag_lock_name(active_lock), active_lock->id);
+ }
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_irq(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion, u32 depth)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = BPF_DIAG_CONTEXT_IRQ,
+ .ctx_depth = depth,
+ };
+
+ bpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ if (depth)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_leak(struct bpf_verifier_env *env, u32 ref_id, u32 alloc_insn, u32 fail_insn)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REF,
+ .ref_id = ref_id,
+ };
+
+ bpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, "unreleased resource");
+ diag_reason(
+ env, "Owned resource (id=%u) was acquired at instruction %u and still needs to be released before this exit path.",
+ ref_id, alloc_insn);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, fail_insn, "error",
+ "owned resource (id=%u) still needs release", ref_id);
+
+ diag_print_history(env, &opts);
+
+ diag_suggestion(
+ env, "Release or transfer ownership of the acquired resource on every path before the program exits.");
+}
+
+static const char *diag_var_offset(struct bpf_verifier_env *env,
+ const struct bpf_diag_reg_snapshot *snapshot)
+{
+ if (tnum_is_const(snapshot->var_off))
+ return bpf_diag_fmt(env, "at offset %lld", (s64)snapshot->var_off.value);
+
+ if (diag_snapshot_unknown(snapshot))
+ return bpf_diag_fmt(env, "with unknown offset");
+
+ return bpf_diag_fmt(env,
+ "with variable offset: known bits %#llx, unknown mask %#llx, %s",
+ snapshot->var_off.value, snapshot->var_off.mask,
+ diag_scalar_range(env, snapshot->r64));
+}
+
+static const char *diag_reg_map_name(const struct bpf_map *map)
+{
+ if (!map || !map->name[0])
+ return NULL;
+
+ return map->name;
+}
+
+static const char *diag_reg_snapshot(struct bpf_verifier_env *env,
+ const struct bpf_diag_reg_snapshot *snapshot)
+{
+ const char *type_name = reg_type_str(env, snapshot->type);
+ const char *offset = diag_var_offset(env, snapshot);
+ const char *btf = snapshot->btf && snapshot->btf_id ?
+ bpf_diag_fmt_btf_type(env, snapshot->btf, snapshot->btf_id) : NULL;
+ const char *map_name;
+
+ if (snapshot->type == SCALAR_VALUE) {
+ if (tnum_is_const(snapshot->var_off))
+ return bpf_diag_fmt(env, "integer scalar value %lld",
+ (s64)snapshot->var_off.value);
+ if (diag_snapshot_unknown(snapshot))
+ return bpf_diag_fmt(env, "integer scalar with unknown value");
+ if (cnum64_is_const(snapshot->r64))
+ return bpf_diag_fmt(env, "integer scalar value %lld",
+ cnum64_smin(snapshot->r64));
+ return bpf_diag_fmt(env, "integer scalar with %s",
+ diag_scalar_range(env, snapshot->r64));
+ }
+
+ if (snapshot->type == NOT_INIT)
+ return bpf_diag_fmt(env, "uninitialized value");
+
+ if (base_type(snapshot->type) == PTR_TO_CTX)
+ return bpf_diag_fmt(env, "context pointer %s", offset);
+
+ if (base_type(snapshot->type) == PTR_TO_STACK)
+ return bpf_diag_fmt(env, "stack pointer %s", offset);
+
+ if (base_type(snapshot->type) == PTR_TO_MAP_VALUE) {
+ const char *kind = type_may_be_null(snapshot->type) ? "nullable map value" :
+ "map value";
+
+ map_name = diag_reg_map_name(snapshot->map_ptr);
+ if (map_name)
+ return bpf_diag_fmt(env, "%s from %s %s", kind, map_name, offset);
+ return bpf_diag_fmt(env, "%s %s", kind, offset);
+ }
+
+ if (base_type(snapshot->type) == CONST_PTR_TO_MAP) {
+ map_name = diag_reg_map_name(snapshot->map_ptr);
+ if (map_name)
+ return bpf_diag_fmt(env, "map pointer for map %s", map_name);
+ return bpf_diag_fmt(env, "map pointer");
+ }
+
+ if (type_is_non_owning_ref(snapshot->type)) {
+ if (btf)
+ return bpf_diag_fmt(env, "borrowed allocated object pointer type=%s", btf);
+ return bpf_diag_fmt(env, "borrowed allocated object pointer");
+ }
+
+ if (type_is_ptr_alloc_obj(snapshot->type)) {
+ if (btf)
+ return bpf_diag_fmt(env, "owned allocated object pointer type=%s", btf);
+ return bpf_diag_fmt(env, "owned allocated object pointer");
+ }
+
+ if (base_type(snapshot->type) == PTR_TO_BTF_ID && btf)
+ return bpf_diag_fmt(env, "%s type=%s %s", type_name, btf, offset);
+
+ return bpf_diag_fmt(env, "%s %s", type_name, offset);
+}
+
+static const char *diag_mod_target_desc(struct bpf_verifier_env *env,
+ const struct bpf_diag_mod_target *target)
+{
+ switch (target->kind) {
+ case BPF_DIAG_MOD_TARGET_REG:
+ return bpf_diag_fmt(env, "R%u", target->regno);
+ case BPF_DIAG_MOD_TARGET_STACK_ARG:
+ return bpf_diag_fmt(env, "stack arg%d", diag_stack_argno(target->stack_arg));
+ case BPF_DIAG_MOD_TARGET_STACK_SLOT:
+ return bpf_diag_fmt(env, "stack slot fp%d", -(target->spi + 1) * BPF_REG_SIZE);
+ default:
+ return "value";
+ }
+}
+
+static void diag_print_mod(struct bpf_verifier_env *env, const struct bpf_diag_history_event *event)
+{
+ const struct bpf_diag_mod_target *target = &event->mod.target;
+ const char *target_desc, *reason = NULL, *old, *new;
+ const char *label = "update";
+
+ if (target->kind == BPF_DIAG_MOD_TARGET_STACK_RANGE) {
+ bpf_diag_source(
+ env, event->insn_idx, "invalidated",
+ "variable-offset stack write may affect bytes fp%d through fp%d",
+ target->range.min_off, target->range.max_off - 1);
+ return;
+ }
+
+ old = diag_reg_snapshot(env, &event->mod.old);
+ new = diag_reg_snapshot(env, &event->mod.new);
+ target_desc = diag_mod_target_desc(env, target);
+
+ switch (event->mod.reason) {
+ case BPF_DIAG_MOD_REF_RELEASE:
+ reason = target->kind == BPF_DIAG_MOD_TARGET_REG ? "resource release invalidated "
+ "this pointer" :
+ "resource release invalidated "
+ "this value";
+ break;
+ case BPF_DIAG_MOD_PKT_DATA_CHANGE:
+ reason = "packet data may have moved";
+ break;
+ case BPF_DIAG_MOD_NON_OWN_REF:
+ reason = "leaving the protected region invalidated this borrowed pointer";
+ break;
+ case BPF_DIAG_MOD_CALLER_SAVED:
+ reason = target->kind == BPF_DIAG_MOD_TARGET_STACK_ARG ?
+ "call invalidated this outgoing stack argument" :
+ "call invalidated this caller-saved register";
+ break;
+ case BPF_DIAG_MOD_WRITE:
+ if (target->kind == BPF_DIAG_MOD_TARGET_STACK_SLOT)
+ reason = "a later stack write overwrote this spilled value";
+ break;
+ case BPF_DIAG_MOD_SPILL:
+ label = "spilled";
+ break;
+ case BPF_DIAG_MOD_VAR_WRITE:
+ default:
+ break;
+ }
+
+ if (reason) {
+ bpf_diag_source(env, event->insn_idx, "invalidated",
+ "%s: %s; previous value was %s", target_desc, reason, old);
+ return;
+ }
+
+ bpf_diag_source(env, event->insn_idx, label, "%s changed from %s to %s", target_desc,
+ old, new);
+}
+
+static void diag_print_ref_event(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ const char *label;
+
+ label = event->kind == BPF_DIAG_HISTORY_REF_ACQUIRE ? "acquired" : "released";
+ bpf_diag_source(env, event->insn_idx, label, "owned resource (id=%u)",
+ event->ref.ref_id);
+}
+
+static const char *diag_context_name(enum bpf_diag_context_kind kind)
+{
+ switch (kind) {
+ case BPF_DIAG_CONTEXT_RCU:
+ return "RCU read lock region";
+ case BPF_DIAG_CONTEXT_PREEMPT:
+ return "non-preemptible region";
+ case BPF_DIAG_CONTEXT_IRQ:
+ return "IRQ-disabled region";
+ case BPF_DIAG_CONTEXT_LOCK:
+ return "lock region";
+ case BPF_DIAG_CONTEXT_NONE:
+ default:
+ return "non-sleepable program";
+ }
+}
+
+static void diag_print_context_event(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ bpf_diag_source(env, event->insn_idx, "context", "%s %s; depth is now %u",
+ event->ctx.enter ? "entered" : "left",
+ diag_context_name(event->ctx.kind), event->ctx.depth);
+}
+
+static void diag_print_history(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_opts *opts)
+{
+ const struct bpf_diag_history_event *event;
+ struct bpf_diag_history_filter filter = {
+ .opts = opts,
+ };
+ struct bpf_diag_log *log;
+ struct diag_fmt_mark mark;
+ bool first = true;
+ int start_idx;
+ u32 i, visible_cnt = 0, visible_idx = 0;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ if (!env->diag)
+ return;
+ log = &env->diag->log;
+
+ diag_build_lineage(env, log, &filter);
+
+ start_idx = diag_history_start_idx(log, &filter);
+ for (i = start_idx; i < log->cnt; i++) {
+ event = &log->events[log_pos(log, i)];
+ if (diag_history_event_visible(event, &filter))
+ visible_cnt++;
+ }
+
+ if (!visible_cnt && !log->first_seq && opts->scope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG)
+ return;
+
+ diag_section(env, "Causal path");
+ mark = diag_fmt_save(env);
+ for (i = start_idx; i < log->cnt; i++) {
+ event = &log->events[log_pos(log, i)];
+ if (!diag_history_event_visible(event, &filter))
+ continue;
+
+ diag_fmt_restore(env, mark);
+ if (visible_cnt > BPF_DIAG_HISTORY_RENDER_MAX &&
+ visible_idx >= BPF_DIAG_HISTORY_RENDER_MAX / 2 &&
+ visible_idx < visible_cnt - BPF_DIAG_HISTORY_RENDER_MAX / 2) {
+ if (visible_idx++ != BPF_DIAG_HISTORY_RENDER_MAX / 2)
+ continue;
+ if (!first)
+ diag_write(env, "\n");
+ first = false;
+ diag_write(env, " %u intermediate causal-history events omitted\n",
+ visible_cnt - BPF_DIAG_HISTORY_RENDER_MAX);
+ continue;
+ }
+ visible_idx++;
+
+ if (!first)
+ diag_write(env, "\n");
+ first = false;
+
+ switch (event->kind) {
+ case BPF_DIAG_HISTORY_BRANCH:
+ bpf_diag_source(env, event->insn_idx, "branch",
+ "took the %s branch of this conditional, goto %s",
+ event->branch.cond_true ? "true" : "false",
+ event->branch.cond_true ? "followed" : "not followed");
+ break;
+ case BPF_DIAG_HISTORY_MOD:
+ diag_print_mod(env, event);
+ break;
+ case BPF_DIAG_HISTORY_REF_ACQUIRE:
+ case BPF_DIAG_HISTORY_REF_RELEASE:
+ diag_print_ref_event(env, event);
+ break;
+ case BPF_DIAG_HISTORY_CONTEXT:
+ diag_print_context_event(env, event);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!visible_cnt)
+ diag_write(env, " no retained diagnostic events on this path\n");
+ if (log->first_seq)
+ diag_write(env, " %llu older causal-history event%s not retained because diagnostic "
+ "event storage reached capacity\n",
+ log->first_seq, log->first_seq == 1 ? "" : "s");
+ diag_fmt_restore(env, mark);
+}
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
new file mode 100644
index 0000000000000..d1b79945008a8
--- /dev/null
+++ b/kernel/bpf/diagnostics.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#ifndef __BPF_DIAGNOSTICS_H
+#define __BPF_DIAGNOSTICS_H
+
+#include <linux/bpf.h>
+#include <linux/compiler_attributes.h>
+#include <linux/stdarg.h>
+#include <linux/types.h>
+
+struct bpf_reference_state;
+struct bpf_func_state;
+struct bpf_reg_state;
+struct bpf_verifier_env;
+struct bpf_verifier_state;
+struct btf;
+
+const char *bpf_diag_fmt_s64_sum(struct bpf_verifier_env *env, s64 value, int addend);
+enum bpf_diag_mod_reason {
+ BPF_DIAG_MOD_WRITE,
+ BPF_DIAG_MOD_SPILL,
+ BPF_DIAG_MOD_VAR_WRITE,
+ BPF_DIAG_MOD_REF_RELEASE,
+ BPF_DIAG_MOD_PKT_DATA_CHANGE,
+ BPF_DIAG_MOD_NON_OWN_REF,
+ BPF_DIAG_MOD_CALLER_SAVED,
+};
+
+enum bpf_diag_context_kind {
+ BPF_DIAG_CONTEXT_NONE,
+ BPF_DIAG_CONTEXT_RCU,
+ BPF_DIAG_CONTEXT_PREEMPT,
+ BPF_DIAG_CONTEXT_IRQ,
+ BPF_DIAG_CONTEXT_LOCK,
+};
+
+enum bpf_diag_invalid_deref_kind {
+ BPF_DIAG_DEREF_SCALAR,
+ BPF_DIAG_DEREF_NULLABLE_PTR,
+ BPF_DIAG_DEREF_MODIFIED_PTR,
+ BPF_DIAG_DEREF_INVALID_PTR,
+};
+
+bool bpf_diag_enabled(const struct bpf_verifier_env *env);
+int bpf_diag_init(struct bpf_verifier_env *env);
+void bpf_diag_init_frame(struct bpf_verifier_env *env, struct bpf_func_state *state);
+char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size);
+const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)
+ __printf(2, 0);
+const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
+const char *bpf_diag_fmt_btf_type(struct bpf_verifier_env *env, const struct btf *btf, u32 type_id);
+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type);
+u64 bpf_diag_event_log_save(struct bpf_verifier_env *env);
+void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos);
+u32 bpf_diag_irq_depth(const struct bpf_verifier_state *state);
+void bpf_diag_free(struct bpf_verifier_env *env);
+void bpf_diag_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *problem, const char *reason, const char *suggestion);
+void bpf_diag_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const struct bpf_reg_state *reg,
+ enum bpf_diag_invalid_deref_kind kind, s64 offset);
+void bpf_diag_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno);
+void bpf_diag_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,
+ int stack_arg_slot, const char *callee_name,
+ const char *arg_name);
+void bpf_diag_memory(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion);
+void bpf_diag_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const char *type_name, const char *proof,
+ int off, int size, u32 mem_size, const struct bpf_reg_state *reg);
+void bpf_diag_res(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion);
+void bpf_diag_lock(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion,
+ const struct bpf_reference_state *active_lock);
+void bpf_diag_irq(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion, u32 depth);
+void bpf_diag_leak(struct bpf_verifier_env *env, u32 ref_id, u32 alloc_insn, u32 fail_insn);
+void bpf_diag_call_type(struct bpf_verifier_env *env, u32 insn_idx, int argno, int regno,
+ int stack_arg_slot, const char *call_name, const char *arg_name,
+ const char *reason, const char *suggestion);
+void bpf_diag_ctx_forbidden(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, const char *suggestion);
+void bpf_diag_ctx_active(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion);
+void bpf_diag_ctx_required(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion);
+void bpf_diag_ctx_underflow(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, enum bpf_diag_context_kind ctx_kind,
+ const char *suggestion);
+void bpf_diag_program_structure(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *problem, const char *suggestion,
+ const char *reason_fmt, ...) __printf(5, 6);
+void bpf_diag_policy(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ const char *reason, const char *suggestion);
+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true);
+void bpf_diag_mod_begin(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ const struct bpf_reg_state *origin, enum bpf_diag_mod_reason reason);
+void bpf_diag_mod_end(struct bpf_verifier_env *env);
+void bpf_diag_record_scrub(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ enum bpf_diag_mod_reason reason);
+void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,
+ const struct bpf_func_state *state, s16 min_off, s16 max_off,
+ enum bpf_diag_mod_reason reason);
+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);
+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);
+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,
+ enum bpf_diag_context_kind ctx_kind, bool enter, u32 depth);
+
+#endif /* __BPF_DIAGNOSTICS_H */
diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
index b740fa73ee26d..589770ca3d3a0 100644
--- a/kernel/bpf/log.c
+++ b/kernel/bpf/log.c
@@ -615,17 +615,6 @@ static void print_scalar_ranges(struct bpf_verifier_env *env,
}
}
-static bool type_is_map_ptr(enum bpf_reg_type t) {
- switch (base_type(t)) {
- case CONST_PTR_TO_MAP:
- case PTR_TO_MAP_KEY:
- case PTR_TO_MAP_VALUE:
- return true;
- default:
- return false;
- }
-}
-
/*
* _a stands for append, was shortened to avoid multiline statements below.
* This macro is used to output a comma separated list of attributes.
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6ac1afced20bf..ff028a8c1cca1 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -34,6 +34,7 @@
#include <linux/trace_events.h>
#include <linux/kallsyms.h>
+#include "diagnostics.h"
#include "disasm.h"
static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
@@ -193,6 +194,7 @@ struct bpf_verifier_stack_elem {
struct bpf_verifier_stack_elem *next;
/* length of verifier log at the time this state was pushed on stack */
u32 log_pos;
+ u64 diag_log_pos;
};
#define BPF_COMPLEXITY_LIMIT_JMP_SEQ 8192
@@ -203,7 +205,8 @@ struct bpf_verifier_stack_elem {
#define BPF_PRIV_STACK_MIN_SIZE 64
static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id);
-static int release_reference_nomark(struct bpf_verifier_state *state, int id);
+static int __release_reference_nomark(struct bpf_verifier_state *state, int id);
+static int release_reference_nomark(struct bpf_verifier_env *env, int id);
static int release_reference(struct bpf_verifier_env *env, int id);
static void invalidate_non_owning_refs(struct bpf_verifier_env *env);
static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env);
@@ -405,7 +408,7 @@ static bool subprog_returns_void(struct bpf_verifier_env *env, int subprog)
return btf_type_is_void(type);
}
-static const char *subprog_name(const struct bpf_verifier_env *env, int subprog)
+const char *bpf_subprog_name(const struct bpf_verifier_env *env, int subprog)
{
struct bpf_func_info *info;
@@ -813,6 +816,10 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
if (dynptr_type_referenced(state->stack[spi].spilled_ptr.dynptr.type) &&
dynptr_ref_cnt(env, state->stack[spi].spilled_ptr.parent_id) <= 1) {
verbose(env, "cannot overwrite referenced dynptr\n");
+ bpf_diag_res(
+ env, env->insn_idx, "referenced dynptr overwrite",
+ "This stack slot contains a dynptr that owns or protects a referenced resource. Overwriting the last dynptr for that resource would lose the verifier-tracked release path.",
+ "Release or clone the dynptr so another live dynptr still tracks the referenced resource before overwriting this stack slot.");
return -EINVAL;
}
@@ -883,26 +890,29 @@ static bool is_dynptr_reg_valid_init(struct bpf_verifier_env *env, struct bpf_re
return true;
}
-static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
- enum bpf_arg_type arg_type)
+static enum bpf_dynptr_type dynptr_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
- struct bpf_func_state *state = bpf_func(env, reg);
- enum bpf_dynptr_type dynptr_type;
+ struct bpf_func_state *state;
int spi;
+ if (reg->type == CONST_PTR_TO_DYNPTR)
+ return reg->dynptr.type;
+
+ spi = dynptr_get_spi(env, reg);
+ if (spi < 0)
+ return BPF_DYNPTR_TYPE_INVALID;
+ state = bpf_func(env, reg);
+ return state->stack[spi].spilled_ptr.dynptr.type;
+}
+
+static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+ enum bpf_arg_type arg_type)
+{
/* ARG_PTR_TO_DYNPTR takes any type of dynptr */
if (arg_type == ARG_PTR_TO_DYNPTR)
return true;
- dynptr_type = arg_to_dynptr_type(arg_type);
- if (reg->type == CONST_PTR_TO_DYNPTR) {
- return reg->dynptr.type == dynptr_type;
- } else {
- spi = dynptr_get_spi(env, reg);
- if (spi < 0)
- return false;
- return state->stack[spi].spilled_ptr.dynptr.type == dynptr_type;
- }
+ return dynptr_reg_type(env, reg) == arg_to_dynptr_type(arg_type);
}
static void __mark_reg_known_zero(struct bpf_reg_state *reg);
@@ -1042,7 +1052,7 @@ static int is_iter_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_s
}
static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx);
-static int release_irq_state(struct bpf_verifier_state *state, int id);
+static int release_irq_state(struct bpf_verifier_env *env, int id);
static int mark_stack_slot_irq_flag(struct bpf_verifier_env *env,
struct bpf_call_arg_meta *meta,
@@ -1095,13 +1105,23 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
if (st->irq.kfunc_class != kfunc_class) {
const char *flag_kfunc = st->irq.kfunc_class == IRQ_NATIVE_KFUNC ? "native" : "lock";
const char *used_kfunc = kfunc_class == IRQ_NATIVE_KFUNC ? "native" : "lock";
+ const char *reason;
verbose(env, "irq flag acquired by %s kfuncs cannot be restored with %s kfuncs\n",
flag_kfunc, used_kfunc);
+ reason = bpf_diag_fmt(env,
+ "This IRQ flag was saved by %s IRQ kfuncs, but the restore call "
+ "belongs to the %s IRQ kfunc family. Save and restore operations "
+ "must use the same family.",
+ flag_kfunc, used_kfunc);
+ bpf_diag_irq(env, env->insn_idx, "IRQ flag restore mismatch", reason,
+ "Restore the flag with the matching IRQ restore kfunc for the save "
+ "operation that created it.",
+ bpf_diag_irq_depth(env->cur_state));
return -EINVAL;
}
- err = release_irq_state(env->cur_state, st->id);
+ err = release_irq_state(env, st->id);
WARN_ON_ONCE(err && err != -EACCES);
if (err) {
int insn_idx = 0;
@@ -1115,6 +1135,11 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
verbose(env, "cannot restore irq state out of order, expected id=%d acquired at insn_idx=%d\n",
env->cur_state->active_irq_id, insn_idx);
+ bpf_diag_irq(env, env->insn_idx, "IRQ flag restore out of order",
+ "IRQ-disabled regions must be restored in last-in, first-out order, "
+ "but this restore does not match the currently active IRQ flag.",
+ "Restore nested IRQ flags in the reverse order they were saved.",
+ bpf_diag_irq_depth(env->cur_state));
return err;
}
@@ -1416,6 +1441,7 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par
s->type = REF_TYPE_PTR;
s->id = ++env->id_gen;
s->parent_id = parent_id;
+ bpf_diag_record_ref_acquire(env, insn_idx, s->id);
return s->id;
}
@@ -1435,6 +1461,8 @@ static int acquire_lock_state(struct bpf_verifier_env *env, int insn_idx, enum r
state->active_locks++;
state->active_lock_id = id;
state->active_lock_ptr = ptr;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_LOCK, true,
+ state->active_locks);
return 0;
}
@@ -1450,6 +1478,8 @@ static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx)
s->id = ++env->id_gen;
state->active_irq_id = s->id;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_IRQ, true,
+ bpf_diag_irq_depth(state));
return s->id;
}
@@ -1491,8 +1521,9 @@ static bool reg_is_referenced(struct bpf_verifier_env *env, const struct bpf_reg
return find_reference_state(env->cur_state, reg->id);
}
-static int release_lock_state(struct bpf_verifier_state *state, int type, int id, void *ptr)
+static int release_lock_state(struct bpf_verifier_env *env, int type, int id, void *ptr)
{
+ struct bpf_verifier_state *state = env->cur_state;
void *prev_ptr = NULL;
u32 prev_id = 0;
int i;
@@ -1505,6 +1536,8 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id
/* Reassign active lock (id, ptr). */
state->active_lock_id = prev_id;
state->active_lock_ptr = prev_ptr;
+ bpf_diag_record_context(env, env->insn_idx, BPF_DIAG_CONTEXT_LOCK,
+ false, state->active_locks);
return 0;
}
if (state->refs[i].type & REF_TYPE_LOCK_MASK) {
@@ -1515,8 +1548,9 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id
return -EINVAL;
}
-static int release_irq_state(struct bpf_verifier_state *state, int id)
+static int release_irq_state(struct bpf_verifier_env *env, int id)
{
+ struct bpf_verifier_state *state = env->cur_state;
u32 prev_id = 0;
int i;
@@ -1529,6 +1563,8 @@ static int release_irq_state(struct bpf_verifier_state *state, int id)
if (state->refs[i].id == id) {
release_reference_state(state, i);
state->active_irq_id = prev_id;
+ bpf_diag_record_context(env, env->insn_idx, BPF_DIAG_CONTEXT_IRQ,
+ false, bpf_diag_irq_depth(state));
return 0;
} else {
prev_id = state->refs[i].id;
@@ -1699,6 +1735,7 @@ static int pop_stack(struct bpf_verifier_env *env, int *prev_insn_idx,
err = bpf_copy_verifier_state(cur, &head->st);
if (err)
return err;
+ bpf_diag_event_log_restore(env, head->diag_log_pos);
}
if (pop_log)
bpf_vlog_reset(&env->log, head->log_pos);
@@ -1742,6 +1779,7 @@ static struct bpf_verifier_state *push_stack(struct bpf_verifier_env *env,
elem->prev_insn_idx = prev_insn_idx;
elem->next = env->head;
elem->log_pos = env->log.end_pos;
+ elem->diag_log_pos = bpf_diag_event_log_save(env);
env->head = elem;
env->stack_size++;
err = bpf_copy_verifier_state(&elem->st, cur);
@@ -1788,6 +1826,17 @@ static const int caller_saved[CALLER_SAVED_REGS] = {
BPF_REG_0, BPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5
};
+static void bpf_diag_record_caller_saved(struct bpf_verifier_env *env,
+ struct bpf_reg_state *regs)
+{
+ int i;
+
+ for (i = 1; i < CALLER_SAVED_REGS; i++) {
+ bpf_diag_record_scrub(env, ®s[caller_saved[i]],
+ BPF_DIAG_MOD_CALLER_SAVED);
+ }
+}
+
/* This helper doesn't clear reg->id */
static void ___mark_reg_known(struct bpf_reg_state *reg, u64 imm)
{
@@ -2241,6 +2290,7 @@ static void init_func_state(struct bpf_verifier_env *env,
{
state->callsite = callsite;
state->frameno = frameno;
+ bpf_diag_init_frame(env, state);
state->subprogno = subprogno;
state->callback_ret_range = retval_range(0, 0);
init_reg_state(env, state);
@@ -2263,6 +2313,7 @@ static struct bpf_verifier_state *push_async_cb(struct bpf_verifier_env *env,
elem->prev_insn_idx = prev_insn_idx;
elem->next = env->head;
elem->log_pos = env->log.end_pos;
+ elem->diag_log_pos = bpf_diag_event_log_save(env);
env->head = elem;
env->stack_size++;
if (env->stack_size > BPF_COMPLEXITY_LIMIT_JMP_SEQ) {
@@ -2624,6 +2675,26 @@ static struct btf *find_kfunc_desc_btf(struct bpf_verifier_env *env, s16 offset)
return btf_vmlinux ?: ERR_PTR(-ENOENT);
}
+static struct btf *find_kfunc_desc_btf_cached(struct bpf_verifier_env *env, s16 offset)
+{
+ struct bpf_kfunc_btf kf_btf = { .offset = offset };
+ struct bpf_kfunc_btf_tab *tab;
+ struct bpf_kfunc_btf *b;
+
+ if (!offset)
+ return btf_vmlinux ?: ERR_PTR(-ENOENT);
+ if (offset < 0)
+ return ERR_PTR(-EINVAL);
+
+ tab = env->prog->aux->kfunc_btf_tab;
+ if (!tab)
+ return ERR_PTR(-ENOENT);
+
+ b = bsearch(&kf_btf, tab->descs, tab->nr_descs,
+ sizeof(tab->descs[0]), kfunc_btf_cmp_by_off);
+ return b ? b->btf : ERR_PTR(-ENOENT);
+}
+
#define KF_IMPL_SUFFIX "_impl"
static const struct btf_type *find_kfunc_impl_proto(struct bpf_verifier_log *log,
@@ -2853,6 +2924,10 @@ static int add_subprogs(struct bpf_verifier_env *env)
if (!env->bpf_capable) {
verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+ bpf_diag_policy(
+ env, i, "BPF-to-BPF function call",
+ "loading or calling other BPF functions requires CAP_BPF or CAP_SYS_ADMIN",
+ "Load this program with the required capability, or avoid BPF-to-BPF function calls in unprivileged programs.");
return -EPERM;
}
@@ -2905,6 +2980,10 @@ static int add_kfuncs(struct bpf_verifier_env *env)
if (!env->bpf_capable) {
verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+ bpf_diag_policy(
+ env, i, "kernel function call",
+ "calling kernel functions requires CAP_BPF or CAP_SYS_ADMIN",
+ "Load this program with the required capability, or avoid kernel function calls in unprivileged programs.");
return -EPERM;
}
@@ -2949,6 +3028,12 @@ static int check_subprogs(struct bpf_verifier_env *env)
off = i + bpf_jmp_offset(&insn[i]) + 1;
if (off < subprog_start || off >= subprog_end) {
verbose(env, "jump out of range from insn %d to %d\n", i, off);
+ bpf_diag_program_structure(
+ env, i, "jump out of range",
+ "Keep branch targets within the same subprogram, or use an explicit subprogram call.",
+ "Instruction %d jumps to instruction %d, but subprogram %d only contains instructions %d through %d. "
+ "A branch target must stay inside the same subprogram.",
+ i, off, cur_subprog, subprog_start, subprog_end - 1);
return -EINVAL;
}
next:
@@ -2961,6 +3046,11 @@ static int check_subprogs(struct bpf_verifier_env *env)
code != (BPF_JMP32 | BPF_JA) &&
code != (BPF_JMP | BPF_JA)) {
verbose(env, "last insn is not an exit or jmp\n");
+ bpf_diag_program_structure(
+ env, i, "subprogram can fall through",
+ "End each subprogram with an exit or an explicit jump that keeps control flow inside the subprogram.",
+ "Subprogram %d reaches its last instruction %d without an exit or jump, so control could continue into the next subprogram.",
+ cur_subprog, i);
return -EINVAL;
}
subprog_start = subprog_end;
@@ -3031,8 +3121,13 @@ static int sort_subprogs_topo(struct bpf_verifier_env *env)
if (bpf_pseudo_func(&insn[idx]))
continue;
verbose(env, "recursive call from %s() to %s()\n",
- subprog_name(env, cur),
- subprog_name(env, callee));
+ bpf_subprog_name(env, cur),
+ bpf_subprog_name(env, callee));
+ bpf_diag_program_structure(
+ env, idx, "recursive subprogram call",
+ "Rewrite the recursion as an explicit bounded loop, or split the logic so subprogram calls do not form a cycle.",
+ "This bpf2bpf call would make the subprogram call graph recursive. "
+ "The verifier requires a finite, acyclic call graph so it can bound stack depth and analysis.");
ret = -EINVAL;
goto out;
}
@@ -3053,7 +3148,7 @@ static int sort_subprogs_topo(struct bpf_verifier_env *env)
if (env->log.level & BPF_LOG_LEVEL2)
for (i = 0; i < cnt; i++)
verbose(env, "topo_order[%d] = %s\n",
- i, subprog_name(env, env->subprog_topo_order[i]));
+ i, bpf_subprog_name(env, env->subprog_topo_order[i]));
out:
kvfree(dfs_stack);
kvfree(color);
@@ -3081,6 +3176,7 @@ static int __check_reg_arg(struct bpf_verifier_env *env, struct bpf_reg_state *r
/* check whether register used as source operand can be read */
if (reg->type == NOT_INIT) {
verbose(env, "R%d !read_ok\n", regno);
+ bpf_diag_unreadable_reg(env, env->insn_idx, regno);
return -EACCES;
}
/* We don't need to worry about FP liveness because it's read-only */
@@ -3197,7 +3293,7 @@ static void linked_regs_unpack(u64 val, struct linked_regs *s)
}
}
-static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
+const char *bpf_disasm_kfunc_name(void *data, const struct bpf_insn *insn)
{
const struct btf_type *func;
struct btf *desc_btf;
@@ -3205,18 +3301,20 @@ static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
if (insn->src_reg != BPF_PSEUDO_KFUNC_CALL)
return NULL;
- desc_btf = find_kfunc_desc_btf(data, insn->off);
+ desc_btf = find_kfunc_desc_btf_cached(data, insn->off);
if (IS_ERR(desc_btf))
return "<error>";
func = btf_type_by_id(desc_btf, insn->imm);
+ if (!func || !btf_type_is_func(func))
+ return "<error>";
return btf_name_by_offset(desc_btf, func->name_off);
}
void bpf_verbose_insn(struct bpf_verifier_env *env, struct bpf_insn *insn)
{
const struct bpf_insn_cbs cbs = {
- .cb_call = disasm_kfunc_name,
+ .cb_call = bpf_disasm_kfunc_name,
.cb_print = verbose,
.private_data = env,
};
@@ -3335,6 +3433,7 @@ static void save_register_state(struct bpf_verifier_env *env,
{
int i;
+ bpf_diag_mod_begin(env, &state->stack[spi].spilled_ptr, reg, BPF_DIAG_MOD_SPILL);
state->stack[spi].spilled_ptr = *reg;
for (i = BPF_REG_SIZE; i > BPF_REG_SIZE - size; i--)
@@ -3343,6 +3442,8 @@ static void save_register_state(struct bpf_verifier_env *env,
/* size < 8 bytes spill */
for (; i; i--)
mark_stack_slot_misc(env, &state->stack[spi].slot_type[i - 1]);
+
+ bpf_diag_mod_end(env);
}
static bool is_bpf_st_mem(struct bpf_insn *insn)
@@ -3415,7 +3516,16 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
bpf_is_spilled_reg(&state->stack[spi]) &&
!bpf_is_spilled_scalar_reg(&state->stack[spi]) &&
size != BPF_REG_SIZE) {
+ const char *reason;
+
verbose(env, "attempt to corrupt spilled pointer on stack\n");
+ reason = bpf_diag_fmt(env,
+ "This store writes %d bytes at stack offset %d into a stack slot that currently holds a spilled pointer. "
+ "Partial writes to spilled pointers are rejected because they can corrupt pointer metadata and leak kernel pointers.",
+ size, off);
+ bpf_diag_memory(
+ env, insn_idx, "stack spill corruption", reason,
+ "Write the full 8-byte spilled pointer slot, or use a separate stack slot for scalar data before overwriting only part of it.");
return -EACCES;
}
@@ -3479,6 +3589,9 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
} else {
u8 type = STACK_MISC;
+ if (bpf_is_spilled_reg(&state->stack[spi]))
+ bpf_diag_record_scrub(env, &state->stack[spi].spilled_ptr,
+ BPF_DIAG_MOD_WRITE);
scrub_special_slot(state, spi);
/* when we zero initialize stack slots mark them as such */
@@ -3639,6 +3752,8 @@ static int check_stack_write_var_off(struct bpf_verifier_env *env,
if (err)
return err;
}
+ bpf_diag_record_scrub_stack(env, state, min_off, max_off,
+ BPF_DIAG_MOD_VAR_WRITE);
return 0;
}
@@ -3702,6 +3817,21 @@ static int mark_reg_stack_read(struct bpf_verifier_env *env,
return 0;
}
+static void bpf_diag_stack_read_uninit(struct bpf_verifier_env *env, int off, int i,
+ int size)
+{
+ const char *reason;
+
+ reason = bpf_diag_fmt(env,
+ "This rejected read uses %d bytes at stack offset %d, but byte %d in that range is uninitialized on this path. "
+ "Programs loaded with CAP_PERFMON can be allowed to read uninitialized stack bytes, but this program is being rejected without that allowance.",
+ size, off, i);
+ bpf_diag_memory(
+ env, env->insn_idx, "uninitialized stack read", reason,
+ "Initialize every byte in the stack range before reading it, adjust the offset and size so the read covers only initialized bytes, "
+ "or load with CAP_PERFMON if uninitialized stack reads are intended.");
+}
+
/* Read the stack at 'off' and put the results into the register indicated by
* 'dst_regno'. It handles reg filling if the addressed stack slot is a
* spilled reg.
@@ -3731,6 +3861,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
mark_stack_slot_scratched(env, spi);
check_fastcall_stack_contract(env, state, env->insn_idx, off);
+ /*
+ * Refine the in-progress load record's origin to the source stack slot.
+ */
+ if (dst_regno >= 0)
+ bpf_diag_mod_begin(env, &state->regs[dst_regno], reg, BPF_DIAG_MOD_WRITE);
+
if (bpf_is_spilled_reg(®_state->stack[spi])) {
u8 spill_size = 1;
@@ -3785,6 +3921,8 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
} else {
verbose(env, "invalid read from stack off %d+%d size %d\n",
off, i, size);
+ bpf_diag_stack_read_uninit(env, off, i,
+ size);
}
return -EACCES;
}
@@ -3843,6 +3981,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
} else {
verbose(env, "invalid read from stack off %d+%d size %d\n",
off, i, size);
+ bpf_diag_stack_read_uninit(env, off, i, size);
}
return -EACCES;
}
@@ -3935,11 +4074,19 @@ static int check_stack_read(struct bpf_verifier_env *env,
* check_stack_read_fixed_off).
*/
if (dst_regno < 0 && var_off) {
+ const char *reason;
char tn_buf[48];
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
verbose(env, "variable offset stack pointer cannot be passed into helper function; var_off=%s off=%d size=%d\n",
tn_buf, off, size);
+ reason = bpf_diag_fmt(env,
+ "The helper would access the stack through variable offset %s plus fixed offset %d and size %d. "
+ "Helper stack memory arguments require a constant stack offset and a precise initialized range.",
+ tn_buf, off, size);
+ bpf_diag_memory(
+ env, env->insn_idx, "variable stack access", reason,
+ "Use a fixed stack offset for helper memory arguments, or copy the needed bytes into a fixed stack slot first.");
return -EACCES;
}
/* Variable offset is prohibited for unprivileged mode for simplicity
@@ -4024,14 +4171,17 @@ static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_s
if (spi + 1 > subprog->max_out_stack_arg_cnt)
subprog->max_out_stack_arg_cnt = spi + 1;
+ arg = &state->stack_arg_regs[spi];
+ bpf_diag_mod_begin(env, arg, value_reg, BPF_DIAG_MOD_WRITE);
+
if (value_reg) {
state->stack_arg_regs[spi] = *value_reg;
} else {
/* BPF_ST: store immediate, treat as scalar */
- arg = &state->stack_arg_regs[spi];
arg->type = SCALAR_VALUE;
__mark_reg_known(arg, env->prog->insnsi[env->insn_idx].imm);
}
+ bpf_diag_mod_end(env);
state->no_stack_arg_load = true;
return bpf_push_jmp_history(env, env->cur_state,
INSN_F_STACK_ARG_ACCESS, spi, 0, 0);
@@ -4064,7 +4214,9 @@ static int check_stack_arg_read(struct bpf_verifier_env *env, struct bpf_func_st
caller = vstate->frame[vstate->curframe - 1];
arg = &caller->stack_arg_regs[spi];
cur = vstate->frame[vstate->curframe];
+ bpf_diag_mod_begin(env, &cur->regs[dst_regno], arg, BPF_DIAG_MOD_WRITE);
cur->regs[dst_regno] = *arg;
+ bpf_diag_mod_end(env);
return bpf_push_jmp_history(env, env->cur_state,
INSN_F_STACK_ARG_ACCESS, spi, 0, 0);
}
@@ -4079,7 +4231,8 @@ static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx)
}
static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller,
- int nargs)
+ int nargs, const char *callee_name, const struct btf *btf,
+ const struct btf_param *args)
{
int i, spi;
@@ -4087,8 +4240,14 @@ static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_fu
spi = i - MAX_BPF_FUNC_REG_ARGS;
if (spi >= caller->out_stack_arg_cnt ||
caller->stack_arg_regs[spi].type == NOT_INIT) {
+ const char *arg_name = NULL;
+
+ if (args && args[i].name_off)
+ arg_name = btf_name_by_offset(btf, args[i].name_off);
verbose(env, "callee expects %d args, stack arg%d is not initialized\n",
nargs, spi + 1);
+ bpf_diag_stack_arg_uninit(env, env->insn_idx, nargs, spi,
+ callee_name, arg_name);
return -EFAULT;
}
}
@@ -4169,6 +4328,9 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
int off, int size, u32 mem_size,
bool zero_size_allowed)
{
+ const char *proof = "";
+ const char *start;
+ s64 max_start, max_end;
int err;
/* We may have adjusted the register pointing to memory region, so we
@@ -4187,14 +4349,28 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
reg_smin(reg) + off < 0)) {
verbose(env, "%s min value is negative, either use unsigned index or do a if (index >=0) check.\n",
reg_arg_name(env, argno));
- return -EACCES;
+ err = -EACCES;
+ if (bpf_diag_enabled(env)) {
+ start = bpf_diag_fmt_s64_sum(env, reg_smin(reg), off);
+ proof = bpf_diag_fmt(
+ env, "the minimal bound for a memory access is a negative value: %s",
+ start);
+ }
+ goto report_error;
}
+
err = __check_mem_access(env, reg, argno, reg_smin(reg) + off, size,
mem_size, zero_size_allowed);
if (err) {
verbose(env, "%s min value is outside of the allowed memory range\n",
reg_arg_name(env, argno));
- return err;
+ if (bpf_diag_enabled(env)) {
+ start = bpf_diag_fmt_s64_sum(env, reg_smin(reg), off);
+ proof = bpf_diag_fmt(
+ env, "the minimal bound for a memory access is %s and is outside of the object of size %u",
+ start, mem_size);
+ }
+ goto report_error;
}
/* If we haven't set a max value then we need to bail since we can't be
@@ -4204,17 +4380,36 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
if (reg_umax(reg) >= BPF_MAX_VAR_OFF) {
verbose(env, "%s unbounded memory access, make sure to bounds check any such access\n",
reg_arg_name(env, argno));
- return -EACCES;
+ err = -EACCES;
+ if (bpf_diag_enabled(env))
+ proof = bpf_diag_fmt(
+ env, "the maximal bound for a memory access is %llu and exceeds maximum allowed offset of %u",
+ reg_umax(reg), BPF_MAX_VAR_OFF);
+ goto report_error;
}
+
err = __check_mem_access(env, reg, argno, reg_umax(reg) + off, size,
mem_size, zero_size_allowed);
if (err) {
verbose(env, "%s max value is outside of the allowed memory range\n",
reg_arg_name(env, argno));
- return err;
+ if (bpf_diag_enabled(env)) {
+ max_start = (s64)reg_umax(reg) + off;
+ max_end = max_start + size;
+ proof = bpf_diag_fmt(
+ env, "the maximal bound for a memory access is %lld: start %lld + access_size %d, beyond object_size %u",
+ max_end, max_start, size, mem_size);
+ }
+ goto report_error;
}
return 0;
+
+report_error:
+ bpf_diag_mem_bounds(env, env->insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg_type_str(env, reg->type), proof,
+ off, size, mem_size, reg);
+ return err;
}
static int __check_ptr_off_reg(struct bpf_verifier_env *env,
@@ -4243,6 +4438,9 @@ static int __check_ptr_off_reg(struct bpf_verifier_env *env,
if (!fixed_off_ok && reg->var_off.value != 0) {
verbose(env, "dereference of modified %s ptr %s off=%lld disallowed\n",
reg_type_str(env, reg->type), reg_arg_name(env, argno), reg->var_off.value);
+ bpf_diag_invalid_deref(env, env->insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg,
+ BPF_DIAG_DEREF_MODIFIED_PTR, reg->var_off.value);
return -EACCES;
}
@@ -6188,6 +6386,9 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (type_may_be_null(reg->type)) {
verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
+ bpf_diag_invalid_deref(env, insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg,
+ BPF_DIAG_DEREF_NULLABLE_PTR, 0);
return -EACCES;
}
@@ -6338,8 +6539,16 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
} else {
+ enum bpf_diag_invalid_deref_kind kind = BPF_DIAG_DEREF_INVALID_PTR;
+
verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
+ if (reg->type == SCALAR_VALUE)
+ kind = BPF_DIAG_DEREF_SCALAR;
+ else if (type_may_be_null(reg->type))
+ kind = BPF_DIAG_DEREF_NULLABLE_PTR;
+ bpf_diag_invalid_deref(env, insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg, kind, 0);
return -EACCES;
}
@@ -6399,15 +6608,19 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
src_reg_type = regs[insn->src_reg].type;
- /* Check if (src_reg + off) is readable. The state of dst_reg will be
- * updated by this call.
+ /*
+ * check_stack_read_fixed_off() may refine the modification's origin to
+ * the source stack slot.
*/
+ bpf_diag_mod_begin(env, ®s[insn->dst_reg], NULL, BPF_DIAG_MOD_WRITE);
err = check_mem_access(env, env->insn_idx, regs + insn->src_reg, argno_from_reg(insn->src_reg), insn->off,
BPF_SIZE(insn->code), BPF_READ, insn->dst_reg,
strict_alignment_once, is_ldsx);
err = err ?: save_aux_ptr_type(env, src_reg_type,
allow_trust_mismatch);
err = err ?: reg_bounds_sanity_check(env, ®s[insn->dst_reg], ctx);
+ if (!err)
+ bpf_diag_mod_end(env);
return err;
}
@@ -6513,10 +6726,14 @@ static int check_atomic_rmw(struct bpf_verifier_env *env,
*/
err = check_mem_access(env, env->insn_idx, dst_reg, argno_from_reg(insn->dst_reg), insn->off,
BPF_SIZE(insn->code), BPF_READ, -1, true, false);
- if (!err && load_reg >= 0)
+ if (!err && load_reg >= 0) {
+ bpf_diag_mod_begin(env, cur_regs(env) + load_reg, NULL, BPF_DIAG_MOD_WRITE);
err = check_mem_access(env, env->insn_idx, dst_reg, argno_from_reg(insn->dst_reg),
insn->off, BPF_SIZE(insn->code),
BPF_READ, load_reg, true, false);
+ if (!err)
+ bpf_diag_mod_end(env);
+ }
if (err)
return err;
@@ -6734,14 +6951,17 @@ static int check_stack_range_initialized(
return 0;
}
-static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
- int access_size, enum bpf_access_type access_type,
- bool zero_size_allowed,
- struct bpf_call_arg_meta *meta)
+static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+ argno_t argno, int access_size,
+ enum bpf_access_type access_type, bool zero_size_allowed,
+ struct bpf_call_arg_meta *meta, bool *known_memory)
{
struct bpf_reg_state *regs = cur_regs(env);
u32 *max_access;
+ if (known_memory)
+ *known_memory = true;
+
switch (base_type(reg->type)) {
case PTR_TO_PACKET:
case PTR_TO_PACKET_META:
@@ -6811,6 +7031,8 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_
if (zero_size_allowed && access_size == 0 &&
bpf_register_is_null(reg))
return 0;
+ if (known_memory && base_type(reg->type) != PTR_TO_CTX)
+ *known_memory = false;
verbose(env, "%s type=%s ", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
@@ -6819,6 +7041,12 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_
}
}
+enum bpf_mem_size_failure {
+ BPF_MEM_SIZE_FAIL_NONE,
+ BPF_MEM_SIZE_FAIL_MEMORY,
+ BPF_MEM_SIZE_FAIL_SIZE,
+};
+
/* verify arguments to helpers or kfuncs consisting of a pointer and an access
* size.
*
@@ -6829,10 +7057,14 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
struct bpf_reg_state *size_reg, argno_t mem_argno,
argno_t size_argno, u32 access_type,
bool zero_size_allowed,
- struct bpf_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta,
+ enum bpf_mem_size_failure *failure)
{
int err = 0;
+ if (failure)
+ *failure = BPF_MEM_SIZE_FAIL_NONE;
+
/* This is used to refine r0 return value bounds for helpers
* that enforce this value as an upper bound on return values.
* See do_refine_retval_range() for helpers that can refine
@@ -6854,27 +7086,32 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
if (reg_smin(size_reg) < 0) {
verbose(env, "%s min value is negative, either use unsigned or 'var &= const'\n",
reg_arg_name(env, size_argno));
- return -EACCES;
+ err = -EACCES;
+ goto size_error;
}
if (reg_umin(size_reg) == 0 && !zero_size_allowed) {
verbose(env, "%s invalid zero-sized read: u64=[%lld,%lld]\n",
reg_arg_name(env, size_argno), reg_umin(size_reg), reg_umax(size_reg));
- return -EACCES;
+ err = -EACCES;
+ goto size_error;
}
if (reg_umax(size_reg) >= BPF_MAX_VAR_SIZ) {
verbose(env, "%s unbounded memory access, use 'var &= const' or 'if (var < const)'\n",
reg_arg_name(env, size_argno));
- return -EACCES;
+ err = -EACCES;
+ goto size_error;
}
if (access_type & BPF_READ)
err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
- BPF_READ, zero_size_allowed, meta);
+ BPF_READ, zero_size_allowed, meta, NULL);
if (!err && access_type & BPF_WRITE)
err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
- BPF_WRITE, zero_size_allowed, meta);
+ BPF_WRITE, zero_size_allowed, meta, NULL);
+ if (err && failure)
+ *failure = BPF_MEM_SIZE_FAIL_MEMORY;
if (!err) {
int regno = reg_from_argno(size_argno);
@@ -6886,16 +7123,23 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
}
return err;
+
+size_error:
+ if (failure)
+ *failure = BPF_MEM_SIZE_FAIL_SIZE;
+ return err;
}
static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
argno_t argno, u32 mem_size, enum bpf_access_type access_type,
- struct bpf_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta, bool *known_memory)
{
int size, err = 0;
if (bpf_register_is_null(reg))
return 0;
+ if (known_memory)
+ *known_memory = true;
if (mem_size > S32_MAX) {
verbose(env, "%s memory size %u is too large\n",
@@ -6910,9 +7154,11 @@ static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg
size = (!meta && base_type(reg->type) == PTR_TO_STACK) ? -(int)mem_size : mem_size;
if (access_type & BPF_READ)
- err = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta);
+ err = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta,
+ known_memory);
if (!err && (access_type & BPF_WRITE))
- err = check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, meta);
+ err = check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, meta,
+ known_memory);
return err;
}
@@ -7013,6 +7259,7 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
bool is_lock = flags & PROCESS_SPIN_LOCK, is_res_lock = flags & PROCESS_RES_LOCK;
const char *lock_str = is_res_lock ? "bpf_res_spin" : "bpf_spin";
struct bpf_verifier_state *cur = env->cur_state;
+ struct bpf_reference_state *lock;
bool is_const = tnum_is_const(reg->var_off);
bool is_irq = flags & PROCESS_LOCK_IRQ;
u64 val = reg->var_off.value;
@@ -7062,14 +7309,25 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
ptr = btf;
if (!is_res_lock && cur->active_locks) {
- if (find_lock_state(env->cur_state, REF_TYPE_LOCK, 0, NULL)) {
+ lock = find_lock_state(cur, REF_TYPE_LOCK, 0, NULL);
+ if (lock) {
verbose(env,
"Locking two bpf_spin_locks are not allowed\n");
+ bpf_diag_lock(
+ env, env->insn_idx, "nested spin lock",
+ "This path already holds a bpf_spin_lock. The verifier allows only one regular BPF spin lock at a time.",
+ "Unlock the current bpf_spin_lock before taking another one.", lock);
return -EINVAL;
}
} else if (is_res_lock && cur->active_locks) {
- if (find_lock_state(env->cur_state, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ, reg->id, ptr)) {
+ lock = find_lock_state(cur, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ,
+ reg->id, ptr);
+ if (lock) {
verbose(env, "Acquiring the same lock again, AA deadlock detected\n");
+ bpf_diag_lock(
+ env, env->insn_idx, "recursive resource spin lock",
+ "This path already holds the same resource spin lock. Taking it again would deadlock.",
+ "Avoid reacquiring the same resource spin lock before it is unlocked.", lock);
return -EINVAL;
}
}
@@ -7096,6 +7354,10 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
if (!cur->active_locks) {
verbose(env, "%s_unlock without taking a lock\n", lock_str);
+ bpf_diag_res(
+ env, env->insn_idx, "unlock without lock",
+ "This unlock operation has no matching active lock on the current path.",
+ "Take the matching lock before this unlock, or remove the unmatched unlock path.");
return -EINVAL;
}
@@ -7105,16 +7367,35 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
type = REF_TYPE_RES_LOCK;
else
type = REF_TYPE_LOCK;
- if (!find_lock_state(cur, type, reg->id, ptr)) {
+
+ lock = find_lock_state(cur, type, reg->id, ptr);
+ if (!lock) {
verbose(env, "%s_unlock of different lock\n", lock_str);
+ lock = find_lock_state(cur, REF_TYPE_LOCK_MASK, cur->active_lock_id,
+ cur->active_lock_ptr);
+ bpf_diag_lock(
+ env, env->insn_idx, "unlock of a different lock",
+ "This unlock does not match any active lock with the same tracked identity on the current path.",
+ "Unlock the same lock object that was most recently acquired.", lock);
return -EINVAL;
}
if (reg->id != cur->active_lock_id || ptr != cur->active_lock_ptr) {
verbose(env, "%s_unlock cannot be out of order\n", lock_str);
+ lock = find_lock_state(cur, REF_TYPE_LOCK_MASK, cur->active_lock_id,
+ cur->active_lock_ptr);
+ bpf_diag_lock(
+ env, env->insn_idx, "unlock out of order",
+ "Locks must be released in last-in, first-out order, but this unlock does not match the currently active lock.",
+ "Release nested locks in the reverse order they were acquired.", lock);
return -EINVAL;
}
- if (release_lock_state(cur, type, reg->id, ptr)) {
+ if (release_lock_state(env, type, reg->id, ptr)) {
verbose(env, "%s_unlock of different lock\n", lock_str);
+ bpf_diag_lock(
+ env, env->insn_idx, "unlock of a different lock",
+ "The verifier could not release a lock state matching this unlock operation.",
+ "Pass the same lock object and lock kind that were used for the matching lock operation.",
+ lock);
return -EINVAL;
}
if (!in_rcu_cs(env))
@@ -7237,6 +7518,12 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,
return 0;
}
+static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
+ const char *call_name, const char *reason, const char *suggestion);
+__printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u32 insn_idx,
+ argno_t argno, const char *call_name,
+ const char *suggestion, const char *fmt, ...);
+
/*
* Validate dynptr arguments for helper, kfunc and subprog.
*
@@ -7261,7 +7548,8 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,
* and checked dynamically during runtime.
*/
static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
- argno_t argno, int insn_idx, enum bpf_arg_type arg_type,
+ argno_t argno, int insn_idx, const char *call_name,
+ enum bpf_arg_type arg_type,
struct ref_obj_desc *ref_obj, struct bpf_dynptr_desc *dynptr)
{
int spi, err = 0;
@@ -7270,6 +7558,11 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
verbose(env,
"%s expected pointer to stack or const struct bpf_dynptr\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, call_name,
+ "Pass the address of a stack dynptr object, or use a const dynptr pointer returned by the verifier-supported path.",
+ "a dynptr argument must be a pointer to a dynptr stack slot or a verifier-provided const struct bpf_dynptr, but %s is %s",
+ reg_arg_name(env, argno), bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -7292,6 +7585,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
if (!is_dynptr_reg_valid_uninit(env, reg)) {
verbose(env, "Dynptr has to be an uninitialized dynptr\n");
+ bpf_diag_res(
+ env, insn_idx, "dynptr is already initialized",
+ "This kfunc constructs a dynptr and requires an uninitialized dynptr stack slot, but the selected slot already holds dynptr state.",
+ "Use a fresh stack dynptr slot, or release/destroy the existing dynptr before reusing the slot.");
return -EINVAL;
}
@@ -7308,21 +7605,35 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
/* For the reg->type == PTR_TO_STACK case, bpf_dynptr is never const */
if (reg->type == CONST_PTR_TO_DYNPTR && (arg_type & OBJ_RELEASE)) {
verbose(env, "CONST_PTR_TO_DYNPTR cannot be released\n");
+ bpf_diag_res(
+ env, insn_idx, "const dynptr release",
+ "This release operation was given a const dynptr. Const dynptr values are verifier-provided views and cannot be released by the program.",
+ "Release only mutable dynptrs that the program initialized or reserved.");
return -EINVAL;
}
if (!is_dynptr_reg_valid_init(env, reg)) {
verbose(env, "Expected an initialized dynptr as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_res(
+ env, insn_idx, "uninitialized dynptr use",
+ "This operation requires an initialized dynptr, but the stack slot does not currently hold a valid dynptr on this path.",
+ "Initialize the dynptr on every path before this call, and avoid overwriting or releasing it before this use.");
return -EINVAL;
}
/* Fold modifiers (in this case, OBJ_RELEASE) when checking expected type */
if (!is_dynptr_type_expected(env, reg, arg_type & ~OBJ_RELEASE)) {
- verbose(env,
- "Expected a dynptr of type %s as %s\n",
- dynptr_type_str(arg_to_dynptr_type(arg_type)),
- reg_arg_name(env, argno));
+ enum bpf_dynptr_type expected_type = arg_to_dynptr_type(arg_type);
+ enum bpf_dynptr_type actual_type = dynptr_reg_type(env, reg);
+
+ verbose(env, "Expected a dynptr of type %s as %s\n",
+ dynptr_type_str(expected_type), reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, call_name,
+ "Use a dynptr constructor that matches this operation, or call an operation that accepts the dynptr's current type.",
+ "the dynptr is initialized with backing object type %s, but this operation expects dynptr type %s",
+ dynptr_type_str(actual_type), dynptr_type_str(expected_type));
return -EINVAL;
}
@@ -7386,6 +7697,11 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
if (reg->type != PTR_TO_STACK) {
verbose(env, "%s expected pointer to an iterator on stack\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, meta->func_name,
+ "Pass the address of a stack iterator object for iterator new, next, and destroy calls.",
+ "iterator state must live in verifier-tracked stack memory, but %s is %s",
+ reg_arg_name(env, argno), bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -7399,6 +7715,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
if (btf_id < 0) {
verbose(env, "expected valid iter pointer as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg(
+ env, insn_idx, argno, meta->func_name,
+ "the kfunc expects a recognized iterator state pointer, but this argument does not match a valid iterator type",
+ "Pass the exact iterator state type expected by this kfunc.");
return -EINVAL;
}
t = btf_type_by_id(meta->btf, btf_id);
@@ -7409,6 +7729,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
if (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {
verbose(env, "expected uninitialized iter_%s as %s\n",
iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
+ bpf_diag_res(
+ env, insn_idx, "iterator is already initialized",
+ "Iterator creation requires an uninitialized iterator stack object, but this stack range already contains iterator state.",
+ "Use a fresh iterator stack slot, or destroy the existing iterator before reusing the slot.");
return -EINVAL;
}
@@ -7433,9 +7757,16 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
case -EINVAL:
verbose(env, "expected an initialized iter_%s as %s\n",
iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
+ bpf_diag_res(
+ env, insn_idx, "uninitialized iterator use",
+ "This iterator operation requires an initialized iterator state object, but the stack range does not contain a live iterator on this path.",
+ "Call the matching iterator new kfunc on every path before calling next or destroy, and do not destroy the iterator before this use.");
return err;
case -EPROTO:
verbose(env, "expected an RCU CS when using %s\n", meta->func_name);
+ bpf_diag_ctx_required(
+ env, insn_idx, meta->func_name, BPF_DIAG_CONTEXT_RCU,
+ "Wrap iterator use in bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.");
return err;
default:
return err;
@@ -7855,13 +8186,70 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
[ARG_PTR_TO_DYNPTR] = &dynptr_types,
};
+static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
+ const char *call_name, const char *reason,
+ const char *suggestion)
+{
+ int arg = arg_from_argno(argno);
+ int regno = reg_from_argno(argno);
+ int stack_slot = -1;
+
+ if (arg < 0 && regno >= BPF_REG_1 && regno <= BPF_REG_5)
+ arg = regno;
+ if (arg > MAX_BPF_FUNC_REG_ARGS)
+ stack_slot = arg - MAX_BPF_FUNC_REG_ARGS - 1;
+
+ bpf_diag_call_type(env, insn_idx, arg, regno, stack_slot,
+ call_name && *call_name ? call_name : "call",
+ reg_arg_name(env, argno), reason, suggestion);
+}
+
+static const char *bpf_diag_arg_name(struct bpf_verifier_env *env, argno_t argno)
+{
+ return bpf_diag_fmt(env, "%s", reg_arg_name(env, argno));
+}
+
+__printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u32 insn_idx,
+ argno_t argno, const char *call_name,
+ const char *suggestion, const char *fmt, ...)
+{
+ const char *reason;
+ va_list args;
+
+ va_start(args, fmt);
+ reason = bpf_diag_vfmt(env, fmt, args);
+ va_end(args);
+
+ bpf_diag_call_arg(env, insn_idx, argno, call_name, reason, suggestion);
+}
+
+static const char *bpf_diag_expected_reg_types(struct bpf_verifier_env *env,
+ const enum bpf_reg_type *types, int count)
+{
+ size_t len = 0, size = 1;
+ char *buf;
+ int i;
+
+ for (i = 0; i < count; i++)
+ size += strlen(reg_type_str(env, types[i])) + (i ? 2 : 0);
+
+ buf = bpf_diag_fmt_buf(env, size);
+ if (!buf)
+ return "";
+
+ for (i = 0; i < count; i++)
+ len += scnprintf(buf + len, size - len, "%s%s", i ? ", " : "",
+ reg_type_str(env, types[i]));
+ return buf;
+}
+
static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
- enum bpf_arg_type arg_type,
- const u32 *arg_btf_id,
- struct bpf_call_arg_meta *meta)
+ enum bpf_arg_type arg_type, const u32 *arg_btf_id,
+ struct bpf_call_arg_meta *meta, const char *call_name)
{
enum bpf_reg_type expected, type = reg->type;
const struct bpf_reg_types *compatible;
+ const char *actual, *accepted;
int i, j, err;
compatible = compatible_reg_types[base_type(arg_type)];
@@ -7908,6 +8296,12 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
for (j = 0; j + 1 < i; j++)
verbose(env, "%s, ", reg_type_str(env, compatible->types[j]));
verbose(env, "%s\n", reg_type_str(env, compatible->types[j]));
+ actual = bpf_diag_fmt(env, "%s", reg_type_str(env, reg->type));
+ accepted = bpf_diag_expected_reg_types(env, compatible->types, i);
+ bpf_diag_call_arg_fmt(env, env->insn_idx, argno, call_name,
+ "Pass a value with one of the accepted pointer or scalar types for this call.",
+ "it has type %s, but this argument accepts %s",
+ actual, accepted);
return -EACCES;
found:
@@ -7944,6 +8338,10 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
(!type_may_be_null(arg_type) || arg_type_is_release(arg_type))) {
verbose(env, "Possibly NULL pointer passed to helper %s\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg(
+ env, env->insn_idx, argno, call_name,
+ "the pointer may be NULL, but this call requires a non-NULL pointer",
+ "Add a NULL check and make the call only on the non-NULL path.");
return -EACCES;
}
@@ -8330,7 +8728,8 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK)
arg_btf_id = fn->arg_btf_id[arg];
- err = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta);
+ err = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta,
+ func_id_name(meta->func_id));
if (err)
return err;
@@ -8343,6 +8742,10 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
!reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
verbose(env, "release helper %s expects referenced PTR_TO_BTF_ID passed to %s\n",
func_id_name(meta->func_id), reg_arg_name(env, argno));
+ bpf_diag_call_arg(
+ env, insn_idx, argno, func_id_name(meta->func_id),
+ "release helpers require a value that owns a live resource returned by a matching acquire helper",
+ "Pass the resource-owning pointer returned by the matching acquire helper, and avoid calling the release helper after ownership has already been transferred or released.");
return -EINVAL;
}
@@ -8371,7 +8774,8 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
return -EFAULT;
}
key_size = meta->map.ptr->key_size;
- err = check_helper_mem_access(env, reg, argno, key_size, BPF_READ, false, NULL);
+ err = check_helper_mem_access(env, reg, argno, key_size, BPF_READ, false, NULL,
+ NULL);
if (err)
return err;
if (can_elide_value_nullness(meta->map.ptr)) {
@@ -8408,7 +8812,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
err = check_helper_mem_access(env, reg, argno, meta->map.ptr->value_size,
arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ,
- false, meta);
+ false, meta, NULL);
break;
case ARG_PTR_TO_PERCPU_BTF_ID:
if (!reg->btf_id) {
@@ -8450,7 +8854,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
*/
if (arg_type & MEM_FIXED_SIZE) {
err = check_mem_reg(env, reg, argno_from_reg(regno), fn->arg_size[arg],
- arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ, meta);
+ arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ, meta, NULL);
if (err)
return err;
if (arg_type & MEM_ALIGNED)
@@ -8461,17 +8865,17 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
err = check_mem_size_reg(env, reg_state(env, regno - 1), reg,
argno_from_reg(regno - 1), argno,
fn->arg_type[arg - 1] & MEM_WRITE ? BPF_WRITE : BPF_READ,
- false, meta);
+ false, meta, NULL);
break;
case ARG_MEM_SIZE_OR_ZERO:
err = check_mem_size_reg(env, reg_state(env, regno - 1), reg,
argno_from_reg(regno - 1), argno,
fn->arg_type[arg - 1] & MEM_WRITE ? BPF_WRITE : BPF_READ,
- true, meta);
+ true, meta, NULL);
break;
case ARG_PTR_TO_DYNPTR:
- err = process_dynptr_func(env, reg, argno, insn_idx, arg_type, &meta->ref_obj,
- &meta->dynptr);
+ err = process_dynptr_func(env, reg, argno, insn_idx, func_id_name(meta->func_id),
+ arg_type, &meta->ref_obj, &meta->dynptr);
if (err)
return err;
break;
@@ -8918,8 +9322,10 @@ static void clear_all_pkt_pointers(struct bpf_verifier_env *env)
struct bpf_reg_state *reg;
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
- if (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg))
+ if (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) {
+ bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_PKT_DATA_CHANGE);
mark_reg_invalid(env, reg);
+ }
}));
}
@@ -8949,7 +9355,7 @@ static void mark_pkt_end(struct bpf_verifier_state *vstate, int regn, bool range
reg->range = AT_PKT_END;
}
-static int release_reference_nomark(struct bpf_verifier_state *state, int id)
+static int __release_reference_nomark(struct bpf_verifier_state *state, int id)
{
int i;
@@ -8964,6 +9370,16 @@ static int release_reference_nomark(struct bpf_verifier_state *state, int id)
return -EINVAL;
}
+static int release_reference_nomark(struct bpf_verifier_env *env, int id)
+{
+ int err;
+
+ err = __release_reference_nomark(env->cur_state, id);
+ if (!err)
+ bpf_diag_record_ref_release(env, env->insn_idx, id);
+ return err;
+}
+
static int idstack_push(struct bpf_idmap *idmap, u32 id)
{
int i;
@@ -9006,8 +9422,10 @@ static int release_reference(struct bpf_verifier_env *env, int id)
if (err)
return err;
- if (find_reference_state(vstate, id))
- WARN_ON_ONCE(release_reference_nomark(vstate, id));
+ if (find_reference_state(vstate, id)) {
+ err = release_reference_nomark(env, id);
+ WARN_ON_ONCE(err);
+ }
while ((id = idstack_pop(idstack))) {
/*
@@ -9035,10 +9453,25 @@ static int release_reference(struct bpf_verifier_env *env, int id)
return err;
}
+ /*
+ * A dynptr occupies two stack slots that invalidate_dynptr()
+ * clears together. Record both scrubs before invalidating it.
+ */
+ if (stack && stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR) {
+ struct bpf_stack_state *dyn_stack = stack;
+
+ if (reg->dynptr.first_slot)
+ dyn_stack--;
+ bpf_diag_record_scrub(env, &dyn_stack[0].spilled_ptr,
+ BPF_DIAG_MOD_REF_RELEASE);
+ bpf_diag_record_scrub(env, &dyn_stack[1].spilled_ptr,
+ BPF_DIAG_MOD_REF_RELEASE);
+ invalidate_dynptr(env, dyn_stack);
+ continue;
+ }
+ bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_REF_RELEASE);
if (!stack || stack->slot_type[BPF_REG_SIZE - 1] == STACK_SPILL)
mark_reg_invalid(env, reg);
- else if (stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR)
- invalidate_dynptr(env, stack);
}));
}
@@ -9051,8 +9484,10 @@ static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
struct bpf_reg_state *reg;
bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
- if (type_is_non_owning_ref(reg->type))
+ if (type_is_non_owning_ref(reg->type)) {
+ bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_NON_OWN_REF);
mark_reg_invalid(env, reg);
+ }
}));
}
@@ -9065,8 +9500,10 @@ static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)
bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack, clear_mask, ({
if (reg->type & MEM_RCU) {
+ bpf_diag_mod_begin(env, reg, NULL, BPF_DIAG_MOD_WRITE);
reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL);
reg->type |= PTR_UNTRUSTED;
+ bpf_diag_mod_end(env);
}
}));
}
@@ -9077,15 +9514,19 @@ static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 id)
struct bpf_reg_state *reg;
int err;
- err = release_reference_nomark(env->cur_state, id);
+ err = release_reference_nomark(env, id);
+ if (err)
+ return err;
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
if (reg->id != id)
continue;
if ((reg->type & MEM_ALLOC) && (reg->type & MEM_PERCPU)) {
+ bpf_diag_mod_begin(env, reg, NULL, BPF_DIAG_MOD_WRITE);
reg->id = 0;
reg->type &= ~MEM_ALLOC;
reg->type |= MEM_RCU;
+ bpf_diag_mod_end(env);
}
}));
@@ -9097,6 +9538,8 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,
{
int i;
+ bpf_diag_record_caller_saved(env, regs);
+
/* after the call registers r0 - r5 were scratched */
for (i = 0; i < CALLER_SAVED_REGS; i++) {
bpf_mark_reg_not_init(env, ®s[caller_saved[i]]);
@@ -9104,13 +9547,15 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,
}
}
-static void invalidate_outgoing_stack_args(const struct bpf_verifier_env *env,
+static void invalidate_outgoing_stack_args(struct bpf_verifier_env *env,
struct bpf_func_state *state)
{
int i, nslots = state->out_stack_arg_cnt;
- for (i = 0; i < nslots; i++)
+ for (i = 0; i < nslots; i++) {
+ bpf_diag_record_scrub(env, &state->stack_arg_regs[i], BPF_DIAG_MOD_CALLER_SAVED);
bpf_mark_reg_not_init(env, &state->stack_arg_regs[i]);
+ }
}
typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env,
@@ -9178,20 +9623,28 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
struct bpf_func_state *caller = cur_func(env);
struct bpf_verifier_log *log = &env->log;
struct ref_obj_desc ref_obj = {};
+ const struct btf_param *args;
+ const struct btf_type *func, *func_proto;
u32 i;
int ret, err;
ret = btf_prepare_func_args(env, subprog);
if (ret) {
if (bpf_in_stack_arg_cnt(sub) > 0) {
- err = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+ err = check_outgoing_stack_args(env, caller, sub->arg_cnt,
+ bpf_subprog_name(env, subprog),
+ NULL, NULL);
if (err)
return err;
}
return ret;
}
- ret = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+ func = btf_type_by_id(btf, env->prog->aux->func_info[subprog].type_id);
+ func_proto = btf_type_by_id(btf, func->type);
+ args = btf_params(func_proto);
+ ret = check_outgoing_stack_args(env, caller, sub->arg_cnt,
+ bpf_subprog_name(env, subprog), btf, args);
if (ret)
return ret;
@@ -9230,7 +9683,8 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
ret = check_func_arg_reg_off(env, reg, argno, ARG_DONTCARE);
if (ret < 0)
return ret;
- if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL))
+ if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL,
+ NULL))
return -EINVAL;
if (!(arg->arg_type & PTR_MAYBE_NULL) &&
(type_may_be_null(reg->type) || bpf_register_is_null(reg))) {
@@ -9256,7 +9710,9 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
if (ret)
return ret;
- ret = process_dynptr_func(env, reg, argno, -1, arg->arg_type, &ref_obj, NULL);
+ ret = process_dynptr_func(env, reg, argno, env->insn_idx,
+ bpf_subprog_name(env, subprog), arg->arg_type,
+ &ref_obj, NULL);
if (ret)
return ret;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
@@ -9267,7 +9723,8 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
continue;
memset(&meta, 0, sizeof(meta)); /* leave func_id as zero */
- err = check_reg_type(env, reg, argno, arg->arg_type, &arg->btf_id, &meta);
+ err = check_reg_type(env, reg, argno, arg->arg_type, &arg->btf_id, &meta,
+ bpf_subprog_name(env, subprog));
err = err ?: check_func_arg_reg_off(env, reg, argno, arg->arg_type);
if (err)
return err;
@@ -9408,17 +9865,25 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (err == -EFAULT)
return err;
if (bpf_subprog_is_global(env, subprog)) {
- const char *sub_name = subprog_name(env, subprog);
+ const char *sub_name = bpf_subprog_name(env, subprog);
+ const char *operation;
+ bool returns_void;
if (env->cur_state->active_locks) {
verbose(env, "global function calls are not allowed while holding a lock,\n"
"use static function instead\n");
+ operation = bpf_diag_fmt(env, "global function %s()", sub_name);
+ bpf_diag_ctx_active(env, *insn_idx, operation, BPF_DIAG_CONTEXT_LOCK,
+ "Release the lock before calling the global function, or use a static function instead.");
return -EINVAL;
}
if (env->subprog_info[subprog].might_sleep && !in_sleepable_context(env)) {
verbose(env, "sleepable global function %s() called in %s\n",
sub_name, non_sleepable_context_description(env));
+ operation = bpf_diag_fmt(env, "sleepable global function %s()", sub_name);
+ bpf_diag_ctx_forbidden(env, *insn_idx, operation,
+ "Move the call outside the critical section, or use a non-sleepable function.");
return -EINVAL;
}
@@ -9431,16 +9896,22 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (env->log.level & BPF_LOG_LEVEL)
verbose(env, "Func#%d ('%s') is global and assumed valid.\n",
subprog, sub_name);
+ returns_void = subprog_returns_void(env, subprog);
if (env->subprog_info[subprog].changes_pkt_data)
clear_all_pkt_pointers(env);
/* mark global subprog for verifying after main prog */
subprog_aux(env, subprog)->called = true;
+ if (returns_void)
+ bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED);
+ else
+ bpf_diag_mod_begin(env, &caller->regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
clear_caller_saved_regs(env, caller->regs);
invalidate_outgoing_stack_args(env, cur_func(env));
/* All non-void global functions return a 64-bit SCALAR_VALUE. */
- if (!subprog_returns_void(env, subprog)) {
+ if (!returns_void) {
mark_reg_unknown(env, caller->regs, BPF_REG_0);
+ bpf_diag_mod_end(env);
}
if (env->subprog_info[subprog].might_throw) {
@@ -9475,6 +9946,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (err)
return err;
+ bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED);
clear_caller_saved_regs(env, caller->regs);
/* and go analyze first insn of the callee */
@@ -9838,7 +10310,9 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
}
} else {
/* return to the caller whatever r0 had in the callee */
+ bpf_diag_mod_begin(env, &caller->regs[BPF_REG_0], r0, BPF_DIAG_MOD_WRITE);
caller->regs[BPF_REG_0] = *r0;
+ bpf_diag_mod_end(env);
}
/* for callbacks like bpf_loop or bpf_for_each_map_elem go back to callsite,
@@ -10044,6 +10518,7 @@ static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exi
continue;
verbose(env, "Unreleased reference id=%d alloc_insn=%d\n",
state->refs[i].id, state->refs[i].insn_idx);
+ bpf_diag_leak(env, state->refs[i].id, state->refs[i].insn_idx, env->insn_idx);
refs_lingering = true;
}
return refs_lingering ? -EINVAL : 0;
@@ -10055,6 +10530,8 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit
if (check_lock && env->cur_state->active_locks) {
verbose(env, "%s cannot be used inside bpf_spin_lock-ed region\n", prefix);
+ bpf_diag_ctx_active(env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_LOCK,
+ "Release the BPF spin lock before this operation on every path.");
return -EINVAL;
}
@@ -10066,16 +10543,23 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit
if (check_lock && env->cur_state->active_irq_id) {
verbose(env, "%s cannot be used inside bpf_local_irq_save-ed region\n", prefix);
+ bpf_diag_ctx_active(env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_IRQ,
+ "Restore the saved IRQ state before this operation on every path.");
return -EINVAL;
}
if (check_lock && env->cur_state->active_rcu_locks) {
verbose(env, "%s cannot be used inside bpf_rcu_read_lock-ed region\n", prefix);
+ bpf_diag_ctx_active(env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_RCU,
+ "Call bpf_rcu_read_unlock() before this operation on every path.");
return -EINVAL;
}
if (check_lock && env->cur_state->active_preempt_locks) {
verbose(env, "%s cannot be used inside bpf_preempt_disable-ed region\n", prefix);
+ bpf_diag_ctx_active(
+ env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_PREEMPT,
+ "Call bpf_preempt_enable() before this operation on every path.");
return -EINVAL;
}
@@ -10257,6 +10741,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
enum bpf_type_flag ret_flag;
struct bpf_reg_state *regs;
struct bpf_call_arg_meta meta;
+ const char *operation;
int insn_idx = *insn_idx_p;
bool changes_data;
int i, err, func_id;
@@ -10272,17 +10757,31 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (err) {
verbose(env, "program of this type cannot use helper %s#%d\n",
func_id_name(func_id), func_id);
+ operation = bpf_diag_fmt(env, "helper %s#%d", func_id_name(func_id), func_id);
+ bpf_diag_policy(
+ env, insn_idx, operation, "this program type does not allow the helper",
+ "Use a helper allowed for this program type, or move the logic to a compatible program type.");
return err;
}
/* eBPF programs must be GPL compatible to use GPL-ed functions */
if (!env->prog->gpl_compatible && fn->gpl_only) {
verbose(env, "cannot call GPL-restricted function from non-GPL compatible program\n");
+ operation = bpf_diag_fmt(env, "helper %s#%d", func_id_name(func_id), func_id);
+ bpf_diag_policy(
+ env, insn_idx, operation,
+ "this helper is restricted to GPL-compatible programs",
+ "Use a GPL-compatible license, or replace the helper with one that is available to non-GPL programs.");
return -EINVAL;
}
if (fn->allowed && !fn->allowed(env->prog)) {
verbose(env, "helper call is not allowed in probe\n");
+ operation = bpf_diag_fmt(env, "helper %s#%d", func_id_name(func_id), func_id);
+ bpf_diag_policy(
+ env, insn_idx, operation,
+ "the helper-specific policy callback rejected this program",
+ "Use the helper only from an allowed attach point or program configuration.");
return -EINVAL;
}
@@ -10304,6 +10803,10 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (fn->might_sleep && !in_sleepable_context(env)) {
verbose(env, "sleepable helper %s#%d in %s\n", func_id_name(func_id), func_id,
non_sleepable_context_description(env));
+ operation = bpf_diag_fmt(env, "sleepable helper %s#%d",
+ func_id_name(func_id), func_id);
+ bpf_diag_ctx_forbidden(env, insn_idx, operation,
+ "Move the helper call outside the critical section, or use a non-sleepable helper.");
return -EINVAL;
}
@@ -10491,12 +10994,14 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
return err;
/* reset caller saved regs */
+ bpf_diag_record_caller_saved(env, regs);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
bpf_mark_reg_not_init(env, ®s[caller_saved[i]]);
check_reg_arg(env, caller_saved[i], DST_OP_NO_MARK);
}
invalidate_outgoing_stack_args(env, cur_func(env));
+ bpf_diag_mod_begin(env, ®s[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
/* update return register (already marked as written above) */
ret_type = fn->ret_type;
ret_flag = type_flag(ret_type);
@@ -10645,6 +11150,8 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (err)
return err;
+ bpf_diag_mod_end(env);
+
/*
* In order for a release of any of the original or cast pointers
* to invalidate all other pointers, reuse the same reference id for
@@ -10661,6 +11168,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
__mark_reg_known_zero(r0);
r0->type = SCALAR_VALUE;
+ bpf_diag_mod_begin(env, ®s[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
regs[BPF_REG_0].type &= ~PTR_MAYBE_NULL;
regs[BPF_REG_0].id = meta.ref_obj.id;
} else if (is_acquire_function(func_id, meta.map.ptr)) {
@@ -10679,6 +11187,8 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (err)
return err;
+ bpf_diag_mod_end(env);
+
err = check_map_func_compatibility(env, meta.map.ptr, func_id);
if (err)
return err;
@@ -11587,6 +12097,12 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
if (!is_irq_flag_reg_valid_uninit(env, reg)) {
verbose(env, "expected uninitialized irq flag as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_res(env, env->insn_idx, "IRQ flag is already initialized",
+ "Saving IRQ state requires an uninitialized stack slot for "
+ "the IRQ flag, but this slot already contains tracked IRQ "
+ "flag state.",
+ "Use a fresh stack slot for this save operation, or restore "
+ "the existing IRQ flag before reusing the slot.");
return -EINVAL;
}
@@ -11603,6 +12119,11 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
if (err) {
verbose(env, "expected an initialized irq flag as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_res(env, env->insn_idx, "uninitialized IRQ flag restore",
+ "Restoring IRQ state requires a stack slot that was "
+ "initialized by a matching IRQ save operation on this path.",
+ "Pass the same stack slot that was previously initialized by "
+ "the matching IRQ save kfunc.");
return err;
}
@@ -11647,8 +12168,10 @@ static void ref_convert_owning_non_owning(struct bpf_verifier_env *env, u32 id)
{
struct bpf_func_state *unused;
struct bpf_reg_state *reg;
+ int err;
- WARN_ON_ONCE(release_reference_nomark(env->cur_state, id));
+ err = release_reference_nomark(env, id);
+ WARN_ON_ONCE(err);
bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
if (reg->id == id) {
@@ -12053,7 +12576,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
args = (const struct btf_param *)(meta->func_proto + 1);
nargs = btf_type_vlen(meta->func_proto);
- ret = check_outgoing_stack_args(env, caller, nargs);
+ ret = check_outgoing_stack_args(env, caller, nargs, func_name, btf, args);
if (ret)
return ret;
@@ -12067,7 +12590,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
argno_t argno = argno_from_arg(i + 1);
int regno = reg_from_argno(argno);
bool btf_id_fixed_off_ok = true;
- u32 ref_id, type_size;
+ u32 ref_id = args[i].type, type_size;
int kf_arg_type = meta->fn->arg_type[i];
if (is_kfunc_arg_prog_aux(btf, &args[i])) {
@@ -12091,29 +12614,43 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
t = btf_type_skip_modifiers(btf, args[i].type, NULL);
- if (btf_type_is_ptr(t) && (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
+ if (btf_type_is_ptr(t)) {
+ ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
+ ref_tname = btf_name_by_offset(btf, ref_t->name_off);
+ }
+
+ if (btf_type_is_ptr(t) &&
+ (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
!type_may_be_null(kf_arg_type)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "Possibly NULL pointer passed to trusted %s\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Add a NULL check and call the kfunc only on the non-NULL path.",
+ "the pointer may be NULL, but this kfunc requires a non-NULL pointer to %s",
+ expected_type);
return -EACCES;
}
if (regno == meta->release_regno && !is_kfunc_arg_dynptr(meta->btf, &args[i]) &&
!reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "release kfunc %s expects referenced PTR_TO_BTF_ID passed to %s\n",
func_name, reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the resource-owning pointer returned by the matching acquire kfunc, and avoid calling the release kfunc after ownership has already been transferred or released.",
+ "release kfuncs require a resource-owning value of type %s returned by a matching acquire kfunc",
+ expected_type);
return -EINVAL;
}
if (reg_is_referenced(env, reg))
update_ref_obj(&meta->ref_obj, reg);
- if (btf_type_is_ptr(t)) {
- ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
- ref_tname = btf_name_by_offset(btf, ref_t->name_off);
- }
-
-
if (bpf_register_is_null(reg) && type_may_be_null(kf_arg_type))
continue;
@@ -12173,35 +12710,67 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
case KF_ARG_CONST:
if (reg->type != SCALAR_VALUE) {
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar value for this argument, not a pointer or resource object.",
+ "the kfunc expects an integer scalar, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
ret = process_const_arg(env, reg, argno, meta);
- if (ret < 0)
+ if (ret < 0) {
+ if (ret == -EINVAL)
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a compile-time constant or a value the verifier can prove is constant at this call.",
+ "the kfunc requires this scalar argument to be a verifier-known constant, but %s is variable on this path",
+ reg_arg_name(env, argno));
return ret;
+ }
break;
case KF_ARG_ANYTHING:
if (reg->type != SCALAR_VALUE) {
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar value for this argument, not a pointer or resource object.",
+ "the kfunc expects an integer scalar, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
break;
case KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:
if (reg->type != SCALAR_VALUE) {
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar value for this argument, not a pointer or resource object.",
+ "the kfunc expects an integer scalar, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size"))
meta->r0_rdonly = true;
ret = process_const_alloc_mem_size(env, reg, argno, &meta->ret_mem);
- if (ret < 0)
+ if (ret < 0) {
+ if (ret == -EINVAL)
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a verifier-known constant size for this kfunc buffer argument.",
+ "the kfunc uses this argument as a return-buffer size, but %s is invalid or variable on this path",
+ reg_arg_name(env, argno));
return ret;
+ }
break;
case KF_ARG_PTR_TO_CTX:
if (reg->type != PTR_TO_CTX) {
verbose(env, "%s expected pointer to ctx, but got %s\n",
reg_arg_name(env, argno), reg_type_str(env, reg->type));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the original program context pointer or preserve it before modifying registers.",
+ "the kfunc expects a context pointer, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -12235,10 +12804,19 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
} else {
verbose(env, "%s expected pointer to allocated object\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a pointer returned by the matching BPF object allocation path.",
+ "the kfunc expects an allocated object pointer, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
if (!reg_is_referenced(env, reg)) {
verbose(env, "allocated object must be referenced\n");
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the owned object pointer before it is released or transferred.",
+ "the allocated object pointer in %s must still carry verifier-tracked ownership, but this pointer no longer owns a live resource",
+ reg_arg_name(env, argno));
return -EINVAL;
}
if (meta->btf == btf_vmlinux) {
@@ -12275,8 +12853,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
dynptr_arg_type |= (unsigned int)get_dynptr_type_flag(parent_type);
}
- ret = process_dynptr_func(env, reg, argno, insn_idx, dynptr_arg_type,
- &meta->ref_obj, &meta->dynptr);
+ ret = process_dynptr_func(env, reg, argno, insn_idx, func_name,
+ dynptr_arg_type, &meta->ref_obj, &meta->dynptr);
if (ret < 0)
return ret;
break;
@@ -12391,13 +12969,31 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (!is_trusted_reg(env, reg) ||
bpf_type_has_unsafe_modifiers(reg->type)) {
if (!is_kfunc_rcu(meta)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "%s must be referenced or trusted\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a pointer acquired from a verifier-tracked source, or call this kfunc only inside the required protection if it accepts RCU pointers.",
+ "the kfunc requires a trusted or resource-owning pointer to %s, but %s is %s",
+ expected_type,
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
if (!is_rcu_reg(reg)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "%s must be a rcu pointer\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Use this kfunc with a pointer that is valid in an RCU read lock region.",
+ "the kfunc requires an RCU-protected pointer to %s, but %s is %s",
+ expected_type,
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
}
@@ -12410,6 +13006,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (!__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0)) {
enum bpf_reg_type reg2btf_type = lookup_reg2btf_ids(ref_id);
+ const char *expected_type;
verbose(env, "%s is %s expected %s %s",
reg_arg_name(env, argno), reg_type_str(env, reg->type),
@@ -12417,6 +13014,12 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (reg2btf_type != NOT_INIT)
verbose(env, " or %s", reg_type_str(env, reg2btf_type));
verbose(env, "\n");
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a verifier-tracked pointer to the expected kernel object type, not a pointer to stack storage or another memory buffer.",
+ "the kfunc expects a pointer to %s, but this argument is %s and cannot be used as that kernel object pointer",
+ expected_type,
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -12428,6 +13031,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
fallthrough;
case KF_ARG_PTR_TO_MEM:
if (kf_arg_type & MEM_FIXED_SIZE) {
+ bool known_memory;
+
resolve_ret = btf_resolve_size(btf, ref_t, &type_size);
if (IS_ERR(resolve_ret)) {
verbose(env, "%s reference type('%s %s') size cannot be determined: %ld\n",
@@ -12435,9 +13040,28 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
ref_tname, PTR_ERR(resolve_ret));
return -EINVAL;
}
- ret = check_mem_reg(env, reg, argno, type_size, BPF_READ | BPF_WRITE, meta);
- if (ret < 0)
+ ret = check_mem_reg(env, reg, argno, type_size, BPF_READ | BPF_WRITE,
+ meta, &known_memory);
+ if (ret < 0) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
+ if (known_memory)
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Pass memory with at least the required number of accessible bytes and suitable read and write access.",
+ "the kfunc expects %u bytes of memory for %s, but the verifier cannot prove that %s provides a readable and writable range of that size",
+ type_size, expected_type,
+ bpf_diag_reg_type_plain(env, reg->type));
+ else
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Pass stack, map, context, or other verifier-known memory of the expected type and size, not an integer cast to a pointer.",
+ "the kfunc expects %u bytes of memory for %s, but it is %s and not verifier-known memory",
+ type_size, expected_type,
+ bpf_diag_reg_type_plain(env, reg->type));
return ret;
+ }
}
break;
case KF_ARG_CONST_MEM_SIZE:
@@ -12450,9 +13074,15 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
struct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, i - 1);
struct bpf_reg_state *size_reg = reg;
argno_t buff_argno = argno_from_arg(i);
+ enum bpf_mem_size_failure failure;
if (reg->type != SCALAR_VALUE) {
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar length for this memory argument.",
+ "the kfunc expects a scalar memory size, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -12460,11 +13090,34 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
break;
ret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,
- BPF_READ | BPF_WRITE, true, meta);
+ BPF_READ | BPF_WRITE, true, meta, &failure);
if (ret < 0) {
+ const char *buff_arg, *size_arg;
+
+ buff_arg = bpf_diag_arg_name(env, buff_argno);
+ size_arg = bpf_diag_arg_name(env, argno);
verbose(env, "%s and ", reg_arg_name(env, buff_argno));
verbose(env, "%s memory, len pair leads to invalid memory access\n",
reg_arg_name(env, argno));
+ if (failure == BPF_MEM_SIZE_FAIL_MEMORY) {
+ bpf_diag_call_arg_fmt(env, insn_idx, buff_argno, func_name,
+ "Pass a stack, map, context, or other verifier-known memory pointer, and keep the paired length within that object.",
+ "it is the memory pointer in a memory/length pair with %s, but %s does not describe verifier-readable memory for the requested length",
+ size_arg, buff_arg);
+ } else if (failure == BPF_MEM_SIZE_FAIL_SIZE) {
+ if (reg_smin(size_reg) < 0)
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Constrain the memory size to a non-negative value smaller than BPF_MAX_VAR_SIZ before this call.",
+ "the memory size in %s may be negative because its signed minimum is %lld",
+ size_arg, reg_smin(size_reg));
+ else
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Constrain the memory size to a non-negative value smaller than BPF_MAX_VAR_SIZ before this call.",
+ "the memory size in %s may reach %llu bytes, but variable memory accesses must stay below %u bytes",
+ size_arg, reg_umax(size_reg), BPF_MAX_VAR_SIZ);
+ }
return ret;
}
break;
@@ -12478,8 +13131,15 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
break;
case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
if (!type_is_ptr_alloc_obj(reg->type)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "%s is neither owning or non-owning ref\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a pointer returned by the matching BPF object allocation or lookup operation for this kfunc.",
+ "the kfunc expects a pointer to BPF-managed refcounted object type %s, but this argument is not such an object pointer",
+ expected_type);
return -EINVAL;
}
if (!type_is_non_owning_ref(reg->type))
@@ -12504,6 +13164,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (reg->type != PTR_TO_MAP_VALUE) {
verbose(env, "%s doesn't point to a const string\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a constant string pointer that the verifier recognizes, such as a string stored in a read-only map value.",
+ "the kfunc expects a pointer to a constant string stored in verifier-known memory, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
ret = check_arg_const_str(env, reg, argno);
@@ -12544,6 +13209,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (reg->type != PTR_TO_STACK) {
verbose(env, "%s doesn't point to an irq flag on stack\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave().",
+ "the kfunc expects a stack pointer to an IRQ flag slot, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
ret = process_irq_flag(env, reg, argno, meta);
@@ -12984,6 +13654,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
const struct btf_type *t, *ptr_type;
struct bpf_call_arg_meta meta;
struct bpf_insn_aux_data *insn_aux;
+ const char *operation;
int err, insn_idx = *insn_idx_p;
u32 i, nargs, ptr_type_id;
struct bpf_kfunc_desc *desc;
@@ -12995,8 +13666,13 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
return 0;
err = bpf_fetch_kfunc_arg_meta(env, insn->imm, insn->off, &meta);
- if (err == -EACCES && meta.func_name)
+ if (err == -EACCES && meta.func_name) {
verbose(env, "calling kernel function %s is not allowed\n", meta.func_name);
+ operation = bpf_diag_fmt(env, "kfunc %s", meta.func_name);
+ bpf_diag_policy(
+ env, insn_idx, operation, "this program cannot call the kfunc",
+ "Use a kfunc allowed for this program type and attach point, or change the program context.");
+ }
if (err)
return err;
desc_btf = meta.btf;
@@ -13043,12 +13719,19 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (is_kfunc_destructive(&meta) && !capable(CAP_SYS_BOOT)) {
verbose(env, "destructive kfunc calls require CAP_SYS_BOOT capability\n");
+ operation = bpf_diag_fmt(env, "destructive kfunc %s", meta.func_name);
+ bpf_diag_policy(
+ env, insn_idx, operation, "destructive kfuncs require CAP_SYS_BOOT",
+ "Load the program with CAP_SYS_BOOT, or avoid destructive kfuncs.");
return -EACCES;
}
sleepable = bpf_is_kfunc_sleepable(&meta);
if (sleepable && !in_sleepable(env)) {
verbose(env, "program must be sleepable to call sleepable kfunc %s\n", func_name);
+ operation = bpf_diag_fmt(env, "sleepable kfunc %s", func_name);
+ bpf_diag_ctx_forbidden(env, insn_idx, operation,
+ "Mark the program sleepable if the program type allows it, or use a non-sleepable kfunc.");
return -EACCES;
}
@@ -13114,22 +13797,36 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (rcu_lock) {
env->cur_state->active_rcu_locks++;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU, true,
+ env->cur_state->active_rcu_locks);
} else if (rcu_unlock) {
if (env->cur_state->active_rcu_locks == 0) {
verbose(env, "unmatched rcu read unlock (kernel function %s)\n", func_name);
+ bpf_diag_ctx_underflow(
+ env, insn_idx, func_name, BPF_DIAG_CONTEXT_RCU,
+ "Remove the extra bpf_rcu_read_unlock() call, or ensure this path first enters an RCU read lock region.");
return -EINVAL;
}
env->cur_state->active_rcu_locks--;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU, false,
+ env->cur_state->active_rcu_locks);
if (!in_rcu_cs(env))
invalidate_rcu_protected_refs(env);
} else if (preempt_disable) {
env->cur_state->active_preempt_locks++;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_PREEMPT, true,
+ env->cur_state->active_preempt_locks);
} else if (preempt_enable) {
if (env->cur_state->active_preempt_locks == 0) {
verbose(env, "unmatched attempt to enable preemption (kernel function %s)\n", func_name);
+ bpf_diag_ctx_underflow(
+ env, insn_idx, func_name, BPF_DIAG_CONTEXT_PREEMPT,
+ "Remove the extra bpf_preempt_enable() call, or ensure this path first disables preemption.");
return -EINVAL;
}
env->cur_state->active_preempt_locks--;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_PREEMPT, false,
+ env->cur_state->active_preempt_locks);
if (!in_rcu_cs(env))
invalidate_rcu_protected_refs(env);
}
@@ -13137,6 +13834,9 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (sleepable && !in_sleepable_context(env)) {
verbose(env, "kernel func %s is sleepable within %s\n",
func_name, non_sleepable_context_description(env));
+ operation = bpf_diag_fmt(env, "sleepable kfunc %s", func_name);
+ bpf_diag_ctx_forbidden(env, insn_idx, operation,
+ "Move the kfunc call outside the critical section, or use a non-sleepable kfunc.");
return -EACCES;
}
@@ -13147,6 +13847,9 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (is_kfunc_rcu_protected(&meta) && !in_rcu_cs(env)) {
verbose(env, "kernel func %s requires RCU critical section protection\n", func_name);
+ bpf_diag_ctx_required(
+ env, insn_idx, func_name, BPF_DIAG_CONTEXT_RCU,
+ "Call this kfunc between bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.");
return -EACCES;
}
@@ -13184,6 +13887,8 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
}
}
+ bpf_diag_record_caller_saved(env, regs);
+ bpf_diag_mod_begin(env, ®s[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
u32 regno = caller_saved[i];
@@ -13335,6 +14040,12 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
caller_info->stack_arg_cnt = stack_arg_cnt;
}
+ /*
+ * Record R0 before process_iter_next_call() snapshots the alternate
+ * iterator path's diagnostic position.
+ */
+ bpf_diag_mod_end(env);
+
if (bpf_is_iter_next_kfunc(&meta)) {
err = process_iter_next_call(env, insn_idx, &meta);
if (err)
@@ -13718,9 +14429,8 @@ static int sanitize_check_bounds(struct bpf_verifier_env *env,
* If we return -EACCES, caller may want to try again treating pointer as a
* scalar. So we only emit a diagnostic if !env->allow_ptr_leaks.
*/
-static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
- struct bpf_insn *insn,
- const struct bpf_reg_state *ptr_reg,
+static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn *insn,
+ u32 ptr_regno, const struct bpf_reg_state *ptr_reg,
const struct bpf_reg_state *off_reg)
{
struct bpf_verifier_state *vstate = env->cur_state;
@@ -13732,6 +14442,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
struct bpf_sanitize_info info = {};
u8 opcode = BPF_OP(insn->code);
u32 dst = insn->dst_reg;
+ const char *reason;
int ret, bounds_ret;
dst_reg = ®s[dst];
@@ -13755,12 +14466,24 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
verbose(env,
"R%d 32-bit pointer arithmetic prohibited\n",
dst);
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. 32-bit ALU operations on pointers discard pointer tracking, so the verifier cannot keep the result as a safe pointer.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "32-bit pointer arithmetic", reason,
+ "Use a 64-bit ALU instruction with an allowed, bounded scalar offset.");
return -EACCES;
}
if (ptr_reg->type & PTR_MAYBE_NULL) {
verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
dst, reg_type_str(env, ptr_reg->type));
+ reason = bpf_diag_fmt(
+ env, "R%d may be NULL (%s). Pointer arithmetic is allowed only after the program proves the pointer is non-NULL on this path.",
+ ptr_regno, reg_type_str(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer arithmetic before NULL check", reason,
+ "Make sure that a NULL check precedes any arithmetic performed on the pointer.");
return -EACCES;
}
@@ -13790,6 +14513,12 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
default:
verbose(env, "R%d pointer arithmetic on %s prohibited\n",
dst, reg_type_str(env, ptr_reg->type));
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. This pointer kind does not allow offset arithmetic.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer arithmetic is not allowed", reason,
+ "Do not change this pointer's offset; use it only in operations accepted for its kind.");
return -EACCES;
}
@@ -13807,9 +14536,25 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
if (base_type(ptr_reg->type) == PTR_TO_MEM && (ptr_reg->type & PTR_UNTRUSTED))
return 0;
- if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type) ||
- !check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type))
+ if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type)) {
+ reason = bpf_diag_fmt(
+ env, "The scalar offset used with R%d is unbounded or outside the verifier's safe pointer-offset range [-%u, %u].",
+ ptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer offset is not safe", reason,
+ "Clamp or bounds-check the scalar offset before applying it to the pointer.");
return -EINVAL;
+ }
+ if (!check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type)) {
+ reason = bpf_diag_fmt(
+ env, "R%d already has an offset outside the verifier's safe range [-%u, %u] for %s.",
+ ptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,
+ bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer offset is not safe", reason,
+ "Keep the base pointer within the verifier's allowed offset range before applying more arithmetic.");
+ return -EINVAL;
+ }
/* pointer types do not carry 32-bit bounds at the moment. */
__mark_reg32_unbounded(dst_reg);
@@ -13852,6 +14597,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
/* scalar -= pointer. Creates an unknown scalar */
verbose(env, "R%d tried to subtract pointer from scalar\n",
dst);
+ reason = bpf_diag_fmt(
+ env, "This operation subtracts pointer register R%d from scalar register R%d. "
+ "The verifier only tracks pointer-minus-scalar arithmetic for allowed pointer types.",
+ ptr_regno, dst);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer subtracted from scalar", reason,
+ "Keep the pointer as the base; only add or subtract bounded scalars when permitted.");
return -EACCES;
}
/* We don't allow subtraction from FP, because (according to
@@ -13861,6 +14613,12 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
if (ptr_reg->type == PTR_TO_STACK) {
verbose(env, "R%d subtraction from stack pointer prohibited\n",
dst);
+ reason = bpf_diag_fmt(
+ env, "R%d is a stack pointer. The verifier does not allow BPF_SUB to move stack pointers.",
+ ptr_regno);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "subtraction from stack pointer", reason,
+ "Use addition from R10 to form stack addresses within the tracked stack frame.");
return -EACCES;
}
dst_reg->r64 = cnum64_add(ptr_reg->r64, cnum64_negate(off_reg->r64));
@@ -13886,16 +14644,38 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
/* bitwise ops on pointers are troublesome, prohibit. */
verbose(env, "R%d bitwise operator %s on pointer prohibited\n",
dst, bpf_alu_string[opcode >> 4]);
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. Bitwise operator %s would destroy the pointer value the verifier is tracking.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type),
+ bpf_alu_string[opcode >> 4]);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "bitwise operation on pointer", reason,
+ "Do bitwise operations on scalar values, not on pointer-valued registers.");
return -EACCES;
default:
/* other operators (e.g. MUL,LSH) produce non-pointer results */
verbose(env, "R%d pointer arithmetic with %s operator prohibited\n",
dst, bpf_alu_string[opcode >> 4]);
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. Operator %s is not one of the limited pointer arithmetic operations the verifier can track.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type),
+ bpf_alu_string[opcode >> 4]);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "invalid pointer arithmetic operator", reason,
+ "Use only verifier-supported addition or subtraction with a bounded scalar offset, or perform this operation on a scalar value.");
return -EACCES;
}
- if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type))
+ if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type)) {
+ reason = bpf_diag_fmt(
+ env, "After this arithmetic, R%d would be outside the verifier's safe offset range [-%u, %u] for %s.",
+ dst, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,
+ bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer offset is not safe", reason,
+ "Tighten the scalar bounds before the arithmetic so the resulting pointer remains within the allowed range.");
return -EINVAL;
+ }
reg_bounds_sync(dst_reg);
bounds_ret = sanitize_check_bounds(env, insn, dst_reg);
if (bounds_ret == -EACCES)
@@ -14867,15 +15647,15 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
if (err)
return err;
off_reg = *dst_reg;
- return adjust_ptr_min_max_vals(env, insn, src_reg, &off_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->src_reg, src_reg,
+ &off_reg);
}
} else if (ptr_reg) {
/* pointer += scalar */
err = mark_chain_precision(env, insn->src_reg);
if (err)
return err;
- return adjust_ptr_min_max_vals(env, insn,
- dst_reg, src_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->dst_reg, dst_reg, src_reg);
} else if (dst_reg->precise) {
/* if dst_reg is precise, src_reg should be precise as well */
err = mark_chain_precision(env, insn->src_reg);
@@ -14890,8 +15670,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
__mark_reg_known(&off_reg, insn->imm);
src_reg = &off_reg;
if (ptr_reg) /* pointer += K */
- return adjust_ptr_min_max_vals(env, insn,
- ptr_reg, src_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->dst_reg, ptr_reg, src_reg);
}
/* Got here implies adding two SCALAR_VALUEs */
@@ -14977,6 +15756,8 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
u8 opcode = BPF_OP(insn->code);
int err;
+ bpf_diag_mod_begin(env, ®s[insn->dst_reg], NULL, BPF_DIAG_MOD_WRITE);
+
if (opcode == BPF_END || opcode == BPF_NEG) {
/* check src operand */
err = check_reg_arg(env, insn->dst_reg, SRC_OP);
@@ -15150,7 +15931,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err;
}
- return reg_bounds_sanity_check(env, ®s[insn->dst_reg], "alu");
+ err = reg_bounds_sanity_check(env, ®s[insn->dst_reg], "alu");
+ if (err)
+ return err;
+
+ bpf_diag_mod_end(env);
+ return 0;
}
static void find_good_pkt_pointers(struct bpf_verifier_state *vstate,
@@ -15765,7 +16551,7 @@ static void mark_ptr_or_null_regs(struct bpf_verifier_state *vstate, u32 regno,
* No one could have freed the reference state before
* doing the NULL check.
*/
- WARN_ON_ONCE(release_reference_nomark(vstate, id));
+ WARN_ON_ONCE(__release_reference_nomark(vstate, id));
bpf_for_each_reg_in_vstate(vstate, state, reg, ({
mark_ptr_or_null_reg(state, reg, id, is_null);
@@ -16244,11 +17030,13 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err;
dst_reg = ®s[insn->dst_reg];
+ bpf_diag_mod_begin(env, dst_reg, NULL, BPF_DIAG_MOD_WRITE);
if (insn->src_reg == 0) {
u64 imm = ((u64)(insn + 1)->imm << 32) | (u32)insn->imm;
dst_reg->type = SCALAR_VALUE;
__mark_reg_known(®s[insn->dst_reg], imm);
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16272,6 +17060,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
verifier_bug(env, "pseudo btf id: unexpected dst reg type");
return -EFAULT;
}
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16291,6 +17080,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
dst_reg->type = PTR_TO_FUNC;
dst_reg->subprogno = subprogno;
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16301,6 +17091,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
if (map->map_type == BPF_MAP_TYPE_ARENA) {
__mark_reg_unknown(env, dst_reg);
dst_reg->map_ptr = map;
+ bpf_diag_mod_end(env);
return 0;
}
__mark_reg_known(dst_reg, aux->map_off);
@@ -16318,6 +17109,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
return -EFAULT;
}
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16396,6 +17188,8 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err;
/* reset caller saved regs to unreadable */
+ bpf_diag_record_caller_saved(env, regs);
+ bpf_diag_mod_begin(env, ®s[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
bpf_mark_reg_not_init(env, ®s[caller_saved[i]]);
check_reg_arg(env, caller_saved[i], DST_OP_NO_MARK);
@@ -16406,6 +17200,7 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
* Already marked as written above.
*/
mark_reg_unknown(env, regs, BPF_REG_0);
+ bpf_diag_mod_end(env);
/*
* See bpf_gen_ld_abs() which emits a hidden BPF_EXIT with r0=0
* which must be explored by the verifier when in a subprog.
@@ -17332,6 +18127,10 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
!kfunc_spin_allowed(env, insn->imm, insn->off))) {
verbose(env,
"function calls are not allowed while holding a lock\n");
+ bpf_diag_ctx_active(
+ env, env->insn_idx,
+ "function call", BPF_DIAG_CONTEXT_LOCK,
+ "Release the BPF spin lock before making this call, or move the call outside the locked region.");
return -EINVAL;
}
}
@@ -17416,6 +18215,27 @@ static int do_check(struct bpf_verifier_env *env)
state->last_insn_idx = env->prev_insn_idx;
state->insn_idx = env->insn_idx;
+ /*
+ * Record the incoming edge so active and queued paths use the same
+ * branch-recording path. A zero-offset conditional has identical
+ * successors, so its outcome cannot be reconstructed from the edge.
+ */
+ if (!state->speculative && prev_insn_idx >= 0 && prev_insn_idx < insn_cnt) {
+ struct bpf_insn *prev_insn = &insns[prev_insn_idx];
+ int fallthrough_idx = prev_insn_idx + 1;
+ int branch_idx = prev_insn_idx + bpf_jmp_offset(prev_insn) + 1;
+ u8 class = BPF_CLASS(prev_insn->code);
+ u8 opcode = BPF_OP(prev_insn->code);
+
+ if ((class == BPF_JMP || class == BPF_JMP32) &&
+ opcode != BPF_JA && opcode != BPF_CALL && opcode != BPF_EXIT &&
+ opcode <= BPF_JCOND && branch_idx != fallthrough_idx) {
+ if (env->insn_idx == branch_idx)
+ bpf_diag_record_branch(env, prev_insn_idx, true);
+ else if (env->insn_idx == fallthrough_idx)
+ bpf_diag_record_branch(env, prev_insn_idx, false);
+ }
+ }
if (bpf_is_prune_point(env, env->insn_idx)) {
err = bpf_is_state_visited(env, env->insn_idx);
@@ -18479,7 +19299,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
regs = state->frame[state->curframe]->regs;
if (subprog || env->prog->type == BPF_PROG_TYPE_EXT) {
- const char *sub_name = subprog_name(env, subprog);
+ const char *sub_name = bpf_subprog_name(env, subprog);
struct bpf_subprog_arg_info *arg;
struct bpf_reg_state *reg;
@@ -18591,8 +19411,11 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
ret = do_check(env);
out:
account_current_path(env);
- if (!ret && pop_log)
- bpf_vlog_reset(&env->log, 0);
+ if (!ret) {
+ if (pop_log)
+ bpf_vlog_reset(&env->log, 0);
+ bpf_diag_event_log_restore(env, 0);
+ }
free_states(env);
/*
@@ -18656,7 +19479,7 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
return ret;
} else if (env->log.level & BPF_LOG_LEVEL) {
verbose(env, "Func#%d ('%s') is safe for any args that match its prototype\n",
- i, subprog_name(env, i));
+ i, bpf_subprog_name(env, i));
}
/* We verified new global subprog, it might have called some
@@ -20188,6 +21011,9 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
ret = bpf_vlog_init(&env->log, attr_log->level, attr_log->ubuf, attr_log->size);
if (ret)
goto err_free_env;
+ ret = bpf_diag_init(env);
+ if (ret)
+ goto err_prep;
if (env->signature) {
ret = bpf_prog_calc_tag(env->prog);
if (ret < 0)
@@ -20478,6 +21304,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
kvfree(env->scc_info);
kvfree(env->succ);
kvfree(env->gotox_tmp_buf);
+ bpf_diag_free(env);
kvfree(env);
return ret;
}
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
index 7918646e5bfcf..d3be69a9a7557 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
@@ -155,6 +155,7 @@ l0_%=: r0 = 0; \
SEC("socket")
__description("forgot null checking on the inner map pointer")
__failure __msg("R1 type=map_ptr_or_null expected=map_ptr")
+__msg("map_ptr_or_null, but this argument accepts map_ptr")
__failure_unpriv
__naked void on_the_inner_map_pointer(void)
{
diff --git a/tools/testing/selftests/bpf/progs/verifier_uninit.c b/tools/testing/selftests/bpf/progs/verifier_uninit.c
index 7718cd7d19ce9..691018a460493 100644
--- a/tools/testing/selftests/bpf/progs/verifier_uninit.c
+++ b/tools/testing/selftests/bpf/progs/verifier_uninit.c
@@ -9,6 +9,7 @@
SEC("socket")
__description("read uninitialized register")
__failure __msg("R2 !read_ok")
+__msg("R2 has never been initialized on this path")
__failure_unpriv
__naked void read_uninitialized_register(void)
{
|
| 3/2 | 2026/08/15 07:05 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 78929 output: 368 thoughts: 2378 Thoughts: **BPF Verifier Diagnostic Framework: A Fuzzing Target**
Alright, let's break this down. This patch is essentially a major overhaul of the BPF verifier, introducing a new diagnostic framework. My initial assessment is that this is a significant functional addition. They've built out a whole suite of new functions within `kernel/bpf/diagnostics.c`, and a few changes in `kernel/bpf/verifier.c` and `kernel/bpf/cfg.c` etc.
Specifically, it seems we have a bunch of new diagnostics functions: `bpf_diag_register_type`, `bpf_diag_invalid_deref`, `bpf_diag_unreadable_reg`, etc. A whole slew of them to deal with various error scenarios. My experience tells me that anytime the verifier gets this much of a diagnostic injection, it's worth a closer look from a security perspective. It's reached during program rejection or with logging, which likely means some interesting paths can be taken.
Now, my focus is going to be on identifying the key entry points and understanding how these new diagnostics are triggered. Let's make sure we find all of the externally visible functions. Let's dig deeper and get a solid list. I'm going to start by getting the list of all the exported/non-static functions within `kernel/bpf/diagnostics.c`.
Here's my list of *FocusSymbols*:
```json
{
"WorthFuzzing": true,
"FocusSymbols": [
"bpf_diag_register_type",
"bpf_diag_invalid_deref",
"bpf_diag_unreadable_reg",
"bpf_diag_stack_arg_uninit",
"bpf_diag_memory",
"bpf_diag_mem_bounds",
"bpf_diag_res",
"bpf_diag_lock",
"bpf_diag_irq",
"bpf_diag_leak",
"bpf_diag_call_type",
"bpf_diag_ctx_forbidden",
"bpf_diag_ctx_active",
"bpf_diag_ctx_required",
"bpf_diag_ctx_underflow",
"bpf_diag_program_structure",
"bpf_diag_policy",
"bpf_diag_record_branch",
"bpf_diag_mod_begin",
"bpf_diag_mod_end",
"bpf_diag_record_scrub",
"bpf_diag_record_scrub_stack",
"bpf_diag_record_ref_acquire",
"bpf_diag_record_ref_release",
"bpf_diag_record_context"
],
"EnableConfigs": [
"BPF_SYSCALL"
],
"Reasoning": "The patch introduces a new diagnostic framework to the BPF verifier, adding detailed diagnostics for verifier errors. This is a significant functional change to the BPF verifier and is reachable when the BPF verifier rejects a program or when logging is enabled."
}
```
Now, it's about determining the best approach for fuzzing these and how to craft inputs to trigger these diagnostic paths. This is definitely a worthwhile target.
|
| 4/2 | 2026/08/15 07:05 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_diag_register_type",
"bpf_diag_invalid_deref",
"bpf_diag_unreadable_reg",
"bpf_diag_stack_arg_uninit",
"bpf_diag_memory",
"bpf_diag_mem_bounds",
"bpf_diag_res",
"bpf_diag_lock",
"bpf_diag_irq",
"bpf_diag_leak",
"bpf_diag_call_type",
"bpf_diag_ctx_forbidden",
"bpf_diag_ctx_active",
"bpf_diag_ctx_required",
"bpf_diag_ctx_underflow",
"bpf_diag_program_structure",
"bpf_diag_policy",
"bpf_diag_record_branch",
"bpf_diag_mod_begin",
"bpf_diag_mod_end",
"bpf_diag_record_scrub",
"bpf_diag_record_scrub_stack",
"bpf_diag_record_ref_acquire",
"bpf_diag_record_ref_release",
"bpf_diag_record_context"
],
"Reasoning": "The patch introduces a new diagnostic framework to the BPF verifier, adding detailed diagnostics for verifier errors. This is a significant functional change to the BPF verifier and is reachable when the BPF verifier rejects a program or when logging is enabled.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_SYSCALL"
],
"FocusSymbols": [
"bpf_diag_register_type",
"bpf_diag_invalid_deref",
"bpf_diag_unreadable_reg",
"bpf_diag_stack_arg_uninit",
"bpf_diag_memory",
"bpf_diag_mem_bounds",
"bpf_diag_res",
"bpf_diag_lock",
"bpf_diag_irq",
"bpf_diag_leak",
"bpf_diag_call_type",
"bpf_diag_ctx_forbidden",
"bpf_diag_ctx_active",
"bpf_diag_ctx_required",
"bpf_diag_ctx_underflow",
"bpf_diag_program_structure",
"bpf_diag_policy",
"bpf_diag_record_branch",
"bpf_diag_mod_begin",
"bpf_diag_mod_end",
"bpf_diag_record_scrub",
"bpf_diag_record_scrub_stack",
"bpf_diag_record_ref_acquire",
"bpf_diag_record_ref_release",
"bpf_diag_record_context"
],
"Reasoning": "The patch introduces a new diagnostic framework to the BPF verifier, adding detailed diagnostics for verifier errors. This is a significant functional change to the BPF verifier and is reachable when the BPF verifier rejects a program or when logging is enabled.",
"WorthFuzzing": true
} |
| 5/1 | 2026/08/15 07:05 | action | If |
2mArgs:{
"WorthFuzzing": true
}Results: null |
| 6/2 | 2026/08/15 07:05 | agent | kmsan-evaluator |
2mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch introduces a new diagnostics framework for the BPF verifier to provide better error messages and causal history of events leading to verifier errors. It adds new internal structures (`struct bpf_diag`, `struct bpf_diag_log`, `struct bpf_diag_history_event`, etc.) to track register and stack modifications, context changes, and branches during verification. These structures are allocated using `kzalloc` (via the `kzalloc_obj` macro) and are only used internally to generate diagnostic text.\n\nWhen a verifier error occurs, the framework formats the recorded events into strings using `vsnprintf` and `scnprintf`, which ensure the strings are properly null-terminated. These strings are then written to the verifier log buffer using `%s` format specifiers. The verifier log is the only data copied to user space, and since it is populated using standard string formatting functions, no uninitialized memory or padding bytes from the internal structures are leaked.\n\nThe patch does not introduce any new structures sent directly to user space via `copy_to_user`, netlink, ioctl, or BPF buffers. Therefore, the changes do not expose any uninitialized memory risks, and a dedicated KMSAN fuzzing session is not justified. Standard KASAN fuzzing is sufficient to catch any potential memory safety issues (e.g., out-of-bounds accesses or use-after-free) in the new verifier code.",
"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: For your convenience, here is the diff of the changes:
commit d335ac0304d75ca7fd84c386623517953d26cfa8
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Aug 15 07:05:20 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index f4e8d372253ac..018823e277293 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -4146,8 +4146,16 @@ static inline bool bpf_is_subprog(const struct bpf_prog *prog)
}
const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off);
-void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
- const char **filep, const char **linep, int *nump);
+struct bpf_linfo_source {
+ const char *file;
+ const char *line;
+ u32 file_name_off;
+ int line_num;
+ int line_col;
+};
+
+void bpf_get_linfo_source(struct btf *btf, const struct bpf_line_info *linfo,
+ struct bpf_linfo_source *src);
int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
const char **linep, int *nump);
struct bpf_prog *bpf_prog_find_from_stack(void);
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 27b43fda9b178..bc2af02547fe5 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -354,6 +354,11 @@ struct bpf_func_state {
* 0 = main function, 1 = first callee.
*/
u32 frameno;
+ /*
+ * Unique diagnostic identity for this function invocation. Frame depth is
+ * reused after returns, while this ID is preserved across state clones.
+ */
+ u32 diag_frame_id;
/* subprog number == index within subprog_info
* zero == main subprog
*/
@@ -833,6 +838,7 @@ static inline u16 bpf_in_stack_arg_cnt(const struct bpf_subprog_info *sub)
return 0;
}
+struct bpf_diag;
struct bpf_verifier_env;
struct backtrack_state {
@@ -950,6 +956,7 @@ struct bpf_verifier_env {
struct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */
const struct bpf_line_info *prev_linfo;
struct bpf_verifier_log log;
+ struct bpf_diag *diag;
struct bpf_subprog_info subprog_info[BPF_MAX_SUBPROGS + 2]; /* max + 2 for the fake and exception subprogs */
/* subprog indices sorted in topological order: leaves first, callers last */
int subprog_topo_order[BPF_MAX_SUBPROGS + 2];
@@ -1349,6 +1356,18 @@ static inline bool type_is_non_owning_ref(u32 type)
return type_is_ptr_alloc_obj(type) && type_flag(type) & NON_OWN_REF;
}
+static inline bool type_is_map_ptr(enum bpf_reg_type type)
+{
+ switch (base_type(type)) {
+ case CONST_PTR_TO_MAP:
+ case PTR_TO_MAP_KEY:
+ case PTR_TO_MAP_VALUE:
+ return true;
+ default:
+ return false;
+ }
+}
+
static inline bool type_is_pkt_pointer(enum bpf_reg_type type)
{
type = base_type(type);
@@ -1433,8 +1452,10 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie
void print_insn_state(struct bpf_verifier_env *env, const struct bpf_verifier_state *vstate,
u32 frameno);
u32 bpf_vlog_alignment(u32 pos);
+const char *bpf_disasm_kfunc_name(void *data, const struct bpf_insn *insn);
struct bpf_subprog_info *bpf_find_containing_subprog(struct bpf_verifier_env *env, int off);
+const char *bpf_subprog_name(const struct bpf_verifier_env *env, int subprog);
int bpf_jmp_offset(struct bpf_insn *insn);
struct bpf_iarray *bpf_insn_successors(struct bpf_verifier_env *env, u32 idx);
void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask);
diff --git a/include/linux/btf.h b/include/linux/btf.h
index 3f5255d095a27..7ea13768c9795 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -214,6 +214,7 @@ int btf_type_seq_show_flags(const struct btf *btf, u32 type_id, void *obj,
*/
int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
char *buf, int len, u64 flags);
+int btf_type_name_to_buf(const struct btf *btf, u32 type_id, char *buf, int len);
int btf_get_fd_by_id(u32 id);
u32 btf_obj_id(const struct btf *btf);
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index 4dc41bf5780cc..90255d80e5be6 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -6,7 +6,7 @@ cflags-nogcse-$(CONFIG_X86)$(CONFIG_CC_IS_GCC) := -fno-gcse
endif
CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)
-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
+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
obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o
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
obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 87ffde865a503..5b9d767895c91 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8316,6 +8316,16 @@ int btf_type_snprintf_show(const struct btf *btf, u32 type_id, void *obj,
return ssnprintf.len;
}
+int btf_type_name_to_buf(const struct btf *btf, u32 type_id, char *buf, int len)
+{
+ struct btf_show show = {
+ .btf = btf,
+ .state.type_id = type_id,
+ };
+
+ return snprintf(buf, len, "%s", btf_show_name(&show));
+}
+
#ifdef CONFIG_PROC_FS
static void bpf_btf_show_fdinfo(struct seq_file *m, struct file *filp)
{
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 818f7afac83a5..0f13c13f41337 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -5,6 +5,8 @@
#include <linux/filter.h>
#include <linux/sort.h>
+#include "diagnostics.h"
+
#define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
/* non-recursive DFS pseudo code
@@ -112,6 +114,10 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
if (w < 0 || w >= env->prog->len) {
verbose_linfo(env, t, "%d: ", t);
verbose(env, "jump out of range from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "jump out of range", "Keep branch targets inside the program.",
+ "Instruction %d jumps to instruction %d, but the program only contains instructions 0 through %d.",
+ t, w, env->prog->len - 1);
return -EINVAL;
}
@@ -135,6 +141,11 @@ static int push_insn(int t, int w, int e, struct bpf_verifier_env *env)
verbose_linfo(env, t, "%d: ", t);
verbose_linfo(env, w, "%d: ", w);
verbose(env, "back-edge from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "back-edge is not allowed",
+ "Load with privileges that allow this back-edge, or rewrite the control flow so it does not branch backward.",
+ "Instruction %d branches back to instruction %d. This program is being rejected without the privilege needed for this back-edge.",
+ t, w);
return -EINVAL;
} else if (insn_state[w] == EXPLORED) {
/* forward- or cross-edge */
@@ -315,6 +326,11 @@ static struct bpf_iarray *jt_from_subprog(struct bpf_verifier_env *env,
if (!jt) {
verbose(env, "no jump tables found for subprog starting at %u\n", subprog_start);
+ bpf_diag_program_structure(
+ env, subprog_start, "missing jump table",
+ "Make sure subprograms containing gotox instructions are accompanied by jump tables referencing these subprograms.",
+ "No jump table was found for the subprogram that starts at instruction %u.",
+ subprog_start);
return ERR_PTR(-EINVAL);
}
@@ -342,6 +358,11 @@ create_jt(int t, struct bpf_verifier_env *env)
if (jt->items[i] < subprog_start || jt->items[i] >= subprog_end) {
verbose(env, "jump table for insn %d points outside of the subprog [%u,%u]\n",
t, subprog_start, subprog_end);
+ bpf_diag_program_structure(
+ env, t, "jump table target out of range",
+ "Keep every jump-table target inside the same subprogram.",
+ "The jump table for instruction %d points outside subprogram range [%u,%u).",
+ t, subprog_start, subprog_end);
kvfree(jt);
return ERR_PTR(-EINVAL);
}
@@ -373,6 +394,11 @@ static int visit_gotox_insn(int t, struct bpf_verifier_env *env)
w = jt->items[i];
if (w < 0 || w >= env->prog->len) {
verbose(env, "indirect jump out of range from insn %d to %d\n", t, w);
+ bpf_diag_program_structure(
+ env, t, "indirect jump out of range",
+ "Keep indirect jump targets inside the program.",
+ "Instruction %d can jump indirectly to instruction %d, but the program only contains instructions 0 through %d.",
+ t, w, env->prog->len - 1);
return -EINVAL;
}
@@ -623,12 +649,21 @@ int bpf_check_cfg(struct bpf_verifier_env *env)
if (insn_state[i] != EXPLORED) {
verbose(env, "unreachable insn %d\n", i);
+ bpf_diag_program_structure(
+ env, i, "unreachable instruction",
+ "Remove the unreachable instruction or add valid control flow that reaches it.",
+ "Instruction %d is not reachable from the program entry point.", i);
ret = -EINVAL;
goto err_free;
}
if (bpf_is_ldimm64(insn)) {
if (insn_state[i + 1] != 0) {
verbose(env, "jump into the middle of ldimm64 insn %d\n", i);
+ bpf_diag_program_structure(
+ env, i, "jump into ldimm64 immediate",
+ "Target the first instruction of the ldimm64 pair, or restructure the jump target.",
+ "Control flow reaches the second half of the ldimm64 instruction pair that starts at instruction %d.",
+ i);
ret = -EINVAL;
goto err_free;
}
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 6a94370a24488..d55e737ed75ad 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3461,24 +3461,14 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(xdp_bulk_tx);
#ifdef CONFIG_BPF_SYSCALL
-void bpf_get_linfo_file_line(struct btf *btf, const struct bpf_line_info *linfo,
- const char **filep, const char **linep, int *nump)
+void bpf_get_linfo_source(struct btf *btf, const struct bpf_line_info *linfo,
+ struct bpf_linfo_source *src)
{
- /* Get base component of the file path. */
- if (filep) {
- *filep = btf_name_by_offset(btf, linfo->file_name_off);
- *filep = kbasename(*filep);
- }
-
- /* Obtain the source line, and strip whitespace in prefix. */
- if (linep) {
- *linep = btf_name_by_offset(btf, linfo->line_off);
- while (isspace(**linep))
- *linep += 1;
- }
-
- if (nump)
- *nump = BPF_LINE_INFO_LINE_NUM(linfo->line_col);
+ src->file = kbasename(btf_name_by_offset(btf, linfo->file_name_off));
+ src->line = btf_name_by_offset(btf, linfo->line_off);
+ src->file_name_off = linfo->file_name_off;
+ src->line_num = BPF_LINE_INFO_LINE_NUM(linfo->line_col);
+ src->line_col = BPF_LINE_INFO_LINE_COL(linfo->line_col);
}
const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn_off)
@@ -3521,6 +3511,7 @@ const struct bpf_line_info *bpf_find_linfo(const struct bpf_prog *prog, u32 insn
int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char **filep,
const char **linep, int *nump)
{
+ struct bpf_linfo_source src;
int idx = -1, insn_start, insn_end, len;
struct bpf_line_info *linfo;
void **jited_linfo;
@@ -3552,7 +3543,15 @@ int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char *
if (idx == -1)
return -ENOENT;
- bpf_get_linfo_file_line(btf, &linfo[idx], filep, linep, nump);
+ bpf_get_linfo_source(btf, &linfo[idx], &src);
+ while (isspace(*src.line))
+ src.line++;
+ if (filep)
+ *filep = src.file;
+ if (linep)
+ *linep = src.line;
+ if (nump)
+ *nump = src.line_num;
return 0;
}
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
new file mode 100644
index 0000000000000..33b7d9e8e2c3a
--- /dev/null
+++ b/kernel/bpf/diagnostics.c
@@ -0,0 +1,2355 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+
+#include <linux/bpf.h>
+#include <linux/bpf_verifier.h>
+#include <linux/btf.h>
+#include <linux/ctype.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/seq_buf.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/stdarg.h>
+#include <linux/string.h>
+
+#include "disasm.h"
+#include "diagnostics.h"
+
+#define REGISTER_TYPE_SAFETY "Register Type Safety"
+#define MEMORY_SAFETY "Memory Safety"
+#define RESOURCE_LIFETIME_SAFETY "Resource Lifetime Safety"
+#define CALL_TYPE_SAFETY "Call Type Safety"
+#define EXECUTION_CONTEXT_SAFETY "Execution Context Safety"
+#define PROGRAM_STRUCTURE "Program Structure"
+#define POLICY "Policy"
+
+#define BPF_DIAG_TEXT_WIDTH 100
+#define BPF_DIAG_TEXT_INDENT " "
+#define BPF_DIAG_CONTEXT 2
+#define BPF_DIAG_CONTEXT_CNT (1 + BPF_DIAG_CONTEXT * 2)
+#define BPF_DIAG_HISTORY_RENDER_MAX 64
+#define BPF_DIAG_SOURCE_LANE_WIDTH 88
+#define BPF_DIAG_TAB_WIDTH 8
+#define BPF_DIAG_FMT_CHUNK_SIZE (PAGE_SIZE - sizeof(struct diag_fmt_chunk))
+#define BPF_DIAG_FMT_BUF_SIZE 256
+#define BPF_DIAG_EVENT_LOG_MAX_SIZE (64U << 20)
+#define DISASM_LINE_LEN 160
+
+enum bpf_diag_mod_target_kind {
+ BPF_DIAG_MOD_TARGET_NONE,
+ BPF_DIAG_MOD_TARGET_REG,
+ BPF_DIAG_MOD_TARGET_STACK_ARG,
+ BPF_DIAG_MOD_TARGET_STACK_SLOT,
+ BPF_DIAG_MOD_TARGET_STACK_RANGE,
+};
+
+struct bpf_diag_mod_target {
+ u32 frame_id;
+ union {
+ struct {
+ s16 min_off;
+ s16 max_off;
+ } range;
+ u16 spi;
+ u8 regno;
+ u8 stack_arg;
+ };
+ u8 frameno;
+ u8 kind;
+};
+
+static struct bpf_diag_mod_target diag_reg_target(u32 frame_id, u8 frameno, u8 regno)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_REG,
+ .regno = regno,
+ };
+}
+
+static struct bpf_diag_mod_target diag_stack_arg_target(u32 frame_id, u8 frameno, u8 slot)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_STACK_ARG,
+ .stack_arg = slot,
+ };
+}
+
+static struct bpf_diag_mod_target diag_stack_slot_target(u32 frame_id, u8 frameno, u16 spi)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_STACK_SLOT,
+ .spi = spi,
+ };
+}
+
+static struct bpf_diag_mod_target diag_stack_range_target(u32 frame_id, u8 frameno,
+ s16 min_off, s16 max_off)
+{
+ return (struct bpf_diag_mod_target){
+ .frame_id = frame_id,
+ .frameno = frameno,
+ .kind = BPF_DIAG_MOD_TARGET_STACK_RANGE,
+ .range.min_off = min_off,
+ .range.max_off = max_off,
+ };
+}
+
+struct bpf_diag_reg_snapshot {
+ u32 type;
+ u32 btf_id;
+ const struct bpf_map *map_ptr;
+ const struct btf *btf;
+ struct tnum var_off;
+ struct cnum64 r64;
+};
+
+enum bpf_diag_history_kind {
+ BPF_DIAG_HISTORY_BRANCH,
+ BPF_DIAG_HISTORY_MOD,
+ BPF_DIAG_HISTORY_REF_ACQUIRE,
+ BPF_DIAG_HISTORY_REF_RELEASE,
+ BPF_DIAG_HISTORY_CONTEXT,
+};
+
+struct bpf_diag_history_event {
+ u32 insn_idx : 24;
+ u32 kind : 8;
+ u8 in_lineage : 1;
+ union {
+ struct {
+ bool cond_true;
+ } branch;
+ struct {
+ struct bpf_diag_mod_target target;
+ struct bpf_diag_mod_target origin;
+ struct bpf_diag_reg_snapshot old, new;
+ u8 reason;
+ bool origin_valid;
+ } mod;
+ struct {
+ u32 ref_id;
+ } ref;
+ struct {
+ u32 depth;
+ u8 kind;
+ bool enter;
+ } ctx;
+ };
+};
+
+enum bpf_diag_history_scope {
+ BPF_DIAG_HISTORY_SCOPE_REG,
+ BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+ BPF_DIAG_HISTORY_SCOPE_REF,
+ BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+};
+
+struct bpf_diag_history_opts {
+ enum bpf_diag_history_scope scope;
+ u32 frame_id;
+ u32 frameno;
+ int regno;
+ int stack_arg_slot;
+ u32 ref_id;
+ enum bpf_diag_context_kind ctx_kind;
+ u32 ctx_depth;
+};
+
+static void diag_print_history(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_opts *opts);
+static bool diag_target_matches(const struct bpf_diag_mod_target *event_target,
+ const struct bpf_diag_mod_target *target);
+static const char *diag_context_name(enum bpf_diag_context_kind kind);
+struct disasm_line {
+ char text[DISASM_LINE_LEN];
+ int idx;
+ bool valid;
+};
+
+struct disasm_ctx {
+ struct bpf_verifier_env *env;
+ struct seq_buf seq;
+};
+
+struct diag_fmt_chunk {
+ struct list_head node;
+ struct seq_buf seq;
+ char data[];
+};
+
+struct diag_fmt_mark {
+ struct diag_fmt_chunk *chunk;
+ size_t len;
+};
+
+struct bpf_diag_log {
+ struct bpf_diag_history_event *events;
+ /* Sequence number of the oldest retained event on the active path. */
+ u64 first_seq;
+ u32 cnt;
+ u32 cap;
+ u32 head;
+ bool growth_failed;
+};
+
+struct bpf_diag_scratch {
+ struct bpf_linfo_source source_lines[BPF_DIAG_CONTEXT_CNT];
+ struct disasm_line disasm_lines[BPF_DIAG_CONTEXT_CNT];
+};
+
+struct bpf_diag_mod_scope {
+ struct bpf_reg_state target_reg_snapshot;
+ struct bpf_diag_mod_target target;
+ struct bpf_diag_mod_target origin;
+ enum bpf_diag_mod_reason reason;
+ u32 insn_idx;
+ bool active;
+ bool origin_valid;
+};
+
+struct bpf_diag {
+ struct bpf_diag_log log;
+ struct bpf_diag_scratch scratch;
+ struct list_head fmt_chunks;
+ struct bpf_diag_mod_scope mod;
+ u32 frame_id_gen;
+};
+
+bool bpf_diag_enabled(const struct bpf_verifier_env *env)
+{
+ return env->log.level & BPF_LOG_LEVEL;
+}
+
+static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
+
+int bpf_diag_init(struct bpf_verifier_env *env)
+{
+ if (!bpf_diag_enabled(env))
+ return 0;
+
+ env->diag = kzalloc_obj(struct bpf_diag, GFP_KERNEL_ACCOUNT);
+ if (!env->diag)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&env->diag->fmt_chunks);
+ return 0;
+}
+
+void bpf_diag_init_frame(struct bpf_verifier_env *env, struct bpf_func_state *state)
+{
+ if (env->diag)
+ state->diag_frame_id = ++env->diag->frame_id_gen;
+}
+
+static char *diag_fmt_alloc(struct bpf_verifier_env *env, size_t size)
+{
+ struct bpf_diag *diag = env->diag;
+ struct diag_fmt_chunk *chunk;
+ size_t capacity, available;
+ char *buf;
+
+ if (!diag || !size || size > INT_MAX)
+ return NULL;
+
+ if (!list_empty(&diag->fmt_chunks)) {
+ chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
+ available = seq_buf_get_buf(&chunk->seq, &buf);
+ if (available >= size)
+ goto commit;
+ }
+
+ capacity = max_t(size_t, BPF_DIAG_FMT_CHUNK_SIZE, size);
+ chunk = kmalloc(struct_size(chunk, data, capacity), GFP_KERNEL_ACCOUNT);
+ if (!chunk)
+ return NULL;
+
+ seq_buf_init(&chunk->seq, chunk->data, capacity);
+ list_add_tail(&chunk->node, &diag->fmt_chunks);
+ available = seq_buf_get_buf(&chunk->seq, &buf);
+ if (WARN_ON_ONCE(available < size))
+ return NULL;
+
+commit:
+ seq_buf_commit(&chunk->seq, size);
+ return buf;
+}
+
+char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size)
+{
+ char *buf;
+
+ buf = diag_fmt_alloc(env, size);
+ if (buf)
+ buf[0] = '\0';
+ return buf;
+}
+
+const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)
+{
+ va_list copy;
+ char *buf;
+ int len;
+
+ va_copy(copy, args);
+ len = vsnprintf(NULL, 0, fmt, copy);
+ va_end(copy);
+ if (len < 0 || len == INT_MAX)
+ return "";
+
+ buf = diag_fmt_alloc(env, len + 1);
+ if (buf)
+ vsnprintf(buf, len + 1, fmt, args);
+ return buf ?: "";
+}
+
+const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ const char *buf;
+ va_list args;
+
+ va_start(args, fmt);
+ buf = bpf_diag_vfmt(env, fmt, args);
+ va_end(args);
+ return buf;
+}
+
+static struct diag_fmt_mark diag_fmt_save(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+ struct diag_fmt_mark mark = {};
+
+ if (!diag || list_empty(&diag->fmt_chunks))
+ return mark;
+
+ mark.chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
+ mark.len = mark.chunk->seq.len;
+ return mark;
+}
+
+static void diag_fmt_restore(struct bpf_verifier_env *env, struct diag_fmt_mark mark)
+{
+ struct bpf_diag *diag = env->diag;
+ struct diag_fmt_chunk *chunk;
+
+ if (!diag)
+ return;
+
+ while (!list_empty(&diag->fmt_chunks)) {
+ chunk = list_last_entry(&diag->fmt_chunks, struct diag_fmt_chunk, node);
+ if (chunk == mark.chunk)
+ break;
+ list_del(&chunk->node);
+ kfree(chunk);
+ }
+
+ if (mark.chunk) {
+ mark.chunk->seq.len = mark.len;
+ seq_buf_str(&mark.chunk->seq);
+ }
+}
+
+void bpf_diag_free(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+
+ if (!diag)
+ return;
+
+ diag_fmt_restore(env, (struct diag_fmt_mark){});
+ kvfree(diag->log.events);
+ kfree(diag);
+ env->diag = NULL;
+}
+
+static void diag_write(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ va_start(args, fmt);
+ bpf_verifier_vlog(&env->log, fmt, args);
+ va_end(args);
+}
+
+static u64 log_end(const struct bpf_diag_log *log)
+{
+ return log->first_seq + log->cnt;
+}
+
+static u32 log_pos(const struct bpf_diag_log *log, u32 idx)
+{
+ u32 pos = log->head + idx;
+
+ return pos < log->cap ? pos : pos - log->cap;
+}
+
+u64 bpf_diag_event_log_save(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+
+ return diag ? log_end(&diag->log) : 0;
+}
+
+void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos)
+{
+ struct bpf_diag *diag = env->diag;
+ struct bpf_diag_log *log;
+ u64 end_seq;
+
+ if (!diag)
+ return;
+
+ log = &diag->log;
+ end_seq = log_end(log);
+ if (WARN_ON_ONCE(log_pos > end_seq))
+ log_pos = end_seq;
+
+ /*
+ * A deep abandoned path may have rotated away the shared prefix. In
+ * that case, restart with an empty retained suffix and remember that
+ * every event before the restored mark is unavailable.
+ */
+ if (log_pos <= log->first_seq) {
+ log->first_seq = log_pos;
+ log->head = 0;
+ log->cnt = 0;
+ return;
+ }
+
+ log->cnt = log_pos - log->first_seq;
+}
+
+u32 bpf_diag_irq_depth(const struct bpf_verifier_state *state)
+{
+ u32 depth = 0;
+ int i;
+
+ for (i = 0; i < state->acquired_refs; i++) {
+ if (state->refs[i].type == REF_TYPE_IRQ)
+ depth++;
+ }
+
+ return depth;
+}
+
+static void diag_append_history(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ struct bpf_diag_history_event *events;
+ struct bpf_diag *diag = env->diag;
+ struct bpf_diag_log *log;
+ u32 cap, max_events;
+
+ if (!diag)
+ return;
+ log = &diag->log;
+
+ if (log->cnt < log->cap) {
+ log->events[log_pos(log, log->cnt++)] = *event;
+ return;
+ }
+
+ max_events = BPF_DIAG_EVENT_LOG_MAX_SIZE / sizeof(*events);
+ if (log->growth_failed || log->cap == max_events)
+ goto rotate;
+
+ cap = min(log->cap ? log->cap * 2 : 64, max_events);
+ events = kvrealloc(log->events, array_size(cap, sizeof(*events)), GFP_KERNEL_ACCOUNT);
+ if (!events) {
+ log->growth_failed = true;
+ goto rotate;
+ }
+ log->events = events;
+ log->cap = cap;
+ log->events[log->cnt++] = *event;
+ return;
+
+rotate:
+ if (log->cap) {
+ log->events[log->head++] = *event;
+ if (log->head == log->cap)
+ log->head = 0;
+ }
+ log->first_seq++;
+}
+
+static void diag_print_wrapped_prefixed(struct bpf_verifier_env *env, const char *first_prefix,
+ const char *next_prefix, const char *text)
+{
+ const char *prefix = first_prefix;
+
+ while (*text) {
+ const char *line = text;
+ int prefix_len = strlen(prefix);
+ int text_width = BPF_DIAG_TEXT_WIDTH - prefix_len;
+ int len = 0, last_space = -1;
+
+ if (text_width < 1)
+ text_width = 1;
+
+ while (line[len] && line[len] != '\n' && len < text_width) {
+ if (line[len] == ' ')
+ last_space = len;
+ len++;
+ }
+
+ if (line[len] && line[len] != '\n' && line[len] != ' ' && last_space > 0)
+ len = last_space;
+
+ diag_write(env, "%s%.*s\n", prefix, len, line);
+
+ text = line + len;
+ while (*text == ' ')
+ text++;
+ if (*text == '\n')
+ text++;
+
+ prefix = next_prefix;
+ }
+}
+
+const char *bpf_diag_fmt_btf_type(struct bpf_verifier_env *env, const struct btf *btf, u32 type_id)
+{
+ char *buf = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
+ size_t len;
+ int ret;
+
+ if (!buf)
+ return "";
+
+ buf[0] = '\0';
+ ret = btf_type_name_to_buf(btf, type_id, buf, BPF_DIAG_FMT_BUF_SIZE);
+ if (ret < 0 || !buf[0]) {
+ scnprintf(buf, BPF_DIAG_FMT_BUF_SIZE, "BTF type ID %u", type_id);
+ return buf;
+ }
+
+ len = strlen(buf);
+ if (len && buf[len - 1] == '{')
+ buf[len - 1] = '\0';
+ return buf;
+}
+
+static void diag_vprint_indented(struct bpf_verifier_env *env, const char *fmt, va_list args)
+ __printf(2, 0);
+
+static void diag_vprint_indented(struct bpf_verifier_env *env, const char *fmt, va_list args)
+{
+ char *buf;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ buf = kvasprintf(GFP_KERNEL_ACCOUNT, fmt, args);
+ if (!buf) {
+ diag_write(env, "%s<failed to allocate diagnostic text>\n", BPF_DIAG_TEXT_INDENT);
+ return;
+ }
+
+ diag_print_wrapped_prefixed(env, BPF_DIAG_TEXT_INDENT, BPF_DIAG_TEXT_INDENT, buf);
+ kfree(buf);
+}
+
+static int diag_line_width(unsigned int line)
+{
+ int width = 1;
+
+ while (line >= 10) {
+ line /= 10;
+ width++;
+ }
+
+ return width;
+}
+
+static int diag_line_indent(const char *line)
+{
+ int indent = 0;
+
+ while (*line == ' ' || *line == '\t') {
+ if (*line == '\t')
+ indent = round_up(indent + 1, BPF_DIAG_TAB_WIDTH);
+ else
+ indent++;
+ line++;
+ }
+
+ return indent;
+}
+
+static void disasm_print(void *private_data, const char *fmt, ...) __printf(2, 3);
+
+static void disasm_print(void *private_data, const char *fmt, ...)
+{
+ struct disasm_ctx *ctx = private_data;
+ va_list args;
+
+ va_start(args, fmt);
+ seq_buf_vprintf(&ctx->seq, fmt, args);
+ va_end(args);
+}
+
+static const char *disasm_kfunc_name(void *private_data, const struct bpf_insn *insn)
+{
+ struct disasm_ctx *ctx = private_data;
+
+ return bpf_disasm_kfunc_name(ctx->env, insn);
+}
+
+static void format_disasm_line(struct bpf_verifier_env *env, int insn_idx,
+ struct disasm_line *line)
+{
+ struct disasm_ctx ctx = { .env = env };
+ struct bpf_insn *insn;
+ const struct bpf_insn_cbs cbs = {
+ .cb_call = disasm_kfunc_name,
+ .cb_print = disasm_print,
+ .private_data = &ctx,
+ };
+
+ line->idx = insn_idx;
+ line->valid = false;
+ seq_buf_init(&ctx.seq, line->text, sizeof(line->text));
+
+ if (insn_idx < 0 || insn_idx >= env->prog->len)
+ return;
+
+ if (insn_idx > 0 && bpf_is_ldimm64(&env->prog->insnsi[insn_idx - 1]))
+ return;
+
+ insn = &env->prog->insnsi[insn_idx];
+ if (bpf_is_ldimm64(insn) && insn_idx + 1 >= env->prog->len)
+ return;
+
+ print_bpf_insn(&cbs, insn, env->allow_ptr_leaks);
+ seq_buf_str(&ctx.seq);
+ ctx.seq.len = strnlen(line->text, sizeof(line->text));
+ while (ctx.seq.len && line->text[ctx.seq.len - 1] == '\n')
+ seq_buf_pop(&ctx.seq);
+ seq_buf_str(&ctx.seq);
+
+ line->valid = true;
+}
+
+static void diag_format_source_text(char *buf, size_t size, const char *line, int width)
+{
+ int col = 0, len = 0;
+
+ if (!size)
+ return;
+ if (width <= 0) {
+ buf[0] = '\0';
+ return;
+ }
+
+ line = line ?: "...";
+ while (*line && col < width && len + 1 < size) {
+ if (*line == '\t') {
+ int next = round_up(col + 1, BPF_DIAG_TAB_WIDTH);
+
+ while (col < next && col < width && len + 1 < size) {
+ buf[len++] = ' ';
+ col++;
+ }
+ line++;
+ continue;
+ }
+
+ buf[len++] = *line++;
+ col++;
+ }
+
+ if (*line) {
+ int ellipsis_len = min(3, width);
+
+ while (len > 0 && col > width - ellipsis_len) {
+ len--;
+ col--;
+ }
+ while (ellipsis_len-- && len + 1 < size)
+ buf[len++] = '.';
+ }
+
+ buf[len] = '\0';
+}
+
+static void diag_format_source_lane(char *buf, size_t size, const char *source_prefix,
+ int source_line_width, int line_num, const char *line)
+{
+ int len, text_width;
+
+ if (line_num <= 0) {
+ buf[0] = '\0';
+ return;
+ }
+
+ len = scnprintf(buf, size, "%s%*d | ", source_prefix, source_line_width, line_num);
+ text_width = BPF_DIAG_SOURCE_LANE_WIDTH - len;
+ diag_format_source_text(buf + len, size - len, line, text_width);
+}
+
+static void bpf_diag_header(struct bpf_verifier_env *env, const char *category,
+ const char *problem)
+{
+ char first;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ category = category ?: "Verifier Error";
+ problem = problem ?: "";
+
+ if (!problem[0]) {
+ diag_write(env, "\nVerification failed: %s\n", category);
+ return;
+ }
+
+ first = toupper(problem[0]);
+ diag_write(env, "\nVerification failed: %s: %c%s\n", category, first, problem + 1);
+}
+
+static void diag_reason(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
+static void diag_suggestion(struct bpf_verifier_env *env, const char *fmt, ...)
+ __printf(2, 3);
+
+static void diag_section(struct bpf_verifier_env *env, const char *title)
+{
+ if (!bpf_diag_enabled(env))
+ return;
+
+ diag_write(env, "\n%s:\n", title);
+}
+
+static void diag_reason(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ diag_section(env, "Reason");
+
+ va_start(args, fmt);
+ diag_vprint_indented(env, fmt, args);
+ va_end(args);
+}
+
+static void diag_suggestion(struct bpf_verifier_env *env, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ diag_section(env, "Suggestion");
+
+ va_start(args, fmt);
+ diag_vprint_indented(env, fmt, args);
+ va_end(args);
+ diag_write(env, "\n");
+}
+
+static void diag_print_source_annotation(struct bpf_verifier_env *env, int line_width, int indent,
+ const char *label, const char *msg)
+{
+ const char *first_prefix, *next_prefix, *text;
+
+ indent = min_t(int, indent, max_t(int, 0, BPF_DIAG_SOURCE_LANE_WIDTH - line_width - 8));
+ text = bpf_diag_fmt(env, "%s: %s", label, msg);
+ first_prefix = bpf_diag_fmt(env, " %*s | %*s^-- ", line_width + 4, "", indent, "");
+ next_prefix = bpf_diag_fmt(env, " %*s | %*s ", line_width + 4, "", indent, "");
+
+ diag_print_wrapped_prefixed(env, first_prefix, next_prefix, text);
+}
+
+static void diag_print_insn_context(struct bpf_verifier_env *env, u32 insn_idx,
+ struct disasm_line *disasm_lines)
+{
+ int insn_width = diag_line_width(env->prog->len ? env->prog->len - 1 : 0);
+ int i;
+
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
+ int row = i - BPF_DIAG_CONTEXT;
+
+ format_disasm_line(env, insn_idx + row, &disasm_lines[i]);
+ }
+
+ diag_write(env, " Instruction context:\n");
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
+ struct disasm_line *line = &disasm_lines[i];
+
+ if (line->valid)
+ diag_write(env, " %s%*d | %s\n",
+ line->idx == insn_idx ? ">>> " : " ",
+ insn_width, line->idx, line->text);
+ }
+}
+
+static void bpf_diag_source(struct bpf_verifier_env *env, u32 insn_idx, const char *label,
+ const char *fmt, ...)
+{
+ struct bpf_diag_scratch *scratch;
+ struct bpf_linfo_source *source_lines;
+ struct disasm_line *disasm_lines;
+ struct bpf_linfo_source src = {};
+ struct diag_fmt_mark mark;
+ const struct bpf_line_info *linfo;
+ const struct bpf_subprog_info *subprog;
+ struct btf *btf = env->prog->aux->btf;
+ char *source_lane;
+ const char *msg;
+ const char *func;
+ int start_line, end_line, width, indent, subprogno, linfo_start, linfo_end, i;
+ va_list args;
+
+ if (!bpf_diag_enabled(env))
+ return;
+ if (!env->diag)
+ return;
+
+ mark = diag_fmt_save(env);
+ label = label ?: "note";
+ scratch = &env->diag->scratch;
+ source_lines = scratch->source_lines;
+ disasm_lines = scratch->disasm_lines;
+ memset(source_lines, 0, sizeof(scratch->source_lines));
+ memset(disasm_lines, 0, sizeof(scratch->disasm_lines));
+
+ va_start(args, fmt);
+ msg = bpf_diag_vfmt(env, fmt, args);
+ va_end(args);
+ if (!*msg)
+ msg = "<failed to allocate diagnostic text>";
+
+ linfo = bpf_find_linfo(env->prog, insn_idx);
+ if (btf && linfo)
+ bpf_get_linfo_source(btf, linfo, &src);
+ if (!src.file || !*src.file || !src.line || !*src.line) {
+ diag_write(env, " insn %u\n", insn_idx);
+ diag_print_source_annotation(env, 0, 0, label, msg);
+ diag_print_insn_context(env, insn_idx, disasm_lines);
+ goto out_restore;
+ }
+
+ subprog = bpf_find_containing_subprog(env, insn_idx);
+ subprogno = subprog ? subprog - env->subprog_info : -ENOENT;
+ func = subprogno >= 0 ? bpf_subprog_name(env, subprogno) : NULL;
+ if (func && *func)
+ diag_write(env, " %s @ %s:%d:%d\n", func, src.file, src.line_num, src.line_col);
+ else
+ diag_write(env, " %s:%d:%d\n", src.file, src.line_num, src.line_col);
+
+ start_line = src.line_num - BPF_DIAG_CONTEXT;
+ end_line = src.line_num + BPF_DIAG_CONTEXT;
+ width = diag_line_width(end_line);
+ indent = diag_line_indent(src.line);
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++)
+ source_lines[i].line_num = start_line + i;
+
+ linfo = env->prog->aux->linfo;
+ linfo_start = subprog ? subprog->linfo_idx : 0;
+ linfo_end = subprogno >= 0 && subprogno + 1 < env->subprog_cnt ?
+ env->subprog_info[subprogno + 1].linfo_idx : env->prog->aux->nr_linfo;
+ for (i = linfo_start; i < linfo_end; i++) {
+ struct bpf_linfo_source line_src;
+ int idx;
+
+ bpf_get_linfo_source(btf, &linfo[i], &line_src);
+ if (line_src.file_name_off != src.file_name_off ||
+ line_src.line_num < start_line || line_src.line_num > end_line ||
+ !line_src.line || !*line_src.line)
+ continue;
+
+ idx = line_src.line_num - start_line;
+ if (!source_lines[idx].line)
+ source_lines[idx] = line_src;
+ }
+
+ diag_write(env, " Source context:\n");
+ source_lane = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
+ if (!source_lane)
+ goto out_restore;
+ for (i = 0; i < BPF_DIAG_CONTEXT_CNT; i++) {
+ const char *source_prefix;
+
+ source_prefix = source_lines[i].line_num == src.line_num ? ">>> " : " ";
+ diag_format_source_lane(source_lane, BPF_DIAG_FMT_BUF_SIZE, source_prefix, width,
+ source_lines[i].line_num, source_lines[i].line);
+ diag_write(env, " %s\n", source_lane);
+ if (source_lines[i].line_num == src.line_num)
+ diag_print_source_annotation(env, width, indent, label, msg);
+ }
+ diag_print_insn_context(env, insn_idx, disasm_lines);
+
+out_restore:
+ diag_fmt_restore(env, mark);
+}
+
+static const struct bpf_func_state *diag_current_frame(const struct bpf_verifier_env *env)
+{
+ return env->cur_state->frame[env->cur_state->curframe];
+}
+
+void bpf_diag_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *problem, const char *reason, const char *suggestion)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type)
+{
+ switch (base_type(type)) {
+ case NOT_INIT:
+ return "an uninitialized value";
+ case SCALAR_VALUE:
+ return "an integer scalar";
+ case PTR_TO_CTX:
+ return "a context pointer";
+ case PTR_TO_STACK:
+ return "a stack pointer";
+ case PTR_TO_MAP_VALUE:
+ if (type_may_be_null(type))
+ return "a nullable map value pointer";
+ return "a map value pointer";
+ case PTR_TO_MEM:
+ if (type_may_be_null(type))
+ return "a nullable memory pointer";
+ return "a memory pointer";
+ case PTR_TO_BTF_ID:
+ if (type_may_be_null(type))
+ return "a nullable kernel object pointer";
+ if (type_is_non_owning_ref(type))
+ return "a borrowed allocated object pointer";
+ if (type_is_ptr_alloc_obj(type))
+ return "an owned allocated object pointer";
+ if (type_flag(type) & PTR_UNTRUSTED)
+ return "an untrusted kernel object pointer";
+ return "a kernel object pointer";
+ default:
+ return reg_type_str(env, type);
+ }
+}
+
+static const char *diag_arg_ordinal(int argno)
+{
+ switch (argno) {
+ case 1:
+ return "first";
+ case 2:
+ return "second";
+ case 3:
+ return "third";
+ case 4:
+ return "fourth";
+ case 5:
+ return "fifth";
+ case 6:
+ return "sixth";
+ case 7:
+ return "seventh";
+ case 8:
+ return "eighth";
+ case 9:
+ return "ninth";
+ case 10:
+ return "tenth";
+ case 11:
+ return "eleventh";
+ case 12:
+ return "twelfth";
+ default:
+ return NULL;
+ }
+}
+
+void bpf_diag_call_type(struct bpf_verifier_env *env, u32 insn_idx, int argno, int regno,
+ int stack_arg_slot, const char *call_name, const char *arg_name,
+ const char *reason, const char *suggestion)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ };
+ const char *ordinal = diag_arg_ordinal(argno);
+ const char *arg_desc;
+ bool print_history = true;
+
+ if (regno >= 0) {
+ opts.scope = BPF_DIAG_HISTORY_SCOPE_REG;
+ opts.regno = regno;
+ } else if (stack_arg_slot >= 0) {
+ opts.scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG;
+ opts.stack_arg_slot = stack_arg_slot;
+ } else {
+ print_history = false;
+ }
+
+ if (ordinal && arg_name)
+ arg_desc = bpf_diag_fmt(env, "%s argument (%s)", ordinal, arg_name);
+ else if (ordinal)
+ arg_desc = bpf_diag_fmt(env, "%s argument", ordinal);
+ else if (arg_name)
+ arg_desc = bpf_diag_fmt(env, "argument %s", arg_name);
+ else
+ arg_desc = "argument";
+
+ bpf_diag_header(env, CALL_TYPE_SAFETY, "invalid call argument");
+ diag_reason(env, "The %s to %s does not satisfy the verifier contract: %s.",
+ arg_desc, call_name, reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "invalid %s for %s", arg_desc, call_name);
+
+ if (print_history)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+static const char *diag_context_constraint(enum bpf_diag_context_kind kind)
+{
+ switch (kind) {
+ case BPF_DIAG_CONTEXT_RCU:
+ return "RCU read-side critical sections cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_PREEMPT:
+ return "preemption-disabled code cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_IRQ:
+ return "IRQ-disabled code cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_LOCK:
+ return "code holding a BPF spin lock cannot call operations that may sleep";
+ case BPF_DIAG_CONTEXT_NONE:
+ default:
+ return NULL;
+ }
+}
+
+static const char *diag_active_context(struct bpf_verifier_env *env, u32 depth,
+ const char *context)
+{
+ if (depth == 1)
+ return bpf_diag_fmt(env, "an active %s (depth 1)", context);
+ return bpf_diag_fmt(env, "%u active %ss (depth %u)", depth, context, depth);
+}
+
+static u32 diag_context_depth(struct bpf_verifier_env *env, enum bpf_diag_context_kind kind)
+{
+ switch (kind) {
+ case BPF_DIAG_CONTEXT_RCU:
+ return env->cur_state->active_rcu_locks;
+ case BPF_DIAG_CONTEXT_PREEMPT:
+ return env->cur_state->active_preempt_locks;
+ case BPF_DIAG_CONTEXT_IRQ:
+ return bpf_diag_irq_depth(env->cur_state);
+ case BPF_DIAG_CONTEXT_LOCK:
+ return env->cur_state->active_locks;
+ case BPF_DIAG_CONTEXT_NONE:
+ default:
+ return 0;
+ }
+}
+
+void bpf_diag_ctx_forbidden(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, const char *suggestion)
+{
+ struct bpf_diag_history_opts opts;
+ enum bpf_diag_context_kind ctx_kind;
+ const char *constraint, *context;
+ u32 depth;
+
+ if (env->cur_state->active_rcu_locks)
+ ctx_kind = BPF_DIAG_CONTEXT_RCU;
+ else if (env->cur_state->active_preempt_locks)
+ ctx_kind = BPF_DIAG_CONTEXT_PREEMPT;
+ else if (env->cur_state->active_irq_id)
+ ctx_kind = BPF_DIAG_CONTEXT_IRQ;
+ else if (env->cur_state->active_locks)
+ ctx_kind = BPF_DIAG_CONTEXT_LOCK;
+ else
+ ctx_kind = BPF_DIAG_CONTEXT_NONE;
+
+ depth = diag_context_depth(env, ctx_kind);
+ opts = (struct bpf_diag_history_opts) {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = ctx_kind,
+ .ctx_depth = depth,
+ };
+ constraint = diag_context_constraint(ctx_kind);
+ context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY,
+ "operation is not allowed in this context");
+ if (constraint) {
+ if (depth) {
+ diag_reason(
+ env, "The operation %s cannot be used in %s because %s. This path is still inside %s.",
+ operation, context, constraint, diag_active_context(env, depth, context));
+ } else {
+ diag_reason(env, "The operation %s cannot be used in %s because %s.",
+ operation, context, constraint);
+ }
+ } else {
+ diag_reason(env, "The operation %s cannot be used in %s.", operation,
+ context);
+ }
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s is not allowed in %s", operation,
+ context);
+
+ if (ctx_kind != BPF_DIAG_CONTEXT_NONE)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_ctx_active(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion)
+{
+ u32 depth = diag_context_depth(env, ctx_kind);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = ctx_kind,
+ .ctx_depth = depth,
+ };
+ const char *context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY,
+ "operation is not allowed in this context");
+ diag_reason(
+ env, "The operation %s cannot be used while this path is still inside %s. Leave the region before this operation.",
+ operation, diag_active_context(env, depth, context));
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s is not allowed before leaving %s",
+ operation, context);
+
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_ctx_required(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion)
+{
+ const char *context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY, "required context is not active");
+ diag_reason(env, "The operation %s requires an active %s, but this path is outside one.",
+ operation, context);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s requires %s", operation, context);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_ctx_underflow(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, enum bpf_diag_context_kind ctx_kind,
+ const char *suggestion)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = ctx_kind,
+ };
+ const char *context = diag_context_name(ctx_kind);
+
+ bpf_diag_header(env, EXECUTION_CONTEXT_SAFETY, "unmatched context exit");
+ diag_reason(
+ env, "The operation %s tries to leave %s, but this path has no active %s to leave. The current depth is 0.",
+ operation, context, context);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s has no matching enter on this path",
+ operation);
+
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_program_structure(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *problem, const char *suggestion,
+ const char *reason_fmt, ...)
+{
+ va_list args;
+
+ bpf_diag_header(env, PROGRAM_STRUCTURE, problem);
+ diag_section(env, "Reason");
+
+ va_start(args, reason_fmt);
+ diag_vprint_indented(env, reason_fmt, args);
+ va_end(args);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_policy(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ const char *reason, const char *suggestion)
+{
+ bpf_diag_header(env, POLICY, "operation is not allowed");
+ diag_reason(env, "The %s is not allowed: %s.", operation, reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "policy check failed for %s", operation);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const struct bpf_reg_state *reg,
+ enum bpf_diag_invalid_deref_kind kind, s64 offset)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+ const char *type_name = bpf_diag_reg_type_plain(env, reg->type);
+
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, "invalid dereference");
+
+ switch (kind) {
+ case BPF_DIAG_DEREF_SCALAR:
+ diag_reason(env, "%s is an integer scalar here, not a pointer to memory.",
+ reg_name);
+ break;
+ case BPF_DIAG_DEREF_NULLABLE_PTR:
+ diag_reason(
+ env, "%s may be NULL here (%s). The program could dereference NULL on this path, so the verifier cannot prove this access is safe.",
+ reg_name, type_name);
+ break;
+ case BPF_DIAG_DEREF_MODIFIED_PTR:
+ diag_reason(
+ env, "%s has offset %lld here, but this pointer type must be dereferenced in its original form.",
+ reg_name, offset);
+ break;
+ case BPF_DIAG_DEREF_INVALID_PTR:
+ default:
+ diag_reason(
+ env, "%s has type %s here, which is not valid for this memory access.",
+ reg_name, type_name);
+ break;
+ }
+
+ diag_section(env, "At");
+ if (kind == BPF_DIAG_DEREF_MODIFIED_PTR)
+ bpf_diag_source(env, insn_idx, "error",
+ "dereference requires the original %s pointer", type_name);
+ else
+ bpf_diag_source(env, insn_idx, "error", "invalid dereference of %s (%s)",
+ reg_name, type_name);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ switch (kind) {
+ case BPF_DIAG_DEREF_NULLABLE_PTR:
+ diag_suggestion(
+ env, "Add a NULL check before the access and dereference the pointer only on the non-NULL path.");
+ break;
+ case BPF_DIAG_DEREF_MODIFIED_PTR:
+ diag_suggestion(
+ env, "Preserve the original pointer in another register, or use only offsets this pointer type permits before dereferencing it.");
+ break;
+ case BPF_DIAG_DEREF_SCALAR:
+ case BPF_DIAG_DEREF_INVALID_PTR:
+ default:
+ diag_suggestion(
+ env, "Preserve a pointer-valued register where needed, or reload and revalidate the pointer after scalar arithmetic, helper calls, or other operations that can invalidate it.");
+ break;
+ }
+}
+
+void bpf_diag_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+ const struct bpf_diag_log *log = env->diag ? &env->diag->log : NULL;
+ struct bpf_diag_mod_target target;
+ bool invalidated = false;
+ int i;
+
+ target = diag_reg_target(opts.frame_id, opts.frameno, regno);
+ for (i = log ? log->cnt : 0; i > 0; i--) {
+ const struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+
+ if (event->kind != BPF_DIAG_HISTORY_MOD ||
+ !diag_target_matches(&event->mod.target, &target))
+ continue;
+ invalidated = event->mod.new.type == NOT_INIT;
+ break;
+ }
+
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, "unreadable register");
+ if (invalidated)
+ diag_reason(
+ env, "R%d is not readable here. A previous operation invalidated this register, so the verifier cannot use it as an input.",
+ regno);
+ else if (log && !log->first_seq)
+ diag_reason(env,
+ "R%d has never been initialized on this path, so the verifier cannot use it as an input.",
+ regno);
+ else
+ diag_reason(
+ env, "R%d is not readable here. It may never have been initialized, or an earlier operation may have invalidated it.",
+ regno);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "R%d is not readable", regno);
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ if (invalidated)
+ diag_suggestion(
+ env, "Avoid using the register after it is invalidated, or initialize it again before this instruction.");
+ else if (log && !log->first_seq)
+ diag_suggestion(env, "Initialize R%d on every path before this instruction.", regno);
+ else
+ diag_suggestion(
+ env, "Initialize the register on every path, or initialize it again after any operation that invalidates it.");
+}
+
+static int diag_stack_argno(u8 slot)
+{
+ return MAX_BPF_FUNC_REG_ARGS + slot + 1;
+}
+
+static void diag_format_stack_arg(char *buf, size_t size, u8 slot, const char *arg_name)
+{
+ int argno = diag_stack_argno(slot);
+ const char *ordinal = diag_arg_ordinal(argno);
+
+ if (ordinal && arg_name)
+ scnprintf(buf, size, "outgoing stack argument %u (%s argument, %s)", slot + 1,
+ ordinal, arg_name);
+ else if (ordinal)
+ scnprintf(buf, size, "outgoing stack argument %u (%s argument)", slot + 1, ordinal);
+ else if (arg_name)
+ scnprintf(buf, size, "outgoing stack argument %u (%s)", slot + 1, arg_name);
+ else
+ scnprintf(buf, size, "outgoing stack argument %u", slot + 1);
+}
+
+void bpf_diag_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,
+ int stack_arg_slot, const char *callee_name,
+ const char *arg_name)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_STACK_ARG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .stack_arg_slot = stack_arg_slot,
+ };
+ const char *arg_buf;
+
+ arg_buf = bpf_diag_fmt_buf(env, BPF_DIAG_FMT_BUF_SIZE);
+ if (arg_buf)
+ diag_format_stack_arg((char *)arg_buf, BPF_DIAG_FMT_BUF_SIZE, stack_arg_slot,
+ arg_name);
+ else
+ arg_buf = "";
+ bpf_diag_header(env, REGISTER_TYPE_SAFETY, "missing stack argument");
+ if (callee_name && *callee_name)
+ diag_reason(
+ env, "Function %s expects %d arguments, but %s is not initialized at this call.",
+ callee_name, nargs, arg_buf);
+ else
+ diag_reason(
+ env, "The callee expects %d arguments, but %s is not initialized at this call.",
+ nargs, arg_buf);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s is not initialized", arg_buf);
+
+ if (stack_arg_slot >= 0)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(
+ env, "Write the outgoing stack argument after any operation that may invalidate stored pointer values, and before making this call.");
+}
+
+void bpf_diag_memory(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion)
+{
+ bpf_diag_header(env, MEMORY_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true)
+{
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = BPF_DIAG_HISTORY_BRANCH,
+ .branch = {
+ .cond_true = cond_true,
+ },
+ };
+
+ diag_append_history(env, &event);
+}
+
+static void diag_snapshot_reg(struct bpf_diag_reg_snapshot *snapshot,
+ const struct bpf_reg_state *reg)
+{
+ snapshot->type = reg->type;
+ if (type_is_map_ptr(reg->type))
+ snapshot->map_ptr = reg->map_ptr;
+ if (base_type(reg->type) == PTR_TO_BTF_ID && reg->btf && reg->btf_id) {
+ snapshot->btf_id = reg->btf_id;
+ snapshot->btf = reg->btf;
+ }
+ snapshot->var_off = reg->var_off;
+ snapshot->r64 = reg->r64;
+}
+
+static bool diag_mod_insn_origin(struct bpf_verifier_env *env, u32 insn_idx,
+ const struct bpf_diag_mod_target *target,
+ struct bpf_diag_mod_target *origin)
+{
+ const struct bpf_insn *insn = &env->prog->insnsi[insn_idx];
+ u8 class = BPF_CLASS(insn->code);
+ const struct bpf_func_state *state;
+
+ if (target->kind == BPF_DIAG_MOD_TARGET_REG && (class == BPF_ALU || class == BPF_ALU64) &&
+ BPF_OP(insn->code) == BPF_MOV && BPF_SRC(insn->code) == BPF_X) {
+ *origin = diag_reg_target(target->frame_id, target->frameno, insn->src_reg);
+ return true;
+ }
+
+ if ((target->kind != BPF_DIAG_MOD_TARGET_STACK_ARG &&
+ target->kind != BPF_DIAG_MOD_TARGET_STACK_SLOT) ||
+ class != BPF_STX)
+ return false;
+
+ state = env->cur_state->frame[env->cur_state->curframe];
+ *origin = diag_reg_target(state->diag_frame_id, state->frameno, insn->src_reg);
+ return true;
+}
+
+static bool diag_mod_keeps_lineage(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ const struct bpf_insn *insn;
+ u8 class;
+
+ if (event->mod.reason != BPF_DIAG_MOD_WRITE ||
+ event->mod.target.kind != BPF_DIAG_MOD_TARGET_REG)
+ return false;
+
+ insn = &env->prog->insnsi[event->insn_idx];
+ class = BPF_CLASS(insn->code);
+ if (class != BPF_ALU && class != BPF_ALU64)
+ return false;
+
+ switch (BPF_OP(insn->code)) {
+ case BPF_ADD:
+ case BPF_SUB:
+ case BPF_MUL:
+ case BPF_OR:
+ case BPF_AND:
+ case BPF_LSH:
+ case BPF_RSH:
+ case BPF_ARSH:
+ case BPF_XOR:
+ case BPF_NEG:
+ case BPF_END:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void diag_record_mod(struct bpf_verifier_env *env, u32 insn_idx,
+ struct bpf_diag_mod_target target,
+ enum bpf_diag_mod_reason reason,
+ const struct bpf_reg_state *old_reg,
+ const struct bpf_reg_state *new_reg,
+ const struct bpf_diag_mod_target *origin)
+{
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = BPF_DIAG_HISTORY_MOD,
+ .mod = {
+ .target = target,
+ .reason = reason,
+ },
+ };
+
+ if (old_reg)
+ diag_snapshot_reg(&event.mod.old, old_reg);
+ if (new_reg)
+ diag_snapshot_reg(&event.mod.new, new_reg);
+ if (origin) {
+ event.mod.origin = *origin;
+ event.mod.origin_valid = true;
+ } else if (diag_mod_insn_origin(env, insn_idx, &target, &event.mod.origin)) {
+ event.mod.origin_valid = true;
+ }
+ if (old_reg && new_reg &&
+ (reason == BPF_DIAG_MOD_WRITE || reason == BPF_DIAG_MOD_SPILL) &&
+ !memcmp(&event.mod.old, &event.mod.new, sizeof(event.mod.old)) &&
+ !event.mod.origin_valid &&
+ diag_mod_keeps_lineage(env, &event))
+ return;
+
+ diag_append_history(env, &event);
+}
+
+static struct bpf_reg_state *target_to_reg(struct bpf_verifier_env *env,
+ const struct bpf_diag_mod_target *target)
+{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ struct bpf_func_state *state;
+
+ state = target->frameno <= vstate->curframe ? vstate->frame[target->frameno] : NULL;
+
+ if (!state)
+ return NULL;
+ if (state->diag_frame_id != target->frame_id)
+ return NULL;
+
+ switch (target->kind) {
+ case BPF_DIAG_MOD_TARGET_REG:
+ if (target->regno >= MAX_BPF_REG)
+ return NULL;
+ return &state->regs[target->regno];
+ case BPF_DIAG_MOD_TARGET_STACK_ARG:
+ if (target->stack_arg >= state->out_stack_arg_cnt)
+ return NULL;
+ return &state->stack_arg_regs[target->stack_arg];
+ case BPF_DIAG_MOD_TARGET_STACK_SLOT:
+ if (target->spi >= state->allocated_stack / BPF_REG_SIZE)
+ return NULL;
+ return &state->stack[target->spi].spilled_ptr;
+ default:
+ return NULL;
+ }
+}
+
+static bool reg_to_target(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ struct bpf_diag_mod_target *target)
+{
+ struct bpf_verifier_state *vstate = env->cur_state;
+ unsigned long addr = (unsigned long)reg;
+ int frame;
+
+ for (frame = 0; frame <= vstate->curframe; frame++) {
+ struct bpf_func_state *state = vstate->frame[frame];
+ unsigned long start, end;
+ u32 nslots = state->allocated_stack / BPF_REG_SIZE;
+ int spi;
+
+ start = (unsigned long)state->regs;
+ end = (unsigned long)(state->regs + MAX_BPF_REG);
+ if (addr >= start && addr < end) {
+ *target = diag_reg_target(state->diag_frame_id, state->frameno,
+ reg - state->regs);
+ return true;
+ }
+
+ start = (unsigned long)state->stack_arg_regs;
+ end = (unsigned long)(state->stack_arg_regs + state->out_stack_arg_cnt);
+ if (state->out_stack_arg_cnt && addr >= start && addr < end) {
+ *target = diag_stack_arg_target(state->diag_frame_id, state->frameno,
+ reg - state->stack_arg_regs);
+ return true;
+ }
+
+ start = (unsigned long)state->stack;
+ end = (unsigned long)(state->stack + nslots);
+ if (nslots && addr >= start && addr < end) {
+ spi = ((const char *)reg - (const char *)state->stack) /
+ sizeof(*state->stack);
+ *target = diag_stack_slot_target(state->diag_frame_id, state->frameno, spi);
+ return true;
+ }
+ }
+ return false;
+}
+
+void bpf_diag_mod_begin(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ const struct bpf_reg_state *origin, enum bpf_diag_mod_reason reason)
+{
+ struct bpf_diag *diag = env->diag;
+
+ if (!diag)
+ return;
+ diag->mod.active = reg_to_target(env, reg, &diag->mod.target);
+ if (!diag->mod.active)
+ return;
+ diag->mod.target_reg_snapshot = *reg;
+ diag->mod.insn_idx = env->insn_idx;
+ diag->mod.reason = reason;
+ diag->mod.origin_valid = origin && reg_to_target(env, origin, &diag->mod.origin);
+}
+
+void bpf_diag_mod_end(struct bpf_verifier_env *env)
+{
+ struct bpf_diag *diag = env->diag;
+ const struct bpf_reg_state *new_reg;
+
+ if (!diag || !diag->mod.active)
+ return;
+ diag->mod.active = false;
+ /*
+ * Resolve the target again because the enclosing function state's stack
+ * may have been reallocated while the modification was in progress.
+ */
+ new_reg = target_to_reg(env, &diag->mod.target);
+ if (!new_reg)
+ return;
+ diag_record_mod(env, diag->mod.insn_idx, diag->mod.target, diag->mod.reason,
+ &diag->mod.target_reg_snapshot, new_reg,
+ diag->mod.origin_valid ? &diag->mod.origin : NULL);
+}
+
+void bpf_diag_record_scrub(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ enum bpf_diag_mod_reason reason)
+{
+ struct bpf_diag_mod_target target;
+
+ if (!env->diag || reg->type == NOT_INIT || !reg_to_target(env, reg, &target))
+ return;
+ diag_record_mod(env, env->insn_idx, target, reason, reg, NULL, NULL);
+}
+
+void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,
+ const struct bpf_func_state *state, s16 min_off, s16 max_off,
+ enum bpf_diag_mod_reason reason)
+{
+ diag_record_mod(env, env->insn_idx,
+ diag_stack_range_target(state->diag_frame_id, state->frameno, min_off, max_off),
+ reason, NULL, NULL, NULL);
+}
+
+static void diag_record_ref(struct bpf_verifier_env *env, u32 insn_idx, u8 kind, u32 ref_id)
+{
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = kind,
+ .ref = {
+ .ref_id = ref_id,
+ },
+ };
+
+ diag_append_history(env, &event);
+}
+
+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)
+{
+ diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_ACQUIRE, ref_id);
+}
+
+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id)
+{
+ diag_record_ref(env, insn_idx, BPF_DIAG_HISTORY_REF_RELEASE, ref_id);
+}
+
+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,
+ enum bpf_diag_context_kind ctx_kind, bool enter, u32 depth)
+{
+ /*
+ * Keep leave events so context rendering can stop at a depth-zero exit
+ * and show nested-region depth accurately for the active path.
+ */
+ struct bpf_diag_history_event event = {
+ .insn_idx = insn_idx,
+ .kind = BPF_DIAG_HISTORY_CONTEXT,
+ .ctx = {
+ .kind = ctx_kind,
+ .enter = enter,
+ .depth = depth,
+ },
+ };
+
+ diag_append_history(env, &event);
+}
+
+static int diag_history_context_start_idx(const struct bpf_diag_log *log,
+ const struct bpf_diag_history_opts *opts)
+{
+ int i;
+
+ if (!opts->ctx_depth)
+ return 0;
+
+ /* Find the most recent outermost entry, or a depth-zero exit. */
+ for (i = log->cnt; i > 0; i--) {
+ const struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+
+ if (event->kind != BPF_DIAG_HISTORY_CONTEXT || event->ctx.kind != opts->ctx_kind)
+ continue;
+
+ if (event->ctx.enter && event->ctx.depth == 1)
+ return i - 1;
+ if (!event->ctx.enter && event->ctx.depth == 0)
+ return 0;
+ }
+
+ return 0;
+}
+
+struct bpf_diag_history_filter {
+ const struct bpf_diag_history_opts *opts;
+ u32 lineage_start;
+ bool lineage_valid;
+};
+
+static bool diag_target_matches(const struct bpf_diag_mod_target *event_target,
+ const struct bpf_diag_mod_target *target)
+{
+ int slot_off;
+
+ if (event_target->frame_id != target->frame_id || event_target->frameno != target->frameno)
+ return false;
+
+ if (event_target->kind == BPF_DIAG_MOD_TARGET_STACK_RANGE &&
+ target->kind == BPF_DIAG_MOD_TARGET_STACK_SLOT) {
+ slot_off = -(target->spi + 1) * BPF_REG_SIZE;
+ return event_target->range.min_off < slot_off + BPF_REG_SIZE &&
+ event_target->range.max_off > slot_off;
+ }
+
+ if (event_target->kind != target->kind)
+ return false;
+
+ switch (target->kind) {
+ case BPF_DIAG_MOD_TARGET_REG:
+ return event_target->regno == target->regno;
+ case BPF_DIAG_MOD_TARGET_STACK_ARG:
+ return event_target->stack_arg == target->stack_arg;
+ case BPF_DIAG_MOD_TARGET_STACK_SLOT:
+ return event_target->spi == target->spi;
+ default:
+ return false;
+ }
+}
+
+static void diag_build_lineage(struct bpf_verifier_env *env, struct bpf_diag_log *log,
+ struct bpf_diag_history_filter *filter)
+{
+ const struct bpf_diag_history_opts *opts = filter->opts;
+ struct bpf_diag_mod_target target;
+ int i;
+
+ for (i = 0; i < log->cnt; i++)
+ log->events[log_pos(log, i)].in_lineage = false;
+
+ if (opts->scope == BPF_DIAG_HISTORY_SCOPE_REG)
+ target = diag_reg_target(opts->frame_id, opts->frameno, opts->regno);
+ else if (opts->scope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG)
+ target = diag_stack_arg_target(opts->frame_id, opts->frameno,
+ opts->stack_arg_slot);
+ else
+ return;
+
+ /*
+ * Find the nearest mutation of the active target. A fill or spill changes
+ * the target to its origin, so the same walk follows register/stack
+ * lineage recursively until it reaches the write that created the value.
+ */
+ for (i = log->cnt; i > 0; i--) {
+ struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+ if (event->kind != BPF_DIAG_HISTORY_MOD ||
+ !diag_target_matches(&event->mod.target, &target))
+ continue;
+
+ event->in_lineage = true;
+ filter->lineage_start = i - 1;
+ filter->lineage_valid = true;
+
+ if (event->mod.origin_valid) {
+ target = event->mod.origin;
+ continue;
+ }
+ if (event->mod.reason != BPF_DIAG_MOD_WRITE &&
+ event->mod.reason != BPF_DIAG_MOD_SPILL)
+ continue;
+ if (diag_mod_keeps_lineage(env, event))
+ continue;
+ break;
+ }
+}
+
+static int diag_history_start_idx(const struct bpf_diag_log *log,
+ const struct bpf_diag_history_filter *filter)
+{
+ const struct bpf_diag_history_opts *opts = filter->opts;
+ int i;
+
+ if (opts->scope == BPF_DIAG_HISTORY_SCOPE_CONTEXT)
+ return diag_history_context_start_idx(log, opts);
+ if (filter->lineage_valid)
+ return filter->lineage_start;
+ if (opts->scope != BPF_DIAG_HISTORY_SCOPE_REF)
+ return 0;
+
+ for (i = log->cnt; i > 0; i--) {
+ const struct bpf_diag_history_event *event;
+
+ event = &log->events[log_pos(log, i - 1)];
+ if (event->kind == BPF_DIAG_HISTORY_REF_ACQUIRE &&
+ event->ref.ref_id == opts->ref_id)
+ return i - 1;
+ }
+
+ return 0;
+}
+
+static bool diag_history_event_visible(const struct bpf_diag_history_event *event,
+ const struct bpf_diag_history_filter *filter)
+{
+ const struct bpf_diag_history_opts *opts = filter->opts;
+
+ switch (event->kind) {
+ case BPF_DIAG_HISTORY_BRANCH:
+ return true;
+ case BPF_DIAG_HISTORY_MOD:
+ return filter->lineage_valid && event->in_lineage;
+ case BPF_DIAG_HISTORY_REF_ACQUIRE:
+ case BPF_DIAG_HISTORY_REF_RELEASE:
+ return opts->scope == BPF_DIAG_HISTORY_SCOPE_REF &&
+ event->ref.ref_id == opts->ref_id;
+ case BPF_DIAG_HISTORY_CONTEXT:
+ return opts->scope == BPF_DIAG_HISTORY_SCOPE_CONTEXT &&
+ event->ctx.kind == opts->ctx_kind;
+ default:
+ return false;
+ }
+}
+
+static const char *diag_s64_bound_name(s64 value)
+{
+ if (value == S64_MIN)
+ return "S64_MIN";
+ if (value == S64_MAX)
+ return "S64_MAX";
+ return NULL;
+}
+
+static const char *diag_u64_bound_name(u64 value)
+{
+ if (value == U64_MAX)
+ return "U64_MAX";
+ return NULL;
+}
+
+static const char *diag_s64_str(struct bpf_verifier_env *env, s64 value)
+{
+ return diag_s64_bound_name(value) ?: bpf_diag_fmt(env, "%lld", value);
+}
+
+static const char *diag_u64_str(struct bpf_verifier_env *env, u64 value)
+{
+ return diag_u64_bound_name(value) ?: bpf_diag_fmt(env, "%llu", value);
+}
+
+static bool diag_cnum64_unknown(struct cnum64 range)
+{
+ return cnum64_smin(range) == S64_MIN && cnum64_smax(range) == S64_MAX &&
+ cnum64_umin(range) == 0 && cnum64_umax(range) == U64_MAX;
+}
+
+static bool diag_snapshot_unknown(const struct bpf_diag_reg_snapshot *snapshot)
+{
+ return tnum_is_unknown(snapshot->var_off) && diag_cnum64_unknown(snapshot->r64);
+}
+
+static const char *diag_scalar_range(struct bpf_verifier_env *env, struct cnum64 range)
+{
+ return bpf_diag_fmt(env, "signed range [%s, %s], unsigned range [%s, %s]",
+ diag_s64_str(env, cnum64_smin(range)),
+ diag_s64_str(env, cnum64_smax(range)),
+ diag_u64_str(env, cnum64_umin(range)),
+ diag_u64_str(env, cnum64_umax(range)));
+}
+
+const char *bpf_diag_fmt_s64_sum(struct bpf_verifier_env *env, s64 value, int addend)
+{
+ s64 sum;
+
+ if (check_add_overflow(value, (s64)addend, &sum))
+ return bpf_diag_fmt(env, "%lld plus %d (%s)", value, addend,
+ addend < 0 ? "below S64_MIN" : "above S64_MAX");
+
+ return bpf_diag_fmt(env, "%lld", sum);
+}
+
+static const char *diag_access_offset(struct bpf_verifier_env *env, int off,
+ const struct bpf_reg_state *reg)
+{
+ if (tnum_is_const(reg->var_off))
+ return bpf_diag_fmt(env, "constant %s",
+ bpf_diag_fmt_s64_sum(env, (s64)reg->var_off.value, off));
+
+ if (tnum_is_unknown(reg->var_off) && diag_cnum64_unknown(reg->r64))
+ return bpf_diag_fmt(env, "unbounded");
+
+ if (off)
+ return bpf_diag_fmt(env,
+ "variable: known bits %#llx, unknown mask %#llx, plus fixed offset %d; %s",
+ (u64)reg->var_off.value, reg->var_off.mask, off,
+ diag_scalar_range(env, reg->r64));
+ return bpf_diag_fmt(env, "variable: known bits %#llx, unknown mask %#llx; %s",
+ (u64)reg->var_off.value, reg->var_off.mask,
+ diag_scalar_range(env, reg->r64));
+}
+
+void bpf_diag_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const char *type_name, const char *proof,
+ int off, int size, u32 mem_size, const struct bpf_reg_state *reg)
+{
+ const struct bpf_func_state *frame = diag_current_frame(env);
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REG,
+ .frame_id = frame->diag_frame_id,
+ .frameno = frame->frameno,
+ .regno = regno,
+ };
+ const char *offset_desc;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ offset_desc = diag_access_offset(env, off, reg);
+
+ bpf_diag_header(env, MEMORY_SAFETY, "access outside bounds");
+ diag_reason(
+ env, "The verifier cannot prove offset + access_size <= object_size. Here, %s. %s is %s; offset is %s; access_size is %d; object_size is %u.",
+ proof, reg_name, type_name, offset_desc, size, mem_size);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "access may be outside object bounds");
+
+ if (regno >= 0)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(
+ env, "Add or adjust a bounds check that proves offset + access_size stays within the object.");
+}
+
+static const char *diag_lock_name(const struct bpf_reference_state *lock)
+{
+ switch (lock->type) {
+ case REF_TYPE_LOCK:
+ return "bpf_spin_lock";
+ case REF_TYPE_RES_LOCK:
+ return "resource spin lock";
+ case REF_TYPE_RES_LOCK_IRQ:
+ return "IRQ-saving resource spin lock";
+ default:
+ return "lock";
+ }
+}
+
+static void diag_res_report(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason)
+{
+ bpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+}
+
+void bpf_diag_res(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion)
+{
+ diag_res_report(env, insn_idx, problem, reason);
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_lock(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion,
+ const struct bpf_reference_state *active_lock)
+{
+ diag_res_report(env, insn_idx, problem, reason);
+
+ if (active_lock) {
+ diag_section(env, "Active lock");
+ bpf_diag_source(env, active_lock->insn_idx, "acquired",
+ "active %s has verifier identity %d",
+ diag_lock_name(active_lock), active_lock->id);
+ }
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_irq(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion, u32 depth)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_CONTEXT,
+ .ctx_kind = BPF_DIAG_CONTEXT_IRQ,
+ .ctx_depth = depth,
+ };
+
+ bpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, problem);
+ diag_reason(env, "%s", reason);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, insn_idx, "error", "%s", problem);
+
+ if (depth)
+ diag_print_history(env, &opts);
+
+ diag_suggestion(env, "%s", suggestion);
+}
+
+void bpf_diag_leak(struct bpf_verifier_env *env, u32 ref_id, u32 alloc_insn, u32 fail_insn)
+{
+ struct bpf_diag_history_opts opts = {
+ .scope = BPF_DIAG_HISTORY_SCOPE_REF,
+ .ref_id = ref_id,
+ };
+
+ bpf_diag_header(env, RESOURCE_LIFETIME_SAFETY, "unreleased resource");
+ diag_reason(
+ env, "Owned resource (id=%u) was acquired at instruction %u and still needs to be released before this exit path.",
+ ref_id, alloc_insn);
+
+ diag_section(env, "At");
+ bpf_diag_source(env, fail_insn, "error",
+ "owned resource (id=%u) still needs release", ref_id);
+
+ diag_print_history(env, &opts);
+
+ diag_suggestion(
+ env, "Release or transfer ownership of the acquired resource on every path before the program exits.");
+}
+
+static const char *diag_var_offset(struct bpf_verifier_env *env,
+ const struct bpf_diag_reg_snapshot *snapshot)
+{
+ if (tnum_is_const(snapshot->var_off))
+ return bpf_diag_fmt(env, "at offset %lld", (s64)snapshot->var_off.value);
+
+ if (diag_snapshot_unknown(snapshot))
+ return bpf_diag_fmt(env, "with unknown offset");
+
+ return bpf_diag_fmt(env,
+ "with variable offset: known bits %#llx, unknown mask %#llx, %s",
+ snapshot->var_off.value, snapshot->var_off.mask,
+ diag_scalar_range(env, snapshot->r64));
+}
+
+static const char *diag_reg_map_name(const struct bpf_map *map)
+{
+ if (!map || !map->name[0])
+ return NULL;
+
+ return map->name;
+}
+
+static const char *diag_reg_snapshot(struct bpf_verifier_env *env,
+ const struct bpf_diag_reg_snapshot *snapshot)
+{
+ const char *type_name = reg_type_str(env, snapshot->type);
+ const char *offset = diag_var_offset(env, snapshot);
+ const char *btf = snapshot->btf && snapshot->btf_id ?
+ bpf_diag_fmt_btf_type(env, snapshot->btf, snapshot->btf_id) : NULL;
+ const char *map_name;
+
+ if (snapshot->type == SCALAR_VALUE) {
+ if (tnum_is_const(snapshot->var_off))
+ return bpf_diag_fmt(env, "integer scalar value %lld",
+ (s64)snapshot->var_off.value);
+ if (diag_snapshot_unknown(snapshot))
+ return bpf_diag_fmt(env, "integer scalar with unknown value");
+ if (cnum64_is_const(snapshot->r64))
+ return bpf_diag_fmt(env, "integer scalar value %lld",
+ cnum64_smin(snapshot->r64));
+ return bpf_diag_fmt(env, "integer scalar with %s",
+ diag_scalar_range(env, snapshot->r64));
+ }
+
+ if (snapshot->type == NOT_INIT)
+ return bpf_diag_fmt(env, "uninitialized value");
+
+ if (base_type(snapshot->type) == PTR_TO_CTX)
+ return bpf_diag_fmt(env, "context pointer %s", offset);
+
+ if (base_type(snapshot->type) == PTR_TO_STACK)
+ return bpf_diag_fmt(env, "stack pointer %s", offset);
+
+ if (base_type(snapshot->type) == PTR_TO_MAP_VALUE) {
+ const char *kind = type_may_be_null(snapshot->type) ? "nullable map value" :
+ "map value";
+
+ map_name = diag_reg_map_name(snapshot->map_ptr);
+ if (map_name)
+ return bpf_diag_fmt(env, "%s from %s %s", kind, map_name, offset);
+ return bpf_diag_fmt(env, "%s %s", kind, offset);
+ }
+
+ if (base_type(snapshot->type) == CONST_PTR_TO_MAP) {
+ map_name = diag_reg_map_name(snapshot->map_ptr);
+ if (map_name)
+ return bpf_diag_fmt(env, "map pointer for map %s", map_name);
+ return bpf_diag_fmt(env, "map pointer");
+ }
+
+ if (type_is_non_owning_ref(snapshot->type)) {
+ if (btf)
+ return bpf_diag_fmt(env, "borrowed allocated object pointer type=%s", btf);
+ return bpf_diag_fmt(env, "borrowed allocated object pointer");
+ }
+
+ if (type_is_ptr_alloc_obj(snapshot->type)) {
+ if (btf)
+ return bpf_diag_fmt(env, "owned allocated object pointer type=%s", btf);
+ return bpf_diag_fmt(env, "owned allocated object pointer");
+ }
+
+ if (base_type(snapshot->type) == PTR_TO_BTF_ID && btf)
+ return bpf_diag_fmt(env, "%s type=%s %s", type_name, btf, offset);
+
+ return bpf_diag_fmt(env, "%s %s", type_name, offset);
+}
+
+static const char *diag_mod_target_desc(struct bpf_verifier_env *env,
+ const struct bpf_diag_mod_target *target)
+{
+ switch (target->kind) {
+ case BPF_DIAG_MOD_TARGET_REG:
+ return bpf_diag_fmt(env, "R%u", target->regno);
+ case BPF_DIAG_MOD_TARGET_STACK_ARG:
+ return bpf_diag_fmt(env, "stack arg%d", diag_stack_argno(target->stack_arg));
+ case BPF_DIAG_MOD_TARGET_STACK_SLOT:
+ return bpf_diag_fmt(env, "stack slot fp%d", -(target->spi + 1) * BPF_REG_SIZE);
+ default:
+ return "value";
+ }
+}
+
+static void diag_print_mod(struct bpf_verifier_env *env, const struct bpf_diag_history_event *event)
+{
+ const struct bpf_diag_mod_target *target = &event->mod.target;
+ const char *target_desc, *reason = NULL, *old, *new;
+ const char *label = "update";
+
+ if (target->kind == BPF_DIAG_MOD_TARGET_STACK_RANGE) {
+ bpf_diag_source(
+ env, event->insn_idx, "invalidated",
+ "variable-offset stack write may affect bytes fp%d through fp%d",
+ target->range.min_off, target->range.max_off - 1);
+ return;
+ }
+
+ old = diag_reg_snapshot(env, &event->mod.old);
+ new = diag_reg_snapshot(env, &event->mod.new);
+ target_desc = diag_mod_target_desc(env, target);
+
+ switch (event->mod.reason) {
+ case BPF_DIAG_MOD_REF_RELEASE:
+ reason = target->kind == BPF_DIAG_MOD_TARGET_REG ? "resource release invalidated "
+ "this pointer" :
+ "resource release invalidated "
+ "this value";
+ break;
+ case BPF_DIAG_MOD_PKT_DATA_CHANGE:
+ reason = "packet data may have moved";
+ break;
+ case BPF_DIAG_MOD_NON_OWN_REF:
+ reason = "leaving the protected region invalidated this borrowed pointer";
+ break;
+ case BPF_DIAG_MOD_CALLER_SAVED:
+ reason = target->kind == BPF_DIAG_MOD_TARGET_STACK_ARG ?
+ "call invalidated this outgoing stack argument" :
+ "call invalidated this caller-saved register";
+ break;
+ case BPF_DIAG_MOD_WRITE:
+ if (target->kind == BPF_DIAG_MOD_TARGET_STACK_SLOT)
+ reason = "a later stack write overwrote this spilled value";
+ break;
+ case BPF_DIAG_MOD_SPILL:
+ label = "spilled";
+ break;
+ case BPF_DIAG_MOD_VAR_WRITE:
+ default:
+ break;
+ }
+
+ if (reason) {
+ bpf_diag_source(env, event->insn_idx, "invalidated",
+ "%s: %s; previous value was %s", target_desc, reason, old);
+ return;
+ }
+
+ bpf_diag_source(env, event->insn_idx, label, "%s changed from %s to %s", target_desc,
+ old, new);
+}
+
+static void diag_print_ref_event(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ const char *label;
+
+ label = event->kind == BPF_DIAG_HISTORY_REF_ACQUIRE ? "acquired" : "released";
+ bpf_diag_source(env, event->insn_idx, label, "owned resource (id=%u)",
+ event->ref.ref_id);
+}
+
+static const char *diag_context_name(enum bpf_diag_context_kind kind)
+{
+ switch (kind) {
+ case BPF_DIAG_CONTEXT_RCU:
+ return "RCU read lock region";
+ case BPF_DIAG_CONTEXT_PREEMPT:
+ return "non-preemptible region";
+ case BPF_DIAG_CONTEXT_IRQ:
+ return "IRQ-disabled region";
+ case BPF_DIAG_CONTEXT_LOCK:
+ return "lock region";
+ case BPF_DIAG_CONTEXT_NONE:
+ default:
+ return "non-sleepable program";
+ }
+}
+
+static void diag_print_context_event(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_event *event)
+{
+ bpf_diag_source(env, event->insn_idx, "context", "%s %s; depth is now %u",
+ event->ctx.enter ? "entered" : "left",
+ diag_context_name(event->ctx.kind), event->ctx.depth);
+}
+
+static void diag_print_history(struct bpf_verifier_env *env,
+ const struct bpf_diag_history_opts *opts)
+{
+ const struct bpf_diag_history_event *event;
+ struct bpf_diag_history_filter filter = {
+ .opts = opts,
+ };
+ struct bpf_diag_log *log;
+ struct diag_fmt_mark mark;
+ bool first = true;
+ int start_idx;
+ u32 i, visible_cnt = 0, visible_idx = 0;
+
+ if (!bpf_diag_enabled(env))
+ return;
+
+ if (!env->diag)
+ return;
+ log = &env->diag->log;
+
+ diag_build_lineage(env, log, &filter);
+
+ start_idx = diag_history_start_idx(log, &filter);
+ for (i = start_idx; i < log->cnt; i++) {
+ event = &log->events[log_pos(log, i)];
+ if (diag_history_event_visible(event, &filter))
+ visible_cnt++;
+ }
+
+ if (!visible_cnt && !log->first_seq && opts->scope == BPF_DIAG_HISTORY_SCOPE_STACK_ARG)
+ return;
+
+ diag_section(env, "Causal path");
+ mark = diag_fmt_save(env);
+ for (i = start_idx; i < log->cnt; i++) {
+ event = &log->events[log_pos(log, i)];
+ if (!diag_history_event_visible(event, &filter))
+ continue;
+
+ diag_fmt_restore(env, mark);
+ if (visible_cnt > BPF_DIAG_HISTORY_RENDER_MAX &&
+ visible_idx >= BPF_DIAG_HISTORY_RENDER_MAX / 2 &&
+ visible_idx < visible_cnt - BPF_DIAG_HISTORY_RENDER_MAX / 2) {
+ if (visible_idx++ != BPF_DIAG_HISTORY_RENDER_MAX / 2)
+ continue;
+ if (!first)
+ diag_write(env, "\n");
+ first = false;
+ diag_write(env, " %u intermediate causal-history events omitted\n",
+ visible_cnt - BPF_DIAG_HISTORY_RENDER_MAX);
+ continue;
+ }
+ visible_idx++;
+
+ if (!first)
+ diag_write(env, "\n");
+ first = false;
+
+ switch (event->kind) {
+ case BPF_DIAG_HISTORY_BRANCH:
+ bpf_diag_source(env, event->insn_idx, "branch",
+ "took the %s branch of this conditional, goto %s",
+ event->branch.cond_true ? "true" : "false",
+ event->branch.cond_true ? "followed" : "not followed");
+ break;
+ case BPF_DIAG_HISTORY_MOD:
+ diag_print_mod(env, event);
+ break;
+ case BPF_DIAG_HISTORY_REF_ACQUIRE:
+ case BPF_DIAG_HISTORY_REF_RELEASE:
+ diag_print_ref_event(env, event);
+ break;
+ case BPF_DIAG_HISTORY_CONTEXT:
+ diag_print_context_event(env, event);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!visible_cnt)
+ diag_write(env, " no retained diagnostic events on this path\n");
+ if (log->first_seq)
+ diag_write(env, " %llu older causal-history event%s not retained because diagnostic "
+ "event storage reached capacity\n",
+ log->first_seq, log->first_seq == 1 ? "" : "s");
+ diag_fmt_restore(env, mark);
+}
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
new file mode 100644
index 0000000000000..d1b79945008a8
--- /dev/null
+++ b/kernel/bpf/diagnostics.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#ifndef __BPF_DIAGNOSTICS_H
+#define __BPF_DIAGNOSTICS_H
+
+#include <linux/bpf.h>
+#include <linux/compiler_attributes.h>
+#include <linux/stdarg.h>
+#include <linux/types.h>
+
+struct bpf_reference_state;
+struct bpf_func_state;
+struct bpf_reg_state;
+struct bpf_verifier_env;
+struct bpf_verifier_state;
+struct btf;
+
+const char *bpf_diag_fmt_s64_sum(struct bpf_verifier_env *env, s64 value, int addend);
+enum bpf_diag_mod_reason {
+ BPF_DIAG_MOD_WRITE,
+ BPF_DIAG_MOD_SPILL,
+ BPF_DIAG_MOD_VAR_WRITE,
+ BPF_DIAG_MOD_REF_RELEASE,
+ BPF_DIAG_MOD_PKT_DATA_CHANGE,
+ BPF_DIAG_MOD_NON_OWN_REF,
+ BPF_DIAG_MOD_CALLER_SAVED,
+};
+
+enum bpf_diag_context_kind {
+ BPF_DIAG_CONTEXT_NONE,
+ BPF_DIAG_CONTEXT_RCU,
+ BPF_DIAG_CONTEXT_PREEMPT,
+ BPF_DIAG_CONTEXT_IRQ,
+ BPF_DIAG_CONTEXT_LOCK,
+};
+
+enum bpf_diag_invalid_deref_kind {
+ BPF_DIAG_DEREF_SCALAR,
+ BPF_DIAG_DEREF_NULLABLE_PTR,
+ BPF_DIAG_DEREF_MODIFIED_PTR,
+ BPF_DIAG_DEREF_INVALID_PTR,
+};
+
+bool bpf_diag_enabled(const struct bpf_verifier_env *env);
+int bpf_diag_init(struct bpf_verifier_env *env);
+void bpf_diag_init_frame(struct bpf_verifier_env *env, struct bpf_func_state *state);
+char *bpf_diag_fmt_buf(struct bpf_verifier_env *env, size_t size);
+const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list args)
+ __printf(2, 0);
+const char *bpf_diag_fmt(struct bpf_verifier_env *env, const char *fmt, ...) __printf(2, 3);
+const char *bpf_diag_fmt_btf_type(struct bpf_verifier_env *env, const struct btf *btf, u32 type_id);
+const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_type type);
+u64 bpf_diag_event_log_save(struct bpf_verifier_env *env);
+void bpf_diag_event_log_restore(struct bpf_verifier_env *env, u64 log_pos);
+u32 bpf_diag_irq_depth(const struct bpf_verifier_state *state);
+void bpf_diag_free(struct bpf_verifier_env *env);
+void bpf_diag_register_type(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *problem, const char *reason, const char *suggestion);
+void bpf_diag_invalid_deref(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const struct bpf_reg_state *reg,
+ enum bpf_diag_invalid_deref_kind kind, s64 offset);
+void bpf_diag_unreadable_reg(struct bpf_verifier_env *env, u32 insn_idx, int regno);
+void bpf_diag_stack_arg_uninit(struct bpf_verifier_env *env, u32 insn_idx, int nargs,
+ int stack_arg_slot, const char *callee_name,
+ const char *arg_name);
+void bpf_diag_memory(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion);
+void bpf_diag_mem_bounds(struct bpf_verifier_env *env, u32 insn_idx, int regno,
+ const char *reg_name, const char *type_name, const char *proof,
+ int off, int size, u32 mem_size, const struct bpf_reg_state *reg);
+void bpf_diag_res(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion);
+void bpf_diag_lock(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion,
+ const struct bpf_reference_state *active_lock);
+void bpf_diag_irq(struct bpf_verifier_env *env, u32 insn_idx, const char *problem,
+ const char *reason, const char *suggestion, u32 depth);
+void bpf_diag_leak(struct bpf_verifier_env *env, u32 ref_id, u32 alloc_insn, u32 fail_insn);
+void bpf_diag_call_type(struct bpf_verifier_env *env, u32 insn_idx, int argno, int regno,
+ int stack_arg_slot, const char *call_name, const char *arg_name,
+ const char *reason, const char *suggestion);
+void bpf_diag_ctx_forbidden(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, const char *suggestion);
+void bpf_diag_ctx_active(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion);
+void bpf_diag_ctx_required(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ enum bpf_diag_context_kind ctx_kind, const char *suggestion);
+void bpf_diag_ctx_underflow(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *operation, enum bpf_diag_context_kind ctx_kind,
+ const char *suggestion);
+void bpf_diag_program_structure(struct bpf_verifier_env *env, u32 insn_idx,
+ const char *problem, const char *suggestion,
+ const char *reason_fmt, ...) __printf(5, 6);
+void bpf_diag_policy(struct bpf_verifier_env *env, u32 insn_idx, const char *operation,
+ const char *reason, const char *suggestion);
+void bpf_diag_record_branch(struct bpf_verifier_env *env, u32 insn_idx, bool cond_true);
+void bpf_diag_mod_begin(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ const struct bpf_reg_state *origin, enum bpf_diag_mod_reason reason);
+void bpf_diag_mod_end(struct bpf_verifier_env *env);
+void bpf_diag_record_scrub(struct bpf_verifier_env *env, const struct bpf_reg_state *reg,
+ enum bpf_diag_mod_reason reason);
+void bpf_diag_record_scrub_stack(struct bpf_verifier_env *env,
+ const struct bpf_func_state *state, s16 min_off, s16 max_off,
+ enum bpf_diag_mod_reason reason);
+void bpf_diag_record_ref_acquire(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);
+void bpf_diag_record_ref_release(struct bpf_verifier_env *env, u32 insn_idx, u32 ref_id);
+void bpf_diag_record_context(struct bpf_verifier_env *env, u32 insn_idx,
+ enum bpf_diag_context_kind ctx_kind, bool enter, u32 depth);
+
+#endif /* __BPF_DIAGNOSTICS_H */
diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
index b740fa73ee26d..589770ca3d3a0 100644
--- a/kernel/bpf/log.c
+++ b/kernel/bpf/log.c
@@ -615,17 +615,6 @@ static void print_scalar_ranges(struct bpf_verifier_env *env,
}
}
-static bool type_is_map_ptr(enum bpf_reg_type t) {
- switch (base_type(t)) {
- case CONST_PTR_TO_MAP:
- case PTR_TO_MAP_KEY:
- case PTR_TO_MAP_VALUE:
- return true;
- default:
- return false;
- }
-}
-
/*
* _a stands for append, was shortened to avoid multiline statements below.
* This macro is used to output a comma separated list of attributes.
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6ac1afced20bf..ff028a8c1cca1 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -34,6 +34,7 @@
#include <linux/trace_events.h>
#include <linux/kallsyms.h>
+#include "diagnostics.h"
#include "disasm.h"
static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
@@ -193,6 +194,7 @@ struct bpf_verifier_stack_elem {
struct bpf_verifier_stack_elem *next;
/* length of verifier log at the time this state was pushed on stack */
u32 log_pos;
+ u64 diag_log_pos;
};
#define BPF_COMPLEXITY_LIMIT_JMP_SEQ 8192
@@ -203,7 +205,8 @@ struct bpf_verifier_stack_elem {
#define BPF_PRIV_STACK_MIN_SIZE 64
static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int parent_id);
-static int release_reference_nomark(struct bpf_verifier_state *state, int id);
+static int __release_reference_nomark(struct bpf_verifier_state *state, int id);
+static int release_reference_nomark(struct bpf_verifier_env *env, int id);
static int release_reference(struct bpf_verifier_env *env, int id);
static void invalidate_non_owning_refs(struct bpf_verifier_env *env);
static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env);
@@ -405,7 +408,7 @@ static bool subprog_returns_void(struct bpf_verifier_env *env, int subprog)
return btf_type_is_void(type);
}
-static const char *subprog_name(const struct bpf_verifier_env *env, int subprog)
+const char *bpf_subprog_name(const struct bpf_verifier_env *env, int subprog)
{
struct bpf_func_info *info;
@@ -813,6 +816,10 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
if (dynptr_type_referenced(state->stack[spi].spilled_ptr.dynptr.type) &&
dynptr_ref_cnt(env, state->stack[spi].spilled_ptr.parent_id) <= 1) {
verbose(env, "cannot overwrite referenced dynptr\n");
+ bpf_diag_res(
+ env, env->insn_idx, "referenced dynptr overwrite",
+ "This stack slot contains a dynptr that owns or protects a referenced resource. Overwriting the last dynptr for that resource would lose the verifier-tracked release path.",
+ "Release or clone the dynptr so another live dynptr still tracks the referenced resource before overwriting this stack slot.");
return -EINVAL;
}
@@ -883,26 +890,29 @@ static bool is_dynptr_reg_valid_init(struct bpf_verifier_env *env, struct bpf_re
return true;
}
-static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
- enum bpf_arg_type arg_type)
+static enum bpf_dynptr_type dynptr_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
{
- struct bpf_func_state *state = bpf_func(env, reg);
- enum bpf_dynptr_type dynptr_type;
+ struct bpf_func_state *state;
int spi;
+ if (reg->type == CONST_PTR_TO_DYNPTR)
+ return reg->dynptr.type;
+
+ spi = dynptr_get_spi(env, reg);
+ if (spi < 0)
+ return BPF_DYNPTR_TYPE_INVALID;
+ state = bpf_func(env, reg);
+ return state->stack[spi].spilled_ptr.dynptr.type;
+}
+
+static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+ enum bpf_arg_type arg_type)
+{
/* ARG_PTR_TO_DYNPTR takes any type of dynptr */
if (arg_type == ARG_PTR_TO_DYNPTR)
return true;
- dynptr_type = arg_to_dynptr_type(arg_type);
- if (reg->type == CONST_PTR_TO_DYNPTR) {
- return reg->dynptr.type == dynptr_type;
- } else {
- spi = dynptr_get_spi(env, reg);
- if (spi < 0)
- return false;
- return state->stack[spi].spilled_ptr.dynptr.type == dynptr_type;
- }
+ return dynptr_reg_type(env, reg) == arg_to_dynptr_type(arg_type);
}
static void __mark_reg_known_zero(struct bpf_reg_state *reg);
@@ -1042,7 +1052,7 @@ static int is_iter_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_s
}
static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx);
-static int release_irq_state(struct bpf_verifier_state *state, int id);
+static int release_irq_state(struct bpf_verifier_env *env, int id);
static int mark_stack_slot_irq_flag(struct bpf_verifier_env *env,
struct bpf_call_arg_meta *meta,
@@ -1095,13 +1105,23 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
if (st->irq.kfunc_class != kfunc_class) {
const char *flag_kfunc = st->irq.kfunc_class == IRQ_NATIVE_KFUNC ? "native" : "lock";
const char *used_kfunc = kfunc_class == IRQ_NATIVE_KFUNC ? "native" : "lock";
+ const char *reason;
verbose(env, "irq flag acquired by %s kfuncs cannot be restored with %s kfuncs\n",
flag_kfunc, used_kfunc);
+ reason = bpf_diag_fmt(env,
+ "This IRQ flag was saved by %s IRQ kfuncs, but the restore call "
+ "belongs to the %s IRQ kfunc family. Save and restore operations "
+ "must use the same family.",
+ flag_kfunc, used_kfunc);
+ bpf_diag_irq(env, env->insn_idx, "IRQ flag restore mismatch", reason,
+ "Restore the flag with the matching IRQ restore kfunc for the save "
+ "operation that created it.",
+ bpf_diag_irq_depth(env->cur_state));
return -EINVAL;
}
- err = release_irq_state(env->cur_state, st->id);
+ err = release_irq_state(env, st->id);
WARN_ON_ONCE(err && err != -EACCES);
if (err) {
int insn_idx = 0;
@@ -1115,6 +1135,11 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r
verbose(env, "cannot restore irq state out of order, expected id=%d acquired at insn_idx=%d\n",
env->cur_state->active_irq_id, insn_idx);
+ bpf_diag_irq(env, env->insn_idx, "IRQ flag restore out of order",
+ "IRQ-disabled regions must be restored in last-in, first-out order, "
+ "but this restore does not match the currently active IRQ flag.",
+ "Restore nested IRQ flags in the reverse order they were saved.",
+ bpf_diag_irq_depth(env->cur_state));
return err;
}
@@ -1416,6 +1441,7 @@ static int acquire_reference(struct bpf_verifier_env *env, int insn_idx, int par
s->type = REF_TYPE_PTR;
s->id = ++env->id_gen;
s->parent_id = parent_id;
+ bpf_diag_record_ref_acquire(env, insn_idx, s->id);
return s->id;
}
@@ -1435,6 +1461,8 @@ static int acquire_lock_state(struct bpf_verifier_env *env, int insn_idx, enum r
state->active_locks++;
state->active_lock_id = id;
state->active_lock_ptr = ptr;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_LOCK, true,
+ state->active_locks);
return 0;
}
@@ -1450,6 +1478,8 @@ static int acquire_irq_state(struct bpf_verifier_env *env, int insn_idx)
s->id = ++env->id_gen;
state->active_irq_id = s->id;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_IRQ, true,
+ bpf_diag_irq_depth(state));
return s->id;
}
@@ -1491,8 +1521,9 @@ static bool reg_is_referenced(struct bpf_verifier_env *env, const struct bpf_reg
return find_reference_state(env->cur_state, reg->id);
}
-static int release_lock_state(struct bpf_verifier_state *state, int type, int id, void *ptr)
+static int release_lock_state(struct bpf_verifier_env *env, int type, int id, void *ptr)
{
+ struct bpf_verifier_state *state = env->cur_state;
void *prev_ptr = NULL;
u32 prev_id = 0;
int i;
@@ -1505,6 +1536,8 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id
/* Reassign active lock (id, ptr). */
state->active_lock_id = prev_id;
state->active_lock_ptr = prev_ptr;
+ bpf_diag_record_context(env, env->insn_idx, BPF_DIAG_CONTEXT_LOCK,
+ false, state->active_locks);
return 0;
}
if (state->refs[i].type & REF_TYPE_LOCK_MASK) {
@@ -1515,8 +1548,9 @@ static int release_lock_state(struct bpf_verifier_state *state, int type, int id
return -EINVAL;
}
-static int release_irq_state(struct bpf_verifier_state *state, int id)
+static int release_irq_state(struct bpf_verifier_env *env, int id)
{
+ struct bpf_verifier_state *state = env->cur_state;
u32 prev_id = 0;
int i;
@@ -1529,6 +1563,8 @@ static int release_irq_state(struct bpf_verifier_state *state, int id)
if (state->refs[i].id == id) {
release_reference_state(state, i);
state->active_irq_id = prev_id;
+ bpf_diag_record_context(env, env->insn_idx, BPF_DIAG_CONTEXT_IRQ,
+ false, bpf_diag_irq_depth(state));
return 0;
} else {
prev_id = state->refs[i].id;
@@ -1699,6 +1735,7 @@ static int pop_stack(struct bpf_verifier_env *env, int *prev_insn_idx,
err = bpf_copy_verifier_state(cur, &head->st);
if (err)
return err;
+ bpf_diag_event_log_restore(env, head->diag_log_pos);
}
if (pop_log)
bpf_vlog_reset(&env->log, head->log_pos);
@@ -1742,6 +1779,7 @@ static struct bpf_verifier_state *push_stack(struct bpf_verifier_env *env,
elem->prev_insn_idx = prev_insn_idx;
elem->next = env->head;
elem->log_pos = env->log.end_pos;
+ elem->diag_log_pos = bpf_diag_event_log_save(env);
env->head = elem;
env->stack_size++;
err = bpf_copy_verifier_state(&elem->st, cur);
@@ -1788,6 +1826,17 @@ static const int caller_saved[CALLER_SAVED_REGS] = {
BPF_REG_0, BPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5
};
+static void bpf_diag_record_caller_saved(struct bpf_verifier_env *env,
+ struct bpf_reg_state *regs)
+{
+ int i;
+
+ for (i = 1; i < CALLER_SAVED_REGS; i++) {
+ bpf_diag_record_scrub(env, ®s[caller_saved[i]],
+ BPF_DIAG_MOD_CALLER_SAVED);
+ }
+}
+
/* This helper doesn't clear reg->id */
static void ___mark_reg_known(struct bpf_reg_state *reg, u64 imm)
{
@@ -2241,6 +2290,7 @@ static void init_func_state(struct bpf_verifier_env *env,
{
state->callsite = callsite;
state->frameno = frameno;
+ bpf_diag_init_frame(env, state);
state->subprogno = subprogno;
state->callback_ret_range = retval_range(0, 0);
init_reg_state(env, state);
@@ -2263,6 +2313,7 @@ static struct bpf_verifier_state *push_async_cb(struct bpf_verifier_env *env,
elem->prev_insn_idx = prev_insn_idx;
elem->next = env->head;
elem->log_pos = env->log.end_pos;
+ elem->diag_log_pos = bpf_diag_event_log_save(env);
env->head = elem;
env->stack_size++;
if (env->stack_size > BPF_COMPLEXITY_LIMIT_JMP_SEQ) {
@@ -2624,6 +2675,26 @@ static struct btf *find_kfunc_desc_btf(struct bpf_verifier_env *env, s16 offset)
return btf_vmlinux ?: ERR_PTR(-ENOENT);
}
+static struct btf *find_kfunc_desc_btf_cached(struct bpf_verifier_env *env, s16 offset)
+{
+ struct bpf_kfunc_btf kf_btf = { .offset = offset };
+ struct bpf_kfunc_btf_tab *tab;
+ struct bpf_kfunc_btf *b;
+
+ if (!offset)
+ return btf_vmlinux ?: ERR_PTR(-ENOENT);
+ if (offset < 0)
+ return ERR_PTR(-EINVAL);
+
+ tab = env->prog->aux->kfunc_btf_tab;
+ if (!tab)
+ return ERR_PTR(-ENOENT);
+
+ b = bsearch(&kf_btf, tab->descs, tab->nr_descs,
+ sizeof(tab->descs[0]), kfunc_btf_cmp_by_off);
+ return b ? b->btf : ERR_PTR(-ENOENT);
+}
+
#define KF_IMPL_SUFFIX "_impl"
static const struct btf_type *find_kfunc_impl_proto(struct bpf_verifier_log *log,
@@ -2853,6 +2924,10 @@ static int add_subprogs(struct bpf_verifier_env *env)
if (!env->bpf_capable) {
verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+ bpf_diag_policy(
+ env, i, "BPF-to-BPF function call",
+ "loading or calling other BPF functions requires CAP_BPF or CAP_SYS_ADMIN",
+ "Load this program with the required capability, or avoid BPF-to-BPF function calls in unprivileged programs.");
return -EPERM;
}
@@ -2905,6 +2980,10 @@ static int add_kfuncs(struct bpf_verifier_env *env)
if (!env->bpf_capable) {
verbose(env, "loading/calling other bpf or kernel functions are allowed for CAP_BPF and CAP_SYS_ADMIN\n");
+ bpf_diag_policy(
+ env, i, "kernel function call",
+ "calling kernel functions requires CAP_BPF or CAP_SYS_ADMIN",
+ "Load this program with the required capability, or avoid kernel function calls in unprivileged programs.");
return -EPERM;
}
@@ -2949,6 +3028,12 @@ static int check_subprogs(struct bpf_verifier_env *env)
off = i + bpf_jmp_offset(&insn[i]) + 1;
if (off < subprog_start || off >= subprog_end) {
verbose(env, "jump out of range from insn %d to %d\n", i, off);
+ bpf_diag_program_structure(
+ env, i, "jump out of range",
+ "Keep branch targets within the same subprogram, or use an explicit subprogram call.",
+ "Instruction %d jumps to instruction %d, but subprogram %d only contains instructions %d through %d. "
+ "A branch target must stay inside the same subprogram.",
+ i, off, cur_subprog, subprog_start, subprog_end - 1);
return -EINVAL;
}
next:
@@ -2961,6 +3046,11 @@ static int check_subprogs(struct bpf_verifier_env *env)
code != (BPF_JMP32 | BPF_JA) &&
code != (BPF_JMP | BPF_JA)) {
verbose(env, "last insn is not an exit or jmp\n");
+ bpf_diag_program_structure(
+ env, i, "subprogram can fall through",
+ "End each subprogram with an exit or an explicit jump that keeps control flow inside the subprogram.",
+ "Subprogram %d reaches its last instruction %d without an exit or jump, so control could continue into the next subprogram.",
+ cur_subprog, i);
return -EINVAL;
}
subprog_start = subprog_end;
@@ -3031,8 +3121,13 @@ static int sort_subprogs_topo(struct bpf_verifier_env *env)
if (bpf_pseudo_func(&insn[idx]))
continue;
verbose(env, "recursive call from %s() to %s()\n",
- subprog_name(env, cur),
- subprog_name(env, callee));
+ bpf_subprog_name(env, cur),
+ bpf_subprog_name(env, callee));
+ bpf_diag_program_structure(
+ env, idx, "recursive subprogram call",
+ "Rewrite the recursion as an explicit bounded loop, or split the logic so subprogram calls do not form a cycle.",
+ "This bpf2bpf call would make the subprogram call graph recursive. "
+ "The verifier requires a finite, acyclic call graph so it can bound stack depth and analysis.");
ret = -EINVAL;
goto out;
}
@@ -3053,7 +3148,7 @@ static int sort_subprogs_topo(struct bpf_verifier_env *env)
if (env->log.level & BPF_LOG_LEVEL2)
for (i = 0; i < cnt; i++)
verbose(env, "topo_order[%d] = %s\n",
- i, subprog_name(env, env->subprog_topo_order[i]));
+ i, bpf_subprog_name(env, env->subprog_topo_order[i]));
out:
kvfree(dfs_stack);
kvfree(color);
@@ -3081,6 +3176,7 @@ static int __check_reg_arg(struct bpf_verifier_env *env, struct bpf_reg_state *r
/* check whether register used as source operand can be read */
if (reg->type == NOT_INIT) {
verbose(env, "R%d !read_ok\n", regno);
+ bpf_diag_unreadable_reg(env, env->insn_idx, regno);
return -EACCES;
}
/* We don't need to worry about FP liveness because it's read-only */
@@ -3197,7 +3293,7 @@ static void linked_regs_unpack(u64 val, struct linked_regs *s)
}
}
-static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
+const char *bpf_disasm_kfunc_name(void *data, const struct bpf_insn *insn)
{
const struct btf_type *func;
struct btf *desc_btf;
@@ -3205,18 +3301,20 @@ static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
if (insn->src_reg != BPF_PSEUDO_KFUNC_CALL)
return NULL;
- desc_btf = find_kfunc_desc_btf(data, insn->off);
+ desc_btf = find_kfunc_desc_btf_cached(data, insn->off);
if (IS_ERR(desc_btf))
return "<error>";
func = btf_type_by_id(desc_btf, insn->imm);
+ if (!func || !btf_type_is_func(func))
+ return "<error>";
return btf_name_by_offset(desc_btf, func->name_off);
}
void bpf_verbose_insn(struct bpf_verifier_env *env, struct bpf_insn *insn)
{
const struct bpf_insn_cbs cbs = {
- .cb_call = disasm_kfunc_name,
+ .cb_call = bpf_disasm_kfunc_name,
.cb_print = verbose,
.private_data = env,
};
@@ -3335,6 +3433,7 @@ static void save_register_state(struct bpf_verifier_env *env,
{
int i;
+ bpf_diag_mod_begin(env, &state->stack[spi].spilled_ptr, reg, BPF_DIAG_MOD_SPILL);
state->stack[spi].spilled_ptr = *reg;
for (i = BPF_REG_SIZE; i > BPF_REG_SIZE - size; i--)
@@ -3343,6 +3442,8 @@ static void save_register_state(struct bpf_verifier_env *env,
/* size < 8 bytes spill */
for (; i; i--)
mark_stack_slot_misc(env, &state->stack[spi].slot_type[i - 1]);
+
+ bpf_diag_mod_end(env);
}
static bool is_bpf_st_mem(struct bpf_insn *insn)
@@ -3415,7 +3516,16 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
bpf_is_spilled_reg(&state->stack[spi]) &&
!bpf_is_spilled_scalar_reg(&state->stack[spi]) &&
size != BPF_REG_SIZE) {
+ const char *reason;
+
verbose(env, "attempt to corrupt spilled pointer on stack\n");
+ reason = bpf_diag_fmt(env,
+ "This store writes %d bytes at stack offset %d into a stack slot that currently holds a spilled pointer. "
+ "Partial writes to spilled pointers are rejected because they can corrupt pointer metadata and leak kernel pointers.",
+ size, off);
+ bpf_diag_memory(
+ env, insn_idx, "stack spill corruption", reason,
+ "Write the full 8-byte spilled pointer slot, or use a separate stack slot for scalar data before overwriting only part of it.");
return -EACCES;
}
@@ -3479,6 +3589,9 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
} else {
u8 type = STACK_MISC;
+ if (bpf_is_spilled_reg(&state->stack[spi]))
+ bpf_diag_record_scrub(env, &state->stack[spi].spilled_ptr,
+ BPF_DIAG_MOD_WRITE);
scrub_special_slot(state, spi);
/* when we zero initialize stack slots mark them as such */
@@ -3639,6 +3752,8 @@ static int check_stack_write_var_off(struct bpf_verifier_env *env,
if (err)
return err;
}
+ bpf_diag_record_scrub_stack(env, state, min_off, max_off,
+ BPF_DIAG_MOD_VAR_WRITE);
return 0;
}
@@ -3702,6 +3817,21 @@ static int mark_reg_stack_read(struct bpf_verifier_env *env,
return 0;
}
+static void bpf_diag_stack_read_uninit(struct bpf_verifier_env *env, int off, int i,
+ int size)
+{
+ const char *reason;
+
+ reason = bpf_diag_fmt(env,
+ "This rejected read uses %d bytes at stack offset %d, but byte %d in that range is uninitialized on this path. "
+ "Programs loaded with CAP_PERFMON can be allowed to read uninitialized stack bytes, but this program is being rejected without that allowance.",
+ size, off, i);
+ bpf_diag_memory(
+ env, env->insn_idx, "uninitialized stack read", reason,
+ "Initialize every byte in the stack range before reading it, adjust the offset and size so the read covers only initialized bytes, "
+ "or load with CAP_PERFMON if uninitialized stack reads are intended.");
+}
+
/* Read the stack at 'off' and put the results into the register indicated by
* 'dst_regno'. It handles reg filling if the addressed stack slot is a
* spilled reg.
@@ -3731,6 +3861,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
mark_stack_slot_scratched(env, spi);
check_fastcall_stack_contract(env, state, env->insn_idx, off);
+ /*
+ * Refine the in-progress load record's origin to the source stack slot.
+ */
+ if (dst_regno >= 0)
+ bpf_diag_mod_begin(env, &state->regs[dst_regno], reg, BPF_DIAG_MOD_WRITE);
+
if (bpf_is_spilled_reg(®_state->stack[spi])) {
u8 spill_size = 1;
@@ -3785,6 +3921,8 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
} else {
verbose(env, "invalid read from stack off %d+%d size %d\n",
off, i, size);
+ bpf_diag_stack_read_uninit(env, off, i,
+ size);
}
return -EACCES;
}
@@ -3843,6 +3981,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env,
} else {
verbose(env, "invalid read from stack off %d+%d size %d\n",
off, i, size);
+ bpf_diag_stack_read_uninit(env, off, i, size);
}
return -EACCES;
}
@@ -3935,11 +4074,19 @@ static int check_stack_read(struct bpf_verifier_env *env,
* check_stack_read_fixed_off).
*/
if (dst_regno < 0 && var_off) {
+ const char *reason;
char tn_buf[48];
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
verbose(env, "variable offset stack pointer cannot be passed into helper function; var_off=%s off=%d size=%d\n",
tn_buf, off, size);
+ reason = bpf_diag_fmt(env,
+ "The helper would access the stack through variable offset %s plus fixed offset %d and size %d. "
+ "Helper stack memory arguments require a constant stack offset and a precise initialized range.",
+ tn_buf, off, size);
+ bpf_diag_memory(
+ env, env->insn_idx, "variable stack access", reason,
+ "Use a fixed stack offset for helper memory arguments, or copy the needed bytes into a fixed stack slot first.");
return -EACCES;
}
/* Variable offset is prohibited for unprivileged mode for simplicity
@@ -4024,14 +4171,17 @@ static int check_stack_arg_write(struct bpf_verifier_env *env, struct bpf_func_s
if (spi + 1 > subprog->max_out_stack_arg_cnt)
subprog->max_out_stack_arg_cnt = spi + 1;
+ arg = &state->stack_arg_regs[spi];
+ bpf_diag_mod_begin(env, arg, value_reg, BPF_DIAG_MOD_WRITE);
+
if (value_reg) {
state->stack_arg_regs[spi] = *value_reg;
} else {
/* BPF_ST: store immediate, treat as scalar */
- arg = &state->stack_arg_regs[spi];
arg->type = SCALAR_VALUE;
__mark_reg_known(arg, env->prog->insnsi[env->insn_idx].imm);
}
+ bpf_diag_mod_end(env);
state->no_stack_arg_load = true;
return bpf_push_jmp_history(env, env->cur_state,
INSN_F_STACK_ARG_ACCESS, spi, 0, 0);
@@ -4064,7 +4214,9 @@ static int check_stack_arg_read(struct bpf_verifier_env *env, struct bpf_func_st
caller = vstate->frame[vstate->curframe - 1];
arg = &caller->stack_arg_regs[spi];
cur = vstate->frame[vstate->curframe];
+ bpf_diag_mod_begin(env, &cur->regs[dst_regno], arg, BPF_DIAG_MOD_WRITE);
cur->regs[dst_regno] = *arg;
+ bpf_diag_mod_end(env);
return bpf_push_jmp_history(env, env->cur_state,
INSN_F_STACK_ARG_ACCESS, spi, 0, 0);
}
@@ -4079,7 +4231,8 @@ static int mark_stack_arg_precision(struct bpf_verifier_env *env, int arg_idx)
}
static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_func_state *caller,
- int nargs)
+ int nargs, const char *callee_name, const struct btf *btf,
+ const struct btf_param *args)
{
int i, spi;
@@ -4087,8 +4240,14 @@ static int check_outgoing_stack_args(struct bpf_verifier_env *env, struct bpf_fu
spi = i - MAX_BPF_FUNC_REG_ARGS;
if (spi >= caller->out_stack_arg_cnt ||
caller->stack_arg_regs[spi].type == NOT_INIT) {
+ const char *arg_name = NULL;
+
+ if (args && args[i].name_off)
+ arg_name = btf_name_by_offset(btf, args[i].name_off);
verbose(env, "callee expects %d args, stack arg%d is not initialized\n",
nargs, spi + 1);
+ bpf_diag_stack_arg_uninit(env, env->insn_idx, nargs, spi,
+ callee_name, arg_name);
return -EFAULT;
}
}
@@ -4169,6 +4328,9 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
int off, int size, u32 mem_size,
bool zero_size_allowed)
{
+ const char *proof = "";
+ const char *start;
+ s64 max_start, max_end;
int err;
/* We may have adjusted the register pointing to memory region, so we
@@ -4187,14 +4349,28 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
reg_smin(reg) + off < 0)) {
verbose(env, "%s min value is negative, either use unsigned index or do a if (index >=0) check.\n",
reg_arg_name(env, argno));
- return -EACCES;
+ err = -EACCES;
+ if (bpf_diag_enabled(env)) {
+ start = bpf_diag_fmt_s64_sum(env, reg_smin(reg), off);
+ proof = bpf_diag_fmt(
+ env, "the minimal bound for a memory access is a negative value: %s",
+ start);
+ }
+ goto report_error;
}
+
err = __check_mem_access(env, reg, argno, reg_smin(reg) + off, size,
mem_size, zero_size_allowed);
if (err) {
verbose(env, "%s min value is outside of the allowed memory range\n",
reg_arg_name(env, argno));
- return err;
+ if (bpf_diag_enabled(env)) {
+ start = bpf_diag_fmt_s64_sum(env, reg_smin(reg), off);
+ proof = bpf_diag_fmt(
+ env, "the minimal bound for a memory access is %s and is outside of the object of size %u",
+ start, mem_size);
+ }
+ goto report_error;
}
/* If we haven't set a max value then we need to bail since we can't be
@@ -4204,17 +4380,36 @@ static int check_mem_region_access(struct bpf_verifier_env *env, struct bpf_reg_
if (reg_umax(reg) >= BPF_MAX_VAR_OFF) {
verbose(env, "%s unbounded memory access, make sure to bounds check any such access\n",
reg_arg_name(env, argno));
- return -EACCES;
+ err = -EACCES;
+ if (bpf_diag_enabled(env))
+ proof = bpf_diag_fmt(
+ env, "the maximal bound for a memory access is %llu and exceeds maximum allowed offset of %u",
+ reg_umax(reg), BPF_MAX_VAR_OFF);
+ goto report_error;
}
+
err = __check_mem_access(env, reg, argno, reg_umax(reg) + off, size,
mem_size, zero_size_allowed);
if (err) {
verbose(env, "%s max value is outside of the allowed memory range\n",
reg_arg_name(env, argno));
- return err;
+ if (bpf_diag_enabled(env)) {
+ max_start = (s64)reg_umax(reg) + off;
+ max_end = max_start + size;
+ proof = bpf_diag_fmt(
+ env, "the maximal bound for a memory access is %lld: start %lld + access_size %d, beyond object_size %u",
+ max_end, max_start, size, mem_size);
+ }
+ goto report_error;
}
return 0;
+
+report_error:
+ bpf_diag_mem_bounds(env, env->insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg_type_str(env, reg->type), proof,
+ off, size, mem_size, reg);
+ return err;
}
static int __check_ptr_off_reg(struct bpf_verifier_env *env,
@@ -4243,6 +4438,9 @@ static int __check_ptr_off_reg(struct bpf_verifier_env *env,
if (!fixed_off_ok && reg->var_off.value != 0) {
verbose(env, "dereference of modified %s ptr %s off=%lld disallowed\n",
reg_type_str(env, reg->type), reg_arg_name(env, argno), reg->var_off.value);
+ bpf_diag_invalid_deref(env, env->insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg,
+ BPF_DIAG_DEREF_MODIFIED_PTR, reg->var_off.value);
return -EACCES;
}
@@ -6188,6 +6386,9 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (type_may_be_null(reg->type)) {
verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
+ bpf_diag_invalid_deref(env, insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg,
+ BPF_DIAG_DEREF_NULLABLE_PTR, 0);
return -EACCES;
}
@@ -6338,8 +6539,16 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
} else {
+ enum bpf_diag_invalid_deref_kind kind = BPF_DIAG_DEREF_INVALID_PTR;
+
verbose(env, "%s invalid mem access '%s'\n", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
+ if (reg->type == SCALAR_VALUE)
+ kind = BPF_DIAG_DEREF_SCALAR;
+ else if (type_may_be_null(reg->type))
+ kind = BPF_DIAG_DEREF_NULLABLE_PTR;
+ bpf_diag_invalid_deref(env, insn_idx, reg_from_argno(argno),
+ reg_arg_name(env, argno), reg, kind, 0);
return -EACCES;
}
@@ -6399,15 +6608,19 @@ static int check_load_mem(struct bpf_verifier_env *env, struct bpf_insn *insn,
src_reg_type = regs[insn->src_reg].type;
- /* Check if (src_reg + off) is readable. The state of dst_reg will be
- * updated by this call.
+ /*
+ * check_stack_read_fixed_off() may refine the modification's origin to
+ * the source stack slot.
*/
+ bpf_diag_mod_begin(env, ®s[insn->dst_reg], NULL, BPF_DIAG_MOD_WRITE);
err = check_mem_access(env, env->insn_idx, regs + insn->src_reg, argno_from_reg(insn->src_reg), insn->off,
BPF_SIZE(insn->code), BPF_READ, insn->dst_reg,
strict_alignment_once, is_ldsx);
err = err ?: save_aux_ptr_type(env, src_reg_type,
allow_trust_mismatch);
err = err ?: reg_bounds_sanity_check(env, ®s[insn->dst_reg], ctx);
+ if (!err)
+ bpf_diag_mod_end(env);
return err;
}
@@ -6513,10 +6726,14 @@ static int check_atomic_rmw(struct bpf_verifier_env *env,
*/
err = check_mem_access(env, env->insn_idx, dst_reg, argno_from_reg(insn->dst_reg), insn->off,
BPF_SIZE(insn->code), BPF_READ, -1, true, false);
- if (!err && load_reg >= 0)
+ if (!err && load_reg >= 0) {
+ bpf_diag_mod_begin(env, cur_regs(env) + load_reg, NULL, BPF_DIAG_MOD_WRITE);
err = check_mem_access(env, env->insn_idx, dst_reg, argno_from_reg(insn->dst_reg),
insn->off, BPF_SIZE(insn->code),
BPF_READ, load_reg, true, false);
+ if (!err)
+ bpf_diag_mod_end(env);
+ }
if (err)
return err;
@@ -6734,14 +6951,17 @@ static int check_stack_range_initialized(
return 0;
}
-static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
- int access_size, enum bpf_access_type access_type,
- bool zero_size_allowed,
- struct bpf_call_arg_meta *meta)
+static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+ argno_t argno, int access_size,
+ enum bpf_access_type access_type, bool zero_size_allowed,
+ struct bpf_call_arg_meta *meta, bool *known_memory)
{
struct bpf_reg_state *regs = cur_regs(env);
u32 *max_access;
+ if (known_memory)
+ *known_memory = true;
+
switch (base_type(reg->type)) {
case PTR_TO_PACKET:
case PTR_TO_PACKET_META:
@@ -6811,6 +7031,8 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_
if (zero_size_allowed && access_size == 0 &&
bpf_register_is_null(reg))
return 0;
+ if (known_memory && base_type(reg->type) != PTR_TO_CTX)
+ *known_memory = false;
verbose(env, "%s type=%s ", reg_arg_name(env, argno),
reg_type_str(env, reg->type));
@@ -6819,6 +7041,12 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_
}
}
+enum bpf_mem_size_failure {
+ BPF_MEM_SIZE_FAIL_NONE,
+ BPF_MEM_SIZE_FAIL_MEMORY,
+ BPF_MEM_SIZE_FAIL_SIZE,
+};
+
/* verify arguments to helpers or kfuncs consisting of a pointer and an access
* size.
*
@@ -6829,10 +7057,14 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
struct bpf_reg_state *size_reg, argno_t mem_argno,
argno_t size_argno, u32 access_type,
bool zero_size_allowed,
- struct bpf_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta,
+ enum bpf_mem_size_failure *failure)
{
int err = 0;
+ if (failure)
+ *failure = BPF_MEM_SIZE_FAIL_NONE;
+
/* This is used to refine r0 return value bounds for helpers
* that enforce this value as an upper bound on return values.
* See do_refine_retval_range() for helpers that can refine
@@ -6854,27 +7086,32 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
if (reg_smin(size_reg) < 0) {
verbose(env, "%s min value is negative, either use unsigned or 'var &= const'\n",
reg_arg_name(env, size_argno));
- return -EACCES;
+ err = -EACCES;
+ goto size_error;
}
if (reg_umin(size_reg) == 0 && !zero_size_allowed) {
verbose(env, "%s invalid zero-sized read: u64=[%lld,%lld]\n",
reg_arg_name(env, size_argno), reg_umin(size_reg), reg_umax(size_reg));
- return -EACCES;
+ err = -EACCES;
+ goto size_error;
}
if (reg_umax(size_reg) >= BPF_MAX_VAR_SIZ) {
verbose(env, "%s unbounded memory access, use 'var &= const' or 'if (var < const)'\n",
reg_arg_name(env, size_argno));
- return -EACCES;
+ err = -EACCES;
+ goto size_error;
}
if (access_type & BPF_READ)
err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
- BPF_READ, zero_size_allowed, meta);
+ BPF_READ, zero_size_allowed, meta, NULL);
if (!err && access_type & BPF_WRITE)
err = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),
- BPF_WRITE, zero_size_allowed, meta);
+ BPF_WRITE, zero_size_allowed, meta, NULL);
+ if (err && failure)
+ *failure = BPF_MEM_SIZE_FAIL_MEMORY;
if (!err) {
int regno = reg_from_argno(size_argno);
@@ -6886,16 +7123,23 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
}
return err;
+
+size_error:
+ if (failure)
+ *failure = BPF_MEM_SIZE_FAIL_SIZE;
+ return err;
}
static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
argno_t argno, u32 mem_size, enum bpf_access_type access_type,
- struct bpf_call_arg_meta *meta)
+ struct bpf_call_arg_meta *meta, bool *known_memory)
{
int size, err = 0;
if (bpf_register_is_null(reg))
return 0;
+ if (known_memory)
+ *known_memory = true;
if (mem_size > S32_MAX) {
verbose(env, "%s memory size %u is too large\n",
@@ -6910,9 +7154,11 @@ static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg
size = (!meta && base_type(reg->type) == PTR_TO_STACK) ? -(int)mem_size : mem_size;
if (access_type & BPF_READ)
- err = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta);
+ err = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta,
+ known_memory);
if (!err && (access_type & BPF_WRITE))
- err = check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, meta);
+ err = check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, meta,
+ known_memory);
return err;
}
@@ -7013,6 +7259,7 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
bool is_lock = flags & PROCESS_SPIN_LOCK, is_res_lock = flags & PROCESS_RES_LOCK;
const char *lock_str = is_res_lock ? "bpf_res_spin" : "bpf_spin";
struct bpf_verifier_state *cur = env->cur_state;
+ struct bpf_reference_state *lock;
bool is_const = tnum_is_const(reg->var_off);
bool is_irq = flags & PROCESS_LOCK_IRQ;
u64 val = reg->var_off.value;
@@ -7062,14 +7309,25 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
ptr = btf;
if (!is_res_lock && cur->active_locks) {
- if (find_lock_state(env->cur_state, REF_TYPE_LOCK, 0, NULL)) {
+ lock = find_lock_state(cur, REF_TYPE_LOCK, 0, NULL);
+ if (lock) {
verbose(env,
"Locking two bpf_spin_locks are not allowed\n");
+ bpf_diag_lock(
+ env, env->insn_idx, "nested spin lock",
+ "This path already holds a bpf_spin_lock. The verifier allows only one regular BPF spin lock at a time.",
+ "Unlock the current bpf_spin_lock before taking another one.", lock);
return -EINVAL;
}
} else if (is_res_lock && cur->active_locks) {
- if (find_lock_state(env->cur_state, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ, reg->id, ptr)) {
+ lock = find_lock_state(cur, REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ,
+ reg->id, ptr);
+ if (lock) {
verbose(env, "Acquiring the same lock again, AA deadlock detected\n");
+ bpf_diag_lock(
+ env, env->insn_idx, "recursive resource spin lock",
+ "This path already holds the same resource spin lock. Taking it again would deadlock.",
+ "Avoid reacquiring the same resource spin lock before it is unlocked.", lock);
return -EINVAL;
}
}
@@ -7096,6 +7354,10 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
if (!cur->active_locks) {
verbose(env, "%s_unlock without taking a lock\n", lock_str);
+ bpf_diag_res(
+ env, env->insn_idx, "unlock without lock",
+ "This unlock operation has no matching active lock on the current path.",
+ "Take the matching lock before this unlock, or remove the unmatched unlock path.");
return -EINVAL;
}
@@ -7105,16 +7367,35 @@ static int process_spin_lock(struct bpf_verifier_env *env, struct bpf_reg_state
type = REF_TYPE_RES_LOCK;
else
type = REF_TYPE_LOCK;
- if (!find_lock_state(cur, type, reg->id, ptr)) {
+
+ lock = find_lock_state(cur, type, reg->id, ptr);
+ if (!lock) {
verbose(env, "%s_unlock of different lock\n", lock_str);
+ lock = find_lock_state(cur, REF_TYPE_LOCK_MASK, cur->active_lock_id,
+ cur->active_lock_ptr);
+ bpf_diag_lock(
+ env, env->insn_idx, "unlock of a different lock",
+ "This unlock does not match any active lock with the same tracked identity on the current path.",
+ "Unlock the same lock object that was most recently acquired.", lock);
return -EINVAL;
}
if (reg->id != cur->active_lock_id || ptr != cur->active_lock_ptr) {
verbose(env, "%s_unlock cannot be out of order\n", lock_str);
+ lock = find_lock_state(cur, REF_TYPE_LOCK_MASK, cur->active_lock_id,
+ cur->active_lock_ptr);
+ bpf_diag_lock(
+ env, env->insn_idx, "unlock out of order",
+ "Locks must be released in last-in, first-out order, but this unlock does not match the currently active lock.",
+ "Release nested locks in the reverse order they were acquired.", lock);
return -EINVAL;
}
- if (release_lock_state(cur, type, reg->id, ptr)) {
+ if (release_lock_state(env, type, reg->id, ptr)) {
verbose(env, "%s_unlock of different lock\n", lock_str);
+ bpf_diag_lock(
+ env, env->insn_idx, "unlock of a different lock",
+ "The verifier could not release a lock state matching this unlock operation.",
+ "Pass the same lock object and lock kind that were used for the matching lock operation.",
+ lock);
return -EINVAL;
}
if (!in_rcu_cs(env))
@@ -7237,6 +7518,12 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,
return 0;
}
+static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
+ const char *call_name, const char *reason, const char *suggestion);
+__printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u32 insn_idx,
+ argno_t argno, const char *call_name,
+ const char *suggestion, const char *fmt, ...);
+
/*
* Validate dynptr arguments for helper, kfunc and subprog.
*
@@ -7261,7 +7548,8 @@ static int process_kptr_func(struct bpf_verifier_env *env, int regno,
* and checked dynamically during runtime.
*/
static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
- argno_t argno, int insn_idx, enum bpf_arg_type arg_type,
+ argno_t argno, int insn_idx, const char *call_name,
+ enum bpf_arg_type arg_type,
struct ref_obj_desc *ref_obj, struct bpf_dynptr_desc *dynptr)
{
int spi, err = 0;
@@ -7270,6 +7558,11 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
verbose(env,
"%s expected pointer to stack or const struct bpf_dynptr\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, call_name,
+ "Pass the address of a stack dynptr object, or use a const dynptr pointer returned by the verifier-supported path.",
+ "a dynptr argument must be a pointer to a dynptr stack slot or a verifier-provided const struct bpf_dynptr, but %s is %s",
+ reg_arg_name(env, argno), bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -7292,6 +7585,10 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
if (!is_dynptr_reg_valid_uninit(env, reg)) {
verbose(env, "Dynptr has to be an uninitialized dynptr\n");
+ bpf_diag_res(
+ env, insn_idx, "dynptr is already initialized",
+ "This kfunc constructs a dynptr and requires an uninitialized dynptr stack slot, but the selected slot already holds dynptr state.",
+ "Use a fresh stack dynptr slot, or release/destroy the existing dynptr before reusing the slot.");
return -EINVAL;
}
@@ -7308,21 +7605,35 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
/* For the reg->type == PTR_TO_STACK case, bpf_dynptr is never const */
if (reg->type == CONST_PTR_TO_DYNPTR && (arg_type & OBJ_RELEASE)) {
verbose(env, "CONST_PTR_TO_DYNPTR cannot be released\n");
+ bpf_diag_res(
+ env, insn_idx, "const dynptr release",
+ "This release operation was given a const dynptr. Const dynptr values are verifier-provided views and cannot be released by the program.",
+ "Release only mutable dynptrs that the program initialized or reserved.");
return -EINVAL;
}
if (!is_dynptr_reg_valid_init(env, reg)) {
verbose(env, "Expected an initialized dynptr as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_res(
+ env, insn_idx, "uninitialized dynptr use",
+ "This operation requires an initialized dynptr, but the stack slot does not currently hold a valid dynptr on this path.",
+ "Initialize the dynptr on every path before this call, and avoid overwriting or releasing it before this use.");
return -EINVAL;
}
/* Fold modifiers (in this case, OBJ_RELEASE) when checking expected type */
if (!is_dynptr_type_expected(env, reg, arg_type & ~OBJ_RELEASE)) {
- verbose(env,
- "Expected a dynptr of type %s as %s\n",
- dynptr_type_str(arg_to_dynptr_type(arg_type)),
- reg_arg_name(env, argno));
+ enum bpf_dynptr_type expected_type = arg_to_dynptr_type(arg_type);
+ enum bpf_dynptr_type actual_type = dynptr_reg_type(env, reg);
+
+ verbose(env, "Expected a dynptr of type %s as %s\n",
+ dynptr_type_str(expected_type), reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, call_name,
+ "Use a dynptr constructor that matches this operation, or call an operation that accepts the dynptr's current type.",
+ "the dynptr is initialized with backing object type %s, but this operation expects dynptr type %s",
+ dynptr_type_str(actual_type), dynptr_type_str(expected_type));
return -EINVAL;
}
@@ -7386,6 +7697,11 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
if (reg->type != PTR_TO_STACK) {
verbose(env, "%s expected pointer to an iterator on stack\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, meta->func_name,
+ "Pass the address of a stack iterator object for iterator new, next, and destroy calls.",
+ "iterator state must live in verifier-tracked stack memory, but %s is %s",
+ reg_arg_name(env, argno), bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -7399,6 +7715,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
if (btf_id < 0) {
verbose(env, "expected valid iter pointer as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg(
+ env, insn_idx, argno, meta->func_name,
+ "the kfunc expects a recognized iterator state pointer, but this argument does not match a valid iterator type",
+ "Pass the exact iterator state type expected by this kfunc.");
return -EINVAL;
}
t = btf_type_by_id(meta->btf, btf_id);
@@ -7409,6 +7729,10 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
if (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {
verbose(env, "expected uninitialized iter_%s as %s\n",
iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
+ bpf_diag_res(
+ env, insn_idx, "iterator is already initialized",
+ "Iterator creation requires an uninitialized iterator stack object, but this stack range already contains iterator state.",
+ "Use a fresh iterator stack slot, or destroy the existing iterator before reusing the slot.");
return -EINVAL;
}
@@ -7433,9 +7757,16 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
case -EINVAL:
verbose(env, "expected an initialized iter_%s as %s\n",
iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
+ bpf_diag_res(
+ env, insn_idx, "uninitialized iterator use",
+ "This iterator operation requires an initialized iterator state object, but the stack range does not contain a live iterator on this path.",
+ "Call the matching iterator new kfunc on every path before calling next or destroy, and do not destroy the iterator before this use.");
return err;
case -EPROTO:
verbose(env, "expected an RCU CS when using %s\n", meta->func_name);
+ bpf_diag_ctx_required(
+ env, insn_idx, meta->func_name, BPF_DIAG_CONTEXT_RCU,
+ "Wrap iterator use in bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.");
return err;
default:
return err;
@@ -7855,13 +8186,70 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
[ARG_PTR_TO_DYNPTR] = &dynptr_types,
};
+static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
+ const char *call_name, const char *reason,
+ const char *suggestion)
+{
+ int arg = arg_from_argno(argno);
+ int regno = reg_from_argno(argno);
+ int stack_slot = -1;
+
+ if (arg < 0 && regno >= BPF_REG_1 && regno <= BPF_REG_5)
+ arg = regno;
+ if (arg > MAX_BPF_FUNC_REG_ARGS)
+ stack_slot = arg - MAX_BPF_FUNC_REG_ARGS - 1;
+
+ bpf_diag_call_type(env, insn_idx, arg, regno, stack_slot,
+ call_name && *call_name ? call_name : "call",
+ reg_arg_name(env, argno), reason, suggestion);
+}
+
+static const char *bpf_diag_arg_name(struct bpf_verifier_env *env, argno_t argno)
+{
+ return bpf_diag_fmt(env, "%s", reg_arg_name(env, argno));
+}
+
+__printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u32 insn_idx,
+ argno_t argno, const char *call_name,
+ const char *suggestion, const char *fmt, ...)
+{
+ const char *reason;
+ va_list args;
+
+ va_start(args, fmt);
+ reason = bpf_diag_vfmt(env, fmt, args);
+ va_end(args);
+
+ bpf_diag_call_arg(env, insn_idx, argno, call_name, reason, suggestion);
+}
+
+static const char *bpf_diag_expected_reg_types(struct bpf_verifier_env *env,
+ const enum bpf_reg_type *types, int count)
+{
+ size_t len = 0, size = 1;
+ char *buf;
+ int i;
+
+ for (i = 0; i < count; i++)
+ size += strlen(reg_type_str(env, types[i])) + (i ? 2 : 0);
+
+ buf = bpf_diag_fmt_buf(env, size);
+ if (!buf)
+ return "";
+
+ for (i = 0; i < count; i++)
+ len += scnprintf(buf + len, size - len, "%s%s", i ? ", " : "",
+ reg_type_str(env, types[i]));
+ return buf;
+}
+
static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,
- enum bpf_arg_type arg_type,
- const u32 *arg_btf_id,
- struct bpf_call_arg_meta *meta)
+ enum bpf_arg_type arg_type, const u32 *arg_btf_id,
+ struct bpf_call_arg_meta *meta, const char *call_name)
{
enum bpf_reg_type expected, type = reg->type;
const struct bpf_reg_types *compatible;
+ const char *actual, *accepted;
int i, j, err;
compatible = compatible_reg_types[base_type(arg_type)];
@@ -7908,6 +8296,12 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
for (j = 0; j + 1 < i; j++)
verbose(env, "%s, ", reg_type_str(env, compatible->types[j]));
verbose(env, "%s\n", reg_type_str(env, compatible->types[j]));
+ actual = bpf_diag_fmt(env, "%s", reg_type_str(env, reg->type));
+ accepted = bpf_diag_expected_reg_types(env, compatible->types, i);
+ bpf_diag_call_arg_fmt(env, env->insn_idx, argno, call_name,
+ "Pass a value with one of the accepted pointer or scalar types for this call.",
+ "it has type %s, but this argument accepts %s",
+ actual, accepted);
return -EACCES;
found:
@@ -7944,6 +8338,10 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
(!type_may_be_null(arg_type) || arg_type_is_release(arg_type))) {
verbose(env, "Possibly NULL pointer passed to helper %s\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg(
+ env, env->insn_idx, argno, call_name,
+ "the pointer may be NULL, but this call requires a non-NULL pointer",
+ "Add a NULL check and make the call only on the non-NULL path.");
return -EACCES;
}
@@ -8330,7 +8728,8 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK)
arg_btf_id = fn->arg_btf_id[arg];
- err = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta);
+ err = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta,
+ func_id_name(meta->func_id));
if (err)
return err;
@@ -8343,6 +8742,10 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
!reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
verbose(env, "release helper %s expects referenced PTR_TO_BTF_ID passed to %s\n",
func_id_name(meta->func_id), reg_arg_name(env, argno));
+ bpf_diag_call_arg(
+ env, insn_idx, argno, func_id_name(meta->func_id),
+ "release helpers require a value that owns a live resource returned by a matching acquire helper",
+ "Pass the resource-owning pointer returned by the matching acquire helper, and avoid calling the release helper after ownership has already been transferred or released.");
return -EINVAL;
}
@@ -8371,7 +8774,8 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
return -EFAULT;
}
key_size = meta->map.ptr->key_size;
- err = check_helper_mem_access(env, reg, argno, key_size, BPF_READ, false, NULL);
+ err = check_helper_mem_access(env, reg, argno, key_size, BPF_READ, false, NULL,
+ NULL);
if (err)
return err;
if (can_elide_value_nullness(meta->map.ptr)) {
@@ -8408,7 +8812,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
err = check_helper_mem_access(env, reg, argno, meta->map.ptr->value_size,
arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ,
- false, meta);
+ false, meta, NULL);
break;
case ARG_PTR_TO_PERCPU_BTF_ID:
if (!reg->btf_id) {
@@ -8450,7 +8854,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
*/
if (arg_type & MEM_FIXED_SIZE) {
err = check_mem_reg(env, reg, argno_from_reg(regno), fn->arg_size[arg],
- arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ, meta);
+ arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ, meta, NULL);
if (err)
return err;
if (arg_type & MEM_ALIGNED)
@@ -8461,17 +8865,17 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
err = check_mem_size_reg(env, reg_state(env, regno - 1), reg,
argno_from_reg(regno - 1), argno,
fn->arg_type[arg - 1] & MEM_WRITE ? BPF_WRITE : BPF_READ,
- false, meta);
+ false, meta, NULL);
break;
case ARG_MEM_SIZE_OR_ZERO:
err = check_mem_size_reg(env, reg_state(env, regno - 1), reg,
argno_from_reg(regno - 1), argno,
fn->arg_type[arg - 1] & MEM_WRITE ? BPF_WRITE : BPF_READ,
- true, meta);
+ true, meta, NULL);
break;
case ARG_PTR_TO_DYNPTR:
- err = process_dynptr_func(env, reg, argno, insn_idx, arg_type, &meta->ref_obj,
- &meta->dynptr);
+ err = process_dynptr_func(env, reg, argno, insn_idx, func_id_name(meta->func_id),
+ arg_type, &meta->ref_obj, &meta->dynptr);
if (err)
return err;
break;
@@ -8918,8 +9322,10 @@ static void clear_all_pkt_pointers(struct bpf_verifier_env *env)
struct bpf_reg_state *reg;
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
- if (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg))
+ if (reg_is_pkt_pointer_any(reg) || reg_is_dynptr_slice_pkt(reg)) {
+ bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_PKT_DATA_CHANGE);
mark_reg_invalid(env, reg);
+ }
}));
}
@@ -8949,7 +9355,7 @@ static void mark_pkt_end(struct bpf_verifier_state *vstate, int regn, bool range
reg->range = AT_PKT_END;
}
-static int release_reference_nomark(struct bpf_verifier_state *state, int id)
+static int __release_reference_nomark(struct bpf_verifier_state *state, int id)
{
int i;
@@ -8964,6 +9370,16 @@ static int release_reference_nomark(struct bpf_verifier_state *state, int id)
return -EINVAL;
}
+static int release_reference_nomark(struct bpf_verifier_env *env, int id)
+{
+ int err;
+
+ err = __release_reference_nomark(env->cur_state, id);
+ if (!err)
+ bpf_diag_record_ref_release(env, env->insn_idx, id);
+ return err;
+}
+
static int idstack_push(struct bpf_idmap *idmap, u32 id)
{
int i;
@@ -9006,8 +9422,10 @@ static int release_reference(struct bpf_verifier_env *env, int id)
if (err)
return err;
- if (find_reference_state(vstate, id))
- WARN_ON_ONCE(release_reference_nomark(vstate, id));
+ if (find_reference_state(vstate, id)) {
+ err = release_reference_nomark(env, id);
+ WARN_ON_ONCE(err);
+ }
while ((id = idstack_pop(idstack))) {
/*
@@ -9035,10 +9453,25 @@ static int release_reference(struct bpf_verifier_env *env, int id)
return err;
}
+ /*
+ * A dynptr occupies two stack slots that invalidate_dynptr()
+ * clears together. Record both scrubs before invalidating it.
+ */
+ if (stack && stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR) {
+ struct bpf_stack_state *dyn_stack = stack;
+
+ if (reg->dynptr.first_slot)
+ dyn_stack--;
+ bpf_diag_record_scrub(env, &dyn_stack[0].spilled_ptr,
+ BPF_DIAG_MOD_REF_RELEASE);
+ bpf_diag_record_scrub(env, &dyn_stack[1].spilled_ptr,
+ BPF_DIAG_MOD_REF_RELEASE);
+ invalidate_dynptr(env, dyn_stack);
+ continue;
+ }
+ bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_REF_RELEASE);
if (!stack || stack->slot_type[BPF_REG_SIZE - 1] == STACK_SPILL)
mark_reg_invalid(env, reg);
- else if (stack->slot_type[BPF_REG_SIZE - 1] == STACK_DYNPTR)
- invalidate_dynptr(env, stack);
}));
}
@@ -9051,8 +9484,10 @@ static void invalidate_non_owning_refs(struct bpf_verifier_env *env)
struct bpf_reg_state *reg;
bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
- if (type_is_non_owning_ref(reg->type))
+ if (type_is_non_owning_ref(reg->type)) {
+ bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_NON_OWN_REF);
mark_reg_invalid(env, reg);
+ }
}));
}
@@ -9065,8 +9500,10 @@ static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)
bpf_for_each_reg_in_vstate_mask(env->cur_state, state, reg, stack, clear_mask, ({
if (reg->type & MEM_RCU) {
+ bpf_diag_mod_begin(env, reg, NULL, BPF_DIAG_MOD_WRITE);
reg->type &= ~(MEM_RCU | PTR_MAYBE_NULL);
reg->type |= PTR_UNTRUSTED;
+ bpf_diag_mod_end(env);
}
}));
}
@@ -9077,15 +9514,19 @@ static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 id)
struct bpf_reg_state *reg;
int err;
- err = release_reference_nomark(env->cur_state, id);
+ err = release_reference_nomark(env, id);
+ if (err)
+ return err;
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
if (reg->id != id)
continue;
if ((reg->type & MEM_ALLOC) && (reg->type & MEM_PERCPU)) {
+ bpf_diag_mod_begin(env, reg, NULL, BPF_DIAG_MOD_WRITE);
reg->id = 0;
reg->type &= ~MEM_ALLOC;
reg->type |= MEM_RCU;
+ bpf_diag_mod_end(env);
}
}));
@@ -9097,6 +9538,8 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,
{
int i;
+ bpf_diag_record_caller_saved(env, regs);
+
/* after the call registers r0 - r5 were scratched */
for (i = 0; i < CALLER_SAVED_REGS; i++) {
bpf_mark_reg_not_init(env, ®s[caller_saved[i]]);
@@ -9104,13 +9547,15 @@ static void clear_caller_saved_regs(struct bpf_verifier_env *env,
}
}
-static void invalidate_outgoing_stack_args(const struct bpf_verifier_env *env,
+static void invalidate_outgoing_stack_args(struct bpf_verifier_env *env,
struct bpf_func_state *state)
{
int i, nslots = state->out_stack_arg_cnt;
- for (i = 0; i < nslots; i++)
+ for (i = 0; i < nslots; i++) {
+ bpf_diag_record_scrub(env, &state->stack_arg_regs[i], BPF_DIAG_MOD_CALLER_SAVED);
bpf_mark_reg_not_init(env, &state->stack_arg_regs[i]);
+ }
}
typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env,
@@ -9178,20 +9623,28 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
struct bpf_func_state *caller = cur_func(env);
struct bpf_verifier_log *log = &env->log;
struct ref_obj_desc ref_obj = {};
+ const struct btf_param *args;
+ const struct btf_type *func, *func_proto;
u32 i;
int ret, err;
ret = btf_prepare_func_args(env, subprog);
if (ret) {
if (bpf_in_stack_arg_cnt(sub) > 0) {
- err = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+ err = check_outgoing_stack_args(env, caller, sub->arg_cnt,
+ bpf_subprog_name(env, subprog),
+ NULL, NULL);
if (err)
return err;
}
return ret;
}
- ret = check_outgoing_stack_args(env, caller, sub->arg_cnt);
+ func = btf_type_by_id(btf, env->prog->aux->func_info[subprog].type_id);
+ func_proto = btf_type_by_id(btf, func->type);
+ args = btf_params(func_proto);
+ ret = check_outgoing_stack_args(env, caller, sub->arg_cnt,
+ bpf_subprog_name(env, subprog), btf, args);
if (ret)
return ret;
@@ -9230,7 +9683,8 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
ret = check_func_arg_reg_off(env, reg, argno, ARG_DONTCARE);
if (ret < 0)
return ret;
- if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL))
+ if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL,
+ NULL))
return -EINVAL;
if (!(arg->arg_type & PTR_MAYBE_NULL) &&
(type_may_be_null(reg->type) || bpf_register_is_null(reg))) {
@@ -9256,7 +9710,9 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
if (ret)
return ret;
- ret = process_dynptr_func(env, reg, argno, -1, arg->arg_type, &ref_obj, NULL);
+ ret = process_dynptr_func(env, reg, argno, env->insn_idx,
+ bpf_subprog_name(env, subprog), arg->arg_type,
+ &ref_obj, NULL);
if (ret)
return ret;
} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
@@ -9267,7 +9723,8 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
continue;
memset(&meta, 0, sizeof(meta)); /* leave func_id as zero */
- err = check_reg_type(env, reg, argno, arg->arg_type, &arg->btf_id, &meta);
+ err = check_reg_type(env, reg, argno, arg->arg_type, &arg->btf_id, &meta,
+ bpf_subprog_name(env, subprog));
err = err ?: check_func_arg_reg_off(env, reg, argno, arg->arg_type);
if (err)
return err;
@@ -9408,17 +9865,25 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (err == -EFAULT)
return err;
if (bpf_subprog_is_global(env, subprog)) {
- const char *sub_name = subprog_name(env, subprog);
+ const char *sub_name = bpf_subprog_name(env, subprog);
+ const char *operation;
+ bool returns_void;
if (env->cur_state->active_locks) {
verbose(env, "global function calls are not allowed while holding a lock,\n"
"use static function instead\n");
+ operation = bpf_diag_fmt(env, "global function %s()", sub_name);
+ bpf_diag_ctx_active(env, *insn_idx, operation, BPF_DIAG_CONTEXT_LOCK,
+ "Release the lock before calling the global function, or use a static function instead.");
return -EINVAL;
}
if (env->subprog_info[subprog].might_sleep && !in_sleepable_context(env)) {
verbose(env, "sleepable global function %s() called in %s\n",
sub_name, non_sleepable_context_description(env));
+ operation = bpf_diag_fmt(env, "sleepable global function %s()", sub_name);
+ bpf_diag_ctx_forbidden(env, *insn_idx, operation,
+ "Move the call outside the critical section, or use a non-sleepable function.");
return -EINVAL;
}
@@ -9431,16 +9896,22 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (env->log.level & BPF_LOG_LEVEL)
verbose(env, "Func#%d ('%s') is global and assumed valid.\n",
subprog, sub_name);
+ returns_void = subprog_returns_void(env, subprog);
if (env->subprog_info[subprog].changes_pkt_data)
clear_all_pkt_pointers(env);
/* mark global subprog for verifying after main prog */
subprog_aux(env, subprog)->called = true;
+ if (returns_void)
+ bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED);
+ else
+ bpf_diag_mod_begin(env, &caller->regs[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
clear_caller_saved_regs(env, caller->regs);
invalidate_outgoing_stack_args(env, cur_func(env));
/* All non-void global functions return a 64-bit SCALAR_VALUE. */
- if (!subprog_returns_void(env, subprog)) {
+ if (!returns_void) {
mark_reg_unknown(env, caller->regs, BPF_REG_0);
+ bpf_diag_mod_end(env);
}
if (env->subprog_info[subprog].might_throw) {
@@ -9475,6 +9946,7 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (err)
return err;
+ bpf_diag_record_scrub(env, &caller->regs[BPF_REG_0], BPF_DIAG_MOD_CALLER_SAVED);
clear_caller_saved_regs(env, caller->regs);
/* and go analyze first insn of the callee */
@@ -9838,7 +10310,9 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
}
} else {
/* return to the caller whatever r0 had in the callee */
+ bpf_diag_mod_begin(env, &caller->regs[BPF_REG_0], r0, BPF_DIAG_MOD_WRITE);
caller->regs[BPF_REG_0] = *r0;
+ bpf_diag_mod_end(env);
}
/* for callbacks like bpf_loop or bpf_for_each_map_elem go back to callsite,
@@ -10044,6 +10518,7 @@ static int check_reference_leak(struct bpf_verifier_env *env, bool exception_exi
continue;
verbose(env, "Unreleased reference id=%d alloc_insn=%d\n",
state->refs[i].id, state->refs[i].insn_idx);
+ bpf_diag_leak(env, state->refs[i].id, state->refs[i].insn_idx, env->insn_idx);
refs_lingering = true;
}
return refs_lingering ? -EINVAL : 0;
@@ -10055,6 +10530,8 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit
if (check_lock && env->cur_state->active_locks) {
verbose(env, "%s cannot be used inside bpf_spin_lock-ed region\n", prefix);
+ bpf_diag_ctx_active(env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_LOCK,
+ "Release the BPF spin lock before this operation on every path.");
return -EINVAL;
}
@@ -10066,16 +10543,23 @@ static int check_resource_leak(struct bpf_verifier_env *env, bool exception_exit
if (check_lock && env->cur_state->active_irq_id) {
verbose(env, "%s cannot be used inside bpf_local_irq_save-ed region\n", prefix);
+ bpf_diag_ctx_active(env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_IRQ,
+ "Restore the saved IRQ state before this operation on every path.");
return -EINVAL;
}
if (check_lock && env->cur_state->active_rcu_locks) {
verbose(env, "%s cannot be used inside bpf_rcu_read_lock-ed region\n", prefix);
+ bpf_diag_ctx_active(env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_RCU,
+ "Call bpf_rcu_read_unlock() before this operation on every path.");
return -EINVAL;
}
if (check_lock && env->cur_state->active_preempt_locks) {
verbose(env, "%s cannot be used inside bpf_preempt_disable-ed region\n", prefix);
+ bpf_diag_ctx_active(
+ env, env->insn_idx, prefix, BPF_DIAG_CONTEXT_PREEMPT,
+ "Call bpf_preempt_enable() before this operation on every path.");
return -EINVAL;
}
@@ -10257,6 +10741,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
enum bpf_type_flag ret_flag;
struct bpf_reg_state *regs;
struct bpf_call_arg_meta meta;
+ const char *operation;
int insn_idx = *insn_idx_p;
bool changes_data;
int i, err, func_id;
@@ -10272,17 +10757,31 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (err) {
verbose(env, "program of this type cannot use helper %s#%d\n",
func_id_name(func_id), func_id);
+ operation = bpf_diag_fmt(env, "helper %s#%d", func_id_name(func_id), func_id);
+ bpf_diag_policy(
+ env, insn_idx, operation, "this program type does not allow the helper",
+ "Use a helper allowed for this program type, or move the logic to a compatible program type.");
return err;
}
/* eBPF programs must be GPL compatible to use GPL-ed functions */
if (!env->prog->gpl_compatible && fn->gpl_only) {
verbose(env, "cannot call GPL-restricted function from non-GPL compatible program\n");
+ operation = bpf_diag_fmt(env, "helper %s#%d", func_id_name(func_id), func_id);
+ bpf_diag_policy(
+ env, insn_idx, operation,
+ "this helper is restricted to GPL-compatible programs",
+ "Use a GPL-compatible license, or replace the helper with one that is available to non-GPL programs.");
return -EINVAL;
}
if (fn->allowed && !fn->allowed(env->prog)) {
verbose(env, "helper call is not allowed in probe\n");
+ operation = bpf_diag_fmt(env, "helper %s#%d", func_id_name(func_id), func_id);
+ bpf_diag_policy(
+ env, insn_idx, operation,
+ "the helper-specific policy callback rejected this program",
+ "Use the helper only from an allowed attach point or program configuration.");
return -EINVAL;
}
@@ -10304,6 +10803,10 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (fn->might_sleep && !in_sleepable_context(env)) {
verbose(env, "sleepable helper %s#%d in %s\n", func_id_name(func_id), func_id,
non_sleepable_context_description(env));
+ operation = bpf_diag_fmt(env, "sleepable helper %s#%d",
+ func_id_name(func_id), func_id);
+ bpf_diag_ctx_forbidden(env, insn_idx, operation,
+ "Move the helper call outside the critical section, or use a non-sleepable helper.");
return -EINVAL;
}
@@ -10491,12 +10994,14 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
return err;
/* reset caller saved regs */
+ bpf_diag_record_caller_saved(env, regs);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
bpf_mark_reg_not_init(env, ®s[caller_saved[i]]);
check_reg_arg(env, caller_saved[i], DST_OP_NO_MARK);
}
invalidate_outgoing_stack_args(env, cur_func(env));
+ bpf_diag_mod_begin(env, ®s[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
/* update return register (already marked as written above) */
ret_type = fn->ret_type;
ret_flag = type_flag(ret_type);
@@ -10645,6 +11150,8 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (err)
return err;
+ bpf_diag_mod_end(env);
+
/*
* In order for a release of any of the original or cast pointers
* to invalidate all other pointers, reuse the same reference id for
@@ -10661,6 +11168,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
__mark_reg_known_zero(r0);
r0->type = SCALAR_VALUE;
+ bpf_diag_mod_begin(env, ®s[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
regs[BPF_REG_0].type &= ~PTR_MAYBE_NULL;
regs[BPF_REG_0].id = meta.ref_obj.id;
} else if (is_acquire_function(func_id, meta.map.ptr)) {
@@ -10679,6 +11187,8 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
if (err)
return err;
+ bpf_diag_mod_end(env);
+
err = check_map_func_compatibility(env, meta.map.ptr, func_id);
if (err)
return err;
@@ -11587,6 +12097,12 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
if (!is_irq_flag_reg_valid_uninit(env, reg)) {
verbose(env, "expected uninitialized irq flag as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_res(env, env->insn_idx, "IRQ flag is already initialized",
+ "Saving IRQ state requires an uninitialized stack slot for "
+ "the IRQ flag, but this slot already contains tracked IRQ "
+ "flag state.",
+ "Use a fresh stack slot for this save operation, or restore "
+ "the existing IRQ flag before reusing the slot.");
return -EINVAL;
}
@@ -11603,6 +12119,11 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
if (err) {
verbose(env, "expected an initialized irq flag as %s\n",
reg_arg_name(env, argno));
+ bpf_diag_res(env, env->insn_idx, "uninitialized IRQ flag restore",
+ "Restoring IRQ state requires a stack slot that was "
+ "initialized by a matching IRQ save operation on this path.",
+ "Pass the same stack slot that was previously initialized by "
+ "the matching IRQ save kfunc.");
return err;
}
@@ -11647,8 +12168,10 @@ static void ref_convert_owning_non_owning(struct bpf_verifier_env *env, u32 id)
{
struct bpf_func_state *unused;
struct bpf_reg_state *reg;
+ int err;
- WARN_ON_ONCE(release_reference_nomark(env->cur_state, id));
+ err = release_reference_nomark(env, id);
+ WARN_ON_ONCE(err);
bpf_for_each_reg_in_vstate(env->cur_state, unused, reg, ({
if (reg->id == id) {
@@ -12053,7 +12576,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
args = (const struct btf_param *)(meta->func_proto + 1);
nargs = btf_type_vlen(meta->func_proto);
- ret = check_outgoing_stack_args(env, caller, nargs);
+ ret = check_outgoing_stack_args(env, caller, nargs, func_name, btf, args);
if (ret)
return ret;
@@ -12067,7 +12590,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
argno_t argno = argno_from_arg(i + 1);
int regno = reg_from_argno(argno);
bool btf_id_fixed_off_ok = true;
- u32 ref_id, type_size;
+ u32 ref_id = args[i].type, type_size;
int kf_arg_type = meta->fn->arg_type[i];
if (is_kfunc_arg_prog_aux(btf, &args[i])) {
@@ -12091,29 +12614,43 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
t = btf_type_skip_modifiers(btf, args[i].type, NULL);
- if (btf_type_is_ptr(t) && (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
+ if (btf_type_is_ptr(t)) {
+ ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
+ ref_tname = btf_name_by_offset(btf, ref_t->name_off);
+ }
+
+ if (btf_type_is_ptr(t) &&
+ (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
!type_may_be_null(kf_arg_type)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "Possibly NULL pointer passed to trusted %s\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Add a NULL check and call the kfunc only on the non-NULL path.",
+ "the pointer may be NULL, but this kfunc requires a non-NULL pointer to %s",
+ expected_type);
return -EACCES;
}
if (regno == meta->release_regno && !is_kfunc_arg_dynptr(meta->btf, &args[i]) &&
!reg_is_referenced(env, reg) && !bpf_register_is_null(reg)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "release kfunc %s expects referenced PTR_TO_BTF_ID passed to %s\n",
func_name, reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the resource-owning pointer returned by the matching acquire kfunc, and avoid calling the release kfunc after ownership has already been transferred or released.",
+ "release kfuncs require a resource-owning value of type %s returned by a matching acquire kfunc",
+ expected_type);
return -EINVAL;
}
if (reg_is_referenced(env, reg))
update_ref_obj(&meta->ref_obj, reg);
- if (btf_type_is_ptr(t)) {
- ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
- ref_tname = btf_name_by_offset(btf, ref_t->name_off);
- }
-
-
if (bpf_register_is_null(reg) && type_may_be_null(kf_arg_type))
continue;
@@ -12173,35 +12710,67 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
case KF_ARG_CONST:
if (reg->type != SCALAR_VALUE) {
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar value for this argument, not a pointer or resource object.",
+ "the kfunc expects an integer scalar, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
ret = process_const_arg(env, reg, argno, meta);
- if (ret < 0)
+ if (ret < 0) {
+ if (ret == -EINVAL)
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a compile-time constant or a value the verifier can prove is constant at this call.",
+ "the kfunc requires this scalar argument to be a verifier-known constant, but %s is variable on this path",
+ reg_arg_name(env, argno));
return ret;
+ }
break;
case KF_ARG_ANYTHING:
if (reg->type != SCALAR_VALUE) {
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar value for this argument, not a pointer or resource object.",
+ "the kfunc expects an integer scalar, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
break;
case KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:
if (reg->type != SCALAR_VALUE) {
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar value for this argument, not a pointer or resource object.",
+ "the kfunc expects an integer scalar, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size"))
meta->r0_rdonly = true;
ret = process_const_alloc_mem_size(env, reg, argno, &meta->ret_mem);
- if (ret < 0)
+ if (ret < 0) {
+ if (ret == -EINVAL)
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a verifier-known constant size for this kfunc buffer argument.",
+ "the kfunc uses this argument as a return-buffer size, but %s is invalid or variable on this path",
+ reg_arg_name(env, argno));
return ret;
+ }
break;
case KF_ARG_PTR_TO_CTX:
if (reg->type != PTR_TO_CTX) {
verbose(env, "%s expected pointer to ctx, but got %s\n",
reg_arg_name(env, argno), reg_type_str(env, reg->type));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the original program context pointer or preserve it before modifying registers.",
+ "the kfunc expects a context pointer, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -12235,10 +12804,19 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
} else {
verbose(env, "%s expected pointer to allocated object\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a pointer returned by the matching BPF object allocation path.",
+ "the kfunc expects an allocated object pointer, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
if (!reg_is_referenced(env, reg)) {
verbose(env, "allocated object must be referenced\n");
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the owned object pointer before it is released or transferred.",
+ "the allocated object pointer in %s must still carry verifier-tracked ownership, but this pointer no longer owns a live resource",
+ reg_arg_name(env, argno));
return -EINVAL;
}
if (meta->btf == btf_vmlinux) {
@@ -12275,8 +12853,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
dynptr_arg_type |= (unsigned int)get_dynptr_type_flag(parent_type);
}
- ret = process_dynptr_func(env, reg, argno, insn_idx, dynptr_arg_type,
- &meta->ref_obj, &meta->dynptr);
+ ret = process_dynptr_func(env, reg, argno, insn_idx, func_name,
+ dynptr_arg_type, &meta->ref_obj, &meta->dynptr);
if (ret < 0)
return ret;
break;
@@ -12391,13 +12969,31 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (!is_trusted_reg(env, reg) ||
bpf_type_has_unsafe_modifiers(reg->type)) {
if (!is_kfunc_rcu(meta)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "%s must be referenced or trusted\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a pointer acquired from a verifier-tracked source, or call this kfunc only inside the required protection if it accepts RCU pointers.",
+ "the kfunc requires a trusted or resource-owning pointer to %s, but %s is %s",
+ expected_type,
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
if (!is_rcu_reg(reg)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "%s must be a rcu pointer\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Use this kfunc with a pointer that is valid in an RCU read lock region.",
+ "the kfunc requires an RCU-protected pointer to %s, but %s is %s",
+ expected_type,
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
}
@@ -12410,6 +13006,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (!__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0)) {
enum bpf_reg_type reg2btf_type = lookup_reg2btf_ids(ref_id);
+ const char *expected_type;
verbose(env, "%s is %s expected %s %s",
reg_arg_name(env, argno), reg_type_str(env, reg->type),
@@ -12417,6 +13014,12 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (reg2btf_type != NOT_INIT)
verbose(env, " or %s", reg_type_str(env, reg2btf_type));
verbose(env, "\n");
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a verifier-tracked pointer to the expected kernel object type, not a pointer to stack storage or another memory buffer.",
+ "the kfunc expects a pointer to %s, but this argument is %s and cannot be used as that kernel object pointer",
+ expected_type,
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -12428,6 +13031,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
fallthrough;
case KF_ARG_PTR_TO_MEM:
if (kf_arg_type & MEM_FIXED_SIZE) {
+ bool known_memory;
+
resolve_ret = btf_resolve_size(btf, ref_t, &type_size);
if (IS_ERR(resolve_ret)) {
verbose(env, "%s reference type('%s %s') size cannot be determined: %ld\n",
@@ -12435,9 +13040,28 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
ref_tname, PTR_ERR(resolve_ret));
return -EINVAL;
}
- ret = check_mem_reg(env, reg, argno, type_size, BPF_READ | BPF_WRITE, meta);
- if (ret < 0)
+ ret = check_mem_reg(env, reg, argno, type_size, BPF_READ | BPF_WRITE,
+ meta, &known_memory);
+ if (ret < 0) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
+ if (known_memory)
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Pass memory with at least the required number of accessible bytes and suitable read and write access.",
+ "the kfunc expects %u bytes of memory for %s, but the verifier cannot prove that %s provides a readable and writable range of that size",
+ type_size, expected_type,
+ bpf_diag_reg_type_plain(env, reg->type));
+ else
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Pass stack, map, context, or other verifier-known memory of the expected type and size, not an integer cast to a pointer.",
+ "the kfunc expects %u bytes of memory for %s, but it is %s and not verifier-known memory",
+ type_size, expected_type,
+ bpf_diag_reg_type_plain(env, reg->type));
return ret;
+ }
}
break;
case KF_ARG_CONST_MEM_SIZE:
@@ -12450,9 +13074,15 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
struct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, i - 1);
struct bpf_reg_state *size_reg = reg;
argno_t buff_argno = argno_from_arg(i);
+ enum bpf_mem_size_failure failure;
if (reg->type != SCALAR_VALUE) {
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass an integer scalar length for this memory argument.",
+ "the kfunc expects a scalar memory size, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
@@ -12460,11 +13090,34 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
break;
ret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,
- BPF_READ | BPF_WRITE, true, meta);
+ BPF_READ | BPF_WRITE, true, meta, &failure);
if (ret < 0) {
+ const char *buff_arg, *size_arg;
+
+ buff_arg = bpf_diag_arg_name(env, buff_argno);
+ size_arg = bpf_diag_arg_name(env, argno);
verbose(env, "%s and ", reg_arg_name(env, buff_argno));
verbose(env, "%s memory, len pair leads to invalid memory access\n",
reg_arg_name(env, argno));
+ if (failure == BPF_MEM_SIZE_FAIL_MEMORY) {
+ bpf_diag_call_arg_fmt(env, insn_idx, buff_argno, func_name,
+ "Pass a stack, map, context, or other verifier-known memory pointer, and keep the paired length within that object.",
+ "it is the memory pointer in a memory/length pair with %s, but %s does not describe verifier-readable memory for the requested length",
+ size_arg, buff_arg);
+ } else if (failure == BPF_MEM_SIZE_FAIL_SIZE) {
+ if (reg_smin(size_reg) < 0)
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Constrain the memory size to a non-negative value smaller than BPF_MAX_VAR_SIZ before this call.",
+ "the memory size in %s may be negative because its signed minimum is %lld",
+ size_arg, reg_smin(size_reg));
+ else
+ bpf_diag_call_arg_fmt(
+ env, insn_idx, argno, func_name,
+ "Constrain the memory size to a non-negative value smaller than BPF_MAX_VAR_SIZ before this call.",
+ "the memory size in %s may reach %llu bytes, but variable memory accesses must stay below %u bytes",
+ size_arg, reg_umax(size_reg), BPF_MAX_VAR_SIZ);
+ }
return ret;
}
break;
@@ -12478,8 +13131,15 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
break;
case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
if (!type_is_ptr_alloc_obj(reg->type)) {
+ const char *expected_type;
+
+ expected_type = bpf_diag_fmt_btf_type(env, btf, ref_id);
verbose(env, "%s is neither owning or non-owning ref\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a pointer returned by the matching BPF object allocation or lookup operation for this kfunc.",
+ "the kfunc expects a pointer to BPF-managed refcounted object type %s, but this argument is not such an object pointer",
+ expected_type);
return -EINVAL;
}
if (!type_is_non_owning_ref(reg->type))
@@ -12504,6 +13164,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (reg->type != PTR_TO_MAP_VALUE) {
verbose(env, "%s doesn't point to a const string\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass a constant string pointer that the verifier recognizes, such as a string stored in a read-only map value.",
+ "the kfunc expects a pointer to a constant string stored in verifier-known memory, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
ret = check_arg_const_str(env, reg, argno);
@@ -12544,6 +13209,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
if (reg->type != PTR_TO_STACK) {
verbose(env, "%s doesn't point to an irq flag on stack\n",
reg_arg_name(env, argno));
+ bpf_diag_call_arg_fmt(env, insn_idx, argno, func_name,
+ "Pass the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave().",
+ "the kfunc expects a stack pointer to an IRQ flag slot, but %s is %s",
+ reg_arg_name(env, argno),
+ bpf_diag_reg_type_plain(env, reg->type));
return -EINVAL;
}
ret = process_irq_flag(env, reg, argno, meta);
@@ -12984,6 +13654,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
const struct btf_type *t, *ptr_type;
struct bpf_call_arg_meta meta;
struct bpf_insn_aux_data *insn_aux;
+ const char *operation;
int err, insn_idx = *insn_idx_p;
u32 i, nargs, ptr_type_id;
struct bpf_kfunc_desc *desc;
@@ -12995,8 +13666,13 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
return 0;
err = bpf_fetch_kfunc_arg_meta(env, insn->imm, insn->off, &meta);
- if (err == -EACCES && meta.func_name)
+ if (err == -EACCES && meta.func_name) {
verbose(env, "calling kernel function %s is not allowed\n", meta.func_name);
+ operation = bpf_diag_fmt(env, "kfunc %s", meta.func_name);
+ bpf_diag_policy(
+ env, insn_idx, operation, "this program cannot call the kfunc",
+ "Use a kfunc allowed for this program type and attach point, or change the program context.");
+ }
if (err)
return err;
desc_btf = meta.btf;
@@ -13043,12 +13719,19 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (is_kfunc_destructive(&meta) && !capable(CAP_SYS_BOOT)) {
verbose(env, "destructive kfunc calls require CAP_SYS_BOOT capability\n");
+ operation = bpf_diag_fmt(env, "destructive kfunc %s", meta.func_name);
+ bpf_diag_policy(
+ env, insn_idx, operation, "destructive kfuncs require CAP_SYS_BOOT",
+ "Load the program with CAP_SYS_BOOT, or avoid destructive kfuncs.");
return -EACCES;
}
sleepable = bpf_is_kfunc_sleepable(&meta);
if (sleepable && !in_sleepable(env)) {
verbose(env, "program must be sleepable to call sleepable kfunc %s\n", func_name);
+ operation = bpf_diag_fmt(env, "sleepable kfunc %s", func_name);
+ bpf_diag_ctx_forbidden(env, insn_idx, operation,
+ "Mark the program sleepable if the program type allows it, or use a non-sleepable kfunc.");
return -EACCES;
}
@@ -13114,22 +13797,36 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (rcu_lock) {
env->cur_state->active_rcu_locks++;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU, true,
+ env->cur_state->active_rcu_locks);
} else if (rcu_unlock) {
if (env->cur_state->active_rcu_locks == 0) {
verbose(env, "unmatched rcu read unlock (kernel function %s)\n", func_name);
+ bpf_diag_ctx_underflow(
+ env, insn_idx, func_name, BPF_DIAG_CONTEXT_RCU,
+ "Remove the extra bpf_rcu_read_unlock() call, or ensure this path first enters an RCU read lock region.");
return -EINVAL;
}
env->cur_state->active_rcu_locks--;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_RCU, false,
+ env->cur_state->active_rcu_locks);
if (!in_rcu_cs(env))
invalidate_rcu_protected_refs(env);
} else if (preempt_disable) {
env->cur_state->active_preempt_locks++;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_PREEMPT, true,
+ env->cur_state->active_preempt_locks);
} else if (preempt_enable) {
if (env->cur_state->active_preempt_locks == 0) {
verbose(env, "unmatched attempt to enable preemption (kernel function %s)\n", func_name);
+ bpf_diag_ctx_underflow(
+ env, insn_idx, func_name, BPF_DIAG_CONTEXT_PREEMPT,
+ "Remove the extra bpf_preempt_enable() call, or ensure this path first disables preemption.");
return -EINVAL;
}
env->cur_state->active_preempt_locks--;
+ bpf_diag_record_context(env, insn_idx, BPF_DIAG_CONTEXT_PREEMPT, false,
+ env->cur_state->active_preempt_locks);
if (!in_rcu_cs(env))
invalidate_rcu_protected_refs(env);
}
@@ -13137,6 +13834,9 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (sleepable && !in_sleepable_context(env)) {
verbose(env, "kernel func %s is sleepable within %s\n",
func_name, non_sleepable_context_description(env));
+ operation = bpf_diag_fmt(env, "sleepable kfunc %s", func_name);
+ bpf_diag_ctx_forbidden(env, insn_idx, operation,
+ "Move the kfunc call outside the critical section, or use a non-sleepable kfunc.");
return -EACCES;
}
@@ -13147,6 +13847,9 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
if (is_kfunc_rcu_protected(&meta) && !in_rcu_cs(env)) {
verbose(env, "kernel func %s requires RCU critical section protection\n", func_name);
+ bpf_diag_ctx_required(
+ env, insn_idx, func_name, BPF_DIAG_CONTEXT_RCU,
+ "Call this kfunc between bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.");
return -EACCES;
}
@@ -13184,6 +13887,8 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
}
}
+ bpf_diag_record_caller_saved(env, regs);
+ bpf_diag_mod_begin(env, ®s[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
u32 regno = caller_saved[i];
@@ -13335,6 +14040,12 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
caller_info->stack_arg_cnt = stack_arg_cnt;
}
+ /*
+ * Record R0 before process_iter_next_call() snapshots the alternate
+ * iterator path's diagnostic position.
+ */
+ bpf_diag_mod_end(env);
+
if (bpf_is_iter_next_kfunc(&meta)) {
err = process_iter_next_call(env, insn_idx, &meta);
if (err)
@@ -13718,9 +14429,8 @@ static int sanitize_check_bounds(struct bpf_verifier_env *env,
* If we return -EACCES, caller may want to try again treating pointer as a
* scalar. So we only emit a diagnostic if !env->allow_ptr_leaks.
*/
-static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
- struct bpf_insn *insn,
- const struct bpf_reg_state *ptr_reg,
+static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn *insn,
+ u32 ptr_regno, const struct bpf_reg_state *ptr_reg,
const struct bpf_reg_state *off_reg)
{
struct bpf_verifier_state *vstate = env->cur_state;
@@ -13732,6 +14442,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
struct bpf_sanitize_info info = {};
u8 opcode = BPF_OP(insn->code);
u32 dst = insn->dst_reg;
+ const char *reason;
int ret, bounds_ret;
dst_reg = ®s[dst];
@@ -13755,12 +14466,24 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
verbose(env,
"R%d 32-bit pointer arithmetic prohibited\n",
dst);
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. 32-bit ALU operations on pointers discard pointer tracking, so the verifier cannot keep the result as a safe pointer.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "32-bit pointer arithmetic", reason,
+ "Use a 64-bit ALU instruction with an allowed, bounded scalar offset.");
return -EACCES;
}
if (ptr_reg->type & PTR_MAYBE_NULL) {
verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
dst, reg_type_str(env, ptr_reg->type));
+ reason = bpf_diag_fmt(
+ env, "R%d may be NULL (%s). Pointer arithmetic is allowed only after the program proves the pointer is non-NULL on this path.",
+ ptr_regno, reg_type_str(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer arithmetic before NULL check", reason,
+ "Make sure that a NULL check precedes any arithmetic performed on the pointer.");
return -EACCES;
}
@@ -13790,6 +14513,12 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
default:
verbose(env, "R%d pointer arithmetic on %s prohibited\n",
dst, reg_type_str(env, ptr_reg->type));
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. This pointer kind does not allow offset arithmetic.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer arithmetic is not allowed", reason,
+ "Do not change this pointer's offset; use it only in operations accepted for its kind.");
return -EACCES;
}
@@ -13807,9 +14536,25 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
if (base_type(ptr_reg->type) == PTR_TO_MEM && (ptr_reg->type & PTR_UNTRUSTED))
return 0;
- if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type) ||
- !check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type))
+ if (!check_reg_sane_offset_scalar(env, off_reg, ptr_reg->type)) {
+ reason = bpf_diag_fmt(
+ env, "The scalar offset used with R%d is unbounded or outside the verifier's safe pointer-offset range [-%u, %u].",
+ ptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer offset is not safe", reason,
+ "Clamp or bounds-check the scalar offset before applying it to the pointer.");
return -EINVAL;
+ }
+ if (!check_reg_sane_offset_ptr(env, ptr_reg, ptr_reg->type)) {
+ reason = bpf_diag_fmt(
+ env, "R%d already has an offset outside the verifier's safe range [-%u, %u] for %s.",
+ ptr_regno, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,
+ bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer offset is not safe", reason,
+ "Keep the base pointer within the verifier's allowed offset range before applying more arithmetic.");
+ return -EINVAL;
+ }
/* pointer types do not carry 32-bit bounds at the moment. */
__mark_reg32_unbounded(dst_reg);
@@ -13852,6 +14597,13 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
/* scalar -= pointer. Creates an unknown scalar */
verbose(env, "R%d tried to subtract pointer from scalar\n",
dst);
+ reason = bpf_diag_fmt(
+ env, "This operation subtracts pointer register R%d from scalar register R%d. "
+ "The verifier only tracks pointer-minus-scalar arithmetic for allowed pointer types.",
+ ptr_regno, dst);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer subtracted from scalar", reason,
+ "Keep the pointer as the base; only add or subtract bounded scalars when permitted.");
return -EACCES;
}
/* We don't allow subtraction from FP, because (according to
@@ -13861,6 +14613,12 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
if (ptr_reg->type == PTR_TO_STACK) {
verbose(env, "R%d subtraction from stack pointer prohibited\n",
dst);
+ reason = bpf_diag_fmt(
+ env, "R%d is a stack pointer. The verifier does not allow BPF_SUB to move stack pointers.",
+ ptr_regno);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "subtraction from stack pointer", reason,
+ "Use addition from R10 to form stack addresses within the tracked stack frame.");
return -EACCES;
}
dst_reg->r64 = cnum64_add(ptr_reg->r64, cnum64_negate(off_reg->r64));
@@ -13886,16 +14644,38 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
/* bitwise ops on pointers are troublesome, prohibit. */
verbose(env, "R%d bitwise operator %s on pointer prohibited\n",
dst, bpf_alu_string[opcode >> 4]);
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. Bitwise operator %s would destroy the pointer value the verifier is tracking.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type),
+ bpf_alu_string[opcode >> 4]);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "bitwise operation on pointer", reason,
+ "Do bitwise operations on scalar values, not on pointer-valued registers.");
return -EACCES;
default:
/* other operators (e.g. MUL,LSH) produce non-pointer results */
verbose(env, "R%d pointer arithmetic with %s operator prohibited\n",
dst, bpf_alu_string[opcode >> 4]);
+ reason = bpf_diag_fmt(
+ env, "R%d holds %s. Operator %s is not one of the limited pointer arithmetic operations the verifier can track.",
+ ptr_regno, bpf_diag_reg_type_plain(env, ptr_reg->type),
+ bpf_alu_string[opcode >> 4]);
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "invalid pointer arithmetic operator", reason,
+ "Use only verifier-supported addition or subtraction with a bounded scalar offset, or perform this operation on a scalar value.");
return -EACCES;
}
- if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type))
+ if (!check_reg_sane_offset_ptr(env, dst_reg, ptr_reg->type)) {
+ reason = bpf_diag_fmt(
+ env, "After this arithmetic, R%d would be outside the verifier's safe offset range [-%u, %u] for %s.",
+ dst, BPF_MAX_VAR_OFF, BPF_MAX_VAR_OFF,
+ bpf_diag_reg_type_plain(env, ptr_reg->type));
+ bpf_diag_register_type(
+ env, env->insn_idx, ptr_regno, "pointer offset is not safe", reason,
+ "Tighten the scalar bounds before the arithmetic so the resulting pointer remains within the allowed range.");
return -EINVAL;
+ }
reg_bounds_sync(dst_reg);
bounds_ret = sanitize_check_bounds(env, insn, dst_reg);
if (bounds_ret == -EACCES)
@@ -14867,15 +15647,15 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
if (err)
return err;
off_reg = *dst_reg;
- return adjust_ptr_min_max_vals(env, insn, src_reg, &off_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->src_reg, src_reg,
+ &off_reg);
}
} else if (ptr_reg) {
/* pointer += scalar */
err = mark_chain_precision(env, insn->src_reg);
if (err)
return err;
- return adjust_ptr_min_max_vals(env, insn,
- dst_reg, src_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->dst_reg, dst_reg, src_reg);
} else if (dst_reg->precise) {
/* if dst_reg is precise, src_reg should be precise as well */
err = mark_chain_precision(env, insn->src_reg);
@@ -14890,8 +15670,7 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
__mark_reg_known(&off_reg, insn->imm);
src_reg = &off_reg;
if (ptr_reg) /* pointer += K */
- return adjust_ptr_min_max_vals(env, insn,
- ptr_reg, src_reg);
+ return adjust_ptr_min_max_vals(env, insn, insn->dst_reg, ptr_reg, src_reg);
}
/* Got here implies adding two SCALAR_VALUEs */
@@ -14977,6 +15756,8 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
u8 opcode = BPF_OP(insn->code);
int err;
+ bpf_diag_mod_begin(env, ®s[insn->dst_reg], NULL, BPF_DIAG_MOD_WRITE);
+
if (opcode == BPF_END || opcode == BPF_NEG) {
/* check src operand */
err = check_reg_arg(env, insn->dst_reg, SRC_OP);
@@ -15150,7 +15931,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err;
}
- return reg_bounds_sanity_check(env, ®s[insn->dst_reg], "alu");
+ err = reg_bounds_sanity_check(env, ®s[insn->dst_reg], "alu");
+ if (err)
+ return err;
+
+ bpf_diag_mod_end(env);
+ return 0;
}
static void find_good_pkt_pointers(struct bpf_verifier_state *vstate,
@@ -15765,7 +16551,7 @@ static void mark_ptr_or_null_regs(struct bpf_verifier_state *vstate, u32 regno,
* No one could have freed the reference state before
* doing the NULL check.
*/
- WARN_ON_ONCE(release_reference_nomark(vstate, id));
+ WARN_ON_ONCE(__release_reference_nomark(vstate, id));
bpf_for_each_reg_in_vstate(vstate, state, reg, ({
mark_ptr_or_null_reg(state, reg, id, is_null);
@@ -16244,11 +17030,13 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err;
dst_reg = ®s[insn->dst_reg];
+ bpf_diag_mod_begin(env, dst_reg, NULL, BPF_DIAG_MOD_WRITE);
if (insn->src_reg == 0) {
u64 imm = ((u64)(insn + 1)->imm << 32) | (u32)insn->imm;
dst_reg->type = SCALAR_VALUE;
__mark_reg_known(®s[insn->dst_reg], imm);
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16272,6 +17060,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
verifier_bug(env, "pseudo btf id: unexpected dst reg type");
return -EFAULT;
}
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16291,6 +17080,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
dst_reg->type = PTR_TO_FUNC;
dst_reg->subprogno = subprogno;
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16301,6 +17091,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
if (map->map_type == BPF_MAP_TYPE_ARENA) {
__mark_reg_unknown(env, dst_reg);
dst_reg->map_ptr = map;
+ bpf_diag_mod_end(env);
return 0;
}
__mark_reg_known(dst_reg, aux->map_off);
@@ -16318,6 +17109,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
return -EFAULT;
}
+ bpf_diag_mod_end(env);
return 0;
}
@@ -16396,6 +17188,8 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
return err;
/* reset caller saved regs to unreadable */
+ bpf_diag_record_caller_saved(env, regs);
+ bpf_diag_mod_begin(env, ®s[BPF_REG_0], NULL, BPF_DIAG_MOD_WRITE);
for (i = 0; i < CALLER_SAVED_REGS; i++) {
bpf_mark_reg_not_init(env, ®s[caller_saved[i]]);
check_reg_arg(env, caller_saved[i], DST_OP_NO_MARK);
@@ -16406,6 +17200,7 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
* Already marked as written above.
*/
mark_reg_unknown(env, regs, BPF_REG_0);
+ bpf_diag_mod_end(env);
/*
* See bpf_gen_ld_abs() which emits a hidden BPF_EXIT with r0=0
* which must be explored by the verifier when in a subprog.
@@ -17332,6 +18127,10 @@ static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)
!kfunc_spin_allowed(env, insn->imm, insn->off))) {
verbose(env,
"function calls are not allowed while holding a lock\n");
+ bpf_diag_ctx_active(
+ env, env->insn_idx,
+ "function call", BPF_DIAG_CONTEXT_LOCK,
+ "Release the BPF spin lock before making this call, or move the call outside the locked region.");
return -EINVAL;
}
}
@@ -17416,6 +18215,27 @@ static int do_check(struct bpf_verifier_env *env)
state->last_insn_idx = env->prev_insn_idx;
state->insn_idx = env->insn_idx;
+ /*
+ * Record the incoming edge so active and queued paths use the same
+ * branch-recording path. A zero-offset conditional has identical
+ * successors, so its outcome cannot be reconstructed from the edge.
+ */
+ if (!state->speculative && prev_insn_idx >= 0 && prev_insn_idx < insn_cnt) {
+ struct bpf_insn *prev_insn = &insns[prev_insn_idx];
+ int fallthrough_idx = prev_insn_idx + 1;
+ int branch_idx = prev_insn_idx + bpf_jmp_offset(prev_insn) + 1;
+ u8 class = BPF_CLASS(prev_insn->code);
+ u8 opcode = BPF_OP(prev_insn->code);
+
+ if ((class == BPF_JMP || class == BPF_JMP32) &&
+ opcode != BPF_JA && opcode != BPF_CALL && opcode != BPF_EXIT &&
+ opcode <= BPF_JCOND && branch_idx != fallthrough_idx) {
+ if (env->insn_idx == branch_idx)
+ bpf_diag_record_branch(env, prev_insn_idx, true);
+ else if (env->insn_idx == fallthrough_idx)
+ bpf_diag_record_branch(env, prev_insn_idx, false);
+ }
+ }
if (bpf_is_prune_point(env, env->insn_idx)) {
err = bpf_is_state_visited(env, env->insn_idx);
@@ -18479,7 +19299,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
regs = state->frame[state->curframe]->regs;
if (subprog || env->prog->type == BPF_PROG_TYPE_EXT) {
- const char *sub_name = subprog_name(env, subprog);
+ const char *sub_name = bpf_subprog_name(env, subprog);
struct bpf_subprog_arg_info *arg;
struct bpf_reg_state *reg;
@@ -18591,8 +19411,11 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
ret = do_check(env);
out:
account_current_path(env);
- if (!ret && pop_log)
- bpf_vlog_reset(&env->log, 0);
+ if (!ret) {
+ if (pop_log)
+ bpf_vlog_reset(&env->log, 0);
+ bpf_diag_event_log_restore(env, 0);
+ }
free_states(env);
/*
@@ -18656,7 +19479,7 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
return ret;
} else if (env->log.level & BPF_LOG_LEVEL) {
verbose(env, "Func#%d ('%s') is safe for any args that match its prototype\n",
- i, subprog_name(env, i));
+ i, bpf_subprog_name(env, i));
}
/* We verified new global subprog, it might have called some
@@ -20188,6 +21011,9 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
ret = bpf_vlog_init(&env->log, attr_log->level, attr_log->ubuf, attr_log->size);
if (ret)
goto err_free_env;
+ ret = bpf_diag_init(env);
+ if (ret)
+ goto err_prep;
if (env->signature) {
ret = bpf_prog_calc_tag(env->prog);
if (ret < 0)
@@ -20478,6 +21304,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
kvfree(env->scc_info);
kvfree(env->succ);
kvfree(env->gotox_tmp_buf);
+ bpf_diag_free(env);
kvfree(env);
return ret;
}
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
index 7918646e5bfcf..d3be69a9a7557 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
@@ -155,6 +155,7 @@ l0_%=: r0 = 0; \
SEC("socket")
__description("forgot null checking on the inner map pointer")
__failure __msg("R1 type=map_ptr_or_null expected=map_ptr")
+__msg("map_ptr_or_null, but this argument accepts map_ptr")
__failure_unpriv
__naked void on_the_inner_map_pointer(void)
{
diff --git a/tools/testing/selftests/bpf/progs/verifier_uninit.c b/tools/testing/selftests/bpf/progs/verifier_uninit.c
index 7718cd7d19ce9..691018a460493 100644
--- a/tools/testing/selftests/bpf/progs/verifier_uninit.c
+++ b/tools/testing/selftests/bpf/progs/verifier_uninit.c
@@ -9,6 +9,7 @@
SEC("socket")
__description("read uninitialized register")
__failure __msg("R2 !read_ok")
+__msg("R2 has never been initialized on this path")
__failure_unpriv
__naked void read_uninitialized_register(void)
{
|
| 7/3 | 2026/08/15 07:05 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 78926 output: 18 thoughts: 428 Thoughts: **Analyzing the BPF Verifier Diagnostics Patch** Okay, so this patch series looks like a significant improvement to the BPF verifier, focusing on better diagnostics when a BPF program gets rejected. They're introducing a new framework with `diagnostics.c` and `.h` and integrating it into the existing verifier code. That's a good step toward making BPF debugging more manageable. I'm especially interested in the claim that it tracks register modifications, stack changes, and context, allowing for a causal history of the error. That sounds like it could save a lot of head-scratching. My first thought, as always, is to check for potential memory safety issues. The patch defines several new data structures: `bpf_diag`, `bpf_diag_log`, `bpf_diag_history_event`, `bpf_diag_scratch`, and `bpf_diag_mod_scope`. Okay, so far, so good – we’ve got some new structs to keep track of this information. The code seems to be allocating them using `kzalloc_obj` and `kvrealloc`, which is standard practice. Hmm... `kzalloc_obj`... that looks a bit odd. I don't recall seeing that in the mainline kernel. Could be a typo? Or, perhaps a custom macro defined somewhere within this specific patch series or project? Let me go back and double-check. The code snippet mentions `env->diag = kzalloc_obj(struct bpf_diag, GFP_KERNEL_ACCOUNT);`, and I don’t see it actually defined in the patch. I need to make sure this isn't a lurking memory safety hazard. Let me try again to figure this out... Wait, maybe it's `kzalloc(sizeof(struct bpf_diag), GFP_KERNEL_ACCOUNT)`? Okay, let me grep for the definition of `kzalloc_obj` and see what it actually is, just to be sure. It's time to dig deeper. |
| 8/3 | 2026/08/15 07:05 | tool | grepper |
0mArgs:{
"Expression": "kzalloc_obj"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 79150 lines.\nUse more precise expression if possible.\n\nDocumentation/core-api/list.rst=103=list:\n--\nDocumentation/core-api/list.rst-114-\nDocumentation/core-api/list.rst:115: grock = kzalloc_obj(*grock);\nDocumentation/core-api/list.rst-116- if (!grock)\n--\nDocumentation/core-api/list.rst-125-\nDocumentation/core-api/list.rst:126: dimitri = kzalloc_obj(*dimitri);\nDocumentation/core-api/list.rst-127- if (!dimitri)\n--\nDocumentation/driver-api/mailbox.rst=45=a message and a callback function to the API and return immediately).\n--\nDocumentation/driver-api/mailbox.rst-89-\nDocumentation/driver-api/mailbox.rst:90:\t\tdc_sync = kzalloc_obj(*dc_sync);\nDocumentation/driver-api/mailbox.rst:91:\t\tdc_async = kzalloc_obj(*dc_async);\nDocumentation/driver-api/mailbox.rst-92-\n--\nDocumentation/driver-api/media/v4l2-fh.rst=26=Example:\n--\nDocumentation/driver-api/media/v4l2-fh.rst-44-\nDocumentation/driver-api/media/v4l2-fh.rst:45:\t\tmy_fh = kzalloc_obj(*my_fh);\nDocumentation/driver-api/media/v4l2-fh.rst-46-\n--\nDocumentation/process/coding-style.rst=938=The kernel provides the following general purpose memory allocators:\nDocumentation/process/coding-style.rst:939:kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), and\nDocumentation/process/coding-style.rst-940-vzalloc(). Please refer to the API documentation for further information\n--\nDocumentation/process/coding-style.rst=964=The preferred form for allocating a zeroed array is the following:\n--\nDocumentation/process/coding-style.rst-967-\nDocumentation/process/coding-style.rst:968:\tp = kzalloc_objs(*p, n, ...);\nDocumentation/process/coding-style.rst-969-\n--\nDocumentation/process/deprecated.rst=398=become, respectively::\n--\nDocumentation/process/deprecated.rst-400-\tptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst:401:\tptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst-402-\tptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst:403:\tptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst-404-\tptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=208=to details explained in the following section.\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-268- /* allocate a chip-specific data with zero filled */\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:269: chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-270- if (chip == NULL)\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=623=After allocating a card instance via :c:func:`snd_card_new()`\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-630- .....\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:631: chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-632-\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=698=destructor and PCI entries. Example code is shown first, below::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-749-\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:750: chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-751- if (chip == NULL) {\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=3823=chip data individually::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3835- ....\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:3836: chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3837- ....\n--\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt=794=int my_open(struct file *file)\n--\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt-801-\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt:802:\tmy_fh = kzalloc_obj(*my_fh);\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt-803-\n--\narch/alpha/kernel/module.c=64=module_frob_arch_sections(Elf64_Ehdr *hdr, Elf64_Shdr *sechdrs,\n--\narch/alpha/kernel/module.c-95-\tnsyms = symtab-\u003esh_size / sizeof(Elf64_Sym);\narch/alpha/kernel/module.c:96:\tchains = kzalloc_objs(struct got_entry, nsyms);\narch/alpha/kernel/module.c-97-\tif (!chains) {\n--\narch/alpha/kernel/setup.c=390=register_cpus(void)\n--\narch/alpha/kernel/setup.c-394-\tfor_each_possible_cpu(i) {\narch/alpha/kernel/setup.c:395:\t\tstruct cpu *p = kzalloc_obj(*p);\narch/alpha/kernel/setup.c-396-\t\tif (!p)\n--\narch/arc/net/bpf_jit_core.c=1120=static int jit_prepare_final_mem_alloc(struct jit_context *ctx)\n--\narch/arc/net/bpf_jit_core.c-1131-\tif (ctx-\u003eneed_extra_pass) {\narch/arc/net/bpf_jit_core.c:1132:\t\tctx-\u003ejit_data = kzalloc_obj(*ctx-\u003ejit_data);\narch/arc/net/bpf_jit_core.c-1133-\t\tif (!ctx-\u003ejit_data)\n--\narch/arm/common/locomo.c=220=locomo_init_one_child(struct locomo *lchip, struct locomo_dev_info *info)\n--\narch/arm/common/locomo.c-224-\narch/arm/common/locomo.c:225:\tdev = kzalloc_obj(struct locomo_dev);\narch/arm/common/locomo.c-226-\tif (!dev) {\n--\narch/arm/common/locomo.c=356=__locomo_probe(struct device *me, struct resource *mem, int irq)\n--\narch/arm/common/locomo.c-362-\narch/arm/common/locomo.c:363:\tlchip = kzalloc_obj(struct locomo);\narch/arm/common/locomo.c-364-\tif (!lchip)\n--\narch/arm/common/sa1111.c=733=sa1111_init_one_child(struct sa1111 *sachip, struct resource *parent,\n--\narch/arm/common/sa1111.c-739-\narch/arm/common/sa1111.c:740:\tdev = kzalloc_obj(struct sa1111_dev);\narch/arm/common/sa1111.c-741-\tif (!dev) {\n--\narch/arm/common/scoop.c=178=static int scoop_probe(struct platform_device *pdev)\n--\narch/arm/common/scoop.c-187-\narch/arm/common/scoop.c:188:\tdevptr = kzalloc_obj(struct scoop_dev);\narch/arm/common/scoop.c-189-\tif (!devptr)\n--\narch/arm/kernel/smp.c=108=static int secondary_biglittle_prepare(unsigned int cpu)\n--\narch/arm/kernel/smp.c-110-\tif (!cpu_vtable[cpu])\narch/arm/kernel/smp.c:111:\t\tcpu_vtable[cpu] = kzalloc_obj(*cpu_vtable[cpu]);\narch/arm/kernel/smp.c-112-\n--\narch/arm/kernel/vdso.c=169=static int __init vdso_init(void)\n--\narch/arm/kernel/vdso.c-181-\t/* Allocate the VDSO text pagelist */\narch/arm/kernel/vdso.c:182:\tvdso_text_pagelist = kzalloc_objs(struct page *, text_pages);\narch/arm/kernel/vdso.c-183-\tif (vdso_text_pagelist == NULL)\n--\narch/arm/mach-footbridge/dc21285.c=261=int __init dc21285_setup(int nr, struct pci_sys_data *sys)\n--\narch/arm/mach-footbridge/dc21285.c-264-\narch/arm/mach-footbridge/dc21285.c:265:\tres = kzalloc_objs(struct resource, 2);\narch/arm/mach-footbridge/dc21285.c-266-\tif (!res) {\n--\narch/arm/mach-footbridge/ebsa285.c=69=static int __init ebsa285_leds_init(void)\n--\narch/arm/mach-footbridge/ebsa285.c-86-\narch/arm/mach-footbridge/ebsa285.c:87:\t\tled = kzalloc_obj(*led);\narch/arm/mach-footbridge/ebsa285.c-88-\t\tif (!led)\n--\narch/arm/mach-footbridge/netwinder-hw.c=720=static int __init netwinder_leds_init(void)\n--\narch/arm/mach-footbridge/netwinder-hw.c-729-\narch/arm/mach-footbridge/netwinder-hw.c:730:\t\tled = kzalloc_obj(*led);\narch/arm/mach-footbridge/netwinder-hw.c-731-\t\tif (!led)\n--\narch/arm/mach-imx/mmdc.c=473=static int imx_mmdc_perf_init(struct platform_device *pdev, void __iomem *mmdc_base,\n--\narch/arm/mach-imx/mmdc.c-479-\narch/arm/mach-imx/mmdc.c:480:\tpmu_mmdc = kzalloc_obj(*pmu_mmdc);\narch/arm/mach-imx/mmdc.c-481-\tif (!pmu_mmdc) {\n--\narch/arm/mach-mvebu/board-v7.c=114=static void __init i2c_quirk(void)\n--\narch/arm/mach-mvebu/board-v7.c-129-\narch/arm/mach-mvebu/board-v7.c:130:\t\tnew_compat = kzalloc_obj(*new_compat);\narch/arm/mach-mvebu/board-v7.c-131-\n--\narch/arm/mach-mvebu/coherency.c=163=static void __init armada_375_380_coherency_init(struct device_node *np)\n--\narch/arm/mach-mvebu/coherency.c-187-\narch/arm/mach-mvebu/coherency.c:188:\t\tp = kzalloc_obj(*p);\narch/arm/mach-mvebu/coherency.c-189-\t\tp-\u003ename = kstrdup(\"arm,io-coherent\", GFP_KERNEL);\n--\narch/arm/mach-mvebu/mvebu-soc-id.c=148=static int __init mvebu_soc_device(void)\n--\narch/arm/mach-mvebu/mvebu-soc-id.c-156-\narch/arm/mach-mvebu/mvebu-soc-id.c:157:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-mvebu/mvebu-soc-id.c-158-\tif (!soc_dev_attr)\n--\narch/arm/mach-mxs/mach-mxs.c=380=static void __init mxs_machine_init(void)\n--\narch/arm/mach-mxs/mach-mxs.c-389-\narch/arm/mach-mxs/mach-mxs.c:390:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-mxs/mach-mxs.c-391-\tif (!soc_dev_attr)\n--\narch/arm/mach-omap1/dma.c=294=static int __init omap1_system_dma_init(void)\n--\narch/arm/mach-omap1/dma.c-321-\narch/arm/mach-omap1/dma.c:322:\td = kzalloc_obj(*d);\narch/arm/mach-omap1/dma.c-323-\tif (!d) {\n--\narch/arm/mach-omap1/mcbsp.c=292=static void omap_mcbsp_register_board_cfg(struct resource *res, int res_count,\n--\narch/arm/mach-omap1/mcbsp.c-296-\narch/arm/mach-omap1/mcbsp.c:297:\tomap_mcbsp_devices = kzalloc_objs(struct platform_device *, size);\narch/arm/mach-omap1/mcbsp.c-298-\tif (!omap_mcbsp_devices) {\n--\narch/arm/mach-omap1/timer.c=51=static int __init omap1_dm_timer_init(void)\n--\narch/arm/mach-omap1/timer.c-127-\narch/arm/mach-omap1/timer.c:128:\t\tpdata = kzalloc_obj(*pdata);\narch/arm/mach-omap1/timer.c-129-\t\tif (!pdata) {\n--\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c=230=void omap2xxx_clkt_vps_init(void)\n--\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c-239-\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c:240:\thw = kzalloc_obj(*hw);\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c-241-\tif (!hw)\n--\narch/arm/mach-omap2/id.c=786=void __init omap_soc_device_init(void)\n--\narch/arm/mach-omap2/id.c-790-\narch/arm/mach-omap2/id.c:791:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-omap2/id.c-792-\tif (!soc_dev_attr)\n--\narch/arm/mach-omap2/omap_device.c=131=static int omap_device_build_from_dt(struct platform_device *pdev)\n--\narch/arm/mach-omap2/omap_device.c-158-\narch/arm/mach-omap2/omap_device.c:159:\thwmods = kzalloc_objs(struct omap_hwmod *, oh_cnt);\narch/arm/mach-omap2/omap_device.c-160-\tif (!hwmods) {\n--\narch/arm/mach-omap2/omap_hwmod.c=3381=static int omap_hwmod_allocate_module(struct device *dev, struct omap_hwmod *oh,\n--\narch/arm/mach-omap2/omap_hwmod.c-3394-\narch/arm/mach-omap2/omap_hwmod.c:3395:\tsysc = kzalloc_obj(*sysc);\narch/arm/mach-omap2/omap_hwmod.c-3396-\tif (!sysc)\n--\narch/arm/mach-omap2/omap_hwmod.c-3424-\tif (list_empty(\u0026oh-\u003eslave_ports)) {\narch/arm/mach-omap2/omap_hwmod.c:3425:\t\toi = kzalloc_obj(*oi);\narch/arm/mach-omap2/omap_hwmod.c-3426-\t\tif (!oi)\n--\narch/arm/mach-omap2/omap_hwmod.c=3513=int omap_hwmod_init_module(struct device *dev,\n--\narch/arm/mach-omap2/omap_hwmod.c-3527-\tif (!oh) {\narch/arm/mach-omap2/omap_hwmod.c:3528:\t\toh = kzalloc_obj(*oh);\narch/arm/mach-omap2/omap_hwmod.c-3529-\t\tif (!oh)\n--\narch/arm/mach-omap2/omap_hwmod.c-3538-\narch/arm/mach-omap2/omap_hwmod.c:3539:\t\toh-\u003eclass = kzalloc_obj(*oh-\u003eclass);\narch/arm/mach-omap2/omap_hwmod.c-3540-\t\tif (!oh-\u003eclass) {\n--\narch/arm/mach-omap2/pm33xx-core.c=379=static int __init amx3_idle_init(struct device_node *cpu_node, int cpu)\n--\narch/arm/mach-omap2/pm33xx-core.c-412-\narch/arm/mach-omap2/pm33xx-core.c:413:\tidle_states = kzalloc_objs(*idle_states, state_count);\narch/arm/mach-omap2/pm33xx-core.c-414-\tif (!idle_states)\n--\narch/arm/mach-omap2/sr_device.c=30=static void __init sr_set_nvalues(struct omap_volt_data *volt_data,\n--\narch/arm/mach-omap2/sr_device.c-41-\narch/arm/mach-omap2/sr_device.c:42:\tnvalue_table = kzalloc_objs(*nvalue_table, count);\narch/arm/mach-omap2/sr_device.c-43-\tif (!nvalue_table)\n--\narch/arm/mach-orion5x/pci.c=139=static int __init pcie_setup(struct pci_sys_data *sys)\n--\narch/arm/mach-orion5x/pci.c-171-\t */\narch/arm/mach-orion5x/pci.c:172:\tres = kzalloc_obj(struct resource);\narch/arm/mach-orion5x/pci.c-173-\tif (!res)\n--\narch/arm/mach-orion5x/pci.c=466=static int __init pci_setup(struct pci_sys_data *sys)\n--\narch/arm/mach-orion5x/pci.c-492-\t */\narch/arm/mach-orion5x/pci.c:493:\tres = kzalloc_obj(struct resource);\narch/arm/mach-orion5x/pci.c-494-\tif (!res)\n--\narch/arm/mach-rpc/ecard.c=689=static struct expansion_card *__init ecard_alloc_card(int type, int slot)\n--\narch/arm/mach-rpc/ecard.c-694-\narch/arm/mach-rpc/ecard.c:695:\tec = kzalloc_obj(ecard_t);\narch/arm/mach-rpc/ecard.c-696-\tif (!ec) {\n--\narch/arm/mach-sa1100/clock.c=93=int __init sa11xx_clk_init(void)\n--\narch/arm/mach-sa1100/clock.c-109-\narch/arm/mach-sa1100/clock.c:110:\thw = kzalloc_obj(*hw);\narch/arm/mach-sa1100/clock.c-111-\tif (!hw)\n--\narch/arm/mach-sa1100/clock.c-131-\narch/arm/mach-sa1100/clock.c:132:\thw = kzalloc_obj(*hw);\narch/arm/mach-sa1100/clock.c-133-\tif (!hw)\n--\narch/arm/mach-sa1100/generic.c=317=int __init sa11x0_register_fixed_regulator(int n,\n--\narch/arm/mach-sa1100/generic.c-323-\narch/arm/mach-sa1100/generic.c:324:\tcfg-\u003einit_data = id = kzalloc_obj(*cfg-\u003einit_data);\narch/arm/mach-sa1100/generic.c-325-\tif (!cfg-\u003einit_data)\n--\narch/arm/mach-sa1100/neponset.c=225=static int neponset_probe(struct platform_device *dev)\n--\narch/arm/mach-sa1100/neponset.c-278-\narch/arm/mach-sa1100/neponset.c:279:\td = kzalloc_obj(*d);\narch/arm/mach-sa1100/neponset.c-280-\tif (!d) {\n--\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c=141=static int __init rcar_gen2_regulator_quirk(void)\n--\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c-166-\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c:167:\t\tquirk = kzalloc_obj(*quirk);\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c-168-\t\tif (!quirk) {\n--\narch/arm/mach-versatile/spc.c=393=static int ve_spc_populate_opps(uint32_t cluster)\n--\narch/arm/mach-versatile/spc.c-397-\narch/arm/mach-versatile/spc.c:398:\topps = kzalloc_objs(*opps, MAX_OPPS);\narch/arm/mach-versatile/spc.c-399-\tif (!opps)\n--\narch/arm/mach-versatile/spc.c=442=int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid, int irq)\n--\narch/arm/mach-versatile/spc.c-444-\tint ret;\narch/arm/mach-versatile/spc.c:445:\tinfo = kzalloc_obj(*info);\narch/arm/mach-versatile/spc.c-446-\tif (!info)\n--\narch/arm/mach-versatile/spc.c=523=static struct clk *ve_spc_clk_register(struct device *cpu_dev)\n--\narch/arm/mach-versatile/spc.c-527-\narch/arm/mach-versatile/spc.c:528:\tspc = kzalloc_obj(*spc);\narch/arm/mach-versatile/spc.c-529-\tif (!spc)\n--\narch/arm/mach-versatile/versatile.c=123=static void __init versatile_dt_pci_init(void)\n--\narch/arm/mach-versatile/versatile.c-144-\narch/arm/mach-versatile/versatile.c:145:\tnewprop = kzalloc_obj(*newprop);\narch/arm/mach-versatile/versatile.c-146-\tif (!newprop)\n--\narch/arm/mach-zynq/common.c=105=static void __init zynq_init_machine(void)\n--\narch/arm/mach-zynq/common.c-110-\narch/arm/mach-zynq/common.c:111:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-zynq/common.c-112-\tif (!soc_dev_attr)\n--\narch/arm/mm/cache-l2x0-pmu.c=503=static __init int l2x0_pmu_init(void)\n--\narch/arm/mm/cache-l2x0-pmu.c-509-\narch/arm/mm/cache-l2x0-pmu.c:510:\tl2x0_pmu = kzalloc_obj(*l2x0_pmu);\narch/arm/mm/cache-l2x0-pmu.c-511-\tif (!l2x0_pmu) {\n--\narch/arm/mm/cache-uniphier.c=315=static int __init __uniphier_cache_init(struct device_node *np,\n--\narch/arm/mm/cache-uniphier.c-344-\narch/arm/mm/cache-uniphier.c:345:\tdata = kzalloc_obj(*data);\narch/arm/mm/cache-uniphier.c-346-\tif (!data)\n--\narch/arm/mm/dma-mapping.c=533=static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,\n--\narch/arm/mm/dma-mapping.c-560-\narch/arm/mm/dma-mapping.c:561:\tbuf = kzalloc_obj(*buf,\narch/arm/mm/dma-mapping.c-562-\t\t\t gfp \u0026 ~(__GFP_DMA | __GFP_DMA32 | __GFP_HIGHMEM));\n--\narch/arm/mm/dma-mapping.c=1487=arm_iommu_create_mapping(struct device *dev, dma_addr_t base, u64 size)\n--\narch/arm/mm/dma-mapping.c-1506-\narch/arm/mm/dma-mapping.c:1507:\tmapping = kzalloc_obj(struct dma_iommu_mapping);\narch/arm/mm/dma-mapping.c-1508-\tif (!mapping)\n--\narch/arm/xen/enlighten.c=316=int __init arch_xen_unpopulated_init(struct resource **res)\n--\narch/arm/xen/enlighten.c-343-\narch/arm/xen/enlighten.c:344:\tregs = kzalloc_objs(*regs, nr_reg);\narch/arm/xen/enlighten.c-345-\tif (!regs) {\n--\narch/arm/xen/enlighten.c-387-\narch/arm/xen/enlighten.c:388:\t\ttmp_res = kzalloc_obj(*tmp_res);\narch/arm/xen/enlighten.c-389-\t\tif (!tmp_res) {\n--\narch/arm/xen/p2m.c=150=bool __set_phys_to_machine_multi(unsigned long pfn,\n--\narch/arm/xen/p2m.c-178-\narch/arm/xen/p2m.c:179:\tp2m_entry = kzalloc_obj(*p2m_entry, GFP_NOWAIT);\narch/arm/xen/p2m.c-180-\tif (!p2m_entry)\n--\narch/arm64/kernel/vdso.c=68=static int __init __vdso_init(enum vdso_abi abi)\n--\narch/arm64/kernel/vdso.c-83-\narch/arm64/kernel/vdso.c:84:\tvdso_pagelist = kzalloc_objs(struct page *, vdso_info[abi].vdso_pages);\narch/arm64/kernel/vdso.c-85-\tif (vdso_pagelist == NULL)\n--\narch/arm64/kvm/mmu.c=480=static int share_pfn_hyp(u64 pfn)\n--\narch/arm64/kvm/mmu.c-492-\narch/arm64/kvm/mmu.c:493:\tthis = kzalloc_obj(*this);\narch/arm64/kvm/mmu.c-494-\tif (!this) {\n--\narch/arm64/kvm/mmu.c=981=int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long type)\n--\narch/arm64/kvm/mmu.c-1007-\narch/arm64/kvm/mmu.c:1008:\tpgt = kzalloc_obj(*pgt, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/mmu.c-1009-\tif (!pgt)\n--\narch/arm64/kvm/mmu.c=1180=int topup_hyp_memcache(struct kvm_hyp_memcache *mc, unsigned long min_pages)\n--\narch/arm64/kvm/mmu.c-1185-\tif (!mc-\u003emapping) {\narch/arm64/kvm/mmu.c:1186:\t\tmc-\u003emapping = kzalloc_obj(struct pkvm_mapping,\narch/arm64/kvm/mmu.c-1187-\t\t\t\t\t GFP_KERNEL_ACCOUNT);\n--\narch/arm64/kvm/mmu.c=2510=int __init kvm_mmu_init(u32 hyp_va_bits)\n--\narch/arm64/kvm/mmu.c-2543-\narch/arm64/kvm/mmu.c:2544:\thyp_pgtable = kzalloc_obj(*hyp_pgtable);\narch/arm64/kvm/mmu.c-2545-\tif (!hyp_pgtable) {\n--\narch/arm64/kvm/nested.c=1328=int kvm_vcpu_allocate_vncr_tlb(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/nested.c-1333-\tif (!vcpu-\u003earch.vncr_tlb) {\narch/arm64/kvm/nested.c:1334:\t\tstruct vncr_tlb *vt = kzalloc_obj(*vcpu-\u003earch.vncr_tlb,\narch/arm64/kvm/nested.c-1335-\t\t\t\t\t\t GFP_KERNEL_ACCOUNT);\n--\narch/arm64/kvm/nested.c=1793=int kvm_init_nv_sysregs(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/nested.c-1802-\narch/arm64/kvm/nested.c:1803:\tkvm-\u003earch.sysreg_masks = kzalloc_obj(*(kvm-\u003earch.sysreg_masks),\narch/arm64/kvm/nested.c-1804-\t\t\t\t\t GFP_KERNEL_ACCOUNT);\n--\narch/arm64/kvm/ptdump.c=116=static struct kvm_ptdump_guest_state *kvm_ptdump_parser_create(struct kvm_s2_mmu *mmu)\n--\narch/arm64/kvm/ptdump.c-121-\narch/arm64/kvm/ptdump.c:122:\tst = kzalloc_obj(struct kvm_ptdump_guest_state, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/ptdump.c-123-\tif (!st)\n--\narch/arm64/kvm/vgic/vgic-init.c=207=static int kvm_vgic_dist_init(struct kvm *kvm, unsigned int nr_spis)\n--\narch/arm64/kvm/vgic/vgic-init.c-213-\tdist-\u003eactive_spis = (atomic_t)ATOMIC_INIT(0);\narch/arm64/kvm/vgic/vgic-init.c:214:\tdist-\u003espis = kzalloc_objs(struct vgic_irq, nr_spis, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-init.c-215-\tif (!dist-\u003espis)\n--\narch/arm64/kvm/vgic/vgic-init.c=316=static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type)\n--\narch/arm64/kvm/vgic/vgic-init.c-331-\narch/arm64/kvm/vgic/vgic-init.c:332:\tvgic_cpu-\u003eprivate_irqs = kzalloc_objs(struct vgic_irq,\narch/arm64/kvm/vgic/vgic-init.c-333-\t\t\t\t\t num_private_irqs,\n--\narch/arm64/kvm/vgic/vgic-irqfd.c=142=int kvm_vgic_setup_default_irq_routing(struct kvm *kvm)\n--\narch/arm64/kvm/vgic/vgic-irqfd.c-148-\narch/arm64/kvm/vgic/vgic-irqfd.c:149:\tentries = kzalloc_objs(*entries, nr, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-irqfd.c-150-\tif (!entries)\n--\narch/arm64/kvm/vgic/vgic-its.c=76=static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,\n--\narch/arm64/kvm/vgic/vgic-its.c-87-\narch/arm64/kvm/vgic/vgic-its.c:88:\tirq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-89-\tif (!irq)\n--\narch/arm64/kvm/vgic/vgic-its.c=971=static int vgic_its_alloc_collection(struct vgic_its *its,\n--\narch/arm64/kvm/vgic/vgic-its.c-976-\narch/arm64/kvm/vgic/vgic-its.c:977:\tcollection = kzalloc_obj(*collection, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-978-\tif (!collection)\n--\narch/arm64/kvm/vgic/vgic-its.c=1015=static struct its_ite *vgic_its_alloc_ite(struct its_device *device,\n--\narch/arm64/kvm/vgic/vgic-its.c-1020-\narch/arm64/kvm/vgic/vgic-its.c:1021:\tite = kzalloc_obj(*ite, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-1022-\tif (!ite)\n--\narch/arm64/kvm/vgic/vgic-its.c=1142=static struct its_device *vgic_its_alloc_device(struct vgic_its *its,\n--\narch/arm64/kvm/vgic/vgic-its.c-1147-\narch/arm64/kvm/vgic/vgic-its.c:1148:\tdevice = kzalloc_obj(*device, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-1149-\tif (!device)\n--\narch/arm64/kvm/vgic/vgic-its.c=1855=static int vgic_its_create(struct kvm_device *dev, u32 type)\n--\narch/arm64/kvm/vgic/vgic-its.c-1862-\narch/arm64/kvm/vgic/vgic-its.c:1863:\tits = kzalloc_obj(struct vgic_its, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-1864-\tif (!its)\n--\narch/arm64/kvm/vgic/vgic-mmio-v3.c=886=static int vgic_v3_alloc_redist_region(struct kvm *kvm, uint32_t index,\n--\narch/arm64/kvm/vgic/vgic-mmio-v3.c-931-\narch/arm64/kvm/vgic/vgic-mmio-v3.c:932:\trdreg = kzalloc_obj(*rdreg, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-mmio-v3.c-933-\tif (!rdreg)\n--\narch/arm64/kvm/vgic/vgic-v4.c=242=int vgic_v4_init(struct kvm *kvm)\n--\narch/arm64/kvm/vgic/vgic-v4.c-258-\narch/arm64/kvm/vgic/vgic-v4.c:259:\tdist-\u003eits_vm.vpes = kzalloc_objs(*dist-\u003eits_vm.vpes, nr_vcpus,\narch/arm64/kvm/vgic/vgic-v4.c-260-\t\t\t\t\t GFP_KERNEL_ACCOUNT);\n--\narch/arm64/net/bpf_jit_comp.c=2156=struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\n--\narch/arm64/net/bpf_jit_comp.c-2177-\tif (!jit_data) {\narch/arm64/net/bpf_jit_comp.c:2178:\t\tjit_data = kzalloc_obj(*jit_data);\narch/arm64/net/bpf_jit_comp.c-2179-\t\tif (!jit_data)\n--\narch/csky/kernel/vdso.c=17=static int __init vdso_init(void)\n--\narch/csky/kernel/vdso.c-22-\tvdso_pagelist =\narch/csky/kernel/vdso.c:23:\t\tkzalloc_objs(struct page *, vdso_pages);\narch/csky/kernel/vdso.c-24-\tif (unlikely(vdso_pagelist == NULL)) {\n--\narch/loongarch/kernel/setup.c=466=static int __init add_legacy_isa_io(struct fwnode_handle *fwnode,\n--\narch/loongarch/kernel/setup.c-472-\narch/loongarch/kernel/setup.c:473:\trange = kzalloc_obj(*range, GFP_ATOMIC);\narch/loongarch/kernel/setup.c-474-\tif (!range)\n--\narch/loongarch/kernel/vdso.c=45=static int __init init_vdso(void)\n--\narch/loongarch/kernel/vdso.c-55-\tvdso_info.code_mapping.pages =\narch/loongarch/kernel/vdso.c:56:\t\tkzalloc_objs(struct page *, vdso_info.size / PAGE_SIZE);\narch/loongarch/kernel/vdso.c-57-\n--\narch/loongarch/kvm/intc/eiointc.c=614=static int kvm_eiointc_create(struct kvm_device *dev, u32 type)\n--\narch/loongarch/kvm/intc/eiointc.c-624-\narch/loongarch/kvm/intc/eiointc.c:625:\ts = kzalloc_obj(struct loongarch_eiointc);\narch/loongarch/kvm/intc/eiointc.c-626-\tif (!s)\n--\narch/loongarch/kvm/intc/ipi.c=394=static int kvm_ipi_create(struct kvm_device *dev, u32 type)\n--\narch/loongarch/kvm/intc/ipi.c-411-\narch/loongarch/kvm/intc/ipi.c:412:\ts = kzalloc_obj(struct loongarch_ipi);\narch/loongarch/kvm/intc/ipi.c-413-\tif (!s)\n\n"
} |
| 9/3 | 2026/08/15 07:05 | llm | kmsan-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 90713 output: 19 thoughts: 8423 Thoughts: **Analysis of Potential Uninitialized Memory Exposure in BPF Diagnostic Patch** Okay, so I've been dissecting this BPF diagnostic patch, trying to nail down whether it introduces any potential for leaking uninitialized memory to user space. The core of my analysis revolves around understanding where memory is allocated, how it's used, and whether any of those allocated regions end up in the verifier log or are otherwise exposed. Initially, I identified `kzalloc_obj` and immediately recognized that it wasn't a standard macro, which piqued my curiosity. Its definition isn't in `include/linux/slab.h`. The patch does a lot with diagnostic events: branches, modifications, and context records. These are stored in an internal `log->events` buffer. My concern was with `kvrealloc`, which doesn't zero the memory. *However*, the code *immediately* overwrites the contents with data from `*event`, which is a `struct bpf_diag_history_event`, so that's okay. I scrutinized `struct bpf_diag_history_event`, particularly the unions within it, because unions always raise flags about the possibility of uninitialized bytes, but I concluded that it is internal to the kernel and never copied to user space directly. I considered `bpf_get_linfo_source` and `bpf_prog_get_file_line`. Here, while `src` is uninitialized initially, `bpf_get_linfo_source` then *does* initialize all the fields of `src`. So, it seems like a false lead. Then I focused on the verifier log itself, which is ultimately what's exposed to user space. I carefully tracked the usage of `diag_fmt_alloc`, which uses `kmalloc`, which doesn't zero the memory. However, this memory is then immediately passed to `vsnprintf`. I had to go over the buffer management carefully. The buffers returned from `diag_fmt_alloc` are passed to `vsnprintf` and `scnprintf`, which null-terminate them, and then I verified that only the initialized (up to the null terminator) part is then copied or passed on to the verifier log through `%s`. This is critical. The same is true for the `bpf_diag_fmt_buf` function, which, I confirmed, results in memory that's fully initialized before being passed on. I also checked other internal data structures like `bpf_diag_mod_target`, `bpf_diag_reg_snapshot`, etc. They contain unions and could potentially have uninitialized bytes, but they are used *only* internally and not exposed to user space. Therefore, there's no leak. So, after a thorough review of the code and the various allocation and usage patterns, I'm confident that the patch does *not* introduce a vulnerability where uninitialized memory is leaked to user space via the BPF verifier log. The null terminators, the careful buffer management, and the internal-only nature of the potentially problematic structs ensure the safety of this patch, as far as uninitialized memory is concerned. |
| 10/3 | 2026/08/15 07:06 | tool | grepper |
0mArgs:{
"Expression": "define kzalloc_obj"
}Results: {
"Output": "include/linux/slab.h=1070=void *kmalloc_nolock(size_t size, gfp_t gfp_flags, int node);\n--\ninclude/linux/slab.h-1151-/* All kzalloc aliases for kmalloc_(obj|objs|flex). */\ninclude/linux/slab.h:1152:#define kzalloc_obj(P, ...) \\\ninclude/linux/slab.h-1153-\t__alloc_objs(kzalloc, default_gfp(__VA_ARGS__), typeof(P), 1)\ninclude/linux/slab.h:1154:#define kzalloc_objs(P, COUNT, ...) \\\ninclude/linux/slab.h-1155-\t__alloc_objs(kzalloc, default_gfp(__VA_ARGS__), typeof(P), COUNT)\n--\ntools/include/linux/slab.h=200=static inline unsigned int kmem_cache_sheaf_size(struct slab_sheaf *sheaf)\n--\ntools/include/linux/slab.h-210-\ntools/include/linux/slab.h:211:#define kzalloc_obj(P, ...) \\\ntools/include/linux/slab.h-212-\t__alloc_objs(kzalloc, default_gfp(__VA_ARGS__), typeof(P), 1)\n"
} |
| 11/3 | 2026/08/15 07:07 | llm | kmsan-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 100156 output: 326 thoughts: 11173 Thoughts: **Analysis of the BPF Verifier Diagnostics Framework for Potential Uninitialized Memory Leaks** Okay, so I've meticulously reviewed this patch introducing a diagnostics framework to the BPF verifier. My primary concern, as always, is whether this introduces any new avenues for leaking uninitialized memory to user space. Let's see... First, the `kzalloc_obj` macro – it zeros allocated memory. Good. The `env->diag` structure is fully zeroed on allocation. The `bpf_diag_free` function properly releases everything. Nothing fishy there. And `diag_fmt_restore` looks fine, handling chunk restoration and freeing. My attention immediately goes to user-space exposure. The verifier log is *the* thing passed up. `bpf_verifier_vlog` is the key, and it uses `vsnprintf`. Aha! That formats the string. The arguments are strings, integers, etc., *not* raw structs. The formatting uses `bpf_diag_fmt` which in turn uses `vsnprintf` into a `kmalloc`ed buffer, and *crucially*, `vsnprintf` null-terminates. Then the log buffer uses `%s` - so only initialized characters are used. No uninitialized memory is leaked *there*. But, are there other places? `bpf_prog_get_file_line` is next. That uses a local variable `src`, initialized by `bpf_get_linfo_source`. All fields of `src` are initialized, and then those fields are copied to `filep`, `linep`, and `nump`. Okay, that's sound. No problem. Similar story with `bpf_diag_source` – `src` is zeroed. Now, into the heart of the diagnostics: `bpf_diag_mod_begin`. We have `diag->mod.target_reg_snapshot = *reg;`. A potential risk! The `reg` structure *could* have padding with uninitialized bytes. But, wait! `diag->mod.target_reg_snapshot` is *only* used internally to generate the diagnostic output! The `diag_snapshot_reg` function only copies *specific* fields (`type`, `map_ptr`, `btf_id`, `var_off`, `r64`). It's not a full copy! So padding bytes are not leaked! Excellent. The `event` is stored in the `log->events` which is internal. The other diagnostics functions (`bpf_diag_record_scrub`, `bpf_diag_record_scrub_stack`, `bpf_diag_record_ref_acquire`, `bpf_diag_record_ref_release`, `bpf_diag_record_context`, `bpf_diag_record_branch`) all use internally-initialized structs and never expose uninitialized memory to the user. Functions like `bpf_diag_stack_arg_uninit`, `bpf_diag_memory`, `bpf_diag_mem_bounds`, `bpf_diag_res`, `bpf_diag_lock`, `bpf_diag_irq`, `bpf_diag_leak`, `bpf_diag_call_type`, `bpf_diag_ctx_forbidden`, `bpf_diag_ctx_active`, `bpf_diag_ctx_required`, `bpf_diag_ctx_underflow`, `bpf_diag_program_structure`, `bpf_diag_policy`, `bpf_diag_invalid_deref`, `bpf_diag_unreadable_reg` they are all safe! After a detailed look, no new uninitialized memory leaks are present. And yes, I checked for *other* changes to the BPF verifier code! No modification of the core functions which could introduce `copy_to_user`, `put_user`, netlink, ioctl, socket options, or BPF buffers. In conclusion, this patch introduces a powerful new diagnostics framework, but the design is *careful*. The internal data structures *are* zeroed or initialized. The only data exposed to user space, the verifier log, relies entirely on well-established string formatting functions (`vsnprintf`) which *ensure* null-termination and that only initialized data is read. The patch does not introduce new attack surfaces. Therefore, a dedicated KMSAN fuzzing session is not necessary; standard KASAN fuzzing will catch any memory safety issues in the new code. We're safe. |
| 12/3 | 2026/08/15 07:08 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch introduces a new diagnostics framework for the BPF verifier to provide better error messages and causal history of events leading to verifier errors. It adds new internal structures (`struct bpf_diag`, `struct bpf_diag_log`, `struct bpf_diag_history_event`, etc.) to track register and stack modifications, context changes, and branches during verification. These structures are allocated using `kzalloc` (via the `kzalloc_obj` macro) and are only used internally to generate diagnostic text.\n\nWhen a verifier error occurs, the framework formats the recorded events into strings using `vsnprintf` and `scnprintf`, which ensure the strings are properly null-terminated. These strings are then written to the verifier log buffer using `%s` format specifiers. The verifier log is the only data copied to user space, and since it is populated using standard string formatting functions, no uninitialized memory or padding bytes from the internal structures are leaked.\n\nThe patch does not introduce any new structures sent directly to user space via `copy_to_user`, netlink, ioctl, or BPF buffers. Therefore, the changes do not expose any uninitialized memory risks, and a dedicated KMSAN fuzzing session is not justified. Standard KASAN fuzzing is sufficient to catch any potential memory safety issues (e.g., out-of-bounds accesses or use-after-free) in the new verifier code.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch introduces a new diagnostics framework for the BPF verifier to provide better error messages and causal history of events leading to verifier errors. It adds new internal structures (`struct bpf_diag`, `struct bpf_diag_log`, `struct bpf_diag_history_event`, etc.) to track register and stack modifications, context changes, and branches during verification. These structures are allocated using `kzalloc` (via the `kzalloc_obj` macro) and are only used internally to generate diagnostic text.\n\nWhen a verifier error occurs, the framework formats the recorded events into strings using `vsnprintf` and `scnprintf`, which ensure the strings are properly null-terminated. These strings are then written to the verifier log buffer using `%s` format specifiers. The verifier log is the only data copied to user space, and since it is populated using standard string formatting functions, no uninitialized memory or padding bytes from the internal structures are leaked.\n\nThe patch does not introduce any new structures sent directly to user space via `copy_to_user`, netlink, ioctl, or BPF buffers. Therefore, the changes do not expose any uninitialized memory risks, and a dedicated KMSAN fuzzing session is not justified. Standard KASAN fuzzing is sufficient to catch any potential memory safety issues (e.g., out-of-bounds accesses or use-after-free) in the new verifier code.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|